From 26861e789c02f6a7e300847a9aa9402881d094e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 16 Dec 2025 18:27:20 +0100 Subject: [PATCH 1/7] Add requirements.txt for system tests This file lists the required Python packages and versions for running system tests. The easiest way to obtain them is: pip install -r requirements.txt The minimum dnspython version is 2.7.0 because it supports TSIG parsing without validation (for tsig/tests_tsig_hypothesis.py) and wire() (for names/tests_names.py). The minimum pytest version was bumped to 7.0.0 because it supports the collection hook API required by pytest 9. The minimum hypothesis version was set to 4.41.2 as prior versions might have issues on FIPS systems. (cherry picked from commit 628e16d057cd204ca1a3bedd180a810061b1c3f6) --- REUSE.toml | 1 + bin/tests/system/requirements.txt | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 bin/tests/system/requirements.txt diff --git a/REUSE.toml b/REUSE.toml index 00d7805bad..93e4e360a4 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -81,6 +81,7 @@ path = [ "bin/tests/system/pipelined/inputb", "bin/tests/system/pipelined/ref", "bin/tests/system/pipelined/refb", + "bin/tests/system/requirements.txt", "bin/tests/system/rsabigexponent/ns2/dsset-example.in", "bin/tests/system/run.gdb", "bin/tests/system/runtime/ctrl-chars", diff --git a/bin/tests/system/requirements.txt b/bin/tests/system/requirements.txt new file mode 100644 index 0000000000..62f31a3e3b --- /dev/null +++ b/bin/tests/system/requirements.txt @@ -0,0 +1,13 @@ +### Test requirements + +dnspython>=2.7.0 + +cryptography +hypothesis>=4.41.2 +jinja2 +pytest>=7.0.0 +requests + +### Utility packages for executing the tests +flaky +pytest-xdist From b1ebbcb7e08116495750260bbf69b85ae8d5eb2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 16 Dec 2025 17:48:04 +0100 Subject: [PATCH 2/7] Add support for pytest>=9.0.0 Use collection_path rather than the deprecated path argument for pytest_ignore_collect() hook. The collection_path argument was added in pytest 7.0.0, which is the minimum supported pytest version from now on. (cherry picked from commit 093bef9211b252653425f4477aa513d85e260cef) --- .gitlab-ci.yml | 5 +---- bin/tests/system/conftest.py | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ca8c457ef9..1144419de4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -506,10 +506,6 @@ stages: - ( if [ "${CI_DISPOSABLE_ENVIRONMENT}" = "true" ]; then sleep 3000; "$PYTHON" "${CI_PROJECT_DIR}/util/get-running-system-tests.py"; fi ) & - cd bin/tests/system - RET=0 - # With pytest 9.0, there's the following error in pytest_ignore_collect(): - # The (path: py.path.local) argument is deprecated, please use (collection_path: pathlib.Path). - # This should be fixed before pytest 9.1, when it becomes ineffective. - - if pytest --version | grep -F "pytest 9.0" >/dev/null; then echo "filterwarnings = ignore::pytest.PytestRemovedIn9Warning" >> pytest.ini; fi - > ("$PYTEST" --junit-xml="$CI_PROJECT_DIR"/junit_pytest.xml -n "$TEST_PARALLEL_JOBS" | tee pytest.out.txt) || RET=1 - *git_clone_bind9-qa @@ -981,6 +977,7 @@ cross-version-config-tests: untracked: true expire_in: "1 day" when: always + allow_failure: true # GL!11415 # Jobs for regular GCC builds on Alpine Linux 3.23 (amd64) diff --git a/bin/tests/system/conftest.py b/bin/tests/system/conftest.py index c23b459f2f..da19901842 100644 --- a/bin/tests/system/conftest.py +++ b/bin/tests/system/conftest.py @@ -137,7 +137,7 @@ def pytest_configure(config): config.option.dist = "loadscope" -def pytest_ignore_collect(path): +def pytest_ignore_collect(collection_path): # System tests are executed in temporary directories inside # bin/tests/system. These temporary directories contain all files # needed for the system tests - including tests_*.py files. Make sure to @@ -146,9 +146,9 @@ def pytest_ignore_collect(path): # convenience symlinks to those test directories. In both of those # cases, the system test name (directory) contains an underscore, which # is otherwise and invalid character for a system test name. - match = SYSTEM_TEST_NAME_RE.search(str(path)) + match = SYSTEM_TEST_NAME_RE.search(str(collection_path)) if match is None: - isctest.log.warning("unexpected test path: %s (ignored)", path) + isctest.log.warning("unexpected test path: %s (ignored)", collection_path) return True system_test_name = match.groups()[0] return "_" in system_test_name From 08ee4c75a5084444fc3e7f138962ad48652cc3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 30 Dec 2025 13:42:53 +0100 Subject: [PATCH 3/7] Remove pytest<7 compatibility hacks Minimum pytest version has been bumped to 7.0.0, thus these are no longer needed. (cherry picked from commit e276c3d5bde2096759acc7563e4810cdf868e753) --- bin/tests/system/conftest.py | 50 ++++++------------------------------ 1 file changed, 8 insertions(+), 42 deletions(-) diff --git a/bin/tests/system/conftest.py b/bin/tests/system/conftest.py index da19901842..d0aaa2b634 100644 --- a/bin/tests/system/conftest.py +++ b/bin/tests/system/conftest.py @@ -18,7 +18,7 @@ import shutil import subprocess import tempfile import time -from typing import Any, Dict, List, Optional +from typing import Dict, List, Optional import pytest @@ -31,20 +31,6 @@ import isctest # pylint: disable=redefined-outer-name -# ----------------- Older pytest / xdist compatibility ------------------- -# As of 2023-01-11, the minimal supported pytest / xdist versions are -# determined by what is available in EL8/EPEL8: -# - pytest 3.4.2 -# - pytest-xdist 1.24.1 -_pytest_ver = pytest.__version__.split(".") -_pytest_major_ver = int(_pytest_ver[0]) -if _pytest_major_ver < 7: - # pytest.Stash/pytest.StashKey mechanism has been added in 7.0.0 - # for older versions, use regular dictionary with string keys instead - FIXTURE_OK = "fixture_ok" # type: Any -else: - FIXTURE_OK = pytest.StashKey[bool]() # pylint: disable=no-member - # ----------------------- Globals definition ----------------------------- XDIST_WORKER = os.environ.get("PYTEST_XDIST_WORKER", "") @@ -328,19 +314,10 @@ def system_test_name(request): return path.parent.name -def _get_marker(node, marker): - try: - # pytest >= 4.x - return node.get_closest_marker(marker) - except AttributeError: - # pytest < 4.x - return node.get_marker(marker) - - @pytest.fixture(autouse=True) def wait_for_zones_loaded(request, servers): """Wait for all zones to be loaded by specified named instances.""" - instances = _get_marker(request.node, "requires_zones_loaded") + instances = request.node.get_closest_marker("requires_zones_loaded") if not instances: return @@ -480,7 +457,7 @@ def system_test_dir(request, env, system_test_name, expected_artifacts): shutil.copytree(system_test_root / system_test_name, testdir) # Create a convenience symlink with a stable and predictable name - module_name = SYMLINK_REPLACEMENT_RE.sub(r"\1", str(_get_node_path(request.node))) + module_name = SYMLINK_REPLACEMENT_RE.sub(r"\1", str(request.node.path)) symlink_dst = system_test_root / module_name unlink(symlink_dst) symlink_dst.symlink_to(os.path.relpath(testdir, start=system_test_root)) @@ -514,7 +491,7 @@ def system_test_dir(request, env, system_test_name, expected_artifacts): "test failure detected, keeping temporary directory %s", testdir ) keep = True - elif not request.node.stash[FIXTURE_OK]: + elif not request.node.stash["fixture_ok"]: isctest.log.debug( "test setup/teardown issue detected, keeping temporary directory %s", testdir, @@ -581,15 +558,6 @@ def _run_script( isctest.log.debug(" exited with %d", returncode) -def _get_node_path(node) -> Path: - if isinstance(node.parent, pytest.Session): - if _pytest_major_ver >= 8: - return Path() - return Path(node.name) - assert node.parent is not None - return _get_node_path(node.parent) / node.name - - @pytest.fixture(scope="module") def shell(env, system_test_dir): """Function to call a shell script with arguments.""" @@ -703,13 +671,11 @@ def system_test( pytest.fail(f"get_core_dumps.sh exited with {exc.returncode}") os.environ.update(env) # Ensure pytests have the same env vars as shell tests. - isctest.log.info(f"test started: {_get_node_path(request.node)}") + isctest.log.info(f"test started: {request.node.path}") port = int(env["PORT"]) isctest.log.info("using port range: <%d, %d>", port, port + PORTS_PER_TEST - 1) - if not hasattr(request.node, "stash"): # compatibility with pytest<7.0.0 - request.node.stash = {} # use regular dict instead of pytest.Stash - request.node.stash[FIXTURE_OK] = True + request.node.stash["fixture_ok"] = True # Perform checks which may skip this test. check_net_interfaces() @@ -718,7 +684,7 @@ def system_test( # Store the fact that this fixture hasn't successfully finished yet. # This is checked before temporary directory teardown to decide whether # it's okay to remove the directory. - request.node.stash[FIXTURE_OK] = False + request.node.stash["fixture_ok"] = False setup_test() try: @@ -729,7 +695,7 @@ def system_test( isctest.log.debug("test(s) finished") stop_servers() get_core_dumps() - request.node.stash[FIXTURE_OK] = True + request.node.stash["fixture_ok"] = True @pytest.fixture(scope="module") From 2b56b9beea38e30e7485fe0210660aa64cf4f77b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 30 Dec 2025 13:45:50 +0100 Subject: [PATCH 4/7] Bump the minimum required python version to 3.10 Drop support of EoL python versions for running system tests. The maintenance cost of supporting end of life ecosystem, especially Python 3.6 on EL8 and the related outdated packages (pytest, dnspython, ...), has become unreasonable. (cherry picked from commit ac8e2905b89d69318ef5351dbffcedcc2f8bdcf9) --- bin/tests/system/conftest.py | 13 +++++-------- configure.ac | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/bin/tests/system/conftest.py b/bin/tests/system/conftest.py index d0aaa2b634..fe3e406367 100644 --- a/bin/tests/system/conftest.py +++ b/bin/tests/system/conftest.py @@ -19,6 +19,7 @@ import subprocess import tempfile import time from typing import Dict, List, Optional +import sys import pytest @@ -30,6 +31,8 @@ import isctest # Silence warnings caused by passing a pytest fixture to another fixture. # pylint: disable=redefined-outer-name +if sys.version_info[1] < 10: + raise RuntimeError("Python 3.10 or newer is required to run system tests.") # ----------------------- Globals definition ----------------------------- @@ -409,12 +412,6 @@ def system_test_dir(request, env, system_test_name, expected_artifacts): assert all(res.outcome == "passed" for res in test_results.values()) return "passed" - def unlink(path): - try: - path.unlink() # missing_ok=True isn't available on Python 3.6 - except FileNotFoundError: - pass - def check_artifacts(source_dir, run_dir): def check_artifacts_recursive(dcmp): def artifact_expected(path, expected): @@ -459,7 +456,7 @@ def system_test_dir(request, env, system_test_name, expected_artifacts): # Create a convenience symlink with a stable and predictable name module_name = SYMLINK_REPLACEMENT_RE.sub(r"\1", str(request.node.path)) symlink_dst = system_test_root / module_name - unlink(symlink_dst) + symlink_dst.unlink(missing_ok=True) symlink_dst.symlink_to(os.path.relpath(testdir, start=system_test_root)) isctest.log.init_module_logger(system_test_name, testdir) @@ -508,7 +505,7 @@ def system_test_dir(request, env, system_test_name, expected_artifacts): isctest.log.deinit_module_logger() if not keep: shutil.rmtree(testdir) - unlink(symlink_dst) + symlink_dst.unlink(missing_ok=True) @pytest.fixture(scope="module") diff --git a/configure.ac b/configure.ac index 125e31d26b..300035968b 100644 --- a/configure.ac +++ b/configure.ac @@ -240,7 +240,7 @@ AM_CONDITIONAL([HAVE_PERL], [test -n "$PERL"]) # # Python is optional, it is used only by some of the system test scripts. # -AM_PATH_PYTHON([3.6], [], [:]) +AM_PATH_PYTHON([3.10], [], [:]) AM_CONDITIONAL([HAVE_PYTHON], [test "$PYTHON" != ":"]) AC_PATH_PROGS([PYTEST], [pytest-3 py.test-3 pytest py.test pytest-pypy], []) From 00b21e55fa5172281fee9870644c3ca92163cfc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 30 Dec 2025 14:29:51 +0100 Subject: [PATCH 5/7] Remove compatibility hacks for dnspython<2.7.0 The minimum required dnspython version is now 2.7.0 and those compatibility hacks can be dropped. (cherry picked from commit ce385d8100b9dfc14b6233453ea7dbcf0db56b3b) --- bin/tests/system/bailiwick/tests_bailiwick.py | 3 - bin/tests/system/checkds/tests_checkds.py | 1 - bin/tests/system/dispatch/tests_connreset.py | 1 - .../tests_malformed_dnskey.py | 3 - bin/tests/system/dnstap/tests_dnstap.py | 1 - bin/tests/system/doth/tests_gnutls.py | 2 +- bin/tests/system/glue/tests_glue.py | 3 - bin/tests/system/isctest/asyncserver.py | 9 --- bin/tests/system/isctest/check.py | 20 ++----- bin/tests/system/isctest/compat.py | 56 ------------------- .../system/isctest/hypothesis/strategies.py | 9 +-- bin/tests/system/isctest/name.py | 3 - bin/tests/system/isctest/query.py | 7 +-- bin/tests/system/isctest/run.py | 4 +- bin/tests/system/limits/tests_limits.py | 3 - bin/tests/system/names/tests_names.py | 4 -- bin/tests/system/nsec3-answer/tests_nsec3.py | 1 - bin/tests/system/optout/tests_optout.py | 2 +- bin/tests/system/rpzextra/tests_rpzextra.py | 7 +-- bin/tests/system/shutdown/tests_shutdown.py | 2 +- bin/tests/system/statschannel/tests_json.py | 2 +- bin/tests/system/statschannel/tests_xml.py | 2 +- bin/tests/system/tcp/tests_tcp.py | 2 +- .../system/timeouts/tests_tcp_timeouts.py | 2 +- .../system/tsig/tests_tsig_hypothesis.py | 2 - .../system/tsiggss/tests_isc_spnego_flaws.py | 2 +- bin/tests/system/wildcard/tests_wildcard.py | 2 +- 27 files changed, 25 insertions(+), 130 deletions(-) delete mode 100644 bin/tests/system/isctest/compat.py diff --git a/bin/tests/system/bailiwick/tests_bailiwick.py b/bin/tests/system/bailiwick/tests_bailiwick.py index d0313346d4..76540008db 100644 --- a/bin/tests/system/bailiwick/tests_bailiwick.py +++ b/bin/tests/system/bailiwick/tests_bailiwick.py @@ -17,9 +17,6 @@ import dns.message import pytest -# isctest.asyncserver requires dnspython >= 2.0.0 -pytest.importorskip("dns", minversion="2.0.0") - import isctest from isctest.instance import NamedInstance diff --git a/bin/tests/system/checkds/tests_checkds.py b/bin/tests/system/checkds/tests_checkds.py index 8e6660a6ef..50dc2c0222 100755 --- a/bin/tests/system/checkds/tests_checkds.py +++ b/bin/tests/system/checkds/tests_checkds.py @@ -21,7 +21,6 @@ import time import isctest import pytest -pytest.importorskip("dns", minversion="2.0.0") import dns.exception import dns.message import dns.name diff --git a/bin/tests/system/dispatch/tests_connreset.py b/bin/tests/system/dispatch/tests_connreset.py index 0b78fb8e0a..01a1bbc5c0 100644 --- a/bin/tests/system/dispatch/tests_connreset.py +++ b/bin/tests/system/dispatch/tests_connreset.py @@ -14,7 +14,6 @@ import pytest import isctest -pytest.importorskip("dns") import dns.message pytestmark = pytest.mark.extra_artifacts( diff --git a/bin/tests/system/dnssec-malformed-dnskey/tests_malformed_dnskey.py b/bin/tests/system/dnssec-malformed-dnskey/tests_malformed_dnskey.py index c028b5259b..05545c1ef4 100644 --- a/bin/tests/system/dnssec-malformed-dnskey/tests_malformed_dnskey.py +++ b/bin/tests/system/dnssec-malformed-dnskey/tests_malformed_dnskey.py @@ -16,9 +16,6 @@ import os import pytest pytest.importorskip("cryptography") -pytest.importorskip( - "dns", minversion="2.7.0" -) # dns.dnssec.sign_zone(deterministic=...) needed from cryptography.hazmat.primitives.asymmetric import ec diff --git a/bin/tests/system/dnstap/tests_dnstap.py b/bin/tests/system/dnstap/tests_dnstap.py index 3c179c2825..bd254a1a86 100644 --- a/bin/tests/system/dnstap/tests_dnstap.py +++ b/bin/tests/system/dnstap/tests_dnstap.py @@ -17,7 +17,6 @@ import re import isctest import pytest -pytest.importorskip("dns", minversion="2.0.0") import dns.rrset pytestmark = pytest.mark.extra_artifacts( diff --git a/bin/tests/system/doth/tests_gnutls.py b/bin/tests/system/doth/tests_gnutls.py index 9c897714ef..42ef3f6973 100644 --- a/bin/tests/system/doth/tests_gnutls.py +++ b/bin/tests/system/doth/tests_gnutls.py @@ -18,7 +18,7 @@ import time import pytest -pytest.importorskip("dns") +import dns import dns.exception import dns.name import dns.rdataclass diff --git a/bin/tests/system/glue/tests_glue.py b/bin/tests/system/glue/tests_glue.py index faf251367d..612e6bef99 100644 --- a/bin/tests/system/glue/tests_glue.py +++ b/bin/tests/system/glue/tests_glue.py @@ -12,12 +12,9 @@ import dns.flags import dns.message -import pytest import isctest -pytest.importorskip("dns", minversion="2.0.0") - def test_glue_full_glue_set(): """test that a ccTLD referral gets a full glue set from the root zone""" diff --git a/bin/tests/system/isctest/asyncserver.py b/bin/tests/system/isctest/asyncserver.py index d2b22d7c12..eeca1b88ec 100644 --- a/bin/tests/system/isctest/asyncserver.py +++ b/bin/tests/system/isctest/asyncserver.py @@ -113,7 +113,6 @@ class AsyncServer: tcp_handler: Optional[_TcpHandler], pidfile: Optional[str] = None, ) -> None: - self._abort_if_on_dnspython_version_less_than_2_0_0() logging.basicConfig( format="%(asctime)s %(levelname)8s %(message)s", level=os.environ.get("ANS_LOG_LEVEL", "INFO").upper(), @@ -141,14 +140,6 @@ class AsyncServer: self._pidfile: Optional[str] = pidfile self._work_done: Optional[asyncio.Future] = None - @classmethod - def _abort_if_on_dnspython_version_less_than_2_0_0(cls) -> None: - if dns.version.MAJOR < 2: - error = f"Using {cls.__name__} requires dnspython >= 2.0.0; " - error += 'add `pytest.importorskip("dns", minversion="2.0.0")` ' - error += "to the test module to skip this test." - raise RuntimeError(error) - def _get_ipv4_address_from_directory_name(self) -> str: containing_directory = pathlib.Path().absolute().stem match_result = re.match(r"ans(?P\d+)", containing_directory) diff --git a/bin/tests/system/isctest/check.py b/bin/tests/system/isctest/check.py index e7187b8853..0a3b199ea4 100644 --- a/bin/tests/system/isctest/check.py +++ b/bin/tests/system/isctest/check.py @@ -13,13 +13,13 @@ import shutil from typing import cast, List, Optional import dns.edns +from dns.edns import EDECode, EDEOption import dns.flags import dns.message import dns.rcode import dns.zone import isctest.log -from isctest.compat import dns_rcode, EDECode, EDEOption def rcode(message: dns.message.Message, expected_rcode) -> None: @@ -27,19 +27,19 @@ def rcode(message: dns.message.Message, expected_rcode) -> None: def noerror(message: dns.message.Message) -> None: - rcode(message, dns_rcode.NOERROR) + rcode(message, dns.rcode.NOERROR) def notimp(message: dns.message.Message) -> None: - rcode(message, dns_rcode.NOTIMP) + rcode(message, dns.rcode.NOTIMP) def refused(message: dns.message.Message) -> None: - rcode(message, dns_rcode.REFUSED) + rcode(message, dns.rcode.REFUSED) def servfail(message: dns.message.Message) -> None: - rcode(message, dns_rcode.SERVFAIL) + rcode(message, dns.rcode.SERVFAIL) def adflag(message: dns.message.Message) -> None: @@ -82,10 +82,6 @@ def _extract_ede_options( def noede(message: dns.message.Message) -> None: """Check that message contains no EDE option.""" - if not hasattr(dns.edns, "EDECode"): - # dnspython<2.2.0 doesn't support EDE, skip check - return - ede_options = _extract_ede_options(message) assert not ede_options, f"unexpected EDE options {ede_options} in {message}" @@ -94,10 +90,6 @@ def ede( message: dns.message.Message, code: EDECode, text: Optional[str] = None ) -> None: """Check if message contains expected EDE code (and its text).""" - if not hasattr(dns.edns, "EDECode"): - # dnspython<2.2.0 doesn't support EDE, skip check - return - msg_opts = _extract_ede_options(message) matching_opts = [opt for opt in msg_opts if opt.code == code] @@ -204,7 +196,7 @@ def is_executable(cmd: str, errmsg: str) -> None: def named_alive(named_proc, resolver_ip): assert named_proc.poll() is None, "named isn't running" msg = isctest.query.create("version.bind", "TXT", "CH") - isctest.query.tcp(msg, resolver_ip, expected_rcode=dns_rcode.NOERROR) + isctest.query.tcp(msg, resolver_ip, expected_rcode=dns.rcode.NOERROR) def notauth(message: dns.message.Message) -> None: diff --git a/bin/tests/system/isctest/compat.py b/bin/tests/system/isctest/compat.py deleted file mode 100644 index 3dc5810745..0000000000 --- a/bin/tests/system/isctest/compat.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# SPDX-License-Identifier: MPL-2.0 -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, you can obtain one at https://mozilla.org/MPL/2.0/. -# -# See the COPYRIGHT file distributed with this work for additional -# information regarding copyright ownership. - -from typing import Any, TYPE_CHECKING - -import dns.edns -import dns.rcode - -# compatiblity with dnspython<2.0.0 -try: - # In dnspython>=2.0.0, dns.rcode.Rcode class is available - # pylint: disable=invalid-name - dns_rcode = dns.rcode.Rcode # type: Any -except AttributeError: - # In dnspython<2.0.0, selected rcodes are available as integers directly - # from dns.rcode - dns_rcode = dns.rcode - - -if TYPE_CHECKING: - EDECode = dns.edns.EDECode - EDEOption = dns.edns.EDEOption -else: - try: # compatiblity with dnspython<2.2.0 - EDECode = dns.edns.EDECode - except AttributeError: - # In dnspython<2.2.0, the dns.edns.EDECode doesn't exist. - # - # The primary use-case is for us to use existing EDECode objects from the - # class, e.g. EDECode.FILTERED. To mimick this behavior, use a string - # factory that just turns the attribute name into a string. - # - # The used compatibility hack doesn't really matter (as long as EDECode.xxx - # doesn't raise exception), as with dnspython versions prior to 2.2.0, any - # EDE checking will be skipped anyway. - class _CompatEDECode: - def __getattr__(self, name: str) -> str: - return name - - EDECode = _CompatEDECode() - try: - EDEOption = dns.edns.EDEOption - except AttributeError: - # In dnspython<2.2.0, the dns.edns.EDEOption doesn't exist, so we stub it to be - # able to use it in type annotations. - class EDEOption: - def __new__(cls, *args, **kwargs): - raise RuntimeError("Using EDEOption requires dnspython>=2.2.0") diff --git a/bin/tests/system/isctest/hypothesis/strategies.py b/bin/tests/system/isctest/hypothesis/strategies.py index a3f9eac2b2..e8badc6802 100644 --- a/bin/tests/system/isctest/hypothesis/strategies.py +++ b/bin/tests/system/isctest/hypothesis/strategies.py @@ -143,13 +143,8 @@ def dns_names( RDATACLASS_MAX = RDATATYPE_MAX = 65535 -try: - dns_rdataclasses = builds(dns.rdataclass.RdataClass, integers(0, RDATACLASS_MAX)) - dns_rdatatypes = builds(dns.rdatatype.RdataType, integers(0, RDATATYPE_MAX)) -except AttributeError: - # In old dnspython versions, RDataTypes and RDataClasses are int and not enums. - dns_rdataclasses = integers(0, RDATACLASS_MAX) # type: ignore - dns_rdatatypes = integers(0, RDATATYPE_MAX) # type: ignore +dns_rdataclasses = builds(dns.rdataclass.RdataClass, integers(0, RDATACLASS_MAX)) +dns_rdatatypes = builds(dns.rdatatype.RdataType, integers(0, RDATATYPE_MAX)) dns_rdataclasses_without_meta = dns_rdataclasses.filter(dns.rdataclass.is_metaclass) # NOTE: This should really be `dns_rdatatypes_without_meta = dns_rdatatypes_without_meta.filter(dns.rdatatype.is_metatype()`, diff --git a/bin/tests/system/isctest/name.py b/bin/tests/system/isctest/name.py index 7cf3e8d696..374eb8ba60 100644 --- a/bin/tests/system/isctest/name.py +++ b/bin/tests/system/isctest/name.py @@ -11,9 +11,6 @@ from typing import Container, Iterable, FrozenSet -import pytest - -pytest.importorskip("dns", minversion="2.3.0") # NameRelation from dns.name import Name, NameRelation import dns.zone import dns.rdatatype diff --git a/bin/tests/system/isctest/query.py b/bin/tests/system/isctest/query.py index b11b165f85..f0c782c1cb 100644 --- a/bin/tests/system/isctest/query.py +++ b/bin/tests/system/isctest/query.py @@ -17,7 +17,6 @@ import dns.query import dns.message import isctest.log -from isctest.compat import dns_rcode QUERY_TIMEOUT = 10 @@ -30,7 +29,7 @@ def generic_query( source: Optional[str] = None, timeout: int = QUERY_TIMEOUT, attempts: int = 10, - expected_rcode: dns_rcode = None, + expected_rcode: Optional[dns.rcode.Rcode] = None, log_query: bool = True, log_response: bool = True, ) -> Any: @@ -61,9 +60,9 @@ def generic_query( return res time.sleep(1) if expected_rcode is not None: - last_rcode = dns_rcode.to_text(res.rcode()) if res else None + last_rcode = dns.rcode.to_text(res.rcode()) if res else None isctest.log.debug( - f"isc.query.{query_func.__name__}(): expected rcode={dns_rcode.to_text(expected_rcode)}, last rcode={last_rcode}" + f"isc.query.{query_func.__name__}(): expected rcode={dns.rcode.to_text(expected_rcode)}, last rcode={last_rcode}" ) raise dns.exception.Timeout diff --git a/bin/tests/system/isctest/run.py b/bin/tests/system/isctest/run.py index 3fbc6ac341..6bc77672c7 100644 --- a/bin/tests/system/isctest/run.py +++ b/bin/tests/system/isctest/run.py @@ -16,9 +16,9 @@ from typing import Optional import isctest.log import isctest.text -from isctest.compat import dns_rcode import dns.message +import dns.rcode class CmdResult: @@ -149,4 +149,4 @@ def get_custom_named_instance(assumed_ns, ports): def assert_custom_named_is_alive(named_proc, resolver_ip): assert named_proc.poll() is None, "named isn't running" msg = dns.message.make_query("version.bind", "TXT", "CH") - isctest.query.tcp(msg, resolver_ip, expected_rcode=dns_rcode.NOERROR) + isctest.query.tcp(msg, resolver_ip, expected_rcode=dns.rcode.NOERROR) diff --git a/bin/tests/system/limits/tests_limits.py b/bin/tests/system/limits/tests_limits.py index ca7214a9de..6949838ec7 100644 --- a/bin/tests/system/limits/tests_limits.py +++ b/bin/tests/system/limits/tests_limits.py @@ -14,9 +14,6 @@ import itertools import isctest import pytest -# Everything from getting a big answer to creating an RR set with thousands -# of records takes minutes of CPU and real time with dnspython < 2.0.0. -pytest.importorskip("dns", minversion="2.0.0") import dns.rrset diff --git a/bin/tests/system/names/tests_names.py b/bin/tests/system/names/tests_names.py index b72fcbec98..38fc983d08 100644 --- a/bin/tests/system/names/tests_names.py +++ b/bin/tests/system/names/tests_names.py @@ -9,10 +9,6 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -import pytest - -pytest.importorskip("dns", minversion="2.7.0") - import isctest diff --git a/bin/tests/system/nsec3-answer/tests_nsec3.py b/bin/tests/system/nsec3-answer/tests_nsec3.py index 2ee8a4fa49..da74a599ff 100755 --- a/bin/tests/system/nsec3-answer/tests_nsec3.py +++ b/bin/tests/system/nsec3-answer/tests_nsec3.py @@ -18,7 +18,6 @@ from typing import Optional, Set, Tuple import pytest -pytest.importorskip("dns", minversion="2.5.0") import dns.dnssec import dns.message import dns.name diff --git a/bin/tests/system/optout/tests_optout.py b/bin/tests/system/optout/tests_optout.py index 08114af511..ce5071ffa7 100755 --- a/bin/tests/system/optout/tests_optout.py +++ b/bin/tests/system/optout/tests_optout.py @@ -19,7 +19,7 @@ import sys import isctest import pytest -pytest.importorskip("dns", minversion="2.0.0") +import dns import dns.exception import dns.message import dns.name diff --git a/bin/tests/system/rpzextra/tests_rpzextra.py b/bin/tests/system/rpzextra/tests_rpzextra.py index e918fa832e..e6a8324942 100644 --- a/bin/tests/system/rpzextra/tests_rpzextra.py +++ b/bin/tests/system/rpzextra/tests_rpzextra.py @@ -15,12 +15,11 @@ import os import pytest -pytest.importorskip("dns", minversion="2.0.0") +import dns import dns.rcode import dns.rrset import isctest -from isctest.compat import dns_rcode pytestmark = pytest.mark.extra_artifacts( @@ -78,13 +77,13 @@ def test_rpz_multiple_views(qname, source, rcode): msg, ip="10.53.0.3", source="10.53.0.2", - expected_rcode=dns_rcode.NOERROR, + expected_rcode=dns.rcode.NOERROR, ) isctest.query.tcp( msg, ip="10.53.0.3", source="10.53.0.5", - expected_rcode=dns_rcode.NOERROR, + expected_rcode=dns.rcode.NOERROR, ) msg = isctest.query.create(qname, "A") diff --git a/bin/tests/system/shutdown/tests_shutdown.py b/bin/tests/system/shutdown/tests_shutdown.py index 52e574543e..71f6826bf5 100755 --- a/bin/tests/system/shutdown/tests_shutdown.py +++ b/bin/tests/system/shutdown/tests_shutdown.py @@ -21,7 +21,7 @@ import time import pytest -pytest.importorskip("dns", minversion="2.0.0") +import dns import dns.exception import isctest diff --git a/bin/tests/system/statschannel/tests_json.py b/bin/tests/system/statschannel/tests_json.py index 105e10d445..9e9a2dc405 100755 --- a/bin/tests/system/statschannel/tests_json.py +++ b/bin/tests/system/statschannel/tests_json.py @@ -14,13 +14,13 @@ from datetime import datetime import pytest +import requests import isctest.mark pytest.register_assert_rewrite("generic") import generic -requests = pytest.importorskip("requests") pytestmark = [ isctest.mark.have_json_c, diff --git a/bin/tests/system/statschannel/tests_xml.py b/bin/tests/system/statschannel/tests_xml.py index 94c0cc1fcc..d41fc4fc76 100755 --- a/bin/tests/system/statschannel/tests_xml.py +++ b/bin/tests/system/statschannel/tests_xml.py @@ -15,13 +15,13 @@ from datetime import datetime import xml.etree.ElementTree as ET import pytest +import requests import isctest.mark pytest.register_assert_rewrite("generic") import generic -requests = pytest.importorskip("requests") pytestmark = [ isctest.mark.have_libxml2, diff --git a/bin/tests/system/tcp/tests_tcp.py b/bin/tests/system/tcp/tests_tcp.py index 8e235596c7..399eaf2049 100644 --- a/bin/tests/system/tcp/tests_tcp.py +++ b/bin/tests/system/tcp/tests_tcp.py @@ -19,7 +19,7 @@ import time import pytest -pytest.importorskip("dns", minversion="2.0.0") +import dns import dns.message import dns.query diff --git a/bin/tests/system/timeouts/tests_tcp_timeouts.py b/bin/tests/system/timeouts/tests_tcp_timeouts.py index cb8defa71e..e25bba5a04 100644 --- a/bin/tests/system/timeouts/tests_tcp_timeouts.py +++ b/bin/tests/system/timeouts/tests_tcp_timeouts.py @@ -18,7 +18,7 @@ import time import pytest -pytest.importorskip("dns", minversion="2.0.0") +import dns import dns.edns import dns.message import dns.name diff --git a/bin/tests/system/tsig/tests_tsig_hypothesis.py b/bin/tests/system/tsig/tests_tsig_hypothesis.py index 9ea5b42643..3a1c05d1d1 100644 --- a/bin/tests/system/tsig/tests_tsig_hypothesis.py +++ b/bin/tests/system/tsig/tests_tsig_hypothesis.py @@ -15,8 +15,6 @@ import time import pytest -pytest.importorskip("dns", minversion="2.7.0") # TSIG parsing without validation - import dns.exception import dns.message import dns.name diff --git a/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py b/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py index 38b424d4c5..7e960ba6e1 100755 --- a/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py +++ b/bin/tests/system/tsiggss/tests_isc_spnego_flaws.py @@ -24,7 +24,7 @@ import pytest import isctest -pytest.importorskip("dns") +import dns import dns.message import dns.name import dns.rdata diff --git a/bin/tests/system/wildcard/tests_wildcard.py b/bin/tests/system/wildcard/tests_wildcard.py index 459e3397b3..b086fc0d13 100755 --- a/bin/tests/system/wildcard/tests_wildcard.py +++ b/bin/tests/system/wildcard/tests_wildcard.py @@ -29,7 +29,7 @@ Limitations - untested properties: import pytest -pytest.importorskip("dns", minversion="2.0.0") +import dns import dns.message import dns.name import dns.query From 39e1eac692aa5d0405da3fadbe65455637960e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Tue, 30 Dec 2025 14:46:23 +0100 Subject: [PATCH 6/7] Remove hypothesis version checks The minimum required hypothesis version has been set in requirements.txt and no longer needs to be checked at runtime. Since the hypothesis package is now a mandatory prerequisite, include it in isctest as the other subpackages. (cherry picked from commit 1291fa1a6da2b4fc52a779a326c438e758e103a8) --- bin/tests/system/isctest/__init__.py | 5 +---- bin/tests/system/isctest/hypothesis/__init__.py | 12 ------------ 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/bin/tests/system/isctest/__init__.py b/bin/tests/system/isctest/__init__.py index 27b915d672..4af893202b 100644 --- a/bin/tests/system/isctest/__init__.py +++ b/bin/tests/system/isctest/__init__.py @@ -11,15 +11,12 @@ from . import check from . import instance +from . import hypothesis from . import query from . import run from . import template from . import log -# isctest.hypothesis is intentionally NOT imported, because it detects proper -# hypothesis support and instructs pytest to skip the tests otherwise. It -# should be manually imported only in the modules that require hypothesis. - # isctest.mark module is intentionally NOT imported, because it relies on # environment variables which might not be set at the time of import of the # `isctest` package. To use the marks, manual `import isctest.mark` is needed diff --git a/bin/tests/system/isctest/hypothesis/__init__.py b/bin/tests/system/isctest/hypothesis/__init__.py index 4cedd8866a..6c2cc00210 100644 --- a/bin/tests/system/isctest/hypothesis/__init__.py +++ b/bin/tests/system/isctest/hypothesis/__init__.py @@ -12,17 +12,5 @@ # This ensures we're using a suitable hypothesis version. A newer version is # required for FIPS-enabled platforms. -import hashlib - -import pytest - -MIN_HYPOTHESIS_VERSION = None - -if "md5" not in hashlib.algorithms_available: - # FIPS mode is enabled, use hypothesis 4.41.2 which doesn't use md5 - MIN_HYPOTHESIS_VERSION = "4.41.2" - -pytest.importorskip("hypothesis", minversion=MIN_HYPOTHESIS_VERSION) - from . import settings from . import strategies From 73a049d99a4ce5addb4a059580f8e62da09d2fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicki=20K=C5=99=C3=AD=C5=BEek?= Date: Wed, 31 Dec 2025 10:53:23 +0100 Subject: [PATCH 7/7] Silence incorrect pylint warnings for hypothesis.assume() With hypothesis>6.148.3, pylint generates W0101: Unreachable code (unreachable) when any code is present after hypothesis.assume(). Silence these until it is fixed upstream. See https://github.com/pylint-dev/pylint/issues/10785#issuecomment-3677224217 (cherry picked from commit 08630ca744a05bacba7eb187274507e64121c965) --- bin/tests/system/nsec3-answer/tests_nsec3.py | 4 ++++ bin/tests/system/tsig/tests_tsig_hypothesis.py | 4 ++++ bin/tests/system/wildcard/tests_wildcard.py | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/bin/tests/system/nsec3-answer/tests_nsec3.py b/bin/tests/system/nsec3-answer/tests_nsec3.py index da74a599ff..72d065e7d5 100755 --- a/bin/tests/system/nsec3-answer/tests_nsec3.py +++ b/bin/tests/system/nsec3-answer/tests_nsec3.py @@ -11,6 +11,10 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +# Silence incorrect warnings cause by hypothesis.assume() +# https://github.com/pylint-dev/pylint/issues/10785#issuecomment-3677224217 +# pylint: disable=unreachable + from dataclasses import dataclass import os from pathlib import Path diff --git a/bin/tests/system/tsig/tests_tsig_hypothesis.py b/bin/tests/system/tsig/tests_tsig_hypothesis.py index 3a1c05d1d1..3e134d5bb0 100644 --- a/bin/tests/system/tsig/tests_tsig_hypothesis.py +++ b/bin/tests/system/tsig/tests_tsig_hypothesis.py @@ -11,6 +11,10 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +# Silence incorrect warnings cause by hypothesis.assume() +# https://github.com/pylint-dev/pylint/issues/10785#issuecomment-3677224217 +# pylint: disable=unreachable + import time import pytest diff --git a/bin/tests/system/wildcard/tests_wildcard.py b/bin/tests/system/wildcard/tests_wildcard.py index b086fc0d13..60d75384f0 100755 --- a/bin/tests/system/wildcard/tests_wildcard.py +++ b/bin/tests/system/wildcard/tests_wildcard.py @@ -27,6 +27,10 @@ Limitations - untested properties: - special behavior of rdtypes like CNAME """ +# Silence incorrect warnings cause by hypothesis.assume() +# https://github.com/pylint-dev/pylint/issues/10785#issuecomment-3677224217 +# pylint: disable=unreachable + import pytest import dns