diff --git a/MANIFEST b/MANIFEST index 22ce6fc81..865ab69d3 100644 --- a/MANIFEST +++ b/MANIFEST @@ -289,6 +289,7 @@ t/Test-syntax06-K.data t/Test-syntax06-K.t t/Test-syntax06-L.data t/Test-syntax06-L.t +t/TestUtil.pm t/Test-zone.data t/Test-zone.t t/Test-zone01-A.t diff --git a/t/Test-dnssec16.t b/t/Test-dnssec16.t index 0fcd110da..c0a669f4c 100644 --- a/t/Test-dnssec16.t +++ b/t/Test-dnssec16.t @@ -1,200 +1,136 @@ -use Test::More; -use File::Slurp; -use File::Basename; use strict; use warnings; - + +use Test::More; +use File::Basename; +use File::Spec::Functions qw( rel2abs ); +use lib dirname( rel2abs( $0 ) ); + BEGIN { use_ok( q{Zonemaster::Engine} ); use_ok( q{Zonemaster::Engine::Nameserver} ); use_ok( q{Zonemaster::Engine::Test::DNSSEC} ); - use_ok( q{Zonemaster::Engine::Util}, qw( parse_hints ) ); + use_ok( q{TestUtil}, qw( perform_testcase_testing ) ); } -my $checking_module = q{DNSSEC}; -my $testcase = 'dnssec16'; - -sub zone_gives { - my ( $test, $zone, $gives_ref ) = @_; - Zonemaster::Engine->logger->clear_history(); - my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone ); - foreach my $gives ( @{$gives_ref} ) { - ok( ( grep { $_->tag eq $gives } @res ), $zone->name->string . " gives $gives" ); - } - return scalar( @res ); -} - -sub zone_gives_not { - my ( $test, $zone, $gives_ref ) = @_; +########### +# dnssec16 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/DNSSEC-TP/dnssec16.md +my $test_module = q{DNSSEC}; +my $test_case = 'dnssec16'; - Zonemaster::Engine->logger->clear_history(); - my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone ); - foreach my $gives ( @{$gives_ref} ) { - ok( !( grep { $_->tag eq $gives } @res ), $zone->name->string . " does not give $gives" ); +# Common hint file (test-zone-data/COMMON/hintfile) +Zonemaster::Engine::Recursor->remove_fake_addresses( '.' ); +Zonemaster::Engine::Recursor->add_fake_addresses( '.', + { 'ns1' => [ '127.1.0.1', 'fda1:b2:c3::127:1:0:1' ], + 'ns2' => [ '127.1.0.2', 'fda1:b2:c3::127:1:0:2' ], } - return scalar( @res ); -} +); + +# Test zone scenarios +# - Documentation: L +# - Format: { SCENARIO_NAME => [ zone_name, [ MANDATORY_MESSAGE_TAGS ], [ FORBIDDEN_MESSAGE_TAGS ], testable ] } +my %subtests = ( + 'CDS-INVALID-RRSIG' => [ + q(cds-invalid-rrsig.dnssec16.xa), + [ qw(DS16_CDS_INVALID_RRSIG) ], + [ qw(DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CD) ], + 1, + ], + 'CDS-MATCHES-NO-DNSKEY' => [ + q(cds-matches-no-dnskey.dnssec16.xa), + [ qw(DS16_CDS_MATCHES_NO_DNSKEY) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1, + ], + 'CDS-MATCHES-NON-SEP-DNSKEY' => [ + q(cds-matches-non-sep-dnskey.dnssec16.xa), + [ qw(DS16_CDS_MATCHES_NON_SEP_DNSKEY) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'CDS-MATCHES-NON-ZONE-DNSKEY' => [ + q(cds-matches-non-zone-dnskey.dnssec16.xa), + [ qw(DS16_CDS_MATCHES_NON_ZONE_DNSKEY) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'CDS-NOT-SIGNED-BY-CDS' => [ + q(cds-not-signed-by-cds.dnssec16.xa), + [ qw(DS16_CDS_NOT_SIGNED_BY_CDS) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'CDS-SIGNED-BY-UNKNOWN-DNSKEY' => [ + q(cds-signed-by-unknown-dnskey.dnssec16.xa), + [ qw(DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'CDS-UNSIGNED' => [ + q(cds-unsigned.dnssec16.xa), + [ qw(DS16_CDS_UNSIGNED DS16_CDS_NOT_SIGNED_BY_CDS) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'CDS-WITHOUT-DNSKEY' => [ + q(cds-without-dnskey.dnssec16.xa), + [ qw(DS16_CDS_WITHOUT_DNSKEY) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'DELETE-CDS' => [ + q(delete-cds.dnssec16.xa), + [ qw(DS16_DELETE_CDS) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'DNSKEY-NOT-SIGNED-BY-CDS' => [ + q(dnskey-not-signed-by-cds.dnssec16.xa), + [ qw(DS16_DNSKEY_NOT_SIGNED_BY_CDS) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'MIXED-DELETE-CDS' => [ + q(mixed-delete-cds.dnssec16.xa), + [ qw(DS16_MIXED_DELETE_CDS) ], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS) ], + 1 + ], + 'NO-CDS' => [ + q(no-cds.dnssec16.xa), + [], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'NOT-AA' => [ + q(not-aa.dnssec16.xa), + [], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ], + 'VALID-CDS' => [ + q(valid-cds.dnssec16.xa), + [], + [ qw(DS16_CDS_INVALID_RRSIG DS16_CDS_MATCHES_NON_SEP_DNSKEY DS16_CDS_MATCHES_NON_ZONE_DNSKEY DS16_CDS_MATCHES_NO_DNSKEY DS16_CDS_NOT_SIGNED_BY_CDS DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY DS16_CDS_UNSIGNED DS16_CDS_WITHOUT_DNSKEY DS16_DELETE_CDS DS16_DNSKEY_NOT_SIGNED_BY_CDS DS16_MIXED_DELETE_CDS) ], + 1 + ] +); +########### my $datafile = 't/' . basename ($0, '.t') . '.data'; + if ( not $ENV{ZONEMASTER_RECORD} ) { die q{Stored data file missing} if not -r $datafile; Zonemaster::Engine::Nameserver->restore( $datafile ); Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 ); } -# Common hint file (test-zone-data/COMMON/hintfile) -my $hints; -{ - $hints = <remove_fake_addresses( '.' ); -Zonemaster::Engine::Recursor->add_fake_addresses( '.', $hints_data ); - -########### -# DNSSEC16 -########### - -my ($json, $profile_test); -$json = qq({ "test_cases": [ "$testcase" ] }); -$profile_test = Zonemaster::Engine::Profile->from_json( $json ); -Zonemaster::Engine::Profile->effective->merge( $profile_test ); - -my $blockline = 0; -my ($zonename, @gives, @gives_not); -while (my $line = ) { - chomp($line); - next if $line =~ /^#/; - next if ($blockline == 0 and $line eq ''); - if ($blockline == 3 and $line eq '') { - $blockline = 0; - next; - }; - if ($blockline == 0) { - $zonename = $line; - $blockline = 1; - next; - }; - if ($blockline == 1) { - if ($line eq '(none)') { - @gives = (); - } else { - $line =~ s/ *, */ /g; - @gives = split (/ +/, $line); - } - $blockline = 2; - next; - }; - if ($blockline == 2) { - if ($line eq '(none)') { - @gives_not = (); - } else { - $line =~ s/ *, */ /g; - @gives_not = split (/ +/, $line); - } - - my $zone = Zonemaster::Engine->zone( $zonename ); - zone_gives( $testcase, $zone, \@gives ) if scalar @gives; - zone_gives_not( $testcase, $zone, \@gives_not ) if scalar @gives_not; - $blockline = 3; - $zonename = ''; - @gives = ''; - @gives_not = ''; - next; - }; - die "Error in data section"; -}; +Zonemaster::Engine::Profile->effective->merge( Zonemaster::Engine::Profile->from_json( qq({ "test_cases": [ "$test_case" ] }) ) ); +perform_testcase_testing( $test_case, $test_module, %subtests ); if ( $ENV{ZONEMASTER_RECORD} ) { Zonemaster::Engine::Nameserver->save( $datafile ); } -Zonemaster::Engine::Profile->effective->set( q{no_network}, 0 ); -Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 0 ); -Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 0 ); - -TODO: { - local $TODO = "Nothing"; -} - done_testing; - - -# In that __DATA__ section each test is a block consisting of three lines: -# 1 Zone name -# 2 Tags tag "gives" -# 3 Tags that "gives not" -# -# If tag line is "(none)" than it should be ignored. -# -# Empty lines between blocks. Lines with "#" in column one are ignored (comments); - - -__DATA__ -cds-invalid-rrsig.dnssec16.xa -DS16_CDS_INVALID_RRSIG -DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -cds-matches-no-dnskey.dnssec16.xa -DS16_CDS_MATCHES_NO_DNSKEY -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -cds-matches-non-sep-dnskey.dnssec16.xa -DS16_CDS_MATCHES_NON_SEP_DNSKEY -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -cds-matches-non-zone-dnskey.dnssec16.xa -DS16_CDS_MATCHES_NON_ZONE_DNSKEY -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -cds-not-signed-by-cds.dnssec16.xa -DS16_CDS_NOT_SIGNED_BY_CDS -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -cds-signed-by-unknown-dnskey.dnssec16.xa -DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -cds-unsigned.dnssec16.xa -DS16_CDS_UNSIGNED, DS16_CDS_NOT_SIGNED_BY_CDS -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -cds-without-dnskey.dnssec16.xa -DS16_CDS_WITHOUT_DNSKEY -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -delete-cds.dnssec16.xa -DS16_DELETE_CDS -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -dnskey-not-signed-by-cds.dnssec16.xa -DS16_DNSKEY_NOT_SIGNED_BY_CDS -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_MIXED_DELETE_CDS - -mixed-delete-cds.dnssec16.xa -DS16_MIXED_DELETE_CDS -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS - -no-cds.dnssec16.xa -(none) -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -not-aa.dnssec16.xa -(none) -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -valid-cds.dnssec16.xa -(none) -DS16_CDS_INVALID_RRSIG, DS16_CDS_MATCHES_NON_SEP_DNSKEY, DS16_CDS_MATCHES_NON_ZONE_DNSKEY, DS16_CDS_MATCHES_NO_DNSKEY, DS16_CDS_NOT_SIGNED_BY_CDS, DS16_CDS_SIGNED_BY_UNKNOWN_DNSKEY, DS16_CDS_UNSIGNED, DS16_CDS_WITHOUT_DNSKEY, DS16_DELETE_CDS, DS16_DNSKEY_NOT_SIGNED_BY_CDS, DS16_MIXED_DELETE_CDS - -# Always an empty line after the last block diff --git a/t/Test-zone09-1.t b/t/Test-zone09-1.t index 88e631929..ee72e24b6 100644 --- a/t/Test-zone09-1.t +++ b/t/Test-zone09-1.t @@ -1,151 +1,58 @@ -use Test::More; -use File::Slurp; -use File::Basename; use strict; use warnings; - + +use Test::More; +use File::Basename; +use File::Spec::Functions qw( rel2abs ); +use lib dirname( rel2abs( $0 ) ); + BEGIN { use_ok( q{Zonemaster::Engine} ); use_ok( q{Zonemaster::Engine::Nameserver} ); use_ok( q{Zonemaster::Engine::Test::Zone} ); - use_ok( q{Zonemaster::Engine::Util}, qw( parse_hints ) ); -} - -my $checking_module = q{Zone}; -my $testcase = 'zone09'; - -sub zone_gives { - my ( $test, $zone, $gives_ref ) = @_; - Zonemaster::Engine->logger->clear_history(); - my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone ); - foreach my $gives ( @{$gives_ref} ) { - ok( ( grep { $_->tag eq $gives } @res ), $zone->name->string . " gives $gives" ); - } - return scalar( @res ); + use_ok( q{TestUtil}, qw( perform_testcase_testing ) ); } -sub zone_gives_not { - my ( $test, $zone, $gives_ref ) = @_; +########### +# zone09 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Zone-TP/zone09.md +my $test_module = q{Zone}; +my $test_case = 'zone09'; - Zonemaster::Engine->logger->clear_history(); - my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone ); - foreach my $gives ( @{$gives_ref} ) { - ok( !( grep { $_->tag eq $gives } @res ), $zone->name->string . " does not give $gives" ); +# Test case specific hints file (test-zone-data/Zone-TP/zone09/hintfile-root-email-domain) +Zonemaster::Engine::Recursor->remove_fake_addresses( '.' ); +Zonemaster::Engine::Recursor->add_fake_addresses( '.', + { 'ns1' => [ '127.19.9.43', 'fda1:b2:c3::127:19:9:43' ], + 'ns2' => [ '127.19.9.44', 'fda1:b2:c3::127:19:9:44' ], } - return scalar( @res ); -} +); + +# Test zone scenarios +# - Documentation: L +# - Format: { SCENARIO_NAME => [ zone_name, [ MANDATORY_MESSAGE_TAGS ], [ FORBIDDEN_MESSAGE_TAGS ], testable ] } +my %subtests = ( + 'ROOT-EMAIL-DOMAIN' => [ + q(.), + [ qw(Z09_ROOT_EMAIL_DOMAIN) ], + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_MISSING_MAIL_TARGET Z09_TLD_EMAIL_DOMAIN Z09_NULL_MX_WITH_OTHER_MX Z09_NULL_MX_NON_ZERO_PREF) ], + 1 + ] +); +########### my $datafile = 't/' . basename ($0, '.t') . '.data'; + if ( not $ENV{ZONEMASTER_RECORD} ) { die q{Stored data file missing} if not -r $datafile; Zonemaster::Engine::Nameserver->restore( $datafile ); Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 ); } -# Test case specific hints file (test-zone-data/Zone-TP/zone09/hintfile-root-email-domain) -my $hints; -{ - $hints = <remove_fake_addresses( '.' ); -Zonemaster::Engine::Recursor->add_fake_addresses( '.', $hints_data ); - -########### -# zone09 -########### - -my ($json, $profile_test); -$json = qq({ "test_cases": [ "$testcase" ] }); -$profile_test = Zonemaster::Engine::Profile->from_json( $json ); -Zonemaster::Engine::Profile->effective->merge( $profile_test ); - -my $blockline = 0; -my ($zonename, @gives, @gives_not); -while (my $line = ) { - chomp($line); - next if $line =~ /^#/; - next if ($blockline == 0 and $line eq ''); - if ($blockline == 3 and $line eq '') { - $blockline = 0; - next; - }; - if ($blockline == 0) { - $zonename = $line; - $blockline = 1; - next; - }; - if ($blockline == 1) { - if ($line eq '(none)') { - @gives = (); - } else { - $line =~ s/ *, */ /g; - @gives = split (/ +/, $line); - } - $blockline = 2; - next; - }; - if ($blockline == 2) { - if ($line eq '(none)') { - @gives_not = (); - } else { - $line =~ s/ *, */ /g; - @gives_not = split (/ +/, $line); - } - - my $zone = Zonemaster::Engine->zone( $zonename ); - zone_gives( $testcase, $zone, \@gives ) if scalar @gives; - zone_gives_not( $testcase, $zone, \@gives_not ) if scalar @gives_not; - $blockline = 3; - $zonename = ''; - @gives = ''; - @gives_not = ''; - next; - }; - die "Error in data section"; -}; +Zonemaster::Engine::Profile->effective->merge( Zonemaster::Engine::Profile->from_json( qq({ "test_cases": [ "$test_case" ] }) ) ); +perform_testcase_testing( $test_case, $test_module, %subtests ); if ( $ENV{ZONEMASTER_RECORD} ) { Zonemaster::Engine::Nameserver->save( $datafile ); } -Zonemaster::Engine::Profile->effective->set( q{no_network}, 0 ); -Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 0 ); -Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 0 ); - -TODO: { - local $TODO = "Nothing"; -} - done_testing; - - -# In that __DATA__ section each test is a block consisting of three lines: -# 1 Zone name -# 2 Tags tag "gives" -# 3 Tags that "gives not" -# -# If tag line is "(none)" than it should be ignored. -# -# Empty lines between blocks. Lines with "#" in column one are ignored (comments); - - -__DATA__ -# Scenario ROOT-EMAIL-DOMAIN -. -Z09_ROOT_EMAIL_DOMAIN -Z09_INCONSISTENT_MX_DATA, Z09_MX_DATA, Z09_MISSING_MAIL_TARGET, Z09_TLD_EMAIL_DOMAIN, Z09_NULL_MX_WITH_OTHER_MX, Z09_NULL_MX_NON_ZERO_PREF - -# Always an emapty line after the last block - diff --git a/t/Test-zone09.t b/t/Test-zone09.t index 49945ea05..08407a52e 100644 --- a/t/Test-zone09.t +++ b/t/Test-zone09.t @@ -1,200 +1,130 @@ -use Test::More; -use File::Slurp; -use File::Basename; use strict; use warnings; - + +use Test::More; +use File::Basename; +use File::Spec::Functions qw( rel2abs ); +use lib dirname( rel2abs( $0 ) ); + BEGIN { use_ok( q{Zonemaster::Engine} ); use_ok( q{Zonemaster::Engine::Nameserver} ); use_ok( q{Zonemaster::Engine::Test::Zone} ); - use_ok( q{Zonemaster::Engine::Util}, qw( parse_hints ) ); + use_ok( q{TestUtil}, qw( perform_testcase_testing ) ); } -my $checking_module = q{Zone}; -my $testcase = 'zone09'; - -sub zone_gives { - my ( $test, $zone, $gives_ref ) = @_; - Zonemaster::Engine->logger->clear_history(); - my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone ); - foreach my $gives ( @{$gives_ref} ) { - ok( ( grep { $_->tag eq $gives } @res ), $zone->name->string . " gives $gives" ); - } - return scalar( @res ); -} - -sub zone_gives_not { - my ( $test, $zone, $gives_ref ) = @_; +########### +# zone09 - https://github.com/zonemaster/zonemaster/blob/master/docs/public/specifications/test-zones/Zone-TP/zone09.md +my $test_module = q{Zone}; +my $test_case = 'zone09'; - Zonemaster::Engine->logger->clear_history(); - my @res = grep { $_->tag !~ /^TEST_CASE_(END|START)$/ } Zonemaster::Engine->test_method( $checking_module, $test, $zone ); - foreach my $gives ( @{$gives_ref} ) { - ok( !( grep { $_->tag eq $gives } @res ), $zone->name->string . " does not give $gives" ); +# Common hint file (test-zone-data/COMMON/hintfile) +Zonemaster::Engine::Recursor->remove_fake_addresses( '.' ); +Zonemaster::Engine::Recursor->add_fake_addresses( '.', + { 'ns1' => [ '127.1.0.1', 'fda1:b2:c3::127:1:0:1' ], + 'ns2' => [ '127.1.0.2', 'fda1:b2:c3::127:1:0:2' ], } - return scalar( @res ); -} +); + +# Test zone scenarios +# - Documentation: L +# - Format: { SCENARIO_NAME => [ zone_name, [ MANDATORY_MESSAGE_TAGS ], [ FORBIDDEN_MESSAGE_TAGS ], testable ] } +my %subtests = ( + 'NO-RESPONSE-MX-QUERY' => [ + q(no-response-mx-query.zone09.xa), + [ qw(Z09_NO_RESPONSE_MX_QUERY) ], + [], + 1 + ], + 'UNEXPECTED-RCODE-MX' => [ + q(unexpected-rcode-mx.zone09.xa), + [ qw(Z09_UNEXPECTED_RCODE_MX) ], + [], + 1 + ], + 'NON-AUTH-MX-RESPONSE' => [ + q(non-auth-mx-response.zone09.xa), + [ qw(Z09_NON_AUTH_MX_RESPONSE) ], + [], + 0 + ], + 'INCONSISTENT-MX' => [ + q(inconsistent-mx.zone09.xa), + [ qw(Z09_INCONSISTENT_MX Z09_MX_FOUND Z09_NO_MX_FOUND Z09_MX_DATA) ], + [ qw(Z09_MISSING_MAIL_TARGET) ], + 1 + ], + 'INCONSISTENT-MX-DATA' => [ + q(inconsistent-mx-data.zone09.xa), + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA) ], + [ qw(Z09_MISSING_MAIL_TARGET Z09_NULL_MX_NON_ZERO_PREF Z09_NULL_MX_WITH_OTHER_MX Z09_ROOT_EMAIL_DOMAIN Z09_TLD_EMAIL_DOMAIN) ], + 1 + ], + 'NULL-MX-WITH-OTHER-MX' => [ + q(null-mx-with-other-mx.zone09.xa), + [ qw(Z09_NULL_MX_WITH_OTHER_MX) ], + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_MISSING_MAIL_TARGET Z09_ROOT_EMAIL_DOMAIN Z09_TLD_EMAIL_DOMAIN) ], + 1 + ], + 'NULL-MX-NON-ZERO-PREF' => [ + q(null-mx-non-zero-pref.zone09.xa), + [ qw(Z09_NULL_MX_NON_ZERO_PREF) ], + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_MISSING_MAIL_TARGET Z09_ROOT_EMAIL_DOMAIN Z09_TLD_EMAIL_DOMAIN) ], + 1 + ], + 'TLD-EMAIL-DOMAIN' => [ + q(tld-email-domain-zone09), + [ qw(Z09_TLD_EMAIL_DOMAIN) ], + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_MISSING_MAIL_TARGET Z09_ROOT_EMAIL_DOMAIN Z09_NULL_MX_WITH_OTHER_MX Z09_NULL_MX_NON_ZERO_PREF) ], + 1 + ], + 'MX-DATA' => [ + q(mx-data.zone09.xa), + [ qw(Z09_MX_DATA) ], + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MISSING_MAIL_TARGET Z09_TLD_EMAIL_DOMAIN Z09_ROOT_EMAIL_DOMAIN Z09_NULL_MX_WITH_OTHER_MX Z09_NULL_MX_NON_ZERO_PREF) ], + 1 + ], + 'NULL-MX' => [ + q(null-mx.zone09.xa), + [], + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_MISSING_MAIL_TARGET Z09_TLD_EMAIL_DOMAIN Z09_ROOT_EMAIL_DOMAIN Z09_NULL_MX_WITH_OTHER_MX Z09_NULL_MX_NON_ZERO_PREF) ], + 1 + ], + 'NO-MX-SLD' => [ + q(no-mx-sld.zone09.xa), + [ qw(Z09_MISSING_MAIL_TARGET) ], + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_TLD_EMAIL_DOMAIN Z09_ROOT_EMAIL_DOMAIN Z09_NULL_MX_WITH_OTHER_MX Z09_NULL_MX_NON_ZERO_PREF) ], + 1 + ], + 'NO-MX-TLD' => [ + q(no-mx-tld-zone09), + [], + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_MISSING_MAIL_TARGET Z09_TLD_EMAIL_DOMAIN Z09_ROOT_EMAIL_DOMAIN Z09_NULL_MX_WITH_OTHER_MX Z09_NULL_MX_NON_ZERO_PREF) ], + 1 + ], + 'NO-MX-ARPA' => [ + q(no-mx-arpa.zone09.arpa), + [], + [ qw(Z09_INCONSISTENT_MX_DATA Z09_MX_DATA Z09_MISSING_MAIL_TARGET Z09_TLD_EMAIL_DOMAIN Z09_ROOT_EMAIL_DOMAIN Z09_NULL_MX_WITH_OTHER_MX Z09_NULL_MX_NON_ZERO_PREF) ], + 1 + ] +); +########### my $datafile = 't/' . basename ($0, '.t') . '.data'; + if ( not $ENV{ZONEMASTER_RECORD} ) { die q{Stored data file missing} if not -r $datafile; Zonemaster::Engine::Nameserver->restore( $datafile ); Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 ); } -# Common hint file (test-zone-data/COMMON/hintfile) -my $hints; -{ - $hints = <remove_fake_addresses( '.' ); -Zonemaster::Engine::Recursor->add_fake_addresses( '.', $hints_data ); - -########### -# zone09 -########### - -my ($json, $profile_test); -$json = qq({ "test_cases": [ "$testcase" ] }); -$profile_test = Zonemaster::Engine::Profile->from_json( $json ); -Zonemaster::Engine::Profile->effective->merge( $profile_test ); - -my $blockline = 0; -my ($zonename, @gives, @gives_not); -while (my $line = ) { - chomp($line); - next if $line =~ /^#/; - next if ($blockline == 0 and $line eq ''); - if ($blockline == 3 and $line eq '') { - $blockline = 0; - next; - }; - if ($blockline == 0) { - $zonename = $line; - $blockline = 1; - next; - }; - if ($blockline == 1) { - if ($line eq '(none)') { - @gives = (); - } else { - $line =~ s/ *, */ /g; - @gives = split (/ +/, $line); - } - $blockline = 2; - next; - }; - if ($blockline == 2) { - if ($line eq '(none)') { - @gives_not = (); - } else { - $line =~ s/ *, */ /g; - @gives_not = split (/ +/, $line); - } - - my $zone = Zonemaster::Engine->zone( $zonename ); - zone_gives( $testcase, $zone, \@gives ) if scalar @gives; - zone_gives_not( $testcase, $zone, \@gives_not ) if scalar @gives_not; - $blockline = 3; - $zonename = ''; - @gives = ''; - @gives_not = ''; - next; - }; - die "Error in data section"; -}; +Zonemaster::Engine::Profile->effective->merge( Zonemaster::Engine::Profile->from_json( qq({ "test_cases": [ "$test_case" ] }) ) ); +perform_testcase_testing( $test_case, $test_module, %subtests ); if ( $ENV{ZONEMASTER_RECORD} ) { Zonemaster::Engine::Nameserver->save( $datafile ); } -Zonemaster::Engine::Profile->effective->set( q{no_network}, 0 ); -Zonemaster::Engine::Profile->effective->set( q{net.ipv4}, 0 ); -Zonemaster::Engine::Profile->effective->set( q{net.ipv6}, 0 ); - -TODO: { - local $TODO = "Scenario NON-AUTH-MX-RESPONSE cannot be tested."; - warn $TODO, "\n";; -} - done_testing; - - -# In that __DATA__ section each test is a block consisting of three lines: -# 1 Zone name -# 2 Tags tag "gives" -# 3 Tags that "gives not" -# -# If tag line is "(none)" than it should be ignored. -# -# Empty lines between blocks. Lines with "#" in column one are ignored (comments); - - -__DATA__ -no-response-mx-query.zone09.xa -Z09_NO_RESPONSE_MX_QUERY -(none) - -unexpected-rcode-mx.zone09.xa -Z09_UNEXPECTED_RCODE_MX -(none) - -# Does not currently work -# -# non-auth-mx-response.zone09.xa -# Z09_NON_AUTH_MX_RESPONSE -# (none) - -inconsistent-mx.zone09.xa -Z09_INCONSISTENT_MX, Z09_MX_FOUND, Z09_NO_MX_FOUND, Z09_MX_DATA -Z09_MISSING_MAIL_TARGET - -inconsistent-mx-data.zone09.xa -Z09_INCONSISTENT_MX_DATA, Z09_MX_DATA -Z09_MISSING_MAIL_TARGET, Z09_NULL_MX_NON_ZERO_PREF, Z09_NULL_MX_WITH_OTHER_MX, Z09_ROOT_EMAIL_DOMAIN, Z09_TLD_EMAIL_DOMAIN - -null-mx-with-other-mx.zone09.xa -Z09_NULL_MX_WITH_OTHER_MX -Z09_INCONSISTENT_MX_DATA, Z09_MX_DATA, Z09_MISSING_MAIL_TARGET, Z09_ROOT_EMAIL_DOMAIN, Z09_TLD_EMAIL_DOMAIN - -null-mx-non-zero-pref.zone09.xa -Z09_NULL_MX_NON_ZERO_PREF -Z09_INCONSISTENT_MX_DATA, Z09_MX_DATA, Z09_MISSING_MAIL_TARGET, Z09_ROOT_EMAIL_DOMAIN, Z09_TLD_EMAIL_DOMAIN - -tld-email-domain-zone09 -Z09_TLD_EMAIL_DOMAIN -Z09_INCONSISTENT_MX_DATA, Z09_MX_DATA, Z09_MISSING_MAIL_TARGET, Z09_ROOT_EMAIL_DOMAIN, Z09_NULL_MX_WITH_OTHER_MX, Z09_NULL_MX_NON_ZERO_PREF - -mx-data.zone09.xa -Z09_MX_DATA -Z09_INCONSISTENT_MX_DATA, Z09_MISSING_MAIL_TARGET, Z09_TLD_EMAIL_DOMAIN, Z09_ROOT_EMAIL_DOMAIN, Z09_NULL_MX_WITH_OTHER_MX, Z09_NULL_MX_NON_ZERO_PREF - -null-mx.zone09.xa -(none) -Z09_INCONSISTENT_MX_DATA, Z09_MX_DATA, Z09_MISSING_MAIL_TARGET, Z09_TLD_EMAIL_DOMAIN, Z09_ROOT_EMAIL_DOMAIN, Z09_NULL_MX_WITH_OTHER_MX, Z09_NULL_MX_NON_ZERO_PREF - -no-mx-sld.zone09.xa -Z09_MISSING_MAIL_TARGET -Z09_INCONSISTENT_MX_DATA, Z09_MX_DATA, Z09_TLD_EMAIL_DOMAIN, Z09_ROOT_EMAIL_DOMAIN, Z09_NULL_MX_WITH_OTHER_MX, Z09_NULL_MX_NON_ZERO_PREF - -no-mx-tld-zone09 -(none) -Z09_INCONSISTENT_MX_DATA, Z09_MX_DATA, Z09_MISSING_MAIL_TARGET, Z09_TLD_EMAIL_DOMAIN, Z09_ROOT_EMAIL_DOMAIN, Z09_NULL_MX_WITH_OTHER_MX, Z09_NULL_MX_NON_ZERO_PREF - -no-mx-arpa.zone09.arpa -(none) -Z09_INCONSISTENT_MX_DATA, Z09_MX_DATA, Z09_MISSING_MAIL_TARGET, Z09_TLD_EMAIL_DOMAIN, Z09_ROOT_EMAIL_DOMAIN, Z09_NULL_MX_WITH_OTHER_MX, Z09_NULL_MX_NON_ZERO_PREF - -# Always an emapty line after the last block - diff --git a/t/TestUtil.pm b/t/TestUtil.pm new file mode 100644 index 000000000..7ef6421a7 --- /dev/null +++ b/t/TestUtil.pm @@ -0,0 +1,132 @@ +package TestUtil; + +use 5.014002; + +use strict; +use warnings; + +use Test::More; +use Zonemaster::Engine; +use Exporter 'import'; + +BEGIN { + our @EXPORT_OK = qw[ perform_testcase_testing ]; + our %EXPORT_TAGS = ( all => \@EXPORT_OK ); + + ## no critic (Modules::ProhibitAutomaticExportation) + our @EXPORT = qw[ perform_testcase_testing ]; +} + +=head1 NAME + +TestUtil - a set of methods to ease Zonemaster::Engine unit testing + +=head1 SYNOPSIS + +Because this package lies in the testing folder C and that folder is +unknown to the include path @INC, it can be including using the following code: + + use File::Basename qw( dirname ); + use File::Spec::Functions qw( rel2abs ); + use lib dirname( rel2abs( $0 ) ); + use TestUtil; + +=head1 METHODS + +=over + +=item perform_testcase_testing() + + perform_testcase_testing( $test_case, $test_module, %subtests ); + +This method loads unit test data (test case name, test module name and test scenarios) and, after some data checks +and if the test scenario is testable, it runs the specified test case and checks for the presence (or absence) of +specific message tags for each specified test scenario. + +Takes a string (test case name), a string (test module name) and a hash - the keys of which are scenario names +(in all uppercase), and their corresponding values are an array of: a string (zone name), an array of strings +(mandatory message tags), an array of strings (forbidden message tags) and a boolean (testable). + +=back + +=cut + +sub perform_testcase_testing { + my ( $test_case, $test_module, %subtests ) = @_; + + my @untested_scenarios = (); + + for my $scenario ( sort ( keys %subtests ) ) { + if ( ref( $scenario ) ne '' or $scenario ne uc($scenario) ) { + diag("Scenario $scenario: Key must (i) not be a reference and (ii) be in all uppercase"); + fail("Hash contains valid keys"); + next; + } + + if ( scalar @{ $subtests{$scenario} } != 4 ) { + diag("Scenario $scenario: Incorrect number of values. " . + "Correct format is: { SCENARIO_NAME => [ zone_name, [ MANDATORY_MESSAGE_TAGS ], [ FORBIDDEN_MESSAGE_TAGS ], testable ] }" + ); + fail("Hash contains valid values"); + next; + } + + my ( $zone_name, $mandatory_message_tags, $forbidden_message_tags, $testable ) = @{ $subtests{$scenario} }; + + if ( ref( $zone_name ) ne '' ) { + diag("Scenario $scenario: Type of zone name must not be a reference"); + fail("Zone name is of the correct type"); + next; + } + + if ( ref( $mandatory_message_tags ) ne 'ARRAY' ) { + diag("Scenario $scenario: Incorrect reference type of mandatory message tags. Expected: ARRAY"); + fail("Mandatory message tags are of the correct type"); + next; + } + + if ( ref( $forbidden_message_tags ) ne 'ARRAY' ) { + diag("Scenario $scenario: Incorrect reference type of forbidden message tags. Expected: ARRAY"); + fail("Forbidden message tags are of the correct type"); + next; + } + + if ( ref( $testable ) ne '' ) { + diag("Scenario $scenario: Type of testable must not be a reference"); + fail("Testable is of the correct type"); + next; + } + + if ( not $testable ) { + push @untested_scenarios, $scenario; + next; + } + + subtest $scenario => sub { + my @messages = Zonemaster::Engine->test_method( $test_module, $test_case, Zonemaster::Engine->zone( $zone_name ) ); + my %res = map { $_->tag => 1 } @messages; + + if ( my ( $error ) = grep { $_->tag eq 'MODULE_ERROR' } @messages ) { + diag("Module died with error: " . $error->args->{"msg"}); + fail("Test case executes properly"); + } + else { + for my $tag ( @{ $mandatory_message_tags } ) { + ok( exists $res{$tag}, "Tag $tag is outputted" ) + or diag "Tag '$tag' should have been outputted, but wasn't"; + } + for my $tag ( @{ $forbidden_message_tags } ) { + ok( !exists $res{$tag}, "Tag $tag is not outputted" ) + or diag "Tag '$tag' was not supposed to be outputted, but it was"; + } + } + }; + } + + if ( @untested_scenarios ) { + warn "Untested scenarios:\n"; + warn "\tScenario $_ cannot be tested.\n" for @untested_scenarios; + } +} + +1;