From 8e30e8b7baff1857eb24c3f9ea8695d6340525d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Bal=C3=A1=C5=BEik?= Date: Tue, 4 Nov 2025 13:40:26 +0100 Subject: [PATCH 1/3] Don't use pytest.importorskip in isctest.name They can be used outside of test modules (like ans.py custom servers) which leads to pytest.outcomes.Skipped being raised in weird places and skipping of tests which don't need dnspython this new. Remove pytest.importskip from top scope in isctest.name, only run the check when ZoneAnalyzer is used. (cherry picked from commit a94aab94404201a662c1e1d3e53cdeae0f5c8db5) --- bin/tests/system/isctest/name.py | 42 +++++++++---------- bin/tests/system/nsec3-answer/tests_nsec3.py | 16 ++++++- .../system/selftest/tests_zone_analyzer.py | 2 + 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/bin/tests/system/isctest/name.py b/bin/tests/system/isctest/name.py index 7cf3e8d696..a2fe3ac424 100644 --- a/bin/tests/system/isctest/name.py +++ b/bin/tests/system/isctest/name.py @@ -9,15 +9,14 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from typing import Container, Iterable, FrozenSet +from typing import Iterable, FrozenSet -import pytest - -pytest.importorskip("dns", minversion="2.3.0") # NameRelation -from dns.name import Name, NameRelation +import dns.name import dns.zone import dns.rdatatype +from dns.name import Name + def prepend_label(label: str, name: Name) -> Name: return Name((label,) + name.labels) @@ -60,6 +59,7 @@ class ZoneAnalyzer: return cls(zonedb) def __init__(self, zone: dns.zone.Zone): + self._abort_on_old_dnspython() self.zone = zone assert self.zone.origin # mypy hack # based on individual nodes but not relationship between nodes @@ -85,6 +85,14 @@ class ZoneAnalyzer: .union(self.reachable_dnames) ) + def _abort_on_old_dnspython(self): + if not hasattr(dns.name, "NameRelation"): + raise RuntimeError( + "ZoneAnalyzer requires dnspython>=2.3.0 for dns.name.NameRelation support. " + "Use pytest.importorskip('dns', minversion='2.3.0') to the test module to " + "skip this test." + ) + def get_names_with_type(self, rdtype) -> FrozenSet[Name]: return frozenset( name for name in self.zone if self.zone.get_rdataset(name, rdtype) @@ -117,15 +125,15 @@ class ZoneAnalyzer: for name in reachable: relation, _, _ = name.fullcompare(self.zone.origin) if relation in ( - NameRelation.NONE, # out of zone? - NameRelation.SUPERDOMAIN, # parent of apex?! + dns.name.NameRelation.NONE, # out of zone? + dns.name.NameRelation.SUPERDOMAIN, # parent of apex?! ): raise NotImplementedError for maybe_occluded in reachable.copy(): for cut in self.delegations: rel, _, _ = maybe_occluded.fullcompare(cut) - if rel == NameRelation.EQUAL: + if rel == dns.name.NameRelation.EQUAL: # data _on_ a parent-side of a zone cut are in limbo: # - are not really authoritative (except for DS) # - but NS is not really 'occluded' @@ -134,14 +142,14 @@ class ZoneAnalyzer: if maybe_occluded in reachable: reachable.remove(maybe_occluded) - if rel == NameRelation.SUBDOMAIN: + if rel == dns.name.NameRelation.SUBDOMAIN: _mark_occluded(maybe_occluded) # do not break cycle - handle also nested NS and DNAME # DNAME itself is authoritative but nothing under it is reachable for dname in self.dnames: rel, _, _ = maybe_occluded.fullcompare(dname) - if rel == NameRelation.SUBDOMAIN: + if rel == dns.name.NameRelation.SUBDOMAIN: _mark_occluded(maybe_occluded) # do not break cycle - handle also nested NS and DNAME @@ -174,7 +182,7 @@ class ZoneAnalyzer: nce = None # Next closer name, RFC 5155 for zname in self.all_existing_names: relation, _, common_labels = qname.fullcompare(zname) - if relation == NameRelation.SUBDOMAIN: + if relation == dns.name.NameRelation.SUBDOMAIN: if not ce or common_labels > len(ce): # longest match so far ce = zname @@ -190,15 +198,3 @@ class ZoneAnalyzer: """ ce, _ = self.closest_encloser(qname) return Name("*") + ce - - -def is_related_to_any( - test_name: Name, - acceptable_relations: Container[NameRelation], - candidates: Iterable[Name], -) -> bool: - for maybe_parent in candidates: - relation, _, _ = test_name.fullcompare(maybe_parent) - if relation in acceptable_relations: - return True - return False diff --git a/bin/tests/system/nsec3-answer/tests_nsec3.py b/bin/tests/system/nsec3-answer/tests_nsec3.py index 33d16aca7f..785f574f42 100755 --- a/bin/tests/system/nsec3-answer/tests_nsec3.py +++ b/bin/tests/system/nsec3-answer/tests_nsec3.py @@ -14,7 +14,7 @@ from dataclasses import dataclass import os from pathlib import Path -from typing import Optional, Set, Tuple +from typing import Container, Iterable, Optional, Set, Tuple import pytest @@ -45,6 +45,18 @@ ZONE = isctest.name.ZoneAnalyzer.read_path( ) +def is_related_to_any( + test_name: dns.name.Name, + acceptable_relations: Container[dns.name.NameRelation], + candidates: Iterable[dns.name.Name], +) -> bool: + for maybe_parent in candidates: + relation, _, _ = test_name.fullcompare(maybe_parent) + if relation in acceptable_relations: + return True + return False + + def do_test_query( qname: dns.name.Name, qtype: dns.rdatatype.RdataType, server: str, named_port: int ) -> Tuple[dns.message.QueryMessage, "NSEC3Checker"]: @@ -103,7 +115,7 @@ def assume_nx_and_no_delegation(qname: dns.name.Name) -> None: # name must not be under a delegation or DNAME: # it would not work with resolver ns2 assume( - not isctest.name.is_related_to_any( + not is_related_to_any( qname, (dns.name.NameRelation.EQUAL, dns.name.NameRelation.SUBDOMAIN), ZONE.reachable_delegations.union(ZONE.reachable_dnames), diff --git a/bin/tests/system/selftest/tests_zone_analyzer.py b/bin/tests/system/selftest/tests_zone_analyzer.py index 9cea8c7986..3c7a2fc609 100755 --- a/bin/tests/system/selftest/tests_zone_analyzer.py +++ b/bin/tests/system/selftest/tests_zone_analyzer.py @@ -26,6 +26,8 @@ import pytest import isctest import isctest.name +pytest.importorskip("dns.name", minversion="2.3.0") + # set of properies present in the tested zone - read by tests_zone_analyzer.py CATEGORIES = frozenset( [ From 8cf8c3c61336a4a12f9ed23746b66f6d8ad48f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Bal=C3=A1=C5=BEik?= Date: Fri, 31 Oct 2025 12:04:39 +0100 Subject: [PATCH 2/3] Require dnspython>2.0.0 in system tests using asyncserver Maintaining compatibility with pre-2.0.0 dnspython became cumbersome leading to failure in nightly CI jobs which are the only ones that run with dnspython this old. Abort all AsyncServer instances when running with old dnspython. Add an importor skip for all system tests using isctest.asyncserver. (cherry picked from commit 072a82a6c58075c248fc35503bfd31c43c112d69) --- bin/tests/system/chain/tests_sh_chain.py | 3 +++ bin/tests/system/cookie/tests_sh_cookie.py | 3 +++ bin/tests/system/dispatch/tests_connreset.py | 4 +++- bin/tests/system/dnssec/tests_sh_dnssec.py | 3 +++ .../system/fetchlimit/tests_sh_fetchlimit.py | 3 +++ bin/tests/system/forward/tests_sh_forward.py | 3 +++ bin/tests/system/hooks/tests_async_plugin.py | 4 ++++ bin/tests/system/isctest/asyncserver.py | 17 +++++++++-------- bin/tests/system/nsupdate/tests_sh_nsupdate.py | 3 +++ bin/tests/system/qmin/tests_sh_qmin.py | 3 +++ .../system/rpzrecurse/tests_sh_rpzrecurse.py | 3 +++ .../rpzrecurse/tests_sh_rpzrecurse_dnsrps.py | 3 +++ .../system/statistics/tests_sh_statistics.py | 3 +++ bin/tests/system/tsig/tests_badtime.py | 2 ++ bin/tests/system/upforwd/tests_sh_upforwd.py | 2 ++ bin/tests/system/xfer/tests_sh_xfer.py | 3 +++ bin/tests/system/zero/tests_sh_zero.py | 3 +++ 17 files changed, 56 insertions(+), 9 deletions(-) diff --git a/bin/tests/system/chain/tests_sh_chain.py b/bin/tests/system/chain/tests_sh_chain.py index f7d7ac3b01..5aa86d9082 100644 --- a/bin/tests/system/chain/tests_sh_chain.py +++ b/bin/tests/system/chain/tests_sh_chain.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff --git a/bin/tests/system/cookie/tests_sh_cookie.py b/bin/tests/system/cookie/tests_sh_cookie.py index 1c9e8280f5..d5f88dedeb 100644 --- a/bin/tests/system/cookie/tests_sh_cookie.py +++ b/bin/tests/system/cookie/tests_sh_cookie.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff --git a/bin/tests/system/dispatch/tests_connreset.py b/bin/tests/system/dispatch/tests_connreset.py index 0b78fb8e0a..6c7f7140a6 100644 --- a/bin/tests/system/dispatch/tests_connreset.py +++ b/bin/tests/system/dispatch/tests_connreset.py @@ -14,7 +14,9 @@ import pytest import isctest -pytest.importorskip("dns") +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + import dns.message pytestmark = pytest.mark.extra_artifacts( diff --git a/bin/tests/system/dnssec/tests_sh_dnssec.py b/bin/tests/system/dnssec/tests_sh_dnssec.py index a824ee7ef9..7ccf744420 100644 --- a/bin/tests/system/dnssec/tests_sh_dnssec.py +++ b/bin/tests/system/dnssec/tests_sh_dnssec.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ ".hypothesis/examples/*", diff --git a/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py b/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py index 23076980b5..26bdff7fc2 100644 --- a/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py +++ b/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff --git a/bin/tests/system/forward/tests_sh_forward.py b/bin/tests/system/forward/tests_sh_forward.py index 0decea61e5..4ffec3fbd7 100644 --- a/bin/tests/system/forward/tests_sh_forward.py +++ b/bin/tests/system/forward/tests_sh_forward.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff --git a/bin/tests/system/hooks/tests_async_plugin.py b/bin/tests/system/hooks/tests_async_plugin.py index cb70c5c78c..4c3b1f714f 100644 --- a/bin/tests/system/hooks/tests_async_plugin.py +++ b/bin/tests/system/hooks/tests_async_plugin.py @@ -10,6 +10,10 @@ # information regarding copyright ownership. import isctest +import pytest + +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") def test_async_hook(): diff --git a/bin/tests/system/isctest/asyncserver.py b/bin/tests/system/isctest/asyncserver.py index e5277696cc..c91f63a123 100644 --- a/bin/tests/system/isctest/asyncserver.py +++ b/bin/tests/system/isctest/asyncserver.py @@ -116,6 +116,7 @@ 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(), @@ -143,6 +144,14 @@ 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) @@ -1053,7 +1062,6 @@ class AsyncDnsServer(AsyncServer): try: query = dns.message.from_wire(wire) except dns.message.UnknownTSIGKey: - self._abort_if_on_dnspython_version_less_than_2_0_0() self._abort_if_tsig_signed_query_received_unless_acknowledged() query = _DnsMessageWithTsigDisabled.from_wire(wire) except dns.exception.DNSException as exc: @@ -1074,13 +1082,6 @@ class AsyncDnsServer(AsyncServer): response_length = struct.pack("!H", len(response)) yield response_length + response - def _abort_if_on_dnspython_version_less_than_2_0_0(self) -> None: - if dns.version.MAJOR < 2: - error = "Receiving TSIG signed queries 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 _abort_if_tsig_signed_query_received_unless_acknowledged(self) -> None: if self._acknowledge_tsig_dnspython_hacks: return diff --git a/bin/tests/system/nsupdate/tests_sh_nsupdate.py b/bin/tests/system/nsupdate/tests_sh_nsupdate.py index bd41ac64c5..c4ce9ab6f0 100644 --- a/bin/tests/system/nsupdate/tests_sh_nsupdate.py +++ b/bin/tests/system/nsupdate/tests_sh_nsupdate.py @@ -13,6 +13,9 @@ import platform import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "Kxxx*", diff --git a/bin/tests/system/qmin/tests_sh_qmin.py b/bin/tests/system/qmin/tests_sh_qmin.py index 1116f7ec6c..4f5af7f56c 100644 --- a/bin/tests/system/qmin/tests_sh_qmin.py +++ b/bin/tests/system/qmin/tests_sh_qmin.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff --git a/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse.py b/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse.py index 983c6c3c33..3798ed6ee6 100644 --- a/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse.py +++ b/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "dig.out.*", diff --git a/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse_dnsrps.py b/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse_dnsrps.py index a32d70cd7a..427af96a79 100644 --- a/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse_dnsrps.py +++ b/bin/tests/system/rpzrecurse/tests_sh_rpzrecurse_dnsrps.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + import isctest.mark pytestmark = [ diff --git a/bin/tests/system/statistics/tests_sh_statistics.py b/bin/tests/system/statistics/tests_sh_statistics.py index c96bb28808..7f3035b0fa 100644 --- a/bin/tests/system/statistics/tests_sh_statistics.py +++ b/bin/tests/system/statistics/tests_sh_statistics.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "curl.out.*", diff --git a/bin/tests/system/tsig/tests_badtime.py b/bin/tests/system/tsig/tests_badtime.py index 1a45e51ec0..4f5a1b9f97 100644 --- a/bin/tests/system/tsig/tests_badtime.py +++ b/bin/tests/system/tsig/tests_badtime.py @@ -18,7 +18,9 @@ import time import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 pytest.importorskip("dns", minversion="2.0.0") + import dns.message import dns.query import dns.tsigkeyring diff --git a/bin/tests/system/upforwd/tests_sh_upforwd.py b/bin/tests/system/upforwd/tests_sh_upforwd.py index 3dfa290a31..28be6c4c2d 100644 --- a/bin/tests/system/upforwd/tests_sh_upforwd.py +++ b/bin/tests/system/upforwd/tests_sh_upforwd.py @@ -11,6 +11,8 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") pytestmark = pytest.mark.extra_artifacts( [ diff --git a/bin/tests/system/xfer/tests_sh_xfer.py b/bin/tests/system/xfer/tests_sh_xfer.py index d94f90b981..5226cfc50b 100644 --- a/bin/tests/system/xfer/tests_sh_xfer.py +++ b/bin/tests/system/xfer/tests_sh_xfer.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "axfr.out", diff --git a/bin/tests/system/zero/tests_sh_zero.py b/bin/tests/system/zero/tests_sh_zero.py index 3191f1c62a..50abfe0f2d 100644 --- a/bin/tests/system/zero/tests_sh_zero.py +++ b/bin/tests/system/zero/tests_sh_zero.py @@ -11,6 +11,9 @@ import pytest +# isctest.asyncserver requires dnspython >= 2.0.0 +pytest.importorskip("dns", minversion="2.0.0") + pytestmark = pytest.mark.extra_artifacts( [ "dig.out*", From b9384f7b799552557a2671babdf109541b363ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Bal=C3=A1=C5=BEik?= Date: Fri, 31 Oct 2025 13:10:46 +0100 Subject: [PATCH 3/3] Run system tests on AlmaLinux 8 on merge requests in CI Ensure that system tests don't blow up completely with pre-2.0.0 dnspython. (cherry picked from commit db00ce14c067e9d0e4d3148e021d249905af733d) --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5c1222277c..6fdae5f5c0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1020,7 +1020,6 @@ gcc:almalinux8:amd64: system:gcc:almalinux8:amd64: <<: *almalinux_8_amd64_image <<: *system_test_job - <<: *api_pipelines_schedules_tags_triggers_web_triggering_rules needs: - job: gcc:almalinux8:amd64 artifacts: true