| """Test-facing helpers for translated httpd tests. | |
| Phase 2 exposes a small, pytest-idiomatic surface that the translated ``.t`` | |
| files use. The Perl Apache::Test vocabulary maps as follows: | |
| * ``plan tests => N`` -> dropped (pytest counts tests itself) | |
| * ``ok $cond`` -> ``assert cond`` | |
| * ``ok t_cmp($got, $exp, $d)`` -> ``assert t_cmp(got, exp), d`` (see :func:`t_cmp`) | |
| * ``need_module 'x'`` (in plan) -> ``@need_module("x")`` marker on the test | |
| * ``have_min_apache_version()`` -> :func:`have_min_apache_version` (runtime gate) | |
| * ``GET`` / ``GET_BODY`` / ... -> methods on the ``http`` fixture (client.py) | |
| The requirement markers (need_*) are evaluated at collection time by | |
| pyhttpd.gating, which probes the target httpd once and skips tests whose | |
| requirements aren't met -- the analog of | |
| Apache::Test's plan-time skipping. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| from typing import Any | |
| import pytest | |
| from pyhttpd.backport import has_change as has_trunk_change # noqa: F401 | |
| from pyhttpd.backport import not_backported # noqa: F401 (one definition, both suites) | |
| # --------------------------------------------------------------------------- # | |
| # Assertions / comparison | |
| # --------------------------------------------------------------------------- # | |
| def t_cmp(received: Any, expected: Any) -> bool: | |
| """Faithful port of Apache::TestUtil::t_cmp / t_is_equal. | |
| * If ``expected`` is a compiled regex (``re.Pattern``), return whether it | |
| matches ``received`` anywhere (Perl ``=~``, i.e. ``re.search``). | |
| * Lists/tuples compare element-wise; dicts compare key/value-wise. | |
| * ``None`` equals only ``None``. | |
| * Otherwise compare by string equality, mirroring Perl's ``eq`` (both sides | |
| stringified) so ``200 == "200"`` holds as in the Perl tests. | |
| Returns a bool so callers write ``assert t_cmp(got, exp), "desc"`` -- the | |
| description becomes the assertion message (the Perl 3rd arg / t_debug). | |
| """ | |
| if isinstance(expected, re.Pattern): | |
| return expected.search(str(received)) is not None | |
| if isinstance(expected, (list, tuple)) and isinstance(received, (list, tuple)): | |
| return len(received) == len(expected) and all( | |
| t_cmp(r, e) for r, e in zip(received, expected) | |
| ) | |
| if isinstance(expected, dict) and isinstance(received, dict): | |
| return received.keys() == expected.keys() and all( | |
| t_cmp(received[k], expected[k]) for k in expected | |
| ) | |
| if received is None or expected is None: | |
| return received is None and expected is None | |
| # Perl `eq` is a string comparison; stringify both sides. | |
| return str(received) == str(expected) | |
| # --------------------------------------------------------------------------- # | |
| # Requirement markers (need_*) -- thin wrappers over pytest.mark | |
| # --------------------------------------------------------------------------- # | |
| # Each marker records its requirement as marker args; pyhttpd.gating reads them | |
| # against the probed HttpdInfo at collection time and skips as needed. | |
| def need_module(*names: str) -> pytest.MarkDecorator: | |
| """Skip unless every named httpd module is available (need_module).""" | |
| return pytest.mark.need_module(*names) | |
| def need_min_apache_version(version: str) -> pytest.MarkDecorator: | |
| """Skip unless the server is at least ``version`` (need_min_apache_version).""" | |
| return pytest.mark.need_min_apache_version(version) | |
| def need_cgi() -> pytest.MarkDecorator: | |
| """Skip unless a CGI module (mod_cgi or mod_cgid) is available (need_cgi).""" | |
| return pytest.mark.need_cgi() | |
| def need_php() -> pytest.MarkDecorator: | |
| """Skip unless a PHP SAPI module is available (need_php).""" | |
| return pytest.mark.need_php() | |
| def need_ssl() -> pytest.MarkDecorator: | |
| """Skip unless mod_ssl is available (need_ssl).""" | |
| return pytest.mark.need_ssl() | |
| def need_lwp() -> pytest.MarkDecorator: | |
| """Always satisfied (the Perl need_lwp gated on the LWP client being present; | |
| the Python client -- httpx -- is always available).""" | |
| return pytest.mark.need_lwp() |