]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
pytest_suite: fix startup failures and add missing Perl helper templates
authorJim Jagielski <jim@apache.org>
Wed, 3 Jun 2026 17:21:22 +0000 (17:21 +0000)
committerJim Jagielski <jim@apache.org>
Wed, 3 Jun 2026 17:21:22 +0000 (17:21 +0000)
Three bugs fixed in the pytest framework:

1. cmodules.py: compiled C test modules were not rebuilt when apxs changed
   (e.g. upgrading from Apache 2.4 to 2.5). The staleness check now also
   compares the .so mtime against apxs itself, so a newer apxs always
   triggers a rebuild and prevents API-signature mismatches (AP24 vs AP25).
   Add clean_modules() to remove all apxs build artifacts (make clean
   equivalent), exposed as --clean-modules pytest option and routed only
   to pytest_suite (not pyhttpd) in run-all-tests.sh.

2. server.py: httpd was started without -DFOREGROUND, so the MPM parent
   forked and exited (code 0) before the port opened. The framework
   misread the fork-exit as a crash and killed the running daemon.
   Add -DFOREGROUND so the Popen process IS the httpd parent.

3. Missing Perl helper scripts: t/conf/extra.conf.in declares a prg-type
   RewriteMap (numbers-prg) whose program file did not exist, causing
   httpd to refuse to start. More broadly, all *.pl CGI scripts,
   ext_filter programs, rewrite-map programs, and the SSL passphrase
   helper referenced by the config and tests were absent.
   Add the full set of *.pl.PL source templates (copied from the
   httpd-tests framework), covering:
     - t/conf/ssl/httpd-passphrase.pl.PL
     - t/htdocs/apache/http_strict/send_hdr.pl.PL (new dir)
     - t/htdocs/modules/cgi/  (28 CGI scripts)
     - t/htdocs/modules/ext_filter/  (2 filter programs, new dir)
     - t/htdocs/modules/negotiation/query/test.pl.PL
     - t/htdocs/modules/rewrite/  (numbers, numbers2, db)
   generate_pl_scripts() regenerates the .pl executables from these
   templates at each test-session start with the correct local perl shebang.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1934936 13f79535-47bb-0310-9956-ffa450edef68

39 files changed:
test/pytest_suite/apache_pytest/__init__.py
test/pytest_suite/apache_pytest/cmodules.py
test/pytest_suite/apache_pytest/server.py
test/pytest_suite/conftest.py
test/pytest_suite/t/conf/ssl/httpd-passphrase.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/apache/http_strict/send_hdr.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/action.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/big.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/bogus-perl.pl.PL [new file with mode: 0755]
test/pytest_suite/t/htdocs/modules/cgi/bogus1k.pl.PL [new file with mode: 0755]
test/pytest_suite/t/htdocs/modules/cgi/empty.pl.PL [new file with mode: 0755]
test/pytest_suite/t/htdocs/modules/cgi/env.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/not-modified.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/nph-102.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/nph-dripfeed.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/nph-foldhdr.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/nph-interim1.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/nph-interim2.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/nph-stderr.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/nph-test.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/ocsp.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/perl.pl.PL [new file with mode: 0755]
test/pytest_suite/t/htdocs/modules/cgi/perl_echo.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/perl_post.pl.PL [new file with mode: 0755]
test/pytest_suite/t/htdocs/modules/cgi/pr37166.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/ranged.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/redirect.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/stderr1.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/stderr2.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/stderr3.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/unique-id.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/cgi/xother.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/ext_filter/eval-cmd.pl.PL [new file with mode: 0755]
test/pytest_suite/t/htdocs/modules/ext_filter/sleepycat.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/negotiation/query/test.pl.PL [new file with mode: 0755]
test/pytest_suite/t/htdocs/modules/rewrite/db.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/rewrite/numbers.pl.PL [new file with mode: 0644]
test/pytest_suite/t/htdocs/modules/rewrite/numbers2.pl.PL [new file with mode: 0755]
test/run-all-tests.sh

