From: Martin Basti Date: Mon, 27 Jul 2026 14:12:10 +0000 (+0000) Subject: Migrate digdelv delv checks to pytest X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=62cfbd3e894d163b7331c4314fe7a26dfc9b97de;p=thirdparty%2Fbind9.git Migrate digdelv delv checks to pytest Move the delv checks from tests.sh into a new tests_delv.py module: +short/+sh, +split/+sp, +unknownformat, the rrcomments/multi matrix, +nosplit field counts, +cookie/+nocookie, class handling, reverse lookups, -q -m, -t ANY, -4/-6 handling and address family mismatches, the malformed query name exit code, key-style and DS-style trust anchors, REFUSED while chasing DS records, and the +yaml output structure for positive and negative responses. check_ttl_range() learns to look inside delv's commented records ("; name ttl class type ..."), where its textual type comparison also matches delv's \-ANY pseudo-type; the duplicated "-c CH" check is folded into one test, and the module is skipped entirely when the delv executable is not available. Add __init__.py to avoid a module-name collision with dnssec/tests_delv.py: without it, pytest imports the file as plain tests_delv, which conflicts. Assisted-by: Claude:claude-fable-5 --- diff --git a/bin/tests/system/digdelv/__init__.py b/bin/tests/system/digdelv/__init__.py new file mode 100644 index 0000000000..3cb1eb11d5 --- /dev/null +++ b/bin/tests/system/digdelv/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/bin/tests/system/digdelv/common.py b/bin/tests/system/digdelv/common.py index e98e25258d..06c54bfb69 100644 --- a/bin/tests/system/digdelv/common.py +++ b/bin/tests/system/digdelv/common.py @@ -44,9 +44,12 @@ def parse_yaml(text): def check_ttl_range(text, rrtype, max_ttl): """Check that a record of the given RR type and class IN (or its unknown-format spelling CLASS1) is present with a TTL not exceeding - max_ttl.""" + max_ttl. A leading ";" token is ignored so that delv's commented + records ("; name ttl class type ...") are checked too.""" for line in text.splitlines(): fields = line.split() + if fields and fields[0] == ";": + fields = fields[1:] if len(fields) < 4: continue if fields[2] not in ("IN", "CLASS1") or fields[3] != rrtype: diff --git a/bin/tests/system/digdelv/tests.sh b/bin/tests/system/digdelv/tests.sh index 17c92096ac..7a181e1694 100644 --- a/bin/tests/system/digdelv/tests.sh +++ b/bin/tests/system/digdelv/tests.sh @@ -19,369 +19,12 @@ set -e status=0 n=0 -# Check if response in file $1 has the correct TTL range. -# The response record must have RRtype $2 and class IN (CLASS1). -# Maximum TTL is given by $3. This works in most cases where TTL is -# the second word on the line. TTL position can be adjusted with -# setting the position $4, but that requires updating this function. -check_ttl_range() { - file=$1 - pos=$4 - - case "$pos" in - "3") - { - awk -v rrtype="$2" -v ttl="$3" '($4 == "IN" || $4 == "CLASS1" ) && $5 == rrtype { if ($3 <= ttl) { ok=1 } } END { exit(ok?0:1) }' <$file - result=$? - } || true - ;; - *) - { - awk -v rrtype="$2" -v ttl="$3" '($3 == "IN" || $3 == "CLASS1" ) && $4 == rrtype { if ($2 <= ttl) { ok=1 } } END { exit(ok?0:1) }' <$file - result=$? - } || true - ;; - esac - - [ $result -eq 0 ] || echo_i "ttl check failed" - return $result -} - # use delv insecure mode by default, as we're mostly not testing dnssec delv_with_opts() { "$DELV" +noroot -p "$PORT" "$@" } -KEYID="$(cat ns2/keyid)" -KEYDATA="$(sed /dev/null && HAS_PYYAML=1 - if [ -x "$DELV" ]; then - n=$((n + 1)) - echo_i "checking delv short form works ($n)" - ret=0 - delv_with_opts @10.53.0.3 +short a a.example >delv.out.test$n || ret=1 - test "$(wc -l delv.out.test$n 2>&1 || ret=1 - grep "; COOKIE:" delv.out.test$n >/dev/null || ret=1 - grep "; answer not validated" delv.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv +nocookie works ($n)" - ret=0 - delv_with_opts @10.53.0.3 +nocookie +strace a a.example -d 10 >delv.out.test$n 2>&1 || ret=1 - grep "; COOKIE:" delv.out.test$n >/dev/null && ret=1 - grep "; answer not validated" delv.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv split width works ($n)" - ret=0 - delv_with_opts @10.53.0.3 +split=4 -t sshfp foo.example >delv.out.test$n || ret=1 - grep " 9ABC DEF6 7890 " /dev/null || ret=1 - check_ttl_range delv.out.test$n "SSHFP" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv +unknownformat works ($n)" - ret=0 - delv_with_opts @10.53.0.3 +unknownformat a a.example >delv.out.test$n || ret=1 - grep "CLASS1[ ][ ]*TYPE1[ ][ ]*\\\\# 4 0A000001" /dev/null || ret=1 - check_ttl_range delv.out.test$n "TYPE1" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv -4 -6 ($n)" - ret=0 - delv_with_opts @10.53.0.3 -4 -6 A a.example >delv.out.test$n 2>&1 && ret=1 - grep "only one of -4 and -6 allowed" /dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv exits cleanly on malformed query name ($n)" - ret=0 - longlabel="$(printf 'a%.0s' $(seq 1 64))" - delv_with_opts @10.53.0.3 -t a "$longlabel.example.com" >delv.out.test$n 2>&1 - rc=$? - # Pre-fix: SIGABRT (exit 134) from dns_client_detach(NULL) in run_resolve cleanup. - [ $rc -eq 134 ] && ret=1 - grep "label too long" delv.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv with IPv6 on IPv4 does not work ($n)" - if testsock6 fd92:7065:b8e:ffff::3 2>/dev/null; then - ret=0 - # following should fail because @IPv4 overrides earlier @IPv6 above - # and -6 forces IPv6 so this should fail, with a message - # "Use of IPv4 disabled by -6" - delv_with_opts @fd92:7065:b8e:ffff::3 @10.53.0.3 -6 -t txt foo.example >delv.out.test$n 2>&1 && ret=1 - # it should have no results but error output - grep "testing" /dev/null && ret=1 - grep "Use of IPv4 disabled by -6" delv.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - else - echo_i "IPv6 unavailable; skipping" - fi - - n=$((n + 1)) - echo_i "checking delv with IPv4 on IPv6 does not work ($n)" - if testsock6 fd92:7065:b8e:ffff::3 2>/dev/null; then - ret=0 - # following should fail because @IPv6 overrides earlier @IPv4 above - # and -4 forces IPv4 so this should fail, with a message - # "Use of IPv6 disabled by -4" - delv_with_opts @10.53.0.3 @fd92:7065:b8e:ffff::3 -4 -t txt foo.example >delv.out.test$n 2>&1 && ret=1 - # it should have no results but error output - grep "testing" delv.out.test$n >/dev/null && ret=1 - grep "Use of IPv6 disabled by -4" delv.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - else - echo_i "IPv6 unavailable; skipping" - fi - - n=$((n + 1)) - echo_i "checking delv with reverse lookup works ($n)" - ret=0 - delv_with_opts @10.53.0.3 -x 127.0.0.1 >delv.out.test$n 2>&1 || ret=1 - # doesn't matter if has answer - grep -i "127\\.in-addr\\.arpa\\." /dev/null || ret=1 - check_ttl_range delv.out.test$n '\\-ANY' 10800 3 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv over TCP works ($n)" - ret=0 - delv_with_opts +tcp @10.53.0.3 a a.example >delv.out.test$n || ret=1 - grep "10\\.0\\.0\\.1$" /dev/null || ret=1 - check_ttl_range delv.out.test$n "A" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv +multi +norrcomments works for DNSKEY (when default is rrcomments)($n)" - ret=0 - delv_with_opts +tcp @10.53.0.3 +multi +norrcomments DNSKEY example >delv.out.test$n || ret=1 - grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" /dev/null && ret=1 - check_ttl_range delv.out.test$n "DNSKEY" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv +multi +norrcomments works for SOA (when default is rrcomments)($n)" - ret=0 - delv_with_opts +tcp @10.53.0.3 +multi +norrcomments SOA example >delv.out.test$n || ret=1 - grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" /dev/null && ret=1 - check_ttl_range delv.out.test$n "SOA" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv +rrcomments works for DNSKEY($n)" - ret=0 - delv_with_opts +tcp @10.53.0.3 +rrcomments DNSKEY example >delv.out.test$n || ret=1 - grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" /dev/null || ret=1 - check_ttl_range delv.out.test$n "DNSKEY" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv +short +rrcomments works for DNSKEY ($n)" - ret=0 - delv_with_opts +tcp @10.53.0.3 +short +rrcomments DNSKEY example >delv.out.test$n || ret=1 - grep "; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" /dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv +short +rrcomments works ($n)" - ret=0 - delv_with_opts +tcp @10.53.0.3 +short +rrcomments DNSKEY example >delv.out.test$n || ret=1 - grep -q "$KEYDATA ; ZSK; alg = $DEFAULT_ALGORITHM ; key id = $KEYID" delv.out.test$n || ret=1 - grep -q "$NOSPLIT" delv.out.test$n || ret=1 - grep -q "$NOSPLIT\$" delv.out.test$n || ret=1 - grep " 9ABC DEF6 7890 " /dev/null || ret=1 - check_ttl_range delv.out.test$n "SSHFP" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv +sh works as an abbriviated form of short ($n)" - ret=0 - delv_with_opts @10.53.0.3 +sh a a.example >delv.out.test$n || ret=1 - test "$(wc -l delv.out.test$n || ret=1 - grep "a.example." /dev/null || ret=1 - check_ttl_range delv.out.test$n "A" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv -c CH is ignored, and treated like IN ($n)" - ret=0 - delv_with_opts @10.53.0.3 -c CH -t a a.example >delv.out.test$n || ret=1 - grep "a.example." /dev/null || ret=1 - check_ttl_range delv.out.test$n "A" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "checking delv -c CH is ignored, and treated like IN ($n)" - ret=0 - delv_with_opts @10.53.0.3 -c CH -t a a.example >delv.out.test$n || ret=1 - grep "a.example." /dev/null || ret=1 - check_ttl_range delv.out.test$n "A" 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "check that delv -q -m works ($n)" - ret=0 - delv_with_opts @10.53.0.3 -q -m >delv.out.test$n 2>&1 || ret=1 - grep '^; -m\..*[0-9]*.*IN.*ANY.*;' delv.out.test$n >/dev/null || ret=1 - grep "^add " delv.out.test$n >/dev/null && ret=1 - grep "^del " delv.out.test$n >/dev/null && ret=1 - check_ttl_range delv.out.test$n '\\-ANY' 300 3 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "check that delv -t ANY works ($n)" - ret=0 - delv_with_opts @10.53.0.3 -t ANY example >delv.out.test$n 2>&1 || ret=1 - grep "^example." /dev/null || ret=1 - check_ttl_range delv.out.test$n NS 300 || ret=1 - check_ttl_range delv.out.test$n SOA 300 || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "check that delv loads key-style trust anchors ($n)" - ret=0 - delv_with_opts -a ns3/anchor.dnskey +root=example @10.53.0.3 -t DNSKEY example >delv.out.test$n 2>&1 || ret=1 - grep "fully validated" delv.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - n=$((n + 1)) - echo_i "check that delv loads DS-style trust anchors ($n)" - ret=0 - delv_with_opts -a ns3/anchor.ds +root=example @10.53.0.3 -t DNSKEY example >delv.out.test$n 2>&1 || ret=1 - grep "fully validated" delv.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - - if [ $HAS_PYYAML -ne 0 ]; then - n=$((n + 1)) - echo_i "check delv +yaml ANY output ($n)" - ret=0 - delv_with_opts +yaml @10.53.0.3 any ns2.example >delv.out.test$n || ret=1 - $PYTHON yamlget.py delv.out.test$n status >yamlget.out.test$n 2>&1 || ret=1 - read -r value yamlget.out.test$n 2>&1 || ret=1 - read -r value yamlget.out.test$n 2>&1 || ret=1 - read -r value delv.out.test$n || ret=1 - $PYTHON yamlget.py delv.out.test$n status >yamlget.out.test$n 2>&1 || ret=1 - read -r value yamlget.out.test$n 2>&1 || ret=1 - read -r value yamlget.out.test$n 2>&1 || ret=1 - read -r value delv.out.test$n || ret=1 - $PYTHON yamlget.py delv.out.test$n status >yamlget.out.test$n 2>&1 || ret=1 - read -r value yamlget.out.test$n 2>&1 || ret=1 - read -r value yamlget.out.test$n 2>&1 || ret=1 - read -r value delv.out.test$n 2>&1 || ret=1 - grep ";; resolution failed: broken trust chain" delv.out.test$n >/dev/null || ret=1 - if [ $ret -ne 0 ]; then echo_i "failed"; fi - status=$((status + ret)) - n=$((n + 1)) echo_i "check NS output from delv +ns ($n)" ret=0 diff --git a/bin/tests/system/digdelv/tests_delv.py b/bin/tests/system/digdelv/tests_delv.py new file mode 100644 index 0000000000..d369784ef7 --- /dev/null +++ b/bin/tests/system/digdelv/tests_delv.py @@ -0,0 +1,274 @@ +# 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. + +""" +Tests for the delv tool. +""" + +from re import compile as Re + +import os +import re + +import pytest + +from digdelv.common import ARTIFACTS, check_ttl_range, needs_pyyaml, parse_yaml +from isctest.util import param + +import isctest +import isctest.mark + +pytestmark = [ + pytest.mark.skipif( + not os.access(os.environ.get("DELV", ""), os.X_OK), + reason="delv executable not available", + ), + pytest.mark.extra_artifacts(ARTIFACTS), +] + + +@pytest.fixture(name="delv") +def delv_fixture(named_port): + # use delv insecure mode by default, as we're mostly not testing dnssec + return isctest.run.EnvCmd("DELV", f"+noroot -p {named_port}") + + +@pytest.mark.parametrize("option", ["+short", "+sh"]) +def test_short(delv, ns3, option): + """Check that delv +short (and its +sh abbreviation) returns a + single-line answer.""" + result = delv(f"@{ns3.ip} {option} a a.example") + assert len(result.out.splitlines()) == 1 + + +def test_cookie(delv, ns3): + """Check that delv +cookie sends a DNS COOKIE option.""" + result = delv(f"@{ns3.ip} +cookie a a.example -d 10") + assert "; COOKIE:" in result.err + assert "; answer not validated" in result.out + + +def test_nocookie(delv, ns3): + """Check that delv +nocookie does not send a DNS COOKIE option.""" + result = delv(f"@{ns3.ip} +nocookie +strace a a.example -d 10") + assert "; COOKIE:" not in result.err + assert "; answer not validated" in result.out + + +@pytest.mark.parametrize("option", ["+split=4", "+sp=4"]) +def test_split_width(delv, ns3, option): + """Check that delv +split (and its +sp abbreviation) splits hex data + into fields of the requested width.""" + result = delv(f"@{ns3.ip} {option} -t sshfp foo.example") + assert " 9ABC DEF6 7890 " in result.out + assert check_ttl_range(result.out, "SSHFP", 300) + + +def test_unknownformat(delv, ns3): + """Check that delv +unknownformat prints RFC 3597 format.""" + result = delv(f"@{ns3.ip} +unknownformat a a.example") + assert Re(r"CLASS1\s+TYPE1\s+\\# 4 0A000001") in result.out + assert check_ttl_range(result.out, "TYPE1", 300) + + +def test_4_and_6_mutually_exclusive(delv, ns3): + """Check that delv rejects -4 combined with -6.""" + result = delv(f"@{ns3.ip} -4 -6 A a.example", raise_on_exception=False) + assert result.rc != 0 + assert "only one of -4 and -6 allowed" in result.err + + +def test_malformed_query_name(delv, ns3): + """Check that delv exits cleanly on a malformed query name instead of + aborting in the dns_client_detach(NULL) cleanup path.""" + longlabel = "a" * 64 + result = delv(f"@{ns3.ip} -t a {longlabel}.example.com", raise_on_exception=False) + assert result.rc >= 0 + assert "label too long" in result.err + + +@isctest.mark.with_ipv6 +@pytest.mark.parametrize( + "server_args,message", + [ + param( + "@fd92:7065:b8e:ffff::3 @{ns3} -6", + "Use of IPv4 disabled by -6", + id="ipv4-server-with-6", + ), + param( + "@{ns3} @fd92:7065:b8e:ffff::3 -4", + "Use of IPv6 disabled by -4", + id="ipv6-server-with-4", + ), + ], +) +def test_address_family_mismatch(delv, ns3, server_args, message): + """Check that the last @server option overrides earlier ones and that + the forced address family makes such a lookup fail.""" + result = delv( + server_args.format(ns3=ns3.ip) + " -t txt foo.example", + raise_on_exception=False, + ) + assert result.rc != 0 + # it should have no results but error output + assert "testing" not in result.out + assert message in result.err + + +def test_reverse_lookup(delv, ns3): + """Check that delv -x works.""" + result = delv(f"@{ns3.ip} -x 127.0.0.1") + # doesn't matter if has answer + assert Re(r"127\.in-addr\.arpa\.", re.IGNORECASE) in result.out + assert check_ttl_range(result.out, r"\-ANY", 10800) + + +def test_tcp(delv, ns3): + """Check that delv over TCP works.""" + result = delv(f"+tcp @{ns3.ip} a a.example") + assert Re(r"10\.0\.0\.1$") in result.out + assert check_ttl_range(result.out, "A", 300) + + +@pytest.mark.parametrize( + "args,expect_rrcomment,ttl_rrtype", + [ + param( + "+multi +norrcomments DNSKEY example", + False, + "DNSKEY", + id="multi-norrcomments-dnskey", + ), + param( + "+multi +norrcomments SOA example", + False, + "SOA", + id="multi-norrcomments-soa", + ), + param("+rrcomments DNSKEY example", True, "DNSKEY", id="rrcomments"), + param("+short +rrcomments DNSKEY example", True, None, id="short-rrcomments"), + ], +) +def test_rrcomments(delv, ns3, zsk, args, expect_rrcomment, ttl_rrtype): + """Check that +[no]rrcomments controls the DNSKEY comment + (the default is rrcomments, even with +multi).""" + result = delv(f"+tcp @{ns3.ip} {args}") + assert (zsk.rrcomment in result.out) == expect_rrcomment + if ttl_rrtype: + assert check_ttl_range(result.out, ttl_rrtype, 300) + + +def test_short_rrcomments_line(delv, ns3, zsk): + """Check the exact delv +short +rrcomments output line.""" + result = delv(f"+tcp @{ns3.ip} +short +rrcomments DNSKEY example") + assert f"{zsk.keydata} {zsk.rrcomment}" in result.out + + +def test_short_nosplit(delv, ns3, zsk): + """Check that delv +short +nosplit does not split the key data.""" + result = delv(f"+tcp @{ns3.ip} +short +nosplit DNSKEY example") + assert zsk.keydata.replace(" ", "") in result.out + assert len(result.out.splitlines()) == 1 + assert len(result.out.split()) == 14 + + +def test_short_nosplit_norrcomments(delv, ns3, zsk): + """Check that delv +short +nosplit +norrcomments prints the bare + unsplit rdata.""" + result = delv(f"+tcp @{ns3.ip} +short +nosplit +norrcomments DNSKEY example") + nosplit = zsk.keydata.replace(" ", "") + assert Re(re.escape(nosplit) + "$") in result.out + assert len(result.out.splitlines()) == 1 + assert len(result.out.split()) == 4 + + +@pytest.mark.parametrize( + "qclass", + [ + param("IN", id="in"), + param("CH", id="ch-ignored"), + ], +) +def test_class_option(delv, ns3, qclass): + """Check that delv -c IN works and that -c CH is ignored and treated + like IN.""" + result = delv(f"@{ns3.ip} -c {qclass} -t a a.example") + assert "a.example." in result.out + assert check_ttl_range(result.out, "A", 300) + + +def test_q_m(delv, ns3): + """Check that -q -m treats -m as a query name, not as the memory + debugging flag.""" + result = delv(f"@{ns3.ip} -q -m") + assert Re(r"^; -m\..*\d*.*IN.*ANY.*;") in result.out + for stream in (result.out, result.err): + assert Re(r"^add ") not in stream + assert Re(r"^del ") not in stream + assert check_ttl_range(result.out, r"\-ANY", 300) + + +def test_any_query(delv, ns3): + """Check that delv -t ANY works.""" + result = delv(f"@{ns3.ip} -t ANY example") + assert Re(r"^example\.") in result.out + assert check_ttl_range(result.out, "NS", 300) + assert check_ttl_range(result.out, "SOA", 300) + + +@pytest.mark.parametrize( + "anchor", + [ + param("anchor.dnskey", id="key-style"), + param("anchor.ds", id="ds-style"), + ], +) +def test_trust_anchors(delv, ns3, anchor): + """Check that delv loads key-style and DS-style trust anchors and + validates with them.""" + result = delv(f"-a ns3/{anchor} +root=example @{ns3.ip} -t DNSKEY example") + assert "fully validated" in result.out + + +def test_refused_chasing_ds(delv, ns2): + """Check that delv handles REFUSED when chasing DS records.""" + result = delv(f"@{ns2.ip} +root xxx.example.tld A") + assert ";; resolution failed: broken trust chain" in result.err + + +@needs_pyyaml +def test_yaml_any(delv, ns3): + """Check the structure of delv +yaml output.""" + result = delv(f"+yaml @{ns3.ip} any ns2.example") + data = parse_yaml(result.out) + assert data["status"] == "success" + assert data["query_name"] == "ns2.example" + answer = data["records"][0]["answer_not_validated"][0] + assert len(str(answer).split()) == 5 + + +@needs_pyyaml +@pytest.mark.parametrize( + "qtype,qname,status", + [ + param("type500", "ns2.example", "ncache nxrrset", id="nodata"), + param("a", "this-does-not-exist.ns2.example", "ncache nxdomain", id="nxdomain"), + ], +) +def test_yaml_negative(delv, ns3, qtype, qname, status): + """Check the structure of delv +yaml output for negative responses.""" + result = delv(f"+yaml @{ns3.ip} {qtype} {qname}") + data = parse_yaml(result.out) + assert data["status"] == status + assert data["query_name"] == qname + answer = data["records"][0]["negative_response_answer_not_validated"][0] + assert len(str(answer).split()) == 5