blob: 5490974b37edc96452c56d3080aa8bc1be36a076 [file]
import sys
import os
import re
import subprocess
import pytest
sys.path.append(os.path.join(os.path.dirname(__file__), '.'))
from pyhttpd.env import HttpdTestEnv
# Imported here before pytest_plugins loads it, so register it for
# assertion rewriting first, to avoid PytestAssertRewriteWarning.
pytest.register_assert_rewrite("pyhttpd.depends")
from pyhttpd.depends import apply_gate, strict_optional_enabled
# pyhttpd.plugin registers the command-line options of both suites;
# pyhttpd.gating evaluates the need_* and backport markers of both;
# pyhttpd.depends provides --strict-optional and the needs_dependency marker.
# pytest_plugins is only honoured in the rootdir conftest, and there is no
# pytest.ini in this tree, so this is the only place it can be declared.
pytest_plugins = ["pyhttpd.plugin", "pyhttpd.gating", "pyhttpd.depends",
"pyhttpd.classic_errorlog",
# pytester: the self-tests of these plugins
"pytester"]
# Everything below test/modules/ drives a real httpd.
MODULES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'modules')
def pytest_report_header(config, start_path):
from pyhttpd.build import config_ini_path, resolve_build
info = resolve_build(config)
if info is None:
# no httpd found (neither --apxs/--httpd nor a build generated
# config.ini); nothing to report
return None
line = f"[apache httpd: {info.version_str}, mpm: {info.mpm}, {info.prefix}, via {info.source}]"
if info.source != "config.ini" and config_ini_path().is_file():
# the modules/ suite tests the same httpd, but still takes its
# ports and client tools from here
line += f"\n[modules/ suite: ports and clients from {config_ini_path()}]"
return line
def _generate_config_ini(config):
"""Generate a config.ini from --httpd/--conf when no config.ini exists."""
httpd_opt = config.getoption("--httpd")
conf_opt = config.getoption("--conf")
if not httpd_opt:
return
httpd_path = os.path.abspath(httpd_opt)
prefix = os.path.dirname(os.path.dirname(httpd_path))
bindir = os.path.join(prefix, "bin")
libexecdir = os.path.join(prefix, "modules")
is_win = sys.platform == "win32"
# version from httpd -v
version = "2.5.0"
try:
p = subprocess.run([httpd_path, "-v"], capture_output=True, text=True)
m = re.search(r"Apache/(\S+)", p.stdout)
if m:
version = m.group(1)
except Exception:
pass
# modules and mpm from conf file
dso_modules = []
mpm_modules = []
if conf_opt:
conf_path = os.path.abspath(conf_opt)
try:
with open(conf_path) as f:
for line in f:
line = line.strip()
if line.startswith("#"):
continue
m = re.match(r"LoadModule\s+(\S+)_module", line)
if m:
name = m.group(1)
if name.startswith("mpm_"):
mpm_modules.append(name)
else:
dso_modules.append(name)
except Exception:
pass
if not mpm_modules:
mpm_modules = ["mpm_winnt"] if is_win else ["mpm_event"]
if is_win and "cgid" in dso_modules and "cgi" not in dso_modules:
dso_modules = ["cgi" if m == "cgid" else m for m in dso_modules]
apachectl = httpd_path if is_win else os.path.join(bindir, "apachectl")
if is_win:
prefix_curl = os.path.join(prefix, "bin", "curl.exe")
curl_bin = prefix_curl if os.path.isfile(prefix_curl) else "curl.exe"
else:
curl_bin = "curl"
test_dir = os.path.dirname(os.path.abspath(__file__))
gen_dir = os.path.join(test_dir, "gen")
ini_content = f"""[global]
prefix = {prefix}
exec_prefix = ${{prefix}}
bindir = ${{prefix}}/bin
sbindir = ${{exec_prefix}}/sbin
libdir = ${{exec_prefix}}/lib
libexecdir = ${{exec_prefix}}/modules
apr_bindir = ${{prefix}}
apxs = ${{prefix}}/bin/apxs
apachectl = {apachectl}
curl_bin = {curl_bin}
nghttp = nghttp
h2load = h2load
pre_built_test_binaries_dir = ${{libexecdir}}
[httpd]
version = {version}
name = httpd
dso_modules = {' '.join(dso_modules)}
mpm_modules = {' '.join(mpm_modules)}
[test]
gen_dir = {gen_dir}
http_port = 5002
https_port = 5001
proxy_port = 5003
http_port2 = 5004
ws_port = 5100
http_tld = tests.httpd.apache.org
test_dir = {test_dir}
test_src_dir = {test_dir}
"""
ini_path = os.path.join(test_dir, "pyhttpd", "config.ini")
with open(ini_path, "w") as f:
f.write(ini_content)
def pytest_configure(config):
if config.getoption("--clean-modules", default=False):
# the c-modules/ of pytest_suite are cleaned by its own fixture
from pyhttpd.cmodules import clean_test_modules
clean_test_modules(os.path.dirname(os.path.abspath(__file__)))
httpd_opt = config.getoption("--httpd", default=None)
if not httpd_opt:
return
ini_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"pyhttpd", "config.ini")
if not os.path.isfile(ini_path):
# No build-generated config.ini (typical on Windows without
# autoconf) — generate one from --httpd/--conf so the framework
# has something to read.
_generate_config_ini(config)
def pytest_collection_modifyitems(config, items):
"""Gate the integration suite on the clients the framework itself needs.
curl is not an optional dependency of the individual test cases that
call curl_get(): apache_restart() proves the server came up with
is_live(), and is_live() is a curl request. Without curl every test
that starts an httpd dies in fixture setup, which is neither a skip nor
a useful failure. So it is gated once here for the whole of
test/modules/ rather than test by test.
Tests outside that directory - the strict-mode self-test - run no httpd
and are left alone.
"""
if not any(str(item.path).startswith(MODULES_DIR) for item in items):
return
env = HttpdTestEnv()
if env.has_curl():
return
missing = [("curl", "no curl available", f"tried to run {env.curl}")]
strict = strict_optional_enabled(config)
for item in items:
if str(item.path).startswith(MODULES_DIR):
apply_gate(item, missing, strict)
def pytest_generate_tests(metafunc):
if "repeat" in metafunc.fixturenames:
count = int(metafunc.config.getoption("repeat"))
metafunc.fixturenames.append('tmp_ct')
metafunc.parametrize('repeat', range(count))