index a360c0cee90fc349dd13f25a4437b325c1b84aac..9e1860d5474b86eabbe10f8cf250e7196eb5f0e7 100644 (file)
@@ -5,7 +5,7 @@ Phase 2: test-facing API (t_cmp, need_* markers) used by the translated tests.
 """
 
 from .client import TestClient
-from .cmodules import compile_all
+from .cmodules import clean_modules, compile_all
 from .config import TestConfig
 from .probe import HttpdInfo, probe
 from .server import HttpdServer
@@ -24,6 +24,7 @@ __all__ = [
     "HttpdServer",
     "TestClient",
     "TestConfig",
+    "clean_modules",
     "compile_all",
     "need_cgi",
     "need_lwp",
index db0cb42638303503a01f0f49ad7d7e29cca97b3b..a9b277a7a4df31ab464641f222337e625b415828 100644 (file)
@@ -21,6 +21,7 @@ actually compiling and loading in the smoke test.
 from __future__ import annotations
 
 import re
+import shutil
 import subprocess
 from dataclasses import dataclass
 from pathlib import Path
@@ -199,6 +200,25 @@ def _apxs_cmd(apxs: Path, defines: list[str], include_dir: Path, src: Path) -> l
     return cmd
 
 
+def clean_modules(cmodules_dir: Path) -> None:
+    """Remove all apxs build artifacts from ``cmodules_dir`` (emulate ``make clean``).
+
+    Deletes the ``.libs/`` directory and libtool intermediates (``*.o``, ``*.lo``,
+    ``*.slo``, ``*.la``) from every module subdirectory, and the generated
+    ``apache_httpd_test.h`` header.  Safe to call when nothing has been built yet.
+    The next ``compile_all`` call will rebuild everything from scratch.
+    """
+    for sub in sorted(p for p in cmodules_dir.iterdir() if p.is_dir()):
+        libs = sub / _LIB_SUBDIR
+        if libs.is_dir():
+            shutil.rmtree(libs)
+        for pattern in ("*.o", "*.lo", "*.slo", "*.la"):
+            for f in sub.glob(pattern):
+                f.unlink(missing_ok=True)
+    header = cmodules_dir / "apache_httpd_test.h"
+    header.unlink(missing_ok=True)
+
+
 def compile_all(
     cmodules_dir: Path,
     apxs: Path,
@@ -216,10 +236,13 @@ def compile_all(
     generate_header(cmodules_dir / "apache_httpd_test.h")
 
     mods, skipped = discover(cmodules_dir, info)
+    apxs_mtime = apxs.stat().st_mtime
     loads: list[tuple[str, Path]] = []
     for mod in mods:
         needs = force or not mod.so.exists() or (
             mod.so.stat().st_mtime < mod.src.stat().st_mtime
+        ) or (
+            mod.so.stat().st_mtime < apxs_mtime
         )
         if needs:
             cmd = _apxs_cmd(apxs, defines, cmodules_dir, mod.src)
index f1351af54f2fe31cffd047f13e3683251eb1c66a..cd111231e3d57840507210e50bfc6adf8777542e 100644 (file)
@@ -108,6 +108,7 @@ class HttpdServer:
             vars_["serverroot"],
             "-f",
             vars_["t_conf_file"],
+            "-DFOREGROUND",
         ]
         for d in self.dversion_defines():
             argv += ["-D", d]
index f1b78302befa1ad17af9bbf16fb66f904e4576a0..5ffb1cbd79e25ec4fcdc7dce25bcf4ad11c995c0 100644 (file)
@@ -24,6 +24,7 @@ from pathlib import Path
 import pytest
 
 from apache_pytest import HttpdServer, TestClient, TestConfig, compile_all, probe
+from apache_pytest.cmodules import clean_modules
 from apache_pytest.probe import HttpdInfo
 
 # The suite is self-contained: all assets it needs (t/conf templates, t/htdocs
@@ -58,6 +59,12 @@ def pytest_addoption(parser: pytest.Parser) -> None:
         default=8999,
         help="TCP port for the managed php-fpm pool (default 8999)",
     )
+    group.addoption(
+        "--clean-modules",
+        action="store_true",
+        default=False,
+        help="remove all compiled C-module artifacts before building (emulate make clean)",
+    )
 
 
 def pytest_configure(config: pytest.Config) -> None:
@@ -224,8 +231,11 @@ def framework(request: pytest.FixtureRequest):
 
     cmodule_loads: list[tuple[str, Path]] = []
     if apxs is not None:
+        cmodules_dir = REPO_ROOT / "c-modules"
+        if request.config.getoption("--clean-modules"):
+            clean_modules(cmodules_dir)
         cmodule_loads, _skipped = compile_all(
-            REPO_ROOT / "c-modules", apxs, info, defines=["APACHE2", "APACHE2_4", *defines]
+            cmodules_dir, apxs, info, defines=["APACHE2", "APACHE2_4", *defines]
         )
 
     config.generate(cmodule_loads=cmodule_loads)
diff --git a/test/pytest_suite/t/conf/ssl/httpd-passphrase.pl.PL b/test/pytest_suite/t/conf/ssl/httpd-passphrase.pl.PL
new file mode 100644 (file)
index 0000000..36eba94
--- /dev/null
@@ -0,0 +1,2 @@
+#for testing SSLPassPhraseDialog  exec:@ServerRoot@/conf/ssl/httpd-passphrase.pl
+print "httpd\n";
diff --git a/test/pytest_suite/t/htdocs/apache/http_strict/send_hdr.pl.PL b/test/pytest_suite/t/htdocs/apache/http_strict/send_hdr.pl.PL
new file mode 100644 (file)
index 0000000..95ccd85
--- /dev/null
@@ -0,0 +1,10 @@
+use MIME::Base64;
+use strict;
+use warnings;
+
+print "Content-type: text/plain\r\n";
+print decode_base64($ENV{QUERY_STRING}), "\r\n";
+print "\r\n";
+print "Hi!\n";
+print "SERVERNAME=$ENV{SERVER_NAME}\n";
+print "HTTP_HOST=$ENV{HTTP_HOST}\n";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/action.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/action.pl.PL
new file mode 100644 (file)
index 0000000..19d6529
--- /dev/null
@@ -0,0 +1,10 @@
+use strict;
+
+print "Content-type: text/plain\n\n";
+
+print $ENV{PATH_INFO} . "\n";
+
+if (my $ct = $ENV{CONTENT_LENGTH}) {
+    read STDIN, my $buffer, $ct;
+    print $buffer;
+}
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/big.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/big.pl.PL
new file mode 100644 (file)
index 0000000..636fb66
--- /dev/null
@@ -0,0 +1,22 @@
+# This is a regression test for PR 31247. 
+
+# By sleeping, it ensures that the CGI bucket is left in the brigade
+# (the first 8K will be morphed into a HEAP bucket), and hence *must*
+# be setaside correctly when the byterange filter calls
+# ap_save_brigade().
+
+# Without the fix for PR 31247, the STDOUT content does not get
+# consumed as expected, so the server will deadlock as it tries to
+# consume STDERR after script execution in mod_cgi, whilst the script
+# tries to write to STDOUT.  So close STDERR to avoid that.
+
+close STDERR;
+
+print "Content-type: text/plain\n\n";
+
+print "x"x8192;
+
+sleep 1;
+
+print "x"x8192;
+
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/bogus-perl.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/bogus-perl.pl.PL
new file mode 100755 (executable)
index 0000000..8abb7b2
--- /dev/null
@@ -0,0 +1,2 @@
+
+print "perl cgi";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/bogus1k.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/bogus1k.pl.PL
new file mode 100755 (executable)
index 0000000..7c3d244
--- /dev/null
@@ -0,0 +1,2 @@
+
+print "N"x1024;
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/empty.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/empty.pl.PL
new file mode 100755 (executable)
index 0000000..31e1ae9
--- /dev/null
@@ -0,0 +1,6 @@
+use strict;
+
+print "Content-type: text/plain\r\n";
+print "Content-Length: 0\r\n";
+print "\r\n";
+
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/env.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/env.pl.PL
new file mode 100644 (file)
index 0000000..f776cab
--- /dev/null
@@ -0,0 +1,7 @@
+use strict;
+
+print "Content-type: text/plain\n\n";
+
+for (sort keys %ENV) {
+    print "$_ = $ENV{$_}\n";
+}
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/not-modified.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/not-modified.pl.PL
new file mode 100644 (file)
index 0000000..6684e48
--- /dev/null
@@ -0,0 +1,4 @@
+use strict;
+
+print "Status: 304 Not Modified\r\n\r\n";
+
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/nph-102.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/nph-102.pl.PL
new file mode 100644 (file)
index 0000000..a49eeaa
--- /dev/null
@@ -0,0 +1,9 @@
+
+print "HTTP/1.1 102 Please Wait...\r\n";
+print "Host: nph-102\r\n\r\n";
+
+print "HTTP/1.1 200 OK\r\n";
+print "Content-Type: text/plain\r\n\r\n";
+
+print "this is nph-stdout";
+
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/nph-dripfeed.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/nph-dripfeed.pl.PL
new file mode 100644 (file)
index 0000000..deaf5f6
--- /dev/null
@@ -0,0 +1,17 @@
+print "HTTP/1.1 200 OK\r\n";
+print "Transfer-Encoding: chunked\r\n";
+print "\r\n";
+
+$| = 1;
+
+sub dripfeed {
+    my $s = shift;
+
+    while (length($s)) {
+        select(undef, undef, undef, 0.2);
+        print substr($s, 0, 1);
+        $s = substr($s, 1);
+    }
+}
+
+dripfeed "0005\r\nabcde\r\n1; foo=bar\r\nf\r\n0\r\n\r\n";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/nph-foldhdr.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/nph-foldhdr.pl.PL
new file mode 100644 (file)
index 0000000..67d7e9f
--- /dev/null
@@ -0,0 +1,12 @@
+# produces output with folded response headers
+
+print "HTTP/1.0 200 OK\r\n";
+
+for (1..50) {
+    print "X-Foo-Bar-$_:\n " . 'x'x($_*10) . "\n";
+    print "X-Bar-$_:\n gamm\r\n beta\n theta\r\n";
+}
+
+print "Content-type: \n  text/plain\n\n";
+
+print "hello, world";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/nph-interim1.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/nph-interim1.pl.PL
new file mode 100644 (file)
index 0000000..87c0931
--- /dev/null
@@ -0,0 +1,16 @@
+foreach $i (1..5) {
+print <<EOT1
+HTTP/1.1 100 Continue
+Server: Sausages/1.0
+
+EOT1
+;
+}
+
+print <<EOT2
+HTTP/1.1 200 OK
+Content-Type: text/html
+
+Hello world
+EOT2
+;
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/nph-interim2.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/nph-interim2.pl.PL
new file mode 100644 (file)
index 0000000..8a90b2f
--- /dev/null
@@ -0,0 +1,16 @@
+foreach $i (1..50) {
+print <<EOT1
+HTTP/1.1 100 Continue
+Server: Sausages/1.0
+
+EOT1
+;
+}
+
+print <<EOT2
+HTTP/1.1 200 OK
+Content-Type: text/html
+
+Hello world
+EOT2
+;
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/nph-stderr.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/nph-stderr.pl.PL
new file mode 100644 (file)
index 0000000..601adf9
--- /dev/null
@@ -0,0 +1,12 @@
+# produces lots of stderr output
+
+print "HTTP/1.0 200 OK\r\n";
+
+print STDERR 'x'x8192;
+print "Content-Type: text/plain\r\n\r\n";
+
+print "this is nph-stdout";
+
+close STDOUT;
+
+print STDERR "this is nph-stderr";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/nph-test.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/nph-test.pl.PL
new file mode 100644 (file)
index 0000000..e679931
--- /dev/null
@@ -0,0 +1,9 @@
+print "HTTP/1.0 200 OK\r\n";
+print join("\n",
+     'Content-type: text/html',
+     'Pragma: no-cache',
+     'Cache-control: must-revalidate, no-cache, no-store',
+     'Expires: -1',
+     "\n");
+
+print "ok\n";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/ocsp.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/ocsp.pl.PL
new file mode 100644 (file)
index 0000000..efdbe8b
--- /dev/null
@@ -0,0 +1,78 @@
+use File::Temp qw/:POSIX/;
+
+my $caroot = $ENV{SSL_CA_ROOT};
+
+if (! -d $caroot) {
+    print <<EOT
+Status: 500 Internal Server Error
+Content-Type: text/plain
+
+Cannot find CA root at "$ENV{SSL_CA_ROOT}"
+EOT
+    ;
+    print STDERR "SSL_CA_ROOT env var not set or can't find CA root.\n";
+    exit(1);
+}
+
+chdir($caroot);
+
+my $filein = tmpnam();
+my $fileout = tmpnam();
+
+# Enable slurp mode (read all lines at once)
+local $/;
+
+# Copy STDIN to $filein, which will be used as input for openssl
+open(IN, '>', "$filein") or die "Could not open file '$filein' for write: $!";
+binmode IN;
+print IN <STDIN>;
+close(IN);
+
+my $cmd = 'openssl ocsp -CA certs/ca.crt'.
+    ' -index index.txt'.
+    ' -rsigner certs/server.crt'.
+    ' -rkey keys/server.pem'.
+    ' -reqin ' . $filein .
+    ' -respout ' . $fileout;
+system($cmd);
+
+# Check system result
+my $err = '';
+if ($? == -1) {
+    my $err = "failed to execute '$cmd': $!\n";
+}
+elsif ($? & 127) {
+    my $err = sprintf("child '$cmd' died with signal %d, %s coredump\n",
+        ($? & 127),  ($? & 128) ? 'with' : 'without');
+}
+else {
+    my $rc = $? >> 8;
+    my $err = "child '$cmd' exited with value $rc\n" if $rc;
+}
+
+unlink($filein);
+
+if ($err ne '') {
+    print <<EOT
+Status: 500 Internal Server Error
+Content-Type: text/plain
+
+$err
+EOT
+    ;
+    print STDERR $err;
+    exit(1);
+}
+
+print <<EOT
+Content-Type: application/ocsp-response
+
+EOT
+;
+
+# Copy openssl result from $fileout to STDOUT
+open(OUT, '<', "$fileout") or die "Could not open file '$fileout' for read: $!";
+binmode OUT;
+print <OUT>;
+close(OUT);
+unlink($fileout);
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/perl.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/perl.pl.PL
new file mode 100755 (executable)
index 0000000..51969cc
--- /dev/null
@@ -0,0 +1,3 @@
+
+print "Content-type: text/plain\n\n";
+print "perl cgi";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/perl_echo.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/perl_echo.pl.PL
new file mode 100644 (file)
index 0000000..b7591a6
--- /dev/null
@@ -0,0 +1,14 @@
+#echo some data back to the client
+
+print "Content-type: text/plain\n\n";
+
+if (my $ct = $ENV{CONTENT_LENGTH}) {
+    read STDIN, my $buffer, $ct;
+    print $buffer;
+}
+elsif (my $qs = $ENV{QUERY_STRING}) {
+    print $qs;
+}
+else {
+    print "nada";
+}
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/perl_post.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/perl_post.pl.PL
new file mode 100755 (executable)
index 0000000..e19d204
--- /dev/null
@@ -0,0 +1,23 @@
+local ($buffer, @pairs, $pair, $name, $value);
+
+print "Content-type: text/plain\n\n";
+
+$ENV{'REQUEST_METHOD'} =~ tr/a-z/A-Z/;
+print "$ENV{'REQUEST_METHOD'}\n";
+
+# Read in text
+if ($ENV{'REQUEST_METHOD'} eq "POST") {
+    read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
+} else {
+    $buffer = $ENV{'QUERY_STRING'};
+}
+
+# Split information into name/value pairs
+@pairs = split(/&/, $buffer);
+foreach $pair (@pairs) {
+    ($name, $value) = split(/=/, $pair);
+    $value =~ tr/+/ /;
+    $value =~ s/%(..)/pack("C", hex($1))/eg;
+
+    print "$name: $value\n";
+}
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/pr37166.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/pr37166.pl.PL
new file mode 100644 (file)
index 0000000..f565c5c
--- /dev/null
@@ -0,0 +1,8 @@
+print <<EOT
+Status: 200
+Last-Modified: Tue, 15 Feb 2005 15:00:00 GMT
+Content-Type: text/html
+
+Hello world
+EOT
+;
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/ranged.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/ranged.pl.PL
new file mode 100644 (file)
index 0000000..9d81f5d
--- /dev/null
@@ -0,0 +1,11 @@
+use strict;
+
+print "Content-type: text/plain\n";
+
+if ($ENV{'HTTP_RANGE'} eq 'bytes=5-10/10') {
+    print "Content-Range: bytes 5-10/10\n\n";
+    print "hello\n";
+} else {
+    print "\npardon?\n";
+}
+
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/redirect.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/redirect.pl.PL
new file mode 100644 (file)
index 0000000..9dc93e8
--- /dev/null
@@ -0,0 +1,5 @@
+print <<EOT
+Location: /foobar.html
+
+EOT
+;
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/stderr1.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/stderr1.pl.PL
new file mode 100644 (file)
index 0000000..71b5a11
--- /dev/null
@@ -0,0 +1,7 @@
+# produces lots of stderr output
+
+print STDERR 'x'x8192;
+
+print "Content-Type: text/plain\n\n";
+
+print "this is stdout";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/stderr2.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/stderr2.pl.PL
new file mode 100644 (file)
index 0000000..a1580af
--- /dev/null
@@ -0,0 +1,9 @@
+# closes stderr during script execution
+
+close STDERR;
+
+print "Content-Type: text/plain\n\n";
+
+sleep 1;
+
+print "this is also stdout";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/stderr3.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/stderr3.pl.PL
new file mode 100644 (file)
index 0000000..f4927b5
--- /dev/null
@@ -0,0 +1,8 @@
+# closes stderr during script execution
+
+print "Content-Type: text/plain\n\n";
+print "this is more stdout";
+
+close STDOUT;
+
+print STDERR "this is a post-stdout-closure error message";
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/unique-id.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/unique-id.pl.PL
new file mode 100644 (file)
index 0000000..e54ba40
--- /dev/null
@@ -0,0 +1,3 @@
+print "Content-type: text/plain\n\n";
+
+print $ENV{UNIQUE_ID};
diff --git a/test/pytest_suite/t/htdocs/modules/cgi/xother.pl.PL b/test/pytest_suite/t/htdocs/modules/cgi/xother.pl.PL
new file mode 100644 (file)
index 0000000..7cd005e
--- /dev/null
@@ -0,0 +1,6 @@
+use strict;
+
+print "X-Foo: bar\n";
+print "Content-type: text/plain\n\n";
+
+print "helloworld";
diff --git a/test/pytest_suite/t/htdocs/modules/ext_filter/eval-cmd.pl.PL b/test/pytest_suite/t/htdocs/modules/ext_filter/eval-cmd.pl.PL
new file mode 100755 (executable)
index 0000000..a416b26
--- /dev/null
@@ -0,0 +1,6 @@
+use strict;
+
+$| = 1;
+
+my $cmd = shift;
+do {eval $cmd; print } while <>;
diff --git a/test/pytest_suite/t/htdocs/modules/ext_filter/sleepycat.pl.PL b/test/pytest_suite/t/htdocs/modules/ext_filter/sleepycat.pl.PL
new file mode 100644 (file)
index 0000000..fc9c399
--- /dev/null
@@ -0,0 +1,3 @@
+$| = 1;
+
+print && select undef,undef,undef,.2 while <>;
diff --git a/test/pytest_suite/t/htdocs/modules/negotiation/query/test.pl.PL b/test/pytest_suite/t/htdocs/modules/negotiation/query/test.pl.PL
new file mode 100755 (executable)
index 0000000..b370163
--- /dev/null
@@ -0,0 +1,8 @@
+
+print "Content-type: text/html\n\n";
+
+foreach my $key (keys %ENV) {
+   if ($key eq "QUERY_STRING") {
+       print "$key --> $ENV{$key}\n";
+   }
+}
diff --git a/test/pytest_suite/t/htdocs/modules/rewrite/db.pl.PL b/test/pytest_suite/t/htdocs/modules/rewrite/db.pl.PL
new file mode 100644 (file)
index 0000000..a897a8a
--- /dev/null
@@ -0,0 +1,10 @@
+
+($txtmap, $dbmmap) = @ARGV;
+open(TXT, "<$txtmap");
+dbmopen(%DB, $dbmmap, 0644);
+while (<TXT>) {
+ next if (m|^s*#.*| or m|^s*$|);
+ $DB{$1} = $2 if (m|^\s*(\S+)\s+(\S+)$|);
+}
+dbmclose(%DB);
+close(TXT)
diff --git a/test/pytest_suite/t/htdocs/modules/rewrite/numbers.pl.PL b/test/pytest_suite/t/htdocs/modules/rewrite/numbers.pl.PL
new file mode 100644 (file)
index 0000000..d3d3067
--- /dev/null
@@ -0,0 +1,26 @@
+
+# numbers.pl
+# program rewrite map for mod_rewrite testing
+#
+$|=1;
+my %map = (    1       =>      'one',
+               2       =>      'two',
+               3       =>      'three',
+               4       =>      'four',
+               5       =>      'five',
+               6       =>      'six'   );
+
+while (<STDIN>) {
+       chomp;
+
+       print STDERR "GOT: ->$_<-\n";
+       my $m = $map{$_};
+       print STDERR "MAPPED: ->$_<-\n";
+
+       if ($m) {
+               print STDOUT "$m\n";
+       } else {
+               print STDOUT "NULL";
+       }
+}
+close (LOG);
diff --git a/test/pytest_suite/t/htdocs/modules/rewrite/numbers2.pl.PL b/test/pytest_suite/t/htdocs/modules/rewrite/numbers2.pl.PL
new file mode 100755 (executable)
index 0000000..d450b68
--- /dev/null
@@ -0,0 +1,9 @@
+
+# numbers.pl
+# program rewrite map for mod_rewrite testing
+#
+funk cold medina.
+$|=1;
+while (<STDIN>) {
+       print $_;
+}
index e80655cf4c1d7c0a2157414ae137d53e48e1b1dc..3e041ad30857bb362f246cf500a8509bdd8ce161 100755 (executable)
@@ -41,6 +41,7 @@ config_ini="$here/pyhttpd/config.ini"
 only=""
 apxs_opt=""
 flags=""
+pysuite_flags=""
 paths=""
 expect_apxs=0
 expect_flagval=0
@@ -51,6 +52,7 @@ for arg in "$@"; do
         --only=*) only="${arg#--only=}" ;;
         --apxs)   expect_apxs=1 ;;
         --apxs=*) apxs_opt="${arg#--apxs=}" ;;
+        --clean-modules) pysuite_flags="$pysuite_flags $arg" ;;  # pysuite-only; pyhttpd has no C modules
         -k|-m|-p) flags="$flags $arg"; expect_flagval=1 ;;  # take a value next
         -*)       flags="$flags $arg" ;;
         *)        paths="$paths $arg" ;;
@@ -86,8 +88,9 @@ run_pysuite() {
     fi
     # runtests.sh handles venv + cgisock cleanup + flag assembly.
     # pytest_suite gets both the shared flags and any positional paths.
+    # pysuite_flags holds options only meaningful to this suite (e.g. --clean-modules).
     # shellcheck disable=SC2086
-    ( cd "$suite_dir" && ./runtests.sh --apxs="$apxs_opt" $php_args $flags $paths ) || return $?
+    ( cd "$suite_dir" && ./runtests.sh --apxs="$apxs_opt" $php_args $flags $pysuite_flags $paths ) || return $?
 }
 
 run_pyhttpd() {