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
"""
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
"HttpdServer",
"TestClient",
"TestConfig",
+ "clean_modules",
"compile_all",
"need_cgi",
"need_lwp",
from __future__ import annotations
import re
+import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
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,
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)
vars_["serverroot"],
"-f",
vars_["t_conf_file"],
+ "-DFOREGROUND",
]
for d in self.dversion_defines():
argv += ["-D", d]
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
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:
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)
--- /dev/null
+#for testing SSLPassPhraseDialog exec:@ServerRoot@/conf/ssl/httpd-passphrase.pl
+print "httpd\n";
--- /dev/null
+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";
--- /dev/null
+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;
+}
--- /dev/null
+# 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;
+
--- /dev/null
+
+print "perl cgi";
--- /dev/null
+
+print "N"x1024;
--- /dev/null
+use strict;
+
+print "Content-type: text/plain\r\n";
+print "Content-Length: 0\r\n";
+print "\r\n";
+
--- /dev/null
+use strict;
+
+print "Content-type: text/plain\n\n";
+
+for (sort keys %ENV) {
+ print "$_ = $ENV{$_}\n";
+}
--- /dev/null
+use strict;
+
+print "Status: 304 Not Modified\r\n\r\n";
+
--- /dev/null
+
+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";
+
--- /dev/null
+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";
--- /dev/null
+# 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";
--- /dev/null
+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
+;
--- /dev/null
+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
+;
--- /dev/null
+# 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";
--- /dev/null
+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";
--- /dev/null
+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);
--- /dev/null
+
+print "Content-type: text/plain\n\n";
+print "perl cgi";
--- /dev/null
+#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";
+}
--- /dev/null
+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";
+}
--- /dev/null
+print <<EOT
+Status: 200
+Last-Modified: Tue, 15 Feb 2005 15:00:00 GMT
+Content-Type: text/html
+
+Hello world
+EOT
+;
--- /dev/null
+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";
+}
+
--- /dev/null
+print <<EOT
+Location: /foobar.html
+
+EOT
+;
--- /dev/null
+# produces lots of stderr output
+
+print STDERR 'x'x8192;
+
+print "Content-Type: text/plain\n\n";
+
+print "this is stdout";
--- /dev/null
+# closes stderr during script execution
+
+close STDERR;
+
+print "Content-Type: text/plain\n\n";
+
+sleep 1;
+
+print "this is also stdout";
--- /dev/null
+# 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";
--- /dev/null
+print "Content-type: text/plain\n\n";
+
+print $ENV{UNIQUE_ID};
--- /dev/null
+use strict;
+
+print "X-Foo: bar\n";
+print "Content-type: text/plain\n\n";
+
+print "helloworld";
--- /dev/null
+use strict;
+
+$| = 1;
+
+my $cmd = shift;
+do {eval $cmd; print } while <>;
--- /dev/null
+$| = 1;
+
+print && select undef,undef,undef,.2 while <>;
--- /dev/null
+
+print "Content-type: text/html\n\n";
+
+foreach my $key (keys %ENV) {
+ if ($key eq "QUERY_STRING") {
+ print "$key --> $ENV{$key}\n";
+ }
+}
--- /dev/null
+
+($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)
--- /dev/null
+
+# 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);
--- /dev/null
+
+# numbers.pl
+# program rewrite map for mod_rewrite testing
+#
+funk cold medina.
+$|=1;
+while (<STDIN>) {
+ print $_;
+}
only=""
apxs_opt=""
flags=""
+pysuite_flags=""
paths=""
expect_apxs=0
expect_flagval=0
--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" ;;
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() {