]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
Merge of 1936270
authorJim Jagielski <jim@apache.org>
Fri, 17 Jul 2026 15:56:06 +0000 (15:56 +0000)
committerJim Jagielski <jim@apache.org>
Fri, 17 Jul 2026 15:56:06 +0000 (15:56 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1936271 13f79535-47bb-0310-9956-ffa450edef68

test/README
test/pyhttpd/runtests.sh [new file with mode: 0755]
test/pytest_suite/runtests.sh

index 9f8be502b88fc0747e899de5757b8fe986908203..e1e4eb929950f308cbadd0f222c578de28edb971 100644 (file)
@@ -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 <suite>/.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 (executable)
index 0000000..3ec52b5
--- /dev/null
@@ -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 "$@"
index a22501012ae0f4218a4e374f0e4af66a33638eca..1bbb62cb022ddba2390110092df3d7173d43abd4 100755 (executable)
@@ -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 "$@"