From: Jim Jagielski Date: Fri, 17 Jul 2026 15:56:06 +0000 (+0000) Subject: Merge of 1936270 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=cc3aacb384d61462ddaae8f3d83fd98e995e6369;p=thirdparty%2Fapache%2Fhttpd.git Merge of 1936270 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1936271 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/test/README b/test/README index 9f8be502b8..e1e4eb9299 100644 --- a/test/README +++ b/test/README @@ -1,3 +1,100 @@ This directory contains useful test code for testing various bits of Apache functionality. This stuff is for the developers only, so we might remove it on public releases. + + +Python / pytest test suites +=========================== + +There are two complementary Python (pytest) test suites in this directory. +Both run against an already-built httpd; they are independent of each other. + + pyhttpd / modules/ httpd's own test framework. Covers HTTP/2, mod_md + (ACME), HTTP/1, proxy and core behaviour. Drives the + server with external clients (curl, nghttp, h2load) and + is configured via pyhttpd/config.ini (generated by + httpd's configure). Framework code lives in pyhttpd/; + tests live in modules/{core,http1,http2,md,proxy}/. + + pytest_suite/ A self-contained port of the classic Perl Apache::Test + suite (core HTTP, per-module tests, security/CVE + regressions, SSL/TLS, PHP). Drives the server with an + in-process Python HTTP client (httpx) and raw sockets. + It is fully self-contained: it ships its own conf + templates, document root, C test modules and helper + framework under pytest_suite/, and reads nothing outside + that directory. See pytest_suite/README.md for details, + including how to add new tests. + +The two suites coexist: pytest_suite/ has its own conftest.py and +pyproject.toml, so pytest treats it as its own rootdir and does NOT pick up +this directory's pyhttpd conftest.py. Run pytest_suite from inside +pytest_suite/ (or via the unified runner below) -- do not point pytest at the +top-level test/ directory expecting to run both at once. + + +Unified runner: run-all-tests.sh +--------------------------------- + +run-all-tests.sh runs BOTH suites against the same built httpd and reports a +combined result. pytest_suite runs first, then the pyhttpd modules/ tests. + + ./run-all-tests.sh # run both suites (pytest_suite first) + ./run-all-tests.sh --only=pysuite # only the pytest_suite tests + ./run-all-tests.sh --only=pyhttpd # only the pyhttpd modules/ tests + ./run-all-tests.sh --apxs=/path/to/apxs # point at a specific httpd build + ./run-all-tests.sh -k status -v # flags pass through to BOTH suites + +How it finds the httpd build: + * pytest_suite is located via apxs -- from --apxs, the $APXS environment + variable, the "prefix" in pyhttpd/config.ini, or apxs on $PATH. + * the pyhttpd tests use pyhttpd/config.ini (built by httpd's configure), via + the system "pytest". + +Selecting tests: + * Flag arguments (-v, -k NAME, -x, ...) are passed to both suites. + * Positional test paths are passed only to pytest_suite (they are + pytest_suite paths). Select pyhttpd tests with the PYHTTPD_TARGETS + environment variable, e.g. + PYHTTPD_TARGETS="modules/http2" ./run-all-tests.sh --only=pyhttpd + By default the pyhttpd side runs every modules/* directory whose conftest + imports successfully in your environment; a directory whose optional + dependency is missing (e.g. modules/md needs pyOpenSSL) is skipped with a + note rather than aborting the run. + +Environment overrides: + APXS path to apxs (default: pyhttpd/config.ini prefix, else PATH) + PHP_FPM path to a php-fpm binary; enables pytest_suite's PHP tests + PYHTTPD_TARGETS pyhttpd test path(s) to run (default: auto-detected modules/*) + +The runner exits non-zero if either suite has failures. + + +Running a suite directly +------------------------ + +Both runtests.sh scripts create their own virtualenv on first run and can be +invoked from any directory -- the paths below are just the convenient way to +type them. The venv is (re)built automatically whenever it is missing or its +pyproject.toml has changed, using `uv sync` if uv is installed and otherwise +`python3 -m venv` + pip (reading the dependency list from pyproject.toml -- no +uv required). To force a clean rebuild yourself, `rm -rf /.venv`. + +pytest_suite (self-contained; the venv holds only pytest + httpx): + + ./pytest_suite/runtests.sh --apxs=/path/to/apxs # all tests + ./pytest_suite/runtests.sh --php-fpm=/path/to/php-fpm tests/t/php # PHP tests + ./pytest_suite/runtests.sh -k rewrite -v # any pytest args pass through + +pyhttpd tests (need pyhttpd/config.ini from httpd's configure, plus curl, +nghttp2/h2load, and -- for modules/md -- pyOpenSSL and an ACME test server): + + ./pyhttpd/runtests.sh modules/http2 # all HTTP/2 tests + ./pyhttpd/runtests.sh modules/core -k test_001 # a subset + +Other contents +-------------- + + unit/ C-based unit tests (libcheck / httpdunit). + clients/ helper client programs used by the tests. + *.c small standalone C test programs. diff --git a/test/pyhttpd/runtests.sh b/test/pyhttpd/runtests.sh new file mode 100755 index 0000000000..3ec52b567a --- /dev/null +++ b/test/pyhttpd/runtests.sh @@ -0,0 +1,95 @@ +#!/bin/sh +# +# runtests.sh -- run the pyhttpd modules/ test suite. +# +# Manages a local .venv under pyhttpd/ so that pytest and the CGI helper scripts +# (which httpd forks) both use the same Python with all required packages +# (cryptography, python-multipart, websockets, etc.) available. +# +# Usage: +# ./runtests.sh # run all modules/ tests +# ./runtests.sh modules/http1 # run a specific suite +# ./runtests.sh -k post -v # any pytest args pass through +# +# Environment: +# PYHTTPD_TARGETS space-separated list of test paths (default: modules) +# +set -eu + +here="$(cd "$(dirname "$0")" && pwd)" + +# --- ensure the venv exists and is current ---------------------------------- +# We invoke .venv/bin/pytest directly rather than `uv run` so the suite works +# even where `uv run` is shimmed/unavailable. +# +# Create $here/.venv on first run, and rebuild it when pyproject.toml is newer +# than the venv (i.e. dependencies changed). Prefer uv (which reads +# pyproject.toml + uv.lock); otherwise fall back to python3 -m venv + pip, +# taking the dependency list straight from pyproject.toml so there is no second +# copy to keep in sync. Absolute paths throughout, so this behaves identically +# regardless of the caller's cwd. This block is kept byte-for-byte identical in +# pytest_suite/runtests.sh and pyhttpd/runtests.sh -- edit both together. +PYTEST="$here/.venv/bin/pytest" +if [ ! -x "$PYTEST" ] || [ "$here/pyproject.toml" -nt "$here/.venv" ]; then + if command -v uv >/dev/null 2>&1; then + echo "runtests.sh: (re)creating $here/.venv via 'uv sync'..." >&2 + uv sync --project "$here" + elif command -v python3 >/dev/null 2>&1; then + echo "runtests.sh: (re)creating $here/.venv via python3 + pip..." >&2 + python3 -m venv "$here/.venv" + # Read [project].dependencies from pyproject.toml (one entry per line, + # double-quoted) so the install list never drifts from the manifest. + deps=$(awk -F'"' '/^dependencies = \[/{f=1; next} f && /^\]/{f=0} f && NF>=2 {print $2}' "$here/pyproject.toml") + # shellcheck disable=SC2086 # deps is an intentional word-split list + "$here/.venv/bin/pip" install --quiet $deps + else + echo "runtests.sh: ERROR: $PYTEST not found and neither 'uv' nor 'python3' is installed." >&2 + exit 1 + fi + # Mark the venv as freshly built so the staleness check above won't retrigger + # until pyproject.toml changes again. + touch "$here/.venv" +fi + +# Prepend the venv's bin dir so that CGI scripts forked by httpd also resolve +# python3 to the venv's interpreter (which has all packages installed), and so +# that any shim wrappers earlier on PATH are shadowed. +export PATH="$here/.venv/bin:$PATH" + +# The modules/ test suite lives in test/, a sibling of this script's directory +# (test/pyhttpd/) -- cd there so both the default target and any +# PYHTTPD_TARGETS/positional path the caller supplies resolve the same way +# regardless of where runtests.sh was invoked from. +cd "$(dirname "$here")" + +# Only fall back to the "modules" default when the caller gave no positional +# test path of their own -- otherwise it would always tag along after theirs +# (`pytest modules modules/http1`), silently widening any subset selection +# back out to the full suite. A positional path is recognized by actually +# existing on disk (relative to test/, our cwd at this point) -- this avoids +# both having to enumerate every pytest flag that takes a separate-word value +# (-k, -m, -p, --tb, --maxfail, -n from pytest-xdist, ...) and misdetecting a +# -k/-m expression that happens to contain '/' (this suite's own parametrize +# IDs look like "/006/006.css", so "-k 006/006" is a realistic selector, and +# it does not exist as a path). +have_path=0 +for arg in "$@"; do + case "$arg" in + -*) ;; + # Strip a trailing ::nodeid (pytest's file::Class::test node-selector + # syntax) before checking existence -- only the file/dir part is real. + *) [ -e "${arg%%::*}" ] && have_path=1 ;; + esac +done + +if [ -n "${PYHTTPD_TARGETS:-}" ]; then + targets="$PYHTTPD_TARGETS" +elif [ "$have_path" = 1 ]; then + targets="" +else + targets="modules" +fi + +echo "runtests.sh: $PYTEST $targets $*" >&2 +# shellcheck disable=SC2086 # $targets is an intentional word-split path list +exec "$PYTEST" $targets "$@" diff --git a/test/pytest_suite/runtests.sh b/test/pytest_suite/runtests.sh index a22501012a..1bbb62cb02 100755 --- a/test/pytest_suite/runtests.sh +++ b/test/pytest_suite/runtests.sh @@ -26,23 +26,37 @@ set -eu here="$(cd "$(dirname "$0")" && pwd)" cd "$here" -# --- locate the virtualenv's pytest ----------------------------------------- +# --- ensure the venv exists and is current ---------------------------------- # We invoke .venv/bin/pytest directly rather than `uv run` so the suite works -# even where `uv run` is shimmed/unavailable. Create the venv with `uv sync` -# (or `python -m venv .venv && .venv/bin/pip install -e .`) if it's missing. +# even where `uv run` is shimmed/unavailable. +# +# Create $here/.venv on first run, and rebuild it when pyproject.toml is newer +# than the venv (i.e. dependencies changed). Prefer uv (which reads +# pyproject.toml + uv.lock); otherwise fall back to python3 -m venv + pip, +# taking the dependency list straight from pyproject.toml so there is no second +# copy to keep in sync. Absolute paths throughout, so this behaves identically +# regardless of the caller's cwd. This block is kept byte-for-byte identical in +# pytest_suite/runtests.sh and pyhttpd/runtests.sh -- edit both together. PYTEST="$here/.venv/bin/pytest" -if [ ! -x "$PYTEST" ]; then +if [ ! -x "$PYTEST" ] || [ "$here/pyproject.toml" -nt "$here/.venv" ]; then if command -v uv >/dev/null 2>&1; then - echo "runtests.sh: .venv not found; running 'uv sync' to create it..." >&2 - uv sync + echo "runtests.sh: (re)creating $here/.venv via 'uv sync'..." >&2 + uv sync --project "$here" elif command -v python3 >/dev/null 2>&1; then - echo "runtests.sh: .venv not found; creating it with python3 + pip..." >&2 - python3 -m venv .venv - .venv/bin/pip install --quiet -e . + echo "runtests.sh: (re)creating $here/.venv via python3 + pip..." >&2 + python3 -m venv "$here/.venv" + # Read [project].dependencies from pyproject.toml (one entry per line, + # double-quoted) so the install list never drifts from the manifest. + deps=$(awk -F'"' '/^dependencies = \[/{f=1; next} f && /^\]/{f=0} f && NF>=2 {print $2}' "$here/pyproject.toml") + # shellcheck disable=SC2086 # deps is an intentional word-split list + "$here/.venv/bin/pip" install --quiet $deps else echo "runtests.sh: ERROR: $PYTEST not found and neither 'uv' nor 'python3' is installed." >&2 exit 1 fi + # Mark the venv as freshly built so the staleness check above won't retrigger + # until pyproject.toml changes again. + touch "$here/.venv" fi # --- discover apxs / httpd / php-fpm ---------------------------------------- @@ -93,6 +107,6 @@ esac rm -f "$here/t/logs/cgisock"* 2>/dev/null || true # --- run -------------------------------------------------------------------- -# shellcheck disable=SC2086 # auto_args is an intentional word-split flag list echo "runtests.sh: $PYTEST $auto_args $*" >&2 +# shellcheck disable=SC2086 # auto_args is an intentional word-split flag list exec "$PYTEST" $auto_args "$@"