pytestCheckHook: support inclusion and exclusion of path globs, test items, keywords, and markers (#386513)

This commit is contained in:
Yueh-Shun Li
2025-03-24 07:15:58 +08:00
committed by GitHub
5 changed files with 266 additions and 85 deletions
@@ -170,6 +170,14 @@ in
"test_print"
] ++ previousPythonAttrs.disabledTests or [ ];
});
disabledTests-expression = objprint.overridePythonAttrs (previousPythonAttrs: {
__structuredAttrs = true;
pname = "test-pytestCheckHook-disabledTests-expression-${previousPythonAttrs.pname}";
disabledTests = [
"TestBasic and test_print"
"test_str"
] ++ previousPythonAttrs.disabledTests or [ ];
});
disabledTestPaths = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-disabledTestPaths-${previousPythonAttrs.pname}";
disabledTestPaths = [
@@ -184,6 +192,12 @@ in
] ++ previousPythonAttrs.disabledTestPaths or [ ];
})
);
disabledTestPaths-item = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-disabledTestPaths-item-${previousPythonAttrs.pname}";
disabledTestPaths = [
"tests/test_basic.py::TestBasic"
] ++ previousPythonAttrs.disabledTestPaths or [ ];
});
disabledTestPaths-glob = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-disabledTestPaths-glob-${previousPythonAttrs.pname}";
disabledTestPaths = [
@@ -198,6 +212,78 @@ in
] ++ previousPythonAttrs.disabledTestPaths or [ ];
})
);
enabledTests = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-enabledTests-${previousPythonAttrs.pname}";
enabledTests = [
"TestBasic"
] ++ previousPythonAttrs.disabledTests or [ ];
});
enabledTests-expression = objprint.overridePythonAttrs (previousPythonAttrs: {
__structuredAttrs = true;
pname = "test-pytestCheckHook-enabledTests-expression-${previousPythonAttrs.pname}";
enabledTests = [
"TestBasic and test_print"
"test_str"
] ++ previousPythonAttrs.disabledTests or [ ];
});
enabledTests-disabledTests = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-enabledTests-disabledTests-${previousPythonAttrs.pname}";
enabledTests = [
"TestBasic"
] ++ previousPythonAttrs.disabledTests or [ ];
disabledTests = [
"test_print"
] ++ previousPythonAttrs.disabledTests or [ ];
});
enabledTestPaths = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-enabledTestPaths-${previousPythonAttrs.pname}";
enabledTestPaths = [
"tests/test_basic.py"
] ++ previousPythonAttrs.enabledTestPaths or [ ];
});
enabledTestPaths-nonexistent = testers.testBuildFailure (
objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-enabledTestPaths-nonexistent-${previousPythonAttrs.pname}";
enabledTestPaths = [
"tests/test_foo.py"
] ++ previousPythonAttrs.enabledTestPaths or [ ];
})
);
enabledTestPaths-dir = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-enabledTestPaths-dir-${previousPythonAttrs.pname}";
enabledTestPaths = [
"tests"
] ++ previousPythonAttrs.enabledTestPaths or [ ];
});
enabledTestPaths-dir-disabledTestPaths = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-enabledTestPaths-dir-disabledTestPaths-${previousPythonAttrs.pname}";
enabledTestPaths = [
"tests"
] ++ previousPythonAttrs.enabledTestPaths or [ ];
disabledTestPaths = [
"tests/test_basic.py"
] ++ previousPythonAttrs.disabledTestPaths or [ ];
});
enabledTestPaths-glob = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-enabledTestPaths-glob-${previousPythonAttrs.pname}";
enabledTestPaths = [
"tests/test_obj*.py"
] ++ previousPythonAttrs.enabledTestPaths or [ ];
});
enabledTestPaths-glob-nonexistent = testers.testBuildFailure (
objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-enabledTestPaths-glob-nonexistent-${previousPythonAttrs.pname}";
enabledTestPaths = [
"tests/test_foo*.py"
] ++ previousPythonAttrs.enabledTestPaths or [ ];
})
);
enabledTestPaths-item = objprint.overridePythonAttrs (previousPythonAttrs: {
pname = "test-pytestCheckHook-enabledTestPaths-item-${previousPythonAttrs.pname}";
enabledTestPaths = [
"tests/test_basic.py::TestBasic"
] ++ previousPythonAttrs.enabledTestPaths or [ ];
});
};
};
} ./pytest-check-hook.sh
@@ -3,22 +3,64 @@
echo "Sourcing pytest-check-hook"
function _pytestIncludeExcludeExpr() {
local includeListName="$1"
local -n includeListRef="$includeListName"
local excludeListName="$2"
local -n excludeListRef="$excludeListName"
local includeString excludeString
if [[ -n "${includeListRef[*]-}" ]]; then
# ((element1) or (element2))
includeString="(($(concatStringsSep ") or (" "$includeListName")))"
fi
if [[ -n "${excludeListRef[*]-}" ]]; then
# and not (element1) and not (element2)
excludeString="${includeString:+ and }not ($(concatStringsSep ") and not (" "$excludeListName"))"
fi
echo "$includeString$excludeString"
}
function pytestCheckPhase() {
echo "Executing pytestCheckPhase"
runHook preCheck
# Compose arguments
local -a flagsArray=(-m pytest)
if [ -n "${disabledTests[*]-}" ]; then
disabledTestsString="not $(concatStringsSep " and not " disabledTests)"
flagsArray+=(-k "$disabledTestsString")
fi
local -a _pathsArray=()
local -a _pathsArray
local path
_pathsArray=()
concatTo _pathsArray enabledTestPaths
for path in "${_pathsArray[@]}"; do
if [[ "$path" =~ "::" ]]; then
flagsArray+=("$path")
else
# The `|| kill "$$"` trick propagates the errors from the process substitutiton subshell,
# which is suggested by a StackOverflow answer: https://unix.stackexchange.com/a/217643
readarray -t -O"${#flagsArray[@]}" flagsArray < <(@pythonCheckInterpreter@ - "$path" <<EOF || kill "$$")
import glob
import sys
path_glob=sys.argv[1]
if not len(path_glob):
sys.exit('Got an empty enabled tests path glob. Aborting')
path_expanded = glob.glob(path_glob)
if not len(path_expanded):
sys.exit('Enabled tests path glob "{}" does not match any paths. Aborting'.format(path_glob))
for path in path_expanded:
print(path)
EOF
fi
done
_pathsArray=()
concatTo _pathsArray disabledTestPaths
for path in "${_pathsArray[@]}"; do
# Check if every path glob matches at least one path
@pythonCheckInterpreter@ - "$path" <<EOF
if [[ "$path" =~ "::" ]]; then
flagsArray+=("--deselect=$path")
else
# Check if every path glob matches at least one path
@pythonCheckInterpreter@ - "$path" <<EOF
import glob
import sys
path_glob=sys.argv[1]
@@ -27,9 +69,18 @@ if not len(path_glob):
if next(glob.iglob(path_glob), None) is None:
sys.exit('Disabled tests path glob "{}" does not match any paths. Aborting'.format(path_glob))
EOF
flagsArray+=("--ignore-glob=$path")
flagsArray+=("--ignore-glob=$path")
fi
done
if [[ -n "${enabledTests[*]-}" ]] || [[ -n "${disabledTests[*]-}" ]]; then
flagsArray+=(-k "$(_pytestIncludeExcludeExpr enabledTests disabledTests)")
fi
if [[ -n "${enabledTestMarks[*]-}" ]] || [[ -n "${disabledTestMarks[*]-}" ]]; then
flagsArray+=(-m "$(_pytestIncludeExcludeExpr enabledTestMarks disabledTestMarks)")
fi
# Compatibility layer to the obsolete pytestFlagsArray
eval "flagsArray+=(${pytestFlagsArray[*]-})"
@@ -106,8 +106,12 @@ let
"doInstallCheck"
"pyproject"
"format"
"disabledTestMarks"
"disabledTestPaths"
"disabledTests"
"enabledTestMarks"
"enabledTestPaths"
"enabledTests"
"pytestFlags"
"pytestFlagsArray"
"unittestFlags"
@@ -439,6 +443,7 @@ let
}
// optionalAttrs (attrs.doCheck or true) (
getOptionalAttrs [
"disabledTestMarks"
"disabledTestPaths"
"disabledTests"
"pytestFlags"
@@ -446,6 +451,21 @@ let
"unittestFlags"
"unittestFlagsArray"
] attrs
//
lib.mapAttrs
(
name: value:
lib.throwIf (
attrs.${name} == [ ]
) "${lib.getName finalAttrs}: ${name} must be unspecified, null or a non-empty list." attrs.${name}
)
(
getOptionalAttrs [
"enabledTestMarks"
"enabledTestPaths"
"enabledTests"
] attrs
)
)
);