blob: 0a3c06145bd0a32e04a69b11c250ebe7e007f7af [file]
#!/bin/sh
#
# run-one-test.sh -- re-run a single test (file, directory or ::nodeid) via
# run-all-tests.sh, picking the right suite from the path.
#
# Usage:
# ./run-one-test.sh PATH[::nodeid] [pytest args...]
#
# PATH may be given as printed in the run-all-tests.sh output, or as a real
# filesystem path from anywhere, e.g.:
# ./run-one-test.sh tests/t/apache/test_404.py # pytest_suite
# ./run-one-test.sh tests/t/apache/test_404.py::test_x -v
# ./run-one-test.sh modules/http2/test_003_get.py # pyhttpd
# ./run-one-test.sh test/modules/http2/test_003_get.py
#
# With no arguments, lists all test files (one PATH per line) for grepping;
# with -l, lists every individual test (PATH::nodeid) instead:
# ./run-one-test.sh | grep -i status
# ./run-one-test.sh -l | grep -i status
#
set -eu
here="$(cd "$(dirname "$0")" && pwd)"
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
sed -n '3,19p' "$0" | sed 's/^# \{0,1\}//'
exit 2
fi
# No arguments: list every test file as a PATH accepted above; -l: list every
# test as PATH::nodeid. This is a static scan (no pytest collection, so no
# built httpd needed); parametrized tests are listed once, without their
# [param] suffix.
if [ $# -eq 0 ] || { [ $# -eq 1 ] && [ "$1" = "-l" ]; }; then
cd "$here"
exec python3 - "${1:-}" pytest_suite modules <<'EOF'
import ast, os, sys, warnings
warnings.simplefilter("ignore", SyntaxWarning)
def tests(body, prefix):
for n in body:
if isinstance(n, ast.ClassDef) and n.name.startswith("Test"):
yield from tests(n.body, prefix + n.name + "::")
elif (isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
and n.name.startswith("test")):
yield prefix + n.name
per_test = sys.argv[1] == "-l"
for root in sys.argv[2:]:
paths = []
for d, dirs, files in os.walk(root):
dirs[:] = [x for x in dirs if not x.startswith((".", "__"))]
paths += [os.path.join(d, f) for f in files
if f.startswith("test_") and f.endswith(".py")]
for p in sorted(paths):
rel = os.path.relpath(p, root) if root == "pytest_suite" else p
if not per_test:
print(rel)
continue
try:
with open(p) as f:
tree = ast.parse(f.read(), p)
except (OSError, SyntaxError) as e:
print(f"run-one-test.sh: skipping {p}: {e}", file=sys.stderr)
continue
for t in tests(tree.body, rel + "::"):
print(t)
EOF
fi
arg="$1"; shift
file="${arg%%::*}"
node=""
[ "$file" != "$arg" ] && node="::${arg#*::}"
# Absolute path of an existing file or directory.
abspath() {
d=$(cd "$(dirname "$1")" 2>/dev/null && pwd) || return 1
echo "$d/$(basename "$1")"
}
# Try the path as given, then relative to each suite's root (how the test
# output prints them), then relative to the source tree root (test/...).
abs=""
for cand in "$file" "$here/pytest_suite/$file" "$here/$file" \
"$(dirname "$here")/$file"; do
if [ -e "$cand" ]; then abs=$(abspath "$cand"); break; fi
done
if [ -z "$abs" ]; then
echo "run-one-test.sh: no such test path: $file" >&2
exit 2
fi
case "$abs" in
"$here/pytest_suite/"*)
exec "$here/run-all-tests.sh" --only=pysuite "$@" \
"${abs#"$here/pytest_suite/"}$node"
;;
"$here/modules/"*)
# pyhttpd paths are taken from PYHTTPD_TARGETS, relative to test/.
PYHTTPD_TARGETS="${abs#"$here/"}$node"
export PYHTTPD_TARGETS
exec "$here/run-all-tests.sh" --only=pyhttpd "$@"
;;
*)
echo "run-one-test.sh: $abs is not under pytest_suite/ or modules/" >&2
exit 2
;;
esac