File Coverage

blib/lib/Zonemaster/Engine/Test.pm
Criterion Covered Total %
statement 140 149 93.9
branch 31 38 81.5
condition 17 27 62.9
subroutine 17 17 100.0
pod 4 4 100.0
total 209 235 88.9


line stmt bran cond sub pod time code
1             package Zonemaster::Engine::Test;
2              
3 26     26   168 use version; our $VERSION = version->declare("v1.1.3");
  26         56  
  26         196  
4              
5 26     26   2715 use 5.014002;
  26         136  
6 26     26   146 use strict;
  26         54  
  26         562  
7 26     26   122 use warnings;
  26         52  
  26         815  
8              
9 26     26   130 use Zonemaster::Engine;
  26         51  
  26         513  
10 26     26   124 use Zonemaster::Engine::Util;
  26         54  
  26         1861  
11 26     26   7450 use Zonemaster::Engine::Test::Basic;
  26         78  
  26         848  
12              
13 26     26   8643 use IO::Socket::INET6; # Lazy-loads, so make sure it's here for the version logging
  26         359236  
  26         167  
14              
15 26     26   12371 use Module::Find qw[useall];
  26         3933  
  26         1224  
16 26     26   161 use Scalar::Util qw[blessed];
  26         59  
  26         1152  
17 26     26   150 use POSIX qw[strftime];
  26         58  
  26         206  
