feat: add full Zonemaster stack with Docker and Spanish UI
- Clone all 5 Zonemaster component repos (LDNS, Engine, CLI, Backend, GUI) - Dockerfile.backend: 8-stage multi-stage build LDNS→Engine→CLI→Backend - Dockerfile.gui: Astro static build served via nginx - docker-compose.yml: backend (internal) + frontend (port 5353) - nginx.conf: root redirects to /es/, /api/ proxied to backend - zonemaster-gui/config.ts: defaultLanguage set to 'es' (Spanish) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
12
zonemaster-cli/t/00-load.t
Normal file
12
zonemaster-cli/t/00-load.t
Normal file
@@ -0,0 +1,12 @@
|
||||
use 5.014002;
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use Test::More;
|
||||
|
||||
plan tests => 1;
|
||||
|
||||
BEGIN {
|
||||
use_ok( 'Zonemaster::CLI' ) || print "Bail out!\n";
|
||||
}
|
||||
|
||||
diag( "Testing Zonemaster::CLI $Zonemaster::CLI::VERSION, Perl $], $^X" );
|
||||
37
zonemaster-cli/t/manifest.t
Normal file
37
zonemaster-cli/t/manifest.t
Normal file
@@ -0,0 +1,37 @@
|
||||
#!perl
|
||||
use v5.14.2;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use Test::More tests => 2;
|
||||
use Test::NoWarnings;
|
||||
|
||||
use File::Basename qw( dirname );
|
||||
|
||||
chdir dirname( dirname( __FILE__ ) ) or BAIL_OUT( "chdir: $!" );
|
||||
|
||||
my $makebin = 'make';
|
||||
|
||||
sub make {
|
||||
my @make_args = @_;
|
||||
|
||||
undef $ENV{MAKEFLAGS};
|
||||
|
||||
my $command = join( ' ', $makebin, '-s', @make_args );
|
||||
my $output = `$command 2>&1`;
|
||||
|
||||
if ( $? == -1 ) {
|
||||
BAIL_OUT( "failed to execute: $!" );
|
||||
}
|
||||
elsif ( $? & 127 ) {
|
||||
BAIL_OUT( "child died with signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without' );
|
||||
}
|
||||
|
||||
return $output, $? >> 8;
|
||||
}
|
||||
|
||||
subtest "distcheck" => sub {
|
||||
my ( $output, $status ) = make "distcheck";
|
||||
is $status, 0, $makebin . ' distcheck exits with value 0';
|
||||
is $output, "", $makebin . ' distcheck gives empty output';
|
||||
};
|
||||
64
zonemaster-cli/t/po-files.t
Normal file
64
zonemaster-cli/t/po-files.t
Normal file
@@ -0,0 +1,64 @@
|
||||
#!perl
|
||||
use v5.14.2;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use Test::More; # see done_testing()
|
||||
|
||||
use File::Basename qw( dirname );
|
||||
|
||||
chdir dirname( dirname( __FILE__ ) ) or BAIL_OUT( "chdir: $!" );
|
||||
chdir 'share' or BAIL_OUT( "chdir: $!" );
|
||||
|
||||
my $makebin = 'make';
|
||||
|
||||
sub make {
|
||||
my @make_args = @_;
|
||||
|
||||
undef $ENV{MAKEFLAGS};
|
||||
|
||||
my $command = join( ' ', $makebin, '--silent', '--no-print-directory', @make_args );
|
||||
my $output = `$command`;
|
||||
|
||||
if ( $? == -1 ) {
|
||||
BAIL_OUT( "failed to execute: $!" );
|
||||
}
|
||||
elsif ( $? & 127 ) {
|
||||
BAIL_OUT( "child died with signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without' );
|
||||
}
|
||||
|
||||
return $output, $? >> 8;
|
||||
}
|
||||
|
||||
subtest "no fuzzy marks" => sub {
|
||||
my ( $output, $status ) = make "show-fuzzy";
|
||||
is $status, 0, $makebin . ' show-fuzzy exits with value 0';
|
||||
is $output, "", $makebin . ' show-fuzzy gives empty output';
|
||||
};
|
||||
|
||||
subtest "check po files" => sub {
|
||||
my ( $output, $status ) = make "check-po";
|
||||
is $status, 0, $makebin . ' check-po exits with value 0';
|
||||
is $output, "", $makebin . ' check-po gives empty output';
|
||||
};
|
||||
|
||||
subtest "tidy po files" => sub {
|
||||
SKIP: {
|
||||
my ( $output, $status );
|
||||
|
||||
$output = `git diff --numstat`;
|
||||
|
||||
skip 'git repo should be clean to run this test', 3 if $output ne '';
|
||||
|
||||
diag "Using msgcat version: " . `msgcat --version | head -n1`;
|
||||
|
||||
( $output, $status ) = make "tidy-po";
|
||||
is $status, 0, $makebin . ' tidy-po exits with value 0';
|
||||
is $output, "", $makebin . ' tidy-po gives empty output';
|
||||
|
||||
$output = `git diff --numstat`;
|
||||
is $output, "", 'all files are tidied (if not run "make tidy-po")';
|
||||
}
|
||||
};
|
||||
|
||||
done_testing();
|
||||
18
zonemaster-cli/t/pod.t
Normal file
18
zonemaster-cli/t/pod.t
Normal file
@@ -0,0 +1,18 @@
|
||||
#!perl -T
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use Test::More;
|
||||
|
||||
# Ensure a recent version of Test::Pod
|
||||
my $min_tp = 1.22;
|
||||
eval "use Test::Pod $min_tp";
|
||||
plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
|
||||
|
||||
my @poddirs;
|
||||
push @poddirs, ( -e 'lib' ? 'lib' : 'blib' );
|
||||
push @poddirs, 'script';
|
||||
|
||||
all_pod_files_ok( all_pod_files( @poddirs ) );
|
||||
|
||||
done_testing;
|
||||
320
zonemaster-cli/t/test_case_set.t
Normal file
320
zonemaster-cli/t/test_case_set.t
Normal file
@@ -0,0 +1,320 @@
|
||||
#!perl
|
||||
use 5.14.2;
|
||||
use utf8;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
|
||||
use Log::Any::Test;
|
||||
use Log::Any qw( $log );
|
||||
use Test::Differences;
|
||||
use Test::Exception;
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Engine::Profile;
|
||||
|
||||
use Zonemaster::CLI::TestCaseSet;
|
||||
|
||||
require Test::NoWarnings;
|
||||
|
||||
lives_ok { # Make sure we get to print log messages in case of errors.
|
||||
subtest 'parse_modifier_expr' => sub {
|
||||
my @cases = (
|
||||
{
|
||||
name => 'empty',
|
||||
expr => '',
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'absolute term',
|
||||
expr => 'term',
|
||||
expected => [ '', 'term' ],
|
||||
},
|
||||
{
|
||||
name => 'absolute additive',
|
||||
expr => 'term',
|
||||
expected => [ '', 'term' ],
|
||||
},
|
||||
{
|
||||
name => 'absolute subtractive',
|
||||
expr => 'term',
|
||||
expected => [ '', 'term' ],
|
||||
},
|
||||
{
|
||||
name => 'absolute multiple modifiers',
|
||||
expr => 'term1+term2',
|
||||
expected => [ '', 'term1', '+', 'term2' ],
|
||||
},
|
||||
{
|
||||
name => 'relative multiple modifiers',
|
||||
expr => '-term1+term2',
|
||||
expected => [ '-', 'term1', '+', 'term2' ],
|
||||
},
|
||||
);
|
||||
for my $case ( @cases ) {
|
||||
subtest $case->{name} => sub {
|
||||
my @actual = Zonemaster::CLI::TestCaseSet->parse_modifier_expr( $case->{expr} );
|
||||
eq_or_diff \@actual, $case->{expected};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
subtest 'new' => sub {
|
||||
my @cases = (
|
||||
{
|
||||
name => 'empty',
|
||||
schema => {},
|
||||
selection => [],
|
||||
expect_ok => {
|
||||
terms => ['all'],
|
||||
methods => [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name => 'multiple test modules and test cases',
|
||||
schema => { 'alpha' => [ 'bravo', 'charlie' ], 'delta' => ['echo'] },
|
||||
selection => [ 'bravo', 'echo' ],
|
||||
expect_ok => {
|
||||
terms => [
|
||||
'all', 'alpha', 'alpha/bravo', 'alpha/charlie',
|
||||
'bravo', 'charlie', 'delta', 'delta/echo',
|
||||
'echo'
|
||||
],
|
||||
methods => [ 'bravo', 'echo' ],
|
||||
},
|
||||
},
|
||||
{
|
||||
name => 'mixed cases',
|
||||
schema => { 'alpha' => [ 'BRAVO', 'charlie' ] },
|
||||
selection => [ 'bravo', 'CHARLIE' ],
|
||||
expect_ok => {
|
||||
terms => [ 'all', 'alpha', 'alpha/bravo', 'alpha/charlie', 'bravo', 'charlie' ],
|
||||
methods => [ 'bravo', 'charlie' ],
|
||||
},
|
||||
},
|
||||
{
|
||||
name => 'illegal test module name 1',
|
||||
schema => { 'all' => [] },
|
||||
selection => [],
|
||||
expect_err => qr/must not be 'all'/i,
|
||||
},
|
||||
{
|
||||
name => 'illegal test module name 2',
|
||||
schema => { 'ALL' => [] },
|
||||
selection => [],
|
||||
expect_err => qr/must not be 'all'/i,
|
||||
},
|
||||
{
|
||||
name => 'illegal test module name 3',
|
||||
schema => { 'alpha/bravo' => [] },
|
||||
selection => [],
|
||||
expect_err => qr{contains forbidden character '/'}i,
|
||||
},
|
||||
{
|
||||
name => 'illegal test case name 1',
|
||||
schema => { 'alpha' => ['all'] },
|
||||
selection => [],
|
||||
expect_err => qr/must not be 'all'/i,
|
||||
},
|
||||
{
|
||||
name => 'illegal test case name 2',
|
||||
schema => { 'alpha' => ['ALL'] },
|
||||
selection => [],
|
||||
expect_err => qr/must not be 'all'/i,
|
||||
},
|
||||
{
|
||||
name => 'illegal test case name 3',
|
||||
schema => { 'alpha' => ['bravo/charlie'] },
|
||||
selection => [],
|
||||
expect_err => qr{contains forbidden character '/'}i,
|
||||
},
|
||||
{
|
||||
name => 'duplicate term 1',
|
||||
schema => { 'alpha' => ['alpha'] },
|
||||
selection => [],
|
||||
expect_err => qr/same name/i,
|
||||
},
|
||||
{
|
||||
name => 'duplicate term 2',
|
||||
schema => { 'alpha' => ['ALPHA'] },
|
||||
selection => [],
|
||||
expect_err => qr/same name/i,
|
||||
},
|
||||
{
|
||||
name => 'duplicate term 3',
|
||||
schema => { 'ALPHA' => ['alpha'] },
|
||||
selection => [],
|
||||
expect_err => qr/same name/i,
|
||||
},
|
||||
{
|
||||
name => 'duplicate term 4',
|
||||
schema => { 'alpha' => [], 'bravo' => ['alpha'] },
|
||||
selection => [],
|
||||
expect_err => qr/same name/i,
|
||||
},
|
||||
{
|
||||
name => 'duplicate term 5',
|
||||
schema => { 'alpha' => [ 'bravo', 'bravo' ] },
|
||||
selection => [],
|
||||
expect_err => qr/same name/i,
|
||||
},
|
||||
{
|
||||
name => 'duplicate term 6',
|
||||
schema => { 'alpha' => ['bravo'], 'charlie' => ['bravo'] },
|
||||
selection => [],
|
||||
expect_err => qr/same name/i,
|
||||
},
|
||||
{
|
||||
name => 'unrecognized test case 1',
|
||||
schema => { 'alpha' => [] },
|
||||
selection => ['all'],
|
||||
expect_err => qr/unrecognized/i,
|
||||
},
|
||||
{
|
||||
name => 'unrecognized test case 2',
|
||||
schema => { 'alpha' => [] },
|
||||
selection => ['alpha'],
|
||||
expect_err => qr/unrecognized/i,
|
||||
},
|
||||
);
|
||||
for my $case ( @cases ) {
|
||||
subtest $case->{name} => sub {
|
||||
my $test_case_set;
|
||||
local $@;
|
||||
eval {
|
||||
$test_case_set = Zonemaster::CLI::TestCaseSet->new( #
|
||||
$case->{selection},
|
||||
$case->{schema},
|
||||
);
|
||||
};
|
||||
|
||||
my $err = $@;
|
||||
my $actual;
|
||||
if ( !$err ) {
|
||||
$actual = {
|
||||
terms => [ sort keys %{ $test_case_set->{_terms} } ],
|
||||
methods => [ $test_case_set->to_list ],
|
||||
};
|
||||
}
|
||||
|
||||
if ( defined $case->{expect_err} ) {
|
||||
like $err, $case->{expect_err}, "error";
|
||||
}
|
||||
else {
|
||||
is $err, "", "no error";
|
||||
}
|
||||
if ( defined $case->{expect_ok} ) {
|
||||
eq_or_diff $actual, $case->{expect_ok}, "result";
|
||||
}
|
||||
else {
|
||||
eq_or_diff $actual, undef, "no result";
|
||||
}
|
||||
}; ## end sub
|
||||
} ## end for my $case ( @cases )
|
||||
}; ## end 'new' => sub
|
||||
|
||||
subtest 'apply_modifier' => sub {
|
||||
my @cases = (
|
||||
{
|
||||
name => 'empty',
|
||||
schema => {},
|
||||
selection => [],
|
||||
modifiers => [],
|
||||
expected => [],
|
||||
},
|
||||
{
|
||||
name => 'no modifiers',
|
||||
schema => { basic => [ 'basic01', 'basic02' ] },
|
||||
selection => ['basic01'],
|
||||
modifiers => [],
|
||||
expected => ['basic01'],
|
||||
},
|
||||
{
|
||||
name => 'add a new case',
|
||||
schema => { basic => [ 'basic01', 'basic02' ] },
|
||||
selection => ['basic01'],
|
||||
modifiers => [ '+', 'basic02' ],
|
||||
expected => [ 'basic01', 'basic02' ],
|
||||
},
|
||||
{
|
||||
name => 'add the same case',
|
||||
schema => { basic => [ 'basic01', 'basic02' ] },
|
||||
selection => ['basic01'],
|
||||
modifiers => [ '+', 'basic01' ],
|
||||
expected => ['basic01'],
|
||||
},
|
||||
{
|
||||
name => 'replace',
|
||||
schema => { basic => [ 'basic01', 'basic02' ] },
|
||||
selection => ['basic01'],
|
||||
modifiers => [ '', 'basic02' ],
|
||||
expected => ['basic02'],
|
||||
},
|
||||
{
|
||||
name => 'module expansion',
|
||||
schema => { basic => ['basic01'], extra => [ 'extra01', 'extra02' ] },
|
||||
selection => ['basic01'],
|
||||
modifiers => [ '', 'extra' ],
|
||||
expected => [ 'extra01', 'extra02' ],
|
||||
},
|
||||
{
|
||||
name => 'all',
|
||||
schema => { basic => ['basic01'], extra => [ 'extra01', 'extra02' ] },
|
||||
selection => ['basic01'],
|
||||
modifiers => [ '', 'all' ],
|
||||
expected => [ 'basic01', 'extra01', 'extra02' ],
|
||||
},
|
||||
{
|
||||
name => 'multiple modifiers',
|
||||
schema => { basic => ['basic01'], extra => [ 'extra01', 'extra02' ] },
|
||||
selection => ['basic01'],
|
||||
modifiers => [ '', 'all', '-', 'basic' ],
|
||||
expected => [ 'extra01', 'extra02' ],
|
||||
},
|
||||
{
|
||||
name => 'invalid operator',
|
||||
schema => { basic => [ 'basic01', 'basic02' ] },
|
||||
selection => ['basic01'],
|
||||
modifiers => [ '*', 'basic02' ],
|
||||
error => qr{unrecognized operator}i,
|
||||
},
|
||||
);
|
||||
for my $case ( @cases ) {
|
||||
subtest $case->{name} => sub {
|
||||
my $test_case_set = Zonemaster::CLI::TestCaseSet->new( #
|
||||
$case->{selection},
|
||||
$case->{schema},
|
||||
);
|
||||
|
||||
local $@ = '';
|
||||
eval {
|
||||
while ( @{ $case->{modifiers} } ) {
|
||||
my $op = shift @{ $case->{modifiers} };
|
||||
my $term = shift @{ $case->{modifiers} };
|
||||
$test_case_set->apply_modifier( $op, $term );
|
||||
}
|
||||
};
|
||||
my $error = $@;
|
||||
|
||||
if ( exists $case->{expected} ) {
|
||||
is $error, '';
|
||||
eq_or_diff [ $test_case_set->to_list ], $case->{expected};
|
||||
}
|
||||
else {
|
||||
like $error, $case->{error};
|
||||
}
|
||||
};
|
||||
} ## end for my $case ( @cases )
|
||||
};
|
||||
};
|
||||
|
||||
for my $msg ( @{ $log->msgs } ) {
|
||||
my $text = sprintf( "%s: %s", $msg->{level}, $msg->{message} );
|
||||
if ( $msg->{level} =~ /trace|debug|info|notice/ ) {
|
||||
note $text;
|
||||
}
|
||||
else {
|
||||
diag $text;
|
||||
}
|
||||
}
|
||||
|
||||
Test::NoWarnings::had_no_warnings();
|
||||
done_testing;
|
||||
138
zonemaster-cli/t/usage.fake-data.data
Normal file
138
zonemaster-cli/t/usage.fake-data.data
Normal file
File diff suppressed because one or more lines are too long
1
zonemaster-cli/t/usage.fake-root.data
Normal file
1
zonemaster-cli/t/usage.fake-root.data
Normal file
@@ -0,0 +1 @@
|
||||
ns1.test 192.0.2.1 {"9kfShNTLVy4wDehBPdpCXg":null}
|
||||
2
zonemaster-cli/t/usage.hints
Normal file
2
zonemaster-cli/t/usage.hints
Normal file
@@ -0,0 +1,2 @@
|
||||
. NS ns1.test
|
||||
ns1.test A 192.0.2.1
|
||||
26
zonemaster-cli/t/usage.normal.data
Normal file
26
zonemaster-cli/t/usage.normal.data
Normal file
@@ -0,0 +1,26 @@
|
||||
b.root-servers.net 2001:0500:0200:0000:0000:0000:0000:000b {}
|
||||
b.root-servers.net 199.9.14.201 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"Q/iEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA==","timestamp":1731580714.30933,"answerfrom":"199.9.14.201","querytime":0}}}}
|
||||
l.root-servers.net 2001:0500:009f:0000:0000:0000:0000:0042 {}
|
||||
l.root-servers.net 199.7.83.42 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"72GEAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKRAAAAAAAAAA","querytime":0,"answerfrom":"199.7.83.42","timestamp":1731580714.6482}}}}
|
||||
h.root-servers.net 198.97.190.53 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":0,"timestamp":1731580714.53711,"answerfrom":"198.97.190.53","data":"jU+EAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKQTQAAAAAAAA"}}}}
|
||||
h.root-servers.net 2001:0500:0001:0000:0000:0000:0000:0053 {}
|
||||
j.root-servers.net 2001:0503:0c27:0000:0000:0000:0002:0030 {}
|
||||
j.root-servers.net 192.58.128.30 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":0,"answerfrom":"192.58.128.30","timestamp":1731580714.58295,"data":"R7KEAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKQXAAAAAAAAA"}}}}
|
||||
f.root-servers.net 192.5.5.241 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":0,"timestamp":1731580714.41004,"answerfrom":"192.5.5.241","data":"1CWEAAABAAEADQAbAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAABAFjwB4AAAIAAQAH6QAABAFlwB4AAAIAAQAH6QAABAFrwB4AAAIAAQAH6QAABAFswB4AAAIAAQAH6QAABAFkwB4AAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBbcAewGcAAQABAAfpAAAEwCEEDMBnABwAAQAH6QAAECABBQAAAgAAAAAAAAAAAAzAdgABAAEAB+kAAATAy+YKwHYAHAABAAfpAAAQIAEFAACoAAAAAAAAAAAADsCFAAEAAQAH6QAABMEADoHAhQAcAAEAB+kAABAgAQf9AAAAAAAAAAAAAAABwJQAAQABAAfpAAAExwdTKsCUABwAAQAH6QAAECABBQAAnwAAAAAAAAAAAELAowABAAEAB+kAAATHB1sNwKMAHAABAAfpAAAQIAEFAAAtAAAAAAAAAAAADcAcAAEAAQAH6QAABMYpAATAHAAcAAEAB+kAABAgAQUDuj4AAAAAAAAAAgAwwL8AAQABAAfpAAAEqveqAsC/ABwAAQAH6QAAECgBAbgAEAAAAAAAAAAAAAvAzgABAAEAB+kAAATAJJQRwM4AHAABAAfpAAAQIAEH/gAAAAAAAAAAAAAAU8DdAAEAAQAH6QAABMAFBfHA3QAcAAEAB+kAABAgAQUAAC8AAAAAAAAAAAAPwOwAAQABAAfpAAAExmG+NcDsABwAAQAH6QAAECABBQAAAQAAAAAAAAAAAFPA+wABAAEAB+kAAATAcCQEwPsAHAABAAfpAAAQIAEFAAASAAAAAAAAAAANDcEKAAEAAQAH6QAABMA6gB7BCgAcAAEAB+kAABAgAQUDDCcAAAAAAAAAAgAwwRkAAQABAAfpAAAEygwbIcEZABwAAQAH6QAAECABDcMAAAAAAAAAAAAAADUAACn//wAAAAAAAA=="}}}}
|
||||
f.root-servers.net 2001:0500:002f:0000:0000:0000:0000:000f {}
|
||||
k.root-servers.net 193.0.14.129 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"EJGEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA==","timestamp":1731580714.61398,"answerfrom":"193.0.14.129","querytime":0}}}}
|
||||
k.root-servers.net 2001:07fd:0000:0000:0000:0000:0000:0001 {}
|
||||
e.root-servers.net 192.203.230.10 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"mDyEAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKRAAAAAAAAAA","querytime":0,"timestamp":1731580714.3888,"answerfrom":"192.203.230.10"}}}}
|
||||
e.root-servers.net 2001:0500:00a8:0000:0000:0000:0000:000e {}
|
||||
d.root-servers.net 199.7.91.13 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"answerfrom":"199.7.91.13","timestamp":1731580714.37701,"querytime":0,"data":"ZOyEAAABAAEADQAOAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAAAsAcAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBbMAeAAACAAEAB+kAAAQBbcAewBwAAQABAAfpAAAExikABMB0AAEAAQAH6QAABKr3qgLAgwABAAEAB+kAAATAIQQMwJIAAQABAAfpAAAExwdbDcChAAEAAQAH6QAABMDL5grAsAABAAEAB+kAAATABQXxwL8AAQABAAfpAAAEwHAkBMDOAAEAAQAH6QAABMZhvjXA3QABAAEAB+kAAATAJJQRwOwAAQABAAfpAAAEwDqAHsD7AAEAAQAH6QAABMEADoHBCgABAAEAB+kAAATHB1MqwRkAAQABAAfpAAAEygwbIQAAKQWqAAAAAAAA"}}}}
|
||||
d.root-servers.net 2001:0500:002d:0000:0000:0000:0000:000d {}
|
||||
g.root-servers.net 2001:0500:0012:0000:0000:0000:0000:0d0d {}
|
||||
g.root-servers.net 192.112.36.4 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"querytime":0,"timestamp":1731580714.42995,"answerfrom":"192.112.36.4","data":"8aWEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA=="}}}}
|
||||
m.root-servers.net 2001:0dc3:0000:0000:0000:0000:0000:0035 {}
|
||||
m.root-servers.net 202.12.27.33 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"timestamp":1731580714.65964,"answerfrom":"202.12.27.33","querytime":0,"data":"reuEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA=="}}}}
|
||||
a.root-servers.net 198.41.0.4 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"answerfrom":"198.41.0.4","timestamp":1731580714.27699,"querytime":0,"data":"9WaEAAABAAEADQALAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAAAIAAQAH6QAABAFswB4AAAIAAQAH6QAABAFqwB4AAAIAAQAH6QAABAFmwB4AAAIAAQAH6QAABAFowB4AAAIAAQAH6QAABAFkwB4AAAIAAQAH6QAABAFiwB4AAAIAAQAH6QAABAFrwB4AAAIAAQAH6QAABAFpwB4AAAIAAQAH6QAABAFtwB4AAAIAAQAH6QAABAFlwB4AAAIAAQAH6QAABAFnwB4AAAIAAQAH6QAABAFjwB4AAAIAAQAH6QAAAsAcwGcAAQABAAfpAAAExwdTKsBnABwAAQAH6QAAECABBQAAnwAAAAAAAAAAAELAdgABAAEAB+kAAATAOoAewHYAHAABAAfpAAAQIAEFAwwnAAAAAAAAAAIAMMCFAAEAAQAH6QAABMAFBfHAhQAcAAEAB+kAABAgAQUAAC8AAAAAAAAAAAAPwJQAAQABAAfpAAAExmG+NcCUABwAAQAH6QAAECABBQAAAQAAAAAAAAAAAFPAowABAAEAB+kAAATHB1sNwLIAAQABAAfpAAAEqveqAgAAKRAAAAAAAAAA"}}},"UzfnoYKACE71u6V/QQ17nw":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"UCOEAAABAA0AAAANAAACAAEAAAIAAQAH6QAAFAFsDHJvb3Qtc2VydmVycwNuZXQAAAACAAEAB+kAAAQBasAeAAACAAEAB+kAAAQBZsAeAAACAAEAB+kAAAQBaMAeAAACAAEAB+kAAAQBZMAeAAACAAEAB+kAAAQBYsAeAAACAAEAB+kAAAQBa8AeAAACAAEAB+kAAAQBacAeAAACAAEAB+kAAAQBbcAeAAACAAEAB+kAAAQBZcAeAAACAAEAB+kAAAQBZ8AeAAACAAEAB+kAAAQBY8AeAAACAAEAB+kAAAQBYcAewBwAAQABAAfpAAAExwdTKsAcABwAAQAH6QAAECABBQAAnwAAAAAAAAAAAELAOwABAAEAB+kAAATAOoAewDsAHAABAAfpAAAQIAEFAwwnAAAAAAAAAAIAMMBKAAEAAQAH6QAABMAFBfHASgAcAAEAB+kAABAgAQUAAC8AAAAAAAAAAAAPwFkAAQABAAfpAAAExmG+NcBZABwAAQAH6QAAECABBQAAAQAAAAAAAAAAAFPAaAABAAEAB+kAAATHB1sNwGgAHAABAAfpAAAQIAEFAAAtAAAAAAAAAAAADcB3AAEAAQAH6QAABKr3qgLAdwAcAAEAB+kAABAoAQG4ABAAAAAAAAAAAAALAAApEAAAAAAAAAA=","timestamp":1731580714.23954,"answerfrom":"198.41.0.4","querytime":0}}}}
|
||||
a.root-servers.net 2001:0503:ba3e:0000:0000:0000:0002:0030 {}
|
||||
i.root-servers.net 2001:07fe:0000:0000:0000:0000:0000:0053 {}
|
||||
i.root-servers.net 192.36.148.17 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"tQyEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA==","querytime":0,"timestamp":1731580714.57324,"answerfrom":"192.36.148.17"}}}}
|
||||
c.root-servers.net 192.33.4.12 {"9kfShNTLVy4wDehBPdpCXg":{"Zonemaster::Engine::Packet":{"Zonemaster::LDNS::Packet":{"data":"b2iEAAABAAEAAAABAAAGAAEAAAYAAQABUYAAQAFhDHJvb3Qtc2VydmVycwNuZXQABW5zdGxkDHZlcmlzaWduLWdycwNjb20AeKV9KAAABwgAAAOEAAk6gAABUYAAACkE0AAAAAAAAA==","querytime":0,"answerfrom":"192.33.4.12","timestamp":1731580714.34358}}}}
|
||||
c.root-servers.net 2001:0500:0002:0000:0000:0000:0000:000c {}
|
||||
20
zonemaster-cli/t/usage.profile
Normal file
20
zonemaster-cli/t/usage.profile
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"net":{
|
||||
"ipv4": false,
|
||||
"ipv6": false
|
||||
},
|
||||
"resolver":{
|
||||
"source4": "192.0.2.1",
|
||||
"source6": "2001:db8::1"
|
||||
},
|
||||
"test_levels" : {
|
||||
"BASIC" : {
|
||||
"B01_CHILD_FOUND" : "NOTICE",
|
||||
"B01_ROOT_HAS_NO_PARENT" : "WARNING",
|
||||
"B02_AUTH_RESPONSE_SOA" : "ERROR"
|
||||
},
|
||||
"SYSTEM" : {
|
||||
"GLOBAL_VERSION" : "INFO"
|
||||
}
|
||||
}
|
||||
}
|
||||
712
zonemaster-cli/t/usage.t
Normal file
712
zonemaster-cli/t/usage.t
Normal file
@@ -0,0 +1,712 @@
|
||||
#!perl
|
||||
use 5.16.0;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use Test::More;
|
||||
|
||||
use Config '%Config';
|
||||
use Encode qw( decode_utf8 );
|
||||
use File::Basename qw( dirname );
|
||||
use File::Slurp qw( read_file write_file );
|
||||
use File::Spec::Functions qw( catfile );
|
||||
use File::Temp qw( tempdir );
|
||||
use IPC::Open3;
|
||||
use JSON::XS;
|
||||
use POSIX;
|
||||
use Readonly;
|
||||
use Symbol qw( gensym );
|
||||
use Test::Differences;
|
||||
use Zonemaster::CLI;
|
||||
use JSON::Validator;
|
||||
|
||||
# Force locale C for these unit tests. They depend on the print outs not being
|
||||
# translated. See zonemaster/zonemaster-cli/issues/438 and
|
||||
# https://github.com/zonemaster/zonemaster-cli/issues/438#issuecomment-2996235684
|
||||
$ENV{LC_ALL} = "C.UTF-8";
|
||||
delete $ENV{LANG};
|
||||
delete $ENV{LANGUAGE};
|
||||
|
||||
|
||||
# CONSTANTS
|
||||
|
||||
Readonly::Array my @SIG_NAMES => do {
|
||||
my @sig_names;
|
||||
@sig_names[ split ' ', $Config{sig_num} ] = split ' ', $Config{sig_name};
|
||||
@sig_names;
|
||||
};
|
||||
|
||||
Readonly::Scalar my $PATH_WRAPPER => catfile( dirname( __FILE__ ), 'usage.wrapper.pl' );
|
||||
Readonly::Scalar my $PATH_NORMAL_DATAFILE => catfile( dirname( __FILE__ ), 'usage.normal.data' );
|
||||
Readonly::Scalar my $PATH_FAKE_DATA_DATAFILE => catfile( dirname( __FILE__ ), 'usage.fake-data.data' );
|
||||
Readonly::Scalar my $PATH_FAKE_ROOT_DATAFILE => catfile( dirname( __FILE__ ), 'usage.fake-root.data' );
|
||||
Readonly::Array my @PERL => do {
|
||||
# Detect whether Devel::Cover is running
|
||||
my $is_covering = !!( eval 'Devel::Cover::get_coverage()' );
|
||||
note $is_covering ? 'Devel::Cover running' : 'Devel::Cover not covering';
|
||||
( $^X, $is_covering ? ( '-MDevel::Cover=-silent,1' ) : () )
|
||||
};
|
||||
|
||||
# MUTABLE GLOBAL VARIABLES
|
||||
|
||||
our $test_datafile;
|
||||
|
||||
# SETUP
|
||||
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
write_file $PATH_NORMAL_DATAFILE, '';
|
||||
write_file $PATH_FAKE_DATA_DATAFILE, '';
|
||||
write_file $PATH_FAKE_ROOT_DATAFILE, '';
|
||||
}
|
||||
|
||||
# HELPERS
|
||||
|
||||
sub json_schema {
|
||||
my ( $schema ) = @_;
|
||||
my $validator = JSON::Validator->new;
|
||||
$validator->schema( $schema );
|
||||
return $validator;
|
||||
}
|
||||
|
||||
sub check_success {
|
||||
my ( $name, $args, $predicate ) = @_;
|
||||
|
||||
subtest $name => sub {
|
||||
my $result = _run_zonemaster_cli( $test_datafile, @$args );
|
||||
|
||||
my $stdout = delete $result->{stdout};
|
||||
my $stderr = delete $result->{stderr};
|
||||
my $exitstatus = delete $result->{exitstatus};
|
||||
|
||||
if ( $stderr ne '' ) {
|
||||
note "stderr:\n$stderr" =~ s/\n/\n /gr;
|
||||
}
|
||||
|
||||
if ( ref $predicate eq 'CODE' ) {
|
||||
if ( $predicate->( $stdout ) ) {
|
||||
pass 'expected stdout (sub)';
|
||||
}
|
||||
else {
|
||||
fail 'expected stdout (sub)';
|
||||
diag "actual stdout:\n$stdout" =~ s/\n/\n /gr;
|
||||
}
|
||||
}
|
||||
elsif ( ref $predicate eq 'Regexp' ) {
|
||||
like $stdout, $predicate, 'expected stdout (regex)';
|
||||
}
|
||||
elsif ( blessed $predicate && blessed $predicate eq 'JSON::Validator' ) {
|
||||
my @items = parse_json_stream( $stdout );
|
||||
my @errors = $predicate->validate( [@items] );
|
||||
if ( !eq_or_diff \@errors, [], "schema validation" ) {
|
||||
diag "actual stdout:\n$stdout" =~ s/\n/\n /gr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BAIL_OUT( "unrecognized predicate type" );
|
||||
}
|
||||
|
||||
is $exitstatus, $Zonemaster::CLI::EXIT_SUCCESS, 'success exit status';
|
||||
}; ## end sub
|
||||
} ## end sub check_success
|
||||
|
||||
sub check_success_report {
|
||||
my ( $name, $args, $predicates ) = @_;
|
||||
|
||||
subtest $name => sub {
|
||||
check_success 'normal mode', $args, $predicates->{text};
|
||||
|
||||
check_success 'raw mode', $args, $predicates->{text};
|
||||
|
||||
check_success 'json mode', [ '--json', @$args ],
|
||||
json_schema(
|
||||
{
|
||||
type => "array",
|
||||
items => $predicates->{json},
|
||||
}
|
||||
);
|
||||
|
||||
check_success 'json-stream mode', [ '--json-stream', @$args ],
|
||||
json_schema(
|
||||
{
|
||||
type => "array",
|
||||
contains => $predicates->{json},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
sub check_usage_error {
|
||||
my ( $name, $args, $error_pattern ) = @_;
|
||||
|
||||
subtest $name => sub {
|
||||
my $result = _run_zonemaster_cli( undef, @$args );
|
||||
|
||||
my $stderr = delete $result->{stderr};
|
||||
like $stderr, $error_pattern, 'expected error message';
|
||||
|
||||
eq_or_diff(
|
||||
$result,
|
||||
{
|
||||
stdout => '',
|
||||
exitstatus => $Zonemaster::CLI::EXIT_USAGE_ERROR,
|
||||
},
|
||||
'no stdout and usage error exit code'
|
||||
) or diag "stderr:\n$stderr" =~ s/\n/\n /gr;
|
||||
};
|
||||
}
|
||||
|
||||
sub parse_json_stream {
|
||||
my ( $text ) = @_;
|
||||
|
||||
my $decoder = JSON::XS->new;
|
||||
|
||||
my @items;
|
||||
while ( 1 ) {
|
||||
$text =~ s/^\s+//;
|
||||
if ( $text eq '' ) {
|
||||
last;
|
||||
}
|
||||
|
||||
my ( $item, $len ) = $decoder->decode_prefix( $text );
|
||||
|
||||
push @items, $item;
|
||||
$text = substr $text, $len;
|
||||
$text =~ s/^\s+//;
|
||||
}
|
||||
return @items;
|
||||
} ## end sub parse_json_stream
|
||||
|
||||
sub has_locale {
|
||||
my ( $locale ) = @_;
|
||||
my $old_locale = setlocale( LC_CTYPE );
|
||||
my $success = defined setlocale( LC_CTYPE, $locale );
|
||||
setlocale( LC_CTYPE, $old_locale );
|
||||
return $success;
|
||||
}
|
||||
|
||||
sub _run_zonemaster_cli {
|
||||
my ( $datafile, @args ) = @_;
|
||||
|
||||
my @cmd = ( @PERL, $PATH_WRAPPER );
|
||||
if ( defined $datafile ) {
|
||||
if ( $ENV{ZONEMASTER_RECORD} ) {
|
||||
push @cmd, '--record';
|
||||
}
|
||||
push @cmd, $datafile;
|
||||
}
|
||||
push @cmd, '--', @args;
|
||||
|
||||
my $pid = open3( my $stdin, my $stdout, my $stderr = gensym, @cmd );
|
||||
waitpid( $pid, 0 );
|
||||
my $exitcode = $?;
|
||||
|
||||
if ( POSIX::WIFEXITED( $exitcode ) ) {
|
||||
local $/ = undef;
|
||||
return {
|
||||
stdout => scalar <$stdout>,
|
||||
stderr => scalar <$stderr>,
|
||||
exitstatus => POSIX::WEXITSTATUS( $exitcode ),
|
||||
};
|
||||
}
|
||||
elsif ( POSIX::WIFSIGNALED( $exitcode ) ) {
|
||||
die "child process terminated by signal: " . $SIG_NAMES[ POSIX::WTERMSIG( $exitcode ) ];
|
||||
}
|
||||
elsif ( POSIX::WIFSTOPPED( $exitcode ) ) {
|
||||
die "child process stopped by signal: " . $SIG_NAMES[ POSIX::WSTOPSIG( $exitcode ) ];
|
||||
}
|
||||
else {
|
||||
die "unrecognized exit code $exitcode";
|
||||
}
|
||||
} ## end sub _run_zonemaster_cli
|
||||
|
||||
# TESTS
|
||||
|
||||
do {
|
||||
local $test_datafile = $PATH_NORMAL_DATAFILE;
|
||||
note "TESTS USING $test_datafile FOR RECORDED DATA:";
|
||||
|
||||
check_success 'normal table output', [ '--test=basic01', '--level=INFO', '.' ], qr{
|
||||
^
|
||||
Seconds \s+ Level \s+ Message \n
|
||||
=+ \s =+ \s =+ \n
|
||||
\s* \Q0.00\E \s+ INFO \s+ .* \s [v0-9.]+ \s .* \n
|
||||
}msx;
|
||||
|
||||
check_success 'normal table output, no optional fields',
|
||||
[ '--test=basic01', '--level=INFO', '--no-time', '--no-show-level', '.' ], qr{
|
||||
^
|
||||
Message \n
|
||||
=+ \n
|
||||
Using .* \n
|
||||
}msx;
|
||||
|
||||
check_success 'normal table output, no optional fields, using underscore alias',
|
||||
[ '--test=basic01', '--level=INFO', '--no-time', '--no-show_level', '.' ], qr{
|
||||
^
|
||||
Message \n
|
||||
=+ \n
|
||||
Using .* \n
|
||||
}msx;
|
||||
|
||||
check_success 'normal table output, all fields',
|
||||
[ '--test=basic01', '--level=INFO', '--show-module', '--show-testcase', '.' ], qr{
|
||||
^
|
||||
Seconds \s+ Level \s+ Module \s+ Testcase \s+ Message \n
|
||||
=+ \s =+ \s =+ \s =+ \s =+ \n
|
||||
\s* \Q0.00\E \s+ INFO \s+ System \s+ Unspecified \s+ Using .* \n
|
||||
}msx;
|
||||
|
||||
check_success 'normal table output, all fields, using underscore aliases',
|
||||
[ '--test=basic01', '--level=INFO', '--show_module', '--show_testcase', '.' ], qr{
|
||||
^
|
||||
Seconds \s+ Level \s+ Module \s+ Testcase \s+ Message \n
|
||||
=+ \s =+ \s =+ \s =+ \s =+ \n
|
||||
\s* \Q0.00\E \s+ INFO \s+ System \s+ Unspecified \s+ Using .* \n
|
||||
}msx;
|
||||
|
||||
check_success '--encoding', [ '--test=basic01', '--json', '--encoding', 'foobar', '.' ], qr{
|
||||
\Q{"results":[]}\E
|
||||
}msx;
|
||||
|
||||
check_success '--json', [ '--test=basic01', '--json', '.' ], qr{
|
||||
\Q{"results":[]}\E
|
||||
}msx;
|
||||
|
||||
check_success '--json-stream', [ '--test=basic01', '--json-stream', '--level=INFO', '.' ], sub {
|
||||
my $found = 0;
|
||||
for my $item ( parse_json_stream( $_[0] ) ) {
|
||||
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
|
||||
if ( $item->{message} !~ /^Using / ) {
|
||||
return 0;
|
||||
}
|
||||
$found = 1;
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
};
|
||||
|
||||
check_success '--json_stream', [ '--test=basic01', '--json_stream', '--level=INFO', '.' ], sub {
|
||||
my $found = 0;
|
||||
for my $item ( parse_json_stream( $_[0] ) ) {
|
||||
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
|
||||
if ( $item->{message} !~ /^Using / ) {
|
||||
return 0;
|
||||
}
|
||||
$found = 1;
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
};
|
||||
|
||||
check_success '--raw', [ '--test=basic01', '--level=INFO', '--raw', '.' ], qr{
|
||||
^
|
||||
\s* \Q0.00\E \s+ INFO \s+ GLOBAL_VERSION \s+ version= [v0-9.]+ \n
|
||||
}msx;
|
||||
|
||||
SKIP: {
|
||||
skip 'sv_SE.UTF-8 locale is unavailable', 5
|
||||
if !has_locale( 'sv_SE.UTF-8' );
|
||||
|
||||
check_success '--json-stream --no-raw',
|
||||
[ '--test=basic01', '--json-stream', '--no-raw', '--locale=sv_SE.UTF-8', '--level=INFO', '.' ], sub {
|
||||
my $found = 0;
|
||||
for my $item ( parse_json_stream( decode_utf8( $_[0] ) ) ) {
|
||||
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
|
||||
if ( $item->{message} !~ qr{^Använder } ) {
|
||||
return 0;
|
||||
}
|
||||
$found = 1;
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
};
|
||||
|
||||
check_success '--json-stream --json-translate',
|
||||
[ '--test=basic01', '--json-stream', '--json-translate', '--locale=sv_SE.UTF-8', '--level=INFO', '.' ], sub {
|
||||
my $found = 0;
|
||||
for my $item ( parse_json_stream( decode_utf8( $_[0] ) ) ) {
|
||||
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
|
||||
if ( $item->{message} !~ qr{^Använder } ) {
|
||||
return 0;
|
||||
}
|
||||
$found = 1;
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
};
|
||||
|
||||
check_success '--json-stream --no-raw --locale',
|
||||
[ '--test=basic01', '--json-stream', '--no-raw', '--locale=sv_SE.UTF-8', '--level=INFO', '.' ], sub {
|
||||
my $found = 0;
|
||||
for my $item ( parse_json_stream( decode_utf8( $_[0] ) ) ) {
|
||||
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
|
||||
if ( $item->{message} !~ qr{^Använder } ) {
|
||||
return 0;
|
||||
}
|
||||
$found = 1;
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
};
|
||||
|
||||
check_success '--json-stream --json-translate --locale',
|
||||
[ '--test=basic01', '--json-stream', '--no-raw', '--locale=sv_SE.UTF-8', '--level=INFO', '.' ], sub {
|
||||
my $found = 0;
|
||||
for my $item ( parse_json_stream( decode_utf8( $_[0] ) ) ) {
|
||||
if ( $item->{tag} eq 'GLOBAL_VERSION' ) {
|
||||
if ( $item->{message} !~ qr{^Använder } ) {
|
||||
return 0;
|
||||
}
|
||||
$found = 1;
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
};
|
||||
|
||||
check_success '--locale', [ '--test=basic01', '--locale=sv_SE.UTF-8', '.' ], qr{
|
||||
\QSer OK ut.\E
|
||||
}msx;
|
||||
} ## end SKIP:
|
||||
|
||||
check_success_report '--count', [ '--test=basic01', '--count', '.' ], {
|
||||
text => qr{
|
||||
\QLooks OK.\E
|
||||
.*
|
||||
Level \s+ \QNumber of log entries\E
|
||||
.*
|
||||
INFO \s+ \d+
|
||||
.*
|
||||
DEBUG \s+ \d+
|
||||
.*
|
||||
Level \s+ \QMessage tag\E \s+ \QCount\E
|
||||
.*
|
||||
INFO \s+ \w+ \s+ \d+
|
||||
.*
|
||||
}msx,
|
||||
json => {
|
||||
type => "object",
|
||||
required => ["count"],
|
||||
patternProperties => {
|
||||
'^[A-Z]+[0-9]*$' => {
|
||||
type => "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
check_success_report '--nstimes', [ '--test=basic01', '--nstimes', '.' ], {
|
||||
text => qr{
|
||||
\QLooks OK.\E
|
||||
.*
|
||||
\QName servers\E \s+ Max \s+ Min \s+ Avg \s+ Stddev \s+ Median \s+ Total \s+ Count
|
||||
.*
|
||||
\QChild zone\E
|
||||
.*
|
||||
\QParent zone\E
|
||||
.*
|
||||
Other
|
||||
.*
|
||||
\QGrand total\E \s+ \d+
|
||||
}msx,
|
||||
json => {
|
||||
type => "object",
|
||||
required => ["nstimes"],
|
||||
properties => {
|
||||
nstimes => {
|
||||
type => "array",
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
child => {
|
||||
type => "array",
|
||||
items => {
|
||||
type => "object",
|
||||
required => [qw( avg max median min ns stddev total count)],
|
||||
},
|
||||
},
|
||||
parent => {
|
||||
type => "array",
|
||||
items => {
|
||||
type => "object",
|
||||
required => [qw( avg max median min ns stddev total count)],
|
||||
},
|
||||
},
|
||||
other => {
|
||||
type => "array",
|
||||
items => {
|
||||
type => "object",
|
||||
required => [qw( avg max median min ns stddev total count)],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
check_success_report '--elapsed', [ '--test=basic01', '--elapsed', '.' ], {
|
||||
text => qr{
|
||||
\QLooks OK.\E
|
||||
.*
|
||||
\QTotal test run time:\E
|
||||
}msx,
|
||||
json => {
|
||||
type => "object",
|
||||
required => ["elapsed"],
|
||||
properties => {
|
||||
elapsed => {
|
||||
type => "number",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
check_success '--level',
|
||||
[ '--profile=t/usage.profile', '--ipv4', '--sourceaddr4', '', '--test=basic', '--raw', '--level=notice', '.' ],
|
||||
sub {
|
||||
my $stdout = $_[0];
|
||||
|
||||
return ( $stdout =~ qr{NOTICE .* WARNING .* ERROR}msx )
|
||||
&& ( $stdout !~ qr{INFO}msx );
|
||||
};
|
||||
|
||||
check_success '--stop-level=', [ '--profile=t/usage.profile', '--stop-level=', '.' ], qr{Looks OK}i;
|
||||
|
||||
check_success '--stop-level=warning',
|
||||
[
|
||||
'--profile=t/usage.profile', '--ipv4', '--sourceaddr4', '',
|
||||
'--test=basic', '--raw', '--stop-level=warning', '.'
|
||||
],
|
||||
sub {
|
||||
my $stdout = $_[0];
|
||||
|
||||
return ( $stdout =~ qr{NOTICE .* WARNING}msx )
|
||||
&& ( $stdout !~ qr{ERROR}m );
|
||||
};
|
||||
|
||||
check_success '--stop_level',
|
||||
[
|
||||
'--profile=t/usage.profile', '--ipv4', '--sourceaddr4', '',
|
||||
'--test=basic', '--raw', '--stop_level=warning', '.'
|
||||
],
|
||||
sub {
|
||||
my $stdout = $_[0];
|
||||
|
||||
return ( $stdout =~ qr{NOTICE .* WARNING}msx )
|
||||
&& ( $stdout !~ qr{ERROR}m );
|
||||
};
|
||||
|
||||
my $tempdir = tempdir( CLEANUP => 1 );
|
||||
my $savefile = catfile( $tempdir, 'saved.data' );
|
||||
check_success 'run command', [ "--save=$savefile", '--test=basic01', '.' ], sub {
|
||||
my @saved_lines = read_file $savefile;
|
||||
my @expected_lines = read_file $PATH_NORMAL_DATAFILE;
|
||||
return scalar( @saved_lines ) == scalar( @expected_lines );
|
||||
};
|
||||
};
|
||||
|
||||
do {
|
||||
local $test_datafile = $PATH_FAKE_DATA_DATAFILE;
|
||||
note "TESTS USING $test_datafile FOR RECORDED DATA:";
|
||||
|
||||
SKIP: {
|
||||
skip 'crashing test that has never worked on replay (FIXME)', 2
|
||||
if not $ENV{ZONEMASTER_RECORD};
|
||||
|
||||
check_success '--ns', [ '--noipv6', '--raw', '--ns=ns1.a.example/9.9.9.9', 'a.se' ], qr{B02_NO_WORKING_NS};
|
||||
|
||||
check_success '--ds',
|
||||
[
|
||||
'--noipv6', '--raw', '--test=dnssec02',
|
||||
'--ds=0,8,2,0000000000000000000000000000000000000000000000000000000000000000',
|
||||
'zonemaster.net'
|
||||
],
|
||||
qr{DS02_NO_DNSKEY_FOR_DS};
|
||||
}
|
||||
};
|
||||
|
||||
do {
|
||||
local $test_datafile = $PATH_FAKE_ROOT_DATAFILE;
|
||||
note "TESTS USING $test_datafile FOR RECORDED DATA:";
|
||||
|
||||
check_success '--hints', [ '--noipv6', '--raw', '--hints=t/usage.hints', 'example.' ], qr{CANNOT_CONTINUE}i;
|
||||
};
|
||||
|
||||
do {
|
||||
local $test_datafile = undef;
|
||||
note "TESTS USING NO NETWORK AND NO FILE FOR RECORDED DATA:";
|
||||
|
||||
check_usage_error 'no domain', [], qr{must give the name of a domain to test}i;
|
||||
|
||||
check_usage_error 'too many domains', [ 'example.com', 'example.net' ],
|
||||
qr{only one domain can be given for testing}i;
|
||||
|
||||
check_usage_error 'invalid domain', ['!%~&'], qr{character not permitted}i;
|
||||
|
||||
check_usage_error 'unrecognized option', ['--foobar'], qr{unknown option}i;
|
||||
|
||||
check_usage_error '--test BAD_MODULE', [ '--test', '!%~&', 'example.' ], qr{unrecognized term '!%~&' in --test}i;
|
||||
|
||||
check_usage_error '--test UNKNOWN_MODULE/TESTCASE', [ '--test', 'foobar/foobar01', 'example.' ],
|
||||
qr{unrecognized term 'foobar/foobar01' in --test}i;
|
||||
|
||||
check_usage_error '--test MODULE/UNKNOWN_TESTCASE', [ '--test', 'basic/foobar01', 'example.' ],
|
||||
qr{unrecognized term 'basic/foobar01' in --test}i;
|
||||
|
||||
check_usage_error '--test MODULE//TESTCASE', [ '--test', 'basic//basic01', 'example.' ],
|
||||
qr{unrecognized term 'basic//basic01' in --test}i;
|
||||
|
||||
check_usage_error '--ns BAD_NAME', [ '--ns', '!%~&', 'example.' ], qr{invalid name}i;
|
||||
|
||||
check_usage_error '--ns NAME//IP', [ '--ns', 'ns1.example//192.0.2.1', 'example.' ], qr{--ns}i;
|
||||
|
||||
check_usage_error '--ns NAME/BAD_IP', [ '--ns', 'ns1.example/foobar', 'example.' ], qr{invalid ip address}i;
|
||||
|
||||
check_usage_error '--sourceaddr4', [ '--sourceaddr4', 'foobar', 'example.' ], qr{invalid value}i;
|
||||
|
||||
check_usage_error '--sourceaddr6', [ '--sourceaddr6', 'foobar', 'example.' ], qr{invalid value}i;
|
||||
|
||||
check_usage_error '--level BAD_LEVEL', [ '--level', 'foobar', 'example.' ], qr{--level}i;
|
||||
|
||||
check_usage_error '--stop-level BAD_LEVEL', [ '--stop-level', 'foobar', 'example.' ],
|
||||
qr{failed to recognize stop level}i;
|
||||
|
||||
check_usage_error '--json-stream and --no-json', [ '--json-stream', '--no-json', 'example.' ],
|
||||
qr{cannot be used together}i;
|
||||
|
||||
check_usage_error 'Bad --hints (directory)', [ '--hints', '/', 'example.' ],
|
||||
qr{error loading hints file}i;
|
||||
|
||||
check_usage_error 'Bad --hints (syntax)', [ '--hints', 't/usage.t', 'example.' ],
|
||||
qr{error loading hints file}i;
|
||||
|
||||
check_success '--help', ['--help'], qr{
|
||||
^Usage:$
|
||||
.*
|
||||
zonemaster-cli
|
||||
.*
|
||||
^Options:$
|
||||
.*
|
||||
--test
|
||||
}msx;
|
||||
|
||||
check_success '-h', ['-h'], qr{
|
||||
^Usage:$
|
||||
.*
|
||||
zonemaster-cli
|
||||
.*
|
||||
^Options:$
|
||||
.*
|
||||
--test
|
||||
}msx;
|
||||
|
||||
check_success 'Single-character option bundling', ['-h?'], qr{
|
||||
--test
|
||||
}msx;
|
||||
|
||||
check_success '--version', ['--version'], qr{
|
||||
^\QZonemaster-CLI version\E .*
|
||||
^\QZonemaster-Engine version\E .*
|
||||
^\QZonemaster-LDNS version\E .*
|
||||
^\QNL NetLabs LDNS version\E .*
|
||||
}msx;
|
||||
|
||||
check_success '--list-tests', ['--list-tests'], qr{
|
||||
Basic
|
||||
.*
|
||||
basic01
|
||||
}msx;
|
||||
|
||||
check_success '--list_tests', ['--list_tests'], qr{
|
||||
Basic
|
||||
.*
|
||||
basic01
|
||||
}msx;
|
||||
|
||||
SKIP: {
|
||||
skip 'test that hang on FreeBSD (FIXME, see #388)', 2;
|
||||
|
||||
check_success '--dump-profile', ['--dump-profile'], qr{
|
||||
"no_network"
|
||||
}msx;
|
||||
|
||||
check_success '--dump_profile', ['--dump_profile'], qr{
|
||||
"no_network"
|
||||
}msx;
|
||||
};
|
||||
|
||||
check_success 'override profile', [ '--dump-profile', '--profile=t/usage.profile' ], sub {
|
||||
my ( $profile ) = parse_json_stream( $_[0] );
|
||||
|
||||
my $ipv4 = exists $profile->{net}{ipv4} ? ( $profile->{net}{ipv4} ? '1' : '0' ) : '<missing>';
|
||||
my $ipv6 = exists $profile->{net}{ipv6} ? ( $profile->{net}{ipv6} ? '1' : '0' ) : '<missing>';
|
||||
my $source4 = $profile->{resolver}{source4} // '<missing>';
|
||||
my $source6 = $profile->{resolver}{source6} // '<missing>';
|
||||
|
||||
return
|
||||
( $ipv4 eq '0' )
|
||||
&& ( $ipv6 eq '0' )
|
||||
&& ( $source4 eq '192.0.2.1' )
|
||||
&& ( $source6 eq '2001:db8::1' );
|
||||
};
|
||||
|
||||
check_success 'override net.ipv4', [ '--dump-profile', '--profile=t/usage.profile', '--ipv4' ], sub {
|
||||
my ( $profile ) = parse_json_stream( $_[0] );
|
||||
|
||||
my $ipv4 = exists $profile->{net}{ipv4} ? ( $profile->{net}{ipv4} ? '1' : '0' ) : '<missing>';
|
||||
|
||||
return $ipv4 eq '1';
|
||||
};
|
||||
|
||||
check_success 'override net.ipv6', [ '--dump-profile', '--profile=t/usage.profile', '--ipv6' ], sub {
|
||||
my ( $profile ) = parse_json_stream( $_[0] );
|
||||
|
||||
my $ipv6 = exists $profile->{net}{ipv6} ? ( $profile->{net}{ipv6} ? '1' : '0' ) : '<missing>';
|
||||
|
||||
return $ipv6 eq '1';
|
||||
};
|
||||
|
||||
check_success 'override resolver.source4',
|
||||
[ '--dump-profile', '--profile=t/usage.profile', '--sourceaddr4', '192.0.2.2' ], sub {
|
||||
my ( $profile ) = parse_json_stream( $_[0] );
|
||||
|
||||
my $source4 = $profile->{resolver}{source4} // '<missing>';
|
||||
|
||||
return $source4 eq '192.0.2.2';
|
||||
};
|
||||
|
||||
check_success 'override resolver.source6',
|
||||
[ '--dump-profile', '--profile=t/usage.profile', '--sourceaddr6', '2001:db8::2' ], sub {
|
||||
my ( $profile ) = parse_json_stream( $_[0] );
|
||||
|
||||
my $source6 = $profile->{resolver}{source6} // '<missing>';
|
||||
|
||||
return $source6 eq '2001:db8::2';
|
||||
};
|
||||
|
||||
check_success 'override test_cases',
|
||||
[ '--dump-profile', '--profile=t/usage.profile', '--test=basic01' ], sub {
|
||||
my ( $profile ) = parse_json_stream( $_[0] );
|
||||
|
||||
return
|
||||
ref $profile->{test_cases} eq 'ARRAY'
|
||||
&& scalar @{ $profile->{test_cases} } == 1
|
||||
&& $profile->{test_cases}[0] eq 'basic01';
|
||||
};
|
||||
|
||||
check_success 'override test_cases twice',
|
||||
[ '--dump-profile', '--profile=t/usage.profile', '--test=-all', '--test=+basic01' ], sub {
|
||||
my ( $profile ) = parse_json_stream( $_[0] );
|
||||
|
||||
return
|
||||
ref $profile->{test_cases} eq 'ARRAY'
|
||||
&& scalar @{ $profile->{test_cases} } == 1
|
||||
&& $profile->{test_cases}[0] eq 'basic01';
|
||||
};
|
||||
|
||||
check_success '--restore', [ "--restore=$PATH_NORMAL_DATAFILE", '--test=basic01', '--level=INFO', '--raw', '.' ],
|
||||
qr{B01_CHILD_FOUND};
|
||||
};
|
||||
|
||||
done_testing;
|
||||
85
zonemaster-cli/t/usage.wrapper.pl
Executable file
85
zonemaster-cli/t/usage.wrapper.pl
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env perl
|
||||
use v5.16;
|
||||
use warnings;
|
||||
|
||||
use File::Basename qw( dirname );
|
||||
use File::Spec::Functions qw( catfile );
|
||||
use Zonemaster::CLI;
|
||||
use Zonemaster::Engine::Nameserver;
|
||||
use Zonemaster::Engine::Profile;
|
||||
|
||||
use lib catfile( dirname( dirname( __FILE__ ) ), 'script' );
|
||||
|
||||
# Help Zonemaster::CLI find zonemaster-cli in test context
|
||||
$Zonemaster::CLI::SCRIPT = catfile( dirname( dirname( __FILE__ ) ), 'script', 'zonemaster-cli' );
|
||||
|
||||
# Parse command line options upto and including '--'.
|
||||
|
||||
my $opt_record = 0;
|
||||
my $opt_datafile;
|
||||
while ( @ARGV ) {
|
||||
my $arg = shift @ARGV;
|
||||
if ( substr( $arg, 0, 2 ) eq '--' ) {
|
||||
if ( $arg eq '--' ) {
|
||||
last;
|
||||
}
|
||||
elsif ( $arg eq '--record' ) {
|
||||
$opt_record = 1;
|
||||
}
|
||||
else {
|
||||
die "unrecognized option '$arg'";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( defined $opt_datafile ) {
|
||||
die "too many data files provided";
|
||||
}
|
||||
$opt_datafile = $arg;
|
||||
}
|
||||
} ## end while ( @ARGV )
|
||||
|
||||
if ( $opt_record && !defined $opt_datafile ) {
|
||||
die "must not specify --record without also specifying a data file";
|
||||
}
|
||||
|
||||
# Prime Zonemaster Engine before letting zonemaster-cli do its thing
|
||||
|
||||
if ( !$opt_record ) {
|
||||
Zonemaster::Engine::Profile->effective->set( q{no_network}, 1 );
|
||||
}
|
||||
|
||||
if ( $opt_datafile ) {
|
||||
Zonemaster::Engine::Nameserver->restore( $opt_datafile );
|
||||
}
|
||||
|
||||
our $EXIT_STATUS;
|
||||
our $EMITTED_WARNING = 0;
|
||||
do {
|
||||
# Intercept warn()
|
||||
local $SIG{__WARN__} = sub {
|
||||
print STDERR "__WARN__: " . $_[0];
|
||||
$EMITTED_WARNING = 1;
|
||||
};
|
||||
|
||||
# Run Zonemaster::CLI
|
||||
eval {
|
||||
$EXIT_STATUS = Zonemaster::CLI->run( @ARGV );
|
||||
1;
|
||||
} or do {
|
||||
print STDERR $@;
|
||||
$EXIT_STATUS = $Zonemaster::CLI::EXIT_GENERIC_ERROR;
|
||||
};
|
||||
};
|
||||
|
||||
# Wrap up and terminate
|
||||
|
||||
if ( $opt_record ) {
|
||||
Zonemaster::Engine::Nameserver->save( $opt_datafile );
|
||||
}
|
||||
|
||||
if ( $EMITTED_WARNING ) {
|
||||
say STDERR "EXIT 125: one or more warnings were emitted";
|
||||
exit 125;
|
||||
}
|
||||
|
||||
exit $EXIT_STATUS;
|
||||
Reference in New Issue
Block a user