18              
19             my @all_test_modules;
20              
21             @all_test_modules =
22             sort { $a cmp $b }
23             map { my $f = $_; $f =~ s|^Zonemaster::Engine::Test::||; $f }
24             grep { $_ ne 'Zonemaster::Engine::Test::Basic' } useall( 'Zonemaster::Engine::Test' );
25              
26             sub _log_versions {
27 248     248   2696 info( GLOBAL_VERSION => { version => Zonemaster::Engine->VERSION } );
28              
29 248         1512 info( DEPENDENCY_VERSION => { name => 'Zonemaster::LDNS', version => $Zonemaster::LDNS::VERSION } );
30 248         1368 info( DEPENDENCY_VERSION => { name => 'IO::Socket::INET6', version => $IO::Socket::INET6::VERSION } );
31 248         1343 info( DEPENDENCY_VERSION => { name => 'Moose', version => $Moose::VERSION } );
32 248         1351 info( DEPENDENCY_VERSION => { name => 'Module::Find', version => $Module::Find::VERSION } );
33 248         1221 info( DEPENDENCY_VERSION => { name => 'JSON', version => $JSON::VERSION } );
34 248         1525 info( DEPENDENCY_VERSION => { name => 'File::ShareDir', version => $File::ShareDir::VERSION } );
35 248         1325 info( DEPENDENCY_VERSION => { name => 'File::Slurp', version => $File::Slurp::VERSION } );
36 248         1326 info( DEPENDENCY_VERSION => { name => 'Net::IP', version => $Net::IP::VERSION } );
37 248         1360 info( DEPENDENCY_VERSION => { name => 'List::MoreUtils', version => $List::MoreUtils::VERSION } );
38 248         1361 info( DEPENDENCY_VERSION => { name => 'Mail::RFC822::Address', version => $Mail::RFC822::Address::VERSION } );
39 248         1308 info( DEPENDENCY_VERSION => { name => 'Scalar::Util', version => $Scalar::Util::VERSION } );
40 248         1342 info( DEPENDENCY_VERSION => { name => 'Hash::Merge', version => $Hash::Merge::VERSION } );
41 248         1405 info( DEPENDENCY_VERSION => { name => 'Readonly', version => $Readonly::VERSION } );
42              
43 248         511 foreach my $file ( @{ Zonemaster::Engine->config->cfiles } ) {
  248         769  
44 248         934 info( CONFIG_FILE => { name => $file } );
45             }
46 248         546 foreach my $file ( @{ Zonemaster::Engine->config->pfiles } ) {
  248         800  
47 1285         3967 info( POLICY_FILE => { name => $file } );
48             }
49              
50 248         537 return;
51             } ## end sub _log_versions
52              
53             sub modules {
54 253     253 1 1421 return @all_test_modules;
55             }
56              
57             sub run_all_for {
58 3     3 1 9 my ( $class, $zone ) = @_;
59 3         7 my @results;
60              
61 3         15 Zonemaster::Engine->start_time_now();
62 3         180 push @results, info( START_TIME => { time_t => time(), string => strftime( "%F %T %z", ( localtime() ) ) } );
63 3         91 push @results, info( TEST_TARGET => { zone => $zone->name->string, module => 'all' } );
64              
65 3         19 info(
66             MODULE_VERSION => {
67             module => 'Zonemaster::Engine::Test::Basic',
68             version => Zonemaster::Engine::Test::Basic->version
69             }
70             );
71 3         14 _log_versions();
72              
73 3 100 66     12 if ( not( Zonemaster::Engine->config->ipv4_ok or Zonemaster::Engine->config->ipv6_ok ) ) {
74 1         3 return info( NO_NETWORK => {} );
75             }
76              
77 2         14 push @results, Zonemaster::Engine::Test::Basic->all( $zone );
78 2         11 info( MODULE_END => { module => 'Zonemaster::Engine::Test::Basic' } );
79              
80 2 50 33     11 if ( Zonemaster::Engine::Test::Basic->can_continue( @results ) and Zonemaster::Engine->can_continue() ) {
81             ## no critic (Modules::RequireExplicitInclusion)
82 2         10 foreach my $mod ( __PACKAGE__->modules ) {
83 10         41 Zonemaster::Engine->config->load_module_policy( $mod );
84              
85 10 100       40 if ( not _policy_allowed( $mod ) ) {
86 1         10 push @results, info( POLICY_DISABLED => { name => $mod } );
87 1         4 next;
88             }
89              
90 9         28 my $module = "Zonemaster::Engine::Test::$mod";
91 9         49 info( MODULE_VERSION => { module => $module, version => $module->version } );
92 9         20 my @res = eval { $module->all( $zone ) };
  9         46  
93 9 100       699 if ( $@ ) {
94 2         6 my $err = $@;
95 2 100 66     17 if ( blessed $err and $err->isa( 'Zonemaster::Engine::Exception' ) ) {
96 1         11 die $err; # Utility exception, pass it on
97             }
98             else {
99 1         8 push @res, info( MODULE_ERROR => { module => $module, msg => "$err" } );
100             }
101             }
102 8         43 info( MODULE_END => { module => $module } );
103              
104 8         45 push @results, @res;
105             } ## end foreach my $mod ( __PACKAGE__...)
106             } ## end if ( Zonemaster::Engine::Test::Basic...)
107             else {
108 0         0 push @results, info( CANNOT_CONTINUE => { zone => $zone->name->string } );
109             }
110              
111 1         35 return @results;
112             } ## end sub run_all_for
113              
114             sub run_module {
115 61     61 1 194 my ( $class, $requested, $zone ) = @_;
116 61         128 my @res;
117 61         250 my ( $module ) = grep { lc( $requested ) eq lc( $_ ) } $class->modules;
  549         996  
118 61 100 100     276 $module = 'Basic' if ( not $module and lc( $requested ) eq 'basic' );
119              
120 61         305 Zonemaster::Engine->start_time_now();
121 61         3297 push @res, info( START_TIME => { time_t => time(), string => strftime( "%F %T %z", ( localtime() ) ) } );
122 61         1567 push @res, info( TEST_TARGET => { zone => $zone->name->string, module => $requested } );
123 61         279 _log_versions();
124 61 100 66     240 if ( not( Zonemaster::Engine->config->ipv4_ok or Zonemaster::Engine->config->ipv6_ok ) ) {
125 1         4 return info( NO_NETWORK => {} );
126             }
127              
128 60 50       294 if ( Zonemaster::Engine->can_continue() ) {
129 60 100       169 if ( $module ) {
130 59         174 Zonemaster::Engine->config->load_module_policy( $module );
131 59         191 my $m = "Zonemaster::Engine::Test::$module";
132 59         320 info( MODULE_VERSION => { module => $m, version => $m->version } );
133 59         160 push @res, eval { $m->all( $zone ) };
  59         291  
134 59 100       202 if ( $@ ) {
135 1         3 my $err = $@;
136 1 50 33     9 if ( blessed $err and $err->isa( 'Zonemaster::Engine::Exception' ) ) {
137 1         7 die $err; # Utility exception, pass it on
138             }
139             else {
140 0         0 push @res, info( MODULE_ERROR => { module => $module, msg => "$err" } );
141             }
142             }
143 58         330 info( MODULE_END => { module => $module } );
144 58         664 return @res;
145             }
146             else {
147 1         6 info( UNKNOWN_MODULE => { name => $requested, method => 'all', known => join( ':', sort $class->modules ) } );
148             }
149             }
150             else {
151 0         0 info( CANNOT_CONTINUE => { zone => $zone->name->string } );
152             }
153              
154 1         6 return;
155             } ## end sub run_module
156              
157             sub run_one {
158 184     184 1 572 my ( $class, $requested, $test, @arguments ) = @_;
159 184         385 my @res;
160 184         655 my ( $module ) = grep { lc( $requested ) eq lc( $_ ) } $class->modules;
  1656         3121  
161 184 100 100     820 $module = 'Basic' if ( not $module and lc( $requested ) eq 'basic' );
162              
163 184         786 Zonemaster::Engine->start_time_now();
164 184         9615 push @res, info( START_TIME => { time_t => time(), string => strftime( "%F %T %z", ( localtime() ) ) } );
165             push @res,
166 184         1748 info( TEST_ARGS => { module => $requested, method => $test, args => join( ';', map { "$_" } @arguments ) } );
  182         1359  
167 184         713 _log_versions();
168 184 100 66     594 if ( not( Zonemaster::Engine->config->ipv4_ok or Zonemaster::Engine->config->ipv6_ok ) ) {
169 35         100 return info( NO_NETWORK => {} );
170             }
171              
172 149 50       632 if ( Zonemaster::Engine->can_continue() ) {
173 149 100       368 if ( $module ) {
174 148         532 Zonemaster::Engine->config->load_module_policy( $module );
175 148         445 my $m = "Zonemaster::Engine::Test::$module";
176 148 100       741 if ( $m->metadata->{$test} ) {
177 147         620 info( MODULE_CALL => { module => $module, method => $test, version => $m->version } );
178 147         384 push @res, eval { $m->$test( @arguments ) };
  147         922  
179 147 100       580 if ( $@ ) {
180 1         2 my $err = $@;
181 1 50 33     9 if ( blessed $err and $err->isa( 'Zonemaster::Engine::Exception' ) ) {
182 1         7 die $err; # Utility exception, pass it on
183             }
184             else {
185 0         0 push @res, info( MODULE_ERROR => { module => $module, msg => "$err" } );
186             }
187             }
188 146         935 info( MODULE_CALL_END => { module => $module, method => $test } );
189 146         2650 return @res;
190             }
191             else {
192 1         6 info( UNKNOWN_METHOD => { module => $m, method => $test } );
193             }
194             } ## end if ( $module )
195             else {
196 1         8 info( UNKNOWN_MODULE => { module => $requested, method => $test, known => join( ':', sort $class->modules ) } );
197             }
198             }
199             else {
200 0         0 my $zname = q{};
201 0         0 foreach my $arg ( @arguments ) {
202 0 0       0 if ( ref($arg) eq q{Zonemaster::Engine::Zone} ) {
203 0         0 $zname = $arg->name;
204             }
205             }
206 0         0 info( CANNOT_CONTINUE => { zone => $zname } );
207             }
208              
209 2         15 return;
210             } ## end sub run_one
211              
212             sub _policy_allowed {
213 10     10   27 my ( $name ) = @_;
214              
215 10         33 return not Zonemaster::Engine::Util::policy()->{ uc( $name ) }{DISABLED};
216             }
217              
218             1;
219              
220             =head1 NAME
221              
222             Zonemaster::Engine::Test - module to find, load and execute all test modules
223              
224             =head1 SYNOPSIS
225              
226             my @results = Zonemaster::Engine::Test->run_all_for($zone);
227             my @results = Zonemaster::Engine::Test->run_module('DNSSEC', $zone);
228              
229              
230             =head1 TEST MODULES
231              
232             Test modules are defined as modules with names starting with
233             "Zonemaster::Engine::Test::". They are expected to provide at least four
234             class methods, and optionally a fifth one.
235              
236             =over
237              
238             =item all($zone)
239              
240             C<all> will be given a zone object as its only argument, and is
241             epected to return a list of L<Zonemaster::Engine::Logger::Entry> objects. This
242             is the entry point used by the C<run_all_for> and C<run_module>
243             methods.
244              
245             =item version()
246              
247             This must return the version of the test module.
248              
249             =item metadata()
250              
251             This must return a reference to a hash where the keys are the names of
252             callable methods implementing tests, and the values are references to
253             arrays with the tags of the messages the test methods can generate.
254              
255             =item translation()
256              
257             This must return a reference to a hash where the keys are all the
258             message tags the test module can produce, and the corresponing keys
259             are the english translations of those messages. The translation
260             strings will be used as keys to look up translations into other
261             languages, so think twice before editing them.
262              
263             =item policy()
264              
265             Optionally, a test module can implement this method, which if
266             implemented should return a reference to a hash where the keys are all
267             the message tags the module can produce and the correspondning values
268             are their recommended default log levels.
269              
270             =back
271              
272             =head1 CLASS METHODS
273              
274             =over
275              
276             =item modules()
277              
278             Returns a list with the names of all available test modules except
279             L<Zonemaster::Engine::Test::Basic> (since that one is a bit special).
280              
281             =item run_all_for($zone)
282              
283             Runs all (default) tests in all test modules found, and returns a list
284             of the log entry objects they returned.
285              
286             The order in which the test modules found will be executed is not
287             defined, except that L<Zonemaster::Engine::Test::Basic> is always executed
288             first. If the Basic tests fail to indicate a very basic level of
289             function (it must have a parent domain, and it must have at least one
290             functional nameserver) for the zone, no further tests will be
291             executed.
292              
293             =item run_module($module, $zone)
294              
295             Runs all default tests in the named module for the given zone.
296              
297             =item run_one($module, $method, @arguments)
298              
299             Run one particular test method in one particular module. The requested
300             module must be in the list of active loaded modules (that is, not a
301             module disabled by the current policy), and the method must be listed
302             in the metadata the module exports. If those requirements are
303             fulfilled, the method will be called with the provided arguments. No
304             attempt is made to check that the provided arguments make sense for
305             the particular method called. That is left entirely to the user.
306              
307             =back
308              
309             =cut