File Coverage

blib/lib/Config/Model/Tester.pm
Criterion Covered Total %
statement 67 416 16.1
branch 6 202 2.9
condition 2 77 2.6
subroutine 17 46 36.9
pod 0 24 0.0
total 92 765 12.0


line stmt bran cond sub pod time code
1             #
2             # This file is part of Config-Model-Tester
3             #
4             # This software is Copyright (c) 2013-2020 by Dominique Dumont.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU Lesser General Public License, Version 2.1, February 1999
9             #
10             package Config::Model::Tester 4.007;
11             # ABSTRACT: Test framework for Config::Model
12              
13 1     1   693 use warnings;
  1         7  
  1         32  
14 1     1   5 use strict;
  1         2  
  1         27  
15 1     1   487 use locale;
  1         680  
  1         5  
16 1     1   637 use utf8;
  1         14  
  1         5  
17 1     1   68 use 5.12.0;
  1         4  
18              
19 1     1   631 use Test::More;
  1         128884  
  1         10  
20 1     1   1940 use Log::Log4perl 1.11 qw(:easy :levels);
  1         62781  
  1         7  
21 1     1   2764 use Path::Tiny;
  1         13741  
  1         69  
22 1     1   1146 use File::Copy::Recursive qw(fcopy rcopy dircopy);
  1         7132  
  1         75  
23              
24 1     1   557 use Test::Warn;
  1         5128  
  1         68  
25 1     1   526 use Test::Exception;
  1         1605  
  1         4  
26 1     1   766 use Test::File::Contents ;
  1         11424  
  1         101  
27 1     1   634 use Test::Differences;
  1         9585  
  1         70  
28 1     1   485 use Test::Memory::Cycle ;
  1         6765  
  1         6  
29              
30 1     1   590 use Config::Model::Tester::Setup qw/init_test setup_test_dir/;
  1         3  
  1         132  
31              
32             # use eval so this module does not have a "hard" dependency on Config::Model
33             # This way, Config::Model can build-depend on Config::Model::Tester without
34             # creating a build dependency loop.
35             eval {
36             require Config::Model;
37             require Config::Model::Lister;
38             require Config::Model::Value;
39             require Config::Model::BackendMgr;
40             } ;
41              
42 1     1   8 use vars qw/@ISA @EXPORT/;
  1         2  
  1         6516  
43              
44             require Exporter;
45             @ISA = qw(Exporter);
46             @EXPORT = qw(run_tests);
47              
48             $File::Copy::Recursive::DirPerms = oct(755);
49              
50             sub setup_test {
51 0     0 0 0 my ( $test_group, $t_name, $wr_root, $trace, $test_suite_data, $t_data ) = @_;
52              
53             # cleanup before tests
54 0         0 $wr_root->remove_tree();
55 0         0 $wr_root->mkpath( { mode => oct(755) } );
56             my ($conf_dir, $conf_file_name, $home_for_test)
57 0         0 = @$test_suite_data{qw/conf_dir conf_file_name home_for_test/};
58              
59 0 0 0     0 if ($conf_dir and $home_for_test) {
60 0         0 $conf_dir =~ s!~/!$home_for_test/!;
61 0         0 $test_suite_data->{conf_dir} = $conf_dir;
62             }
63              
64 0         0 my $wr_dir = $wr_root->child($test_group)->child('test-' . $t_name);
65 0         0 my $wr_dir2 = $wr_root->child($test_group)->child('test-' . $t_name.'-w');
66 0         0 $wr_dir->mkpath;
67 0         0 $wr_dir2->mkpath;
68              
69 0         0 my $conf_file ;
70 0 0 0     0 $conf_file = $wr_dir->child($conf_dir,$conf_file_name)
71             if $conf_dir and $conf_file_name;
72              
73 0   0     0 my $ex_dir = $t_data->{data_from_group} // $test_group;
74 0         0 my $ex_path = path('t')->child('model_tests.d', "$ex_dir-examples");
75 0   0     0 my $ex_data = $ex_path->child($t_data->{data_from} // $t_name);
76              
77 0         0 my @file_list;
78              
79 0 0       0 if (my $setup = $t_data->{setup}) {
    0          
    0          
80 0         0 foreach my $file (keys %$setup) {
81 0         0 my $map = $setup->{$file} ;
82             my $destination_str
83             = ref ($map) eq 'HASH' ? $map->{$^O} // $map->{default}
84 0 0 0     0 : ref ($map) eq 'ARRAY' ? $map->[-1]
    0          
85             : $map;
86 0 0       0 if (not defined $destination_str) {
87 0         0 die "$test_group $t_name setup error: cannot find destination for test file $file" ;
88             }
89 0 0       0 $destination_str =~ s!~/!$home_for_test/! if $home_for_test;
90 0         0 my $destination = $wr_dir->child($destination_str) ;
91 0         0 $destination->parent->mkpath( { mode => oct(755) }) ;
92 0         0 my $data_file = $ex_data->child($file);
93 0 0       0 die "cannot find $data_file" unless $data_file->exists;
94 0         0 my $data = $data_file->slurp() ;
95 0         0 $destination->spew( $data );
96 0 0       0 if (ref $map eq 'ARRAY') {
97 0         0 my @tmp = @$map;
98 0         0 pop @tmp; # remove destination
99 0         0 foreach my $link_str (@tmp) {
100 0 0       0 $link_str =~ s!~/!$home_for_test/! if $home_for_test;
101 0         0 my $link = $wr_dir->child($link_str);
102 0         0 $link->parent->mkpath( { mode => oct(755) }) ;
103 0         0 symlink $destination->absolute->stringify, $link->stringify;
104             }
105             }
106 0         0 @file_list = list_test_files ($wr_dir);
107             }
108             }
109             elsif ( $ex_data->is_dir ) {
110             # copy whole dir
111 0 0       0 my $destination_dir = $conf_dir ? $wr_dir->child($conf_dir) : $wr_dir ;
112 0         0 $destination_dir->mkpath( { mode => oct(755) });
113 0 0       0 say "dircopy ". $ex_data->stringify . '->'. $destination_dir->stringify
114             if $trace ;
115 0 0       0 dircopy( $ex_data->stringify, $destination_dir->stringify )
116             || die "dircopy $ex_data -> $destination_dir failed:$!";
117 0         0 @file_list = list_test_files ($destination_dir);
118             }
119             elsif ( $ex_data->exists ) {
120             # either one if true if $conf_file is undef
121 0 0       0 die "test data is missing global \$conf_dir" unless defined $conf_dir;
122 0 0       0 die "test data is missing global \$conf_file_name" unless defined $conf_file;
123              
124             # just copy file
125 0 0       0 say "file copy ". $ex_data->stringify . '->'. $conf_file->stringify
126             if $trace ;
127 0 0       0 fcopy( $ex_data->stringify, $conf_file->stringify )
128             || die "copy $ex_data -> $conf_file failed:$!";
129             }
130             else {
131 0         0 note ('starting test without original config data, i.e. from scratch');
132             }
133 0         0 ok( 1, "Copied $test_group example $t_name" );
134              
135 0         0 return ( $wr_dir, $wr_dir2, $conf_file, $ex_data, @file_list );
136             }
137              
138             #
139             # New subroutine "list_test_files" extracted - Thu Nov 17 17:27:20 2011.
140             #
141             sub list_test_files {
142 0     0 0 0 my $debian_dir = shift;
143 0         0 my @file_list ;
144              
145 0         0 my $iter = $debian_dir->iterator({ recurse => 1 });
146 0         0 my $debian_str = $debian_dir->stringify;
147              
148 0         0 while ( my $child = $iter->() ) {
149 0 0       0 next if $child->is_dir ;
150              
151 0         0 push @file_list, '/' . $child->relative($debian_str)->stringify;
152             };
153              
154             # don't use return sort -> undefined behavior in scalar context.
155 0         0 my @res = sort @file_list;
156 0         0 return @res;
157             }
158              
159             sub write_config_file {
160 0     0 0 0 my ($conf_dir,$wr_dir,$t) = @_;
161              
162 0 0       0 if ($t->{config_file}) {
163 0 0       0 my $file = $conf_dir ? "$conf_dir/" : '';
164 0         0 $file .= $t->{config_file} ;
165 0         0 $wr_dir->child($file)->parent->mkpath({mode => oct(755)} ) ;
166             }
167             }
168              
169             sub check_load_warnings {
170 0     0 0 0 my ($root,$t) = @_ ;
171              
172 0 0 0     0 if ( my $info = $t->{log4perl_load_warnings} or $::_use_log4perl_to_warn) {
    0 0        
      0        
173 0   0     0 my $tw = Test::Log::Log4perl->expect( @{ $info // [] } );
  0         0  
174 0         0 $root->init;
175             }
176             elsif ( ($t->{no_warnings} or exists $t->{load_warnings}) and not defined $t->{load_warnings}) {
177 0         0 local $Config::Model::Value::nowarning = 1;
178 0         0 $root->init;
179 0         0 note("load_warnings param is DEPRECATED. Please use log4perl_load_warnings");
180 0         0 ok( 1,"Read configuration and created instance with init() method without warning check" );
181             }
182             else {
183 0     0   0 warnings_like { $root->init; } $t->{load_warnings},
184 0         0 "Read configuration and created instance with init() method with warning check ";
185             }
186             }
187              
188             sub run_update {
189 0     0 0 0 my ($inst, $dir, $t) = @_;
190 0         0 my %args = %{$t->{update}};
  0         0  
191              
192 0         0 my $ret = delete $args{returns};
193              
194 0   0     0 local $Config::Model::Value::nowarning = $args{no_warnings} || $t->{no_warnings} || 0;
195              
196 0         0 my $res ;
197 0 0       0 if ( my $info = $t->{log4perl_update_warnings}) {
    0          
198 0         0 my $tw = Test::Log::Log4perl->expect( $info );
199 0         0 note("updating config with log4perl warning check and args: ". join(' ',%args));
200 0         0 $res = $inst->update( from_dir => $dir, %args ) ;
201             }
202             elsif (my $uw = delete $args{update_warnings}) {
203 0         0 note("update_warnings param is DEPRECATED. Please use log4perl_update_warnings");
204 0         0 note("updating config with warning check and args: ". join(' ',%args));
205 0     0   0 warnings_like { $res = $inst->update( from_dir => $dir, %args ); } $uw,
  0         0  
206             "Updated configuration with warning check ";
207             }
208             else {
209 0         0 note("updating config with no warning check and args: ". join(' ',%args));
210 0         0 $res = $inst->update( from_dir => $dir, %args ) ;
211             }
212              
213 0 0       0 if (defined $ret) {
214 0         0 is($res,$ret,"updated configuration, got expected return value");
215             }
216             else {
217 0         0 ok(1,"dumped configuration");
218             }
219             }
220              
221             sub load_instructions {
222 0     0 0 0 my ($root,$steps,$trace) = @_ ;
223              
224 0 0       0 print "Loading $steps\n" if $trace ;
225 0         0 $root->load( $steps );
226 0         0 ok( 1, "load called" );
227             }
228              
229             sub apply_fix {
230 0     0 0 0 my $inst = shift;
231 0         0 local $Config::Model::Value::nowarning = 1;
232 0         0 $inst->apply_fixes;
233 0         0 ok( 1, "apply_fixes called" );
234             }
235              
236             sub dump_tree {
237 0     0 0 0 my ($test_group, $root, $mode, $no_warnings, $t, $test_logs, $trace) = @_;
238              
239 0 0       0 print "dumping tree ...\n" if $trace;
240 0         0 my $dump = '';
241             my $risky = sub {
242 0     0   0 $dump = $root->dump_tree( mode => $mode );
243 0         0 };
244              
245 0 0       0 if ( defined $t->{dump_errors} ) {
246 0         0 my $nb = 0;
247 0         0 my @tf = @{ $t->{dump_errors} };
  0         0  
248 0         0 while (@tf) {
249 0         0 my $qr = shift @tf;
250 0     0   0 throws_ok { &$risky } $qr, "Failed dump $nb of $test_group config tree";
  0         0  
251 0         0 my $fix = shift @tf;
252 0         0 $root->load($fix);
253 0         0 ok( 1, "Fixed error nb " . $nb++ );
254             }
255             }
256              
257 0 0 0     0 if ( $test_logs and (my $info = $t->{log4perl_dump_warnings} or $::_use_log4perl_to_warn)) {
    0 0        
    0 0        
      0        
258 0         0 note("checking logged warning while dumping");
259 0   0     0 my $tw = Test::Log::Log4perl->expect( @{$info // [] } );
  0         0  
260 0         0 $risky->();
261             }
262             elsif ( not $test_logs or $no_warnings ) {
263 0         0 local $Config::Model::Value::nowarning = 1;
264 0         0 &$risky;
265 0         0 ok( 1, "Ran dump_tree (no warning check)" );
266             }
267             elsif ( exists $t->{dump_warnings} and not defined $t->{dump_warnings} ) {
268 0         0 local $Config::Model::Value::nowarning = 1;
269 0         0 &$risky;
270 0         0 ok( 1, "Ran dump_tree with DEPRECATED dump_warnings parameter (no warning check)" );
271             }
272             else {
273 0 0       0 note("dump_warnings parameter is DEPRECATED") if $t->{dump_warnings};
274 0     0   0 warnings_like { &$risky; } $t->{dump_warnings}, "Ran dump_tree";
  0         0  
275             }
276 0         0 ok( $dump, "Dumped $test_group config tree in $mode mode" );
277              
278 0 0       0 print $dump if $trace;
279 0         0 return $dump;
280             }
281              
282             sub check_data {
283 0     0 0 0 my ($label, $root, $c, $nw) = @_;
284              
285 0   0     0 local $Config::Model::Value::nowarning = $nw || 0;
286             my @checks = ref $c eq 'ARRAY' ? @$c
287 0 0       0 : map { ( $_ => $c->{$_})} sort keys %$c ;
  0         0  
288              
289 0         0 while (@checks) {
290 0         0 my $path = shift @checks;
291 0         0 my $v = shift @checks;
292 0         0 check_one_item($label, $root,$path, $v);
293             }
294             }
295              
296             sub check_one_item {
297 0     0 0 0 my ($label, $root,$path, $check_data_l) = @_;
298              
299 0 0       0 my @checks = ref $check_data_l eq 'ARRAY' ? @$check_data_l : ($check_data_l);
300              
301 0         0 foreach my $check_data (@checks) {
302 0 0       0 my $check_v_l = ref $check_data eq 'HASH' ? delete $check_data->{value} : $check_data;
303 0 0       0 my @check_args = ref $check_data eq 'HASH' ? %$check_data : ();
304 0 0       0 my $check_str = @check_args ? " (@check_args)" : '';
305 0         0 my $obj = $root->grab( step => $path, type => ['leaf','check_list'], @check_args );
306 0         0 my $got = $obj->fetch(@check_args);
307              
308 0 0       0 my @check_v = ref($check_v_l) eq 'ARRAY' ? @$check_v_l : ($check_v_l);
309 0         0 foreach my $check_v (@check_v) {
310 0 0       0 if (ref $check_v eq 'Regexp') {
311 0         0 like( $got, $check_v, "$label check '$path' value with regexp$check_str" );
312             }
313             else {
314 0         0 is( $got, $check_v, "$label check '$path' value$check_str" );
315             }
316             }
317             }
318             }
319              
320             sub check_annotation {
321 0     0 0 0 my ($root, $t) = @_;
322              
323 0         0 my $annot_check = $t->{verify_annotation};
324 0         0 foreach my $path (keys %$annot_check) {
325 0         0 my $note = $annot_check->{$path};
326 0         0 is( $root->grab($path)->annotation, $note, "check $path annotation" );
327             }
328             }
329              
330             sub has_key {
331 0     0 0 0 my ($root, $c, $nw) = @_;
332              
333 0         0 _test_key($root, $c, $nw, 0);
334             }
335              
336             sub has_not_key {
337 0     0 0 0 my ($root, $c, $nw) = @_;
338              
339 0         0 _test_key($root, $c, $nw, 1);
340             }
341              
342             sub _test_key {
343 0     0   0 my ($root, $c, $nw, $invert) = @_;
344              
345             my @checks = ref $c eq 'ARRAY' ? @$c
346 0 0       0 : map { ( $_ => $c->{$_})} sort keys %$c ;
  0         0  
347              
348 0         0 while (@checks) {
349 0         0 my $path = shift @checks;
350 0         0 my $spec = shift @checks;
351 0 0       0 my @key_checks = ref $spec eq 'ARRAY' ? @$spec: ($spec);
352              
353 0         0 my $obj = $root->grab( step => $path, type => 'hash' );
354 0         0 my @keys = $obj->fetch_all_indexes;
355 0         0 my $res = 0;
356 0         0 foreach my $check (@key_checks) {
357 0         0 my @match ;
358 0         0 foreach my $k (@keys) {
359 0 0       0 if (ref $check eq 'Regexp') {
360 0 0       0 push @match, $k if $k =~ $check;
361             }
362             else {
363 0 0       0 push @match, $k if $k eq $check;
364             }
365             }
366 0 0       0 if ($invert) {
367 0         0 is(scalar @match,0, "check $check matched no key" );
368             }
369             else {
370 0         0 ok(scalar @match, "check $check matched with keys @match" );
371             }
372             }
373             }
374             }
375              
376             sub write_data_back {
377 0     0 0 0 my ($test_group, $inst, $t) = @_;
378 0   0     0 local $Config::Model::Value::nowarning = $t->{no_warnings} || 0;
379 0         0 $inst->write_back( force => 1 );
380 0         0 ok( 1, "$test_group write back done" );
381             }
382              
383             sub check_file_mode {
384 0     0 0 0 my ($wr_dir, $t) = @_;
385              
386 0 0 0     0 if ($^O eq 'MSWin32' and my $fm = $t->{file_mode}) {
387 0         0 note("skipping file mode tests on Windows");
388 0         0 return;
389             }
390              
391 0 0       0 if (my $fm = $t->{file_mode}) {
392 0         0 foreach my $f (keys %$fm) {
393 0         0 my $expected_mode = $fm->{$f} ;
394 0         0 my $stat = $wr_dir->child($f)->stat;
395 0         0 ok($stat ,"stat found file $f");
396 0 0       0 if ($stat) {
397 0         0 my $mode = $stat->mode & oct(7777) ;
398 0         0 is($mode, $expected_mode, sprintf("check $f mode (got %o vs %o)",$mode,$expected_mode));
399             }
400             }
401             }
402             }
403              
404             sub check_file_content {
405 0     0 0 0 my ($wr_dir, $t) = @_;
406              
407 0 0 0     0 if (my $fc = $t->{file_contents} || $t->{file_content}) {
408 0         0 foreach my $f (keys %$fc) {
409 0         0 my $t = $fc->{$f} ;
410 0 0       0 my @tests = ref $t eq 'ARRAY' ? @$t : ($t) ;
411 0         0 foreach my $subtest (@tests) {
412 0         0 file_contents_eq_or_diff $wr_dir->child($f)->stringify, $subtest, { encoding => 'UTF-8' },
413             "check that $f contains $subtest";
414             }
415             }
416             }
417              
418 0 0       0 if (my $fc = $t->{file_contents_like}) {
419 0         0 foreach my $f (keys %$fc) {
420 0         0 my $t = $fc->{$f} ;
421 0 0       0 my @tests = ref $t eq 'ARRAY' ? @$t : ($t) ;
422 0         0 foreach my $subtest (@tests) {
423 0         0 file_contents_like $wr_dir->child($f)->stringify, $subtest, { encoding => 'UTF-8' },
424             "check that $f matches regexp $subtest";
425             }
426             }
427             }
428              
429 0 0       0 if (my $fc = $t->{file_contents_unlike}) {
430 0         0 foreach my $f (keys %$fc) {
431 0         0 my $t = $fc->{$f} ;
432 0 0       0 my @tests = ref $t eq 'ARRAY' ? @$t : ($t) ;
433 0         0 foreach my $subtest (@tests) {
434 0         0 file_contents_unlike $wr_dir->child($f)->stringify, $subtest, { encoding => 'UTF-8' },
435             "check that $f does not match regexp $subtest";
436             }
437             }
438             }
439             }
440              
441             sub check_added_or_removed_files {
442 0     0 0 0 my ( $conf_dir, $wr_dir, $t, @file_list) = @_;
443              
444             # copy whole dir
445             my $destination_dir
446 0 0       0 = $t->{setup} ? $wr_dir
    0          
447             : $conf_dir ? $wr_dir->child($conf_dir)
448             : $wr_dir ;
449 0         0 my @new_file_list = list_test_files($destination_dir) ;
450 0 0       0 $t->{file_check_sub}->( \@file_list ) if defined $t->{file_check_sub};
451 0         0 eq_or_diff( \@new_file_list, [ sort @file_list ], "check added or removed files" );
452             }
453              
454             sub create_second_instance {
455 0     0 0 0 my ($model, $test_group, $t_name, $wr_dir, $wr_dir2, $test_suite_data, $t, $config_dir_override) = @_;
456              
457             # create another instance to read the conf file that was just written
458 0 0       0 dircopy( $wr_dir->stringify, $wr_dir2->stringify )
459             or die "can't copy from $wr_dir to $wr_dir2: $!";
460              
461 0         0 my @options;
462 0 0       0 push @options, backend_arg => $t->{backend_arg} if $t->{backend_arg};
463              
464             my $i2_test = $model->instance(
465             root_class_name => $test_suite_data->{model_to_test},
466             root_dir => $wr_dir2->stringify,
467             config_file => $t->{config_file} ,
468             instance_name => "$test_group-$t_name-w",
469             application => $test_suite_data->{app_to_test},
470 0   0     0 check => $t->{load_check2} || 'yes',
471             config_dir => $config_dir_override,
472             @options
473             );
474              
475 0         0 ok( $i2_test, "Created instance $test_group-test-$t_name-w" );
476              
477 0   0     0 local $Config::Model::Value::nowarning = $t->{no_warnings} || 0;
478 0         0 my $i2_root = $i2_test->config_root;
479 0         0 $i2_root->init;
480              
481 0         0 return $i2_root;
482             }
483              
484             sub create_test_class {
485 0     0 0 0 my ($model, $config_classes) = @_;
486 0 0       0 return unless $config_classes;
487              
488 0         0 foreach my $c ( @$config_classes) {
489 0 0       0 my @parms = ref($c) eq 'HASH' ? %$c : @$c;
490 0         0 $model->create_config_class(@parms);
491             }
492             }
493              
494             our ($model, $conf_file_name, $conf_dir, $model_to_test, $app_to_test, $home_for_test, @tests, $skip);
495              
496             sub load_test_suite_data {
497 0     0 0 0 my ($model_obj, $test_group, $test_group_conf) = @_;
498              
499 0         0 local ($model, $conf_file_name, $conf_dir, $model_to_test, $app_to_test, $home_for_test, @tests, $skip);
500              
501 0         0 $skip = 0;
502 0         0 undef $conf_file_name ;
503 0         0 undef $conf_dir ;
504 0         0 undef $home_for_test ;
505 0         0 undef $model_to_test ; # deprecated
506 0         0 undef $app_to_test;
507 0         0 $model = $model_obj; # $model is used by Config::Model tests
508              
509 0         0 note("Beginning $test_group test ($test_group_conf)");
510              
511 0         0 my $result;
512 0 0       0 unless ( $result = do "./$test_group_conf" ) {
513 0 0       0 warn "couldn't parse $test_group_conf: $@" if $@;
514 0 0       0 warn "couldn't do $test_group_conf: $!" unless defined $result;
515 0 0       0 warn "couldn't run $test_group_conf" unless $result;
516             }
517              
518 0         0 my $test_suite_data;
519 0 0       0 if (ref($result) eq 'ARRAY') {
    0          
520             # simple list of tests
521 0         0 $test_suite_data = { tests => $result };
522             }
523             elsif (ref($result) eq 'HASH') {
524 0         0 $test_suite_data = $result;
525             }
526             else {
527 0         0 note(qq!warning: $test_group_conf should return a data structure instead of "1;". !
528             . qq!See Config::Model::Tester for details!);
529 0         0 $test_suite_data = {
530             tests => [ @tests ],
531             skip => $skip,
532             conf_file_name => $conf_file_name ,
533             conf_dir => $conf_dir ,
534             home_for_test => $home_for_test ,
535             model_to_test => $model_to_test,
536             app_to_test => $app_to_test,
537             };
538             }
539              
540 0         0 create_test_class($model, $test_suite_data->{config_classes});
541              
542 0   0     0 $test_suite_data->{app_to_test} ||= $test_group;
543              
544 0 0       0 if ($test_suite_data->{skip}) {
545 0         0 note("Skipped $test_group test ($test_group_conf)");
546 0         0 return;
547             }
548              
549 0         0 my ($trash, $appli_info, $applications) = Config::Model::Lister::available_models(1);
550 0         0 $test_suite_data->{appli_info} = $appli_info;
551              
552             # even undef, this resets the global variable there
553 0         0 Config::Model::BackendMgr::_set_test_home($test_suite_data->{home_for_test}) ;
554              
555 0 0       0 if (not defined $test_suite_data->{model_to_test}) {
556 0         0 $test_suite_data->{model_to_test} = $applications->{$test_suite_data->{app_to_test}};
557 0 0       0 if (not defined $test_suite_data->{model_to_test}) {
558 0         0 my @k = sort values %$applications;
559 0   0     0 my @files = map { $_->{_file} // 'unknown' } values %$appli_info ;
  0         0  
560 0         0 die "Cannot find application or model for $test_group in files >@files<. Known applications are",
561             sort keys %$applications, ". Known models are >@k<. ".
562             "Check your test name (the file ending with -test-conf.pl) or set app_to_test parameter\n";
563             }
564             }
565              
566 0         0 return $test_suite_data;
567             }
568              
569             sub run_model_test {
570 0     0 0 0 my ($test_group, $test_group_conf, $do, $model, $trace, $wr_root, $test_logs) = @_ ;
571              
572 0         0 my $test_suite_data = load_test_suite_data($model,$test_group, $test_group_conf);
573 0         0 my $appli_info = $test_suite_data->{appli_info};
574              
575 0         0 my $config_dir_override = $appli_info->{$test_group}{config_dir}; # may be undef
576              
577 0         0 my $note ="$test_group uses ".$test_suite_data->{model_to_test}." model";
578 0         0 my $conf_file_name = $test_suite_data->{conf_file_name};
579 0 0       0 $note .= " on file $conf_file_name" if defined $conf_file_name;
580 0         0 note($note);
581              
582 0         0 my $idx = 0;
583 0         0 foreach my $t (@{$test_suite_data->{tests}}) {
  0         0  
584 0         0 translate_test_data($t);
585 0   0     0 my $t_name = $t->{name} || "t$idx";
586 0 0 0     0 if ( defined $do and $t_name !~ /$do/) {
587 0         0 $idx++;
588 0         0 next;
589             }
590 0         0 note("Beginning subtest $test_group $t_name");
591              
592 0         0 my ($wr_dir, $wr_dir2, $conf_file, $ex_data, @file_list)
593             = setup_test ($test_group, $t_name, $wr_root,$trace, $test_suite_data, $t);
594              
595 0         0 write_config_file($test_suite_data->{conf_dir},$wr_dir,$t);
596              
597 0         0 my $inst_name = "$test_group-" . $t_name;
598              
599 0 0       0 die "Duplicated test name $t_name for app $test_group\n"
600             if $model->has_instance ($inst_name);
601              
602 0         0 my @options;
603 0 0       0 push @options, backend_arg => $t->{backend_arg} if $t->{backend_arg};
604              
605             # eventually, we may end up with several instances of Dpkg
606             # model in the same process. So we can't play with chdir
607             my $inst = $model->instance(
608             root_class_name => $test_suite_data->{model_to_test},
609             # need to keed root_dir to handle config files like
610             # /etc/foo.ini (absolute path, like in /etc/)
611             root_dir => $wr_dir->stringify,
612             instance_name => $inst_name,
613             application => $test_suite_data->{app_to_test},
614             config_file => $t->{config_file} ,
615 0   0     0 check => $t->{load_check} || 'yes',
616             config_dir => $config_dir_override,
617             @options
618             );
619              
620 0         0 my $root = $inst->config_root;
621              
622 0 0       0 check_load_warnings ($root,$t) if $test_logs;
623              
624 0 0       0 run_update($inst,$wr_dir,$t) if $t->{update};
625              
626 0 0       0 load_instructions ($root,$t->{load},$trace) if $t->{load} ;
627              
628             dump_tree ('before fix '.$test_group , $root, 'full', $t->{no_warnings}, $t->{check_before_fix}, $test_logs, $trace)
629 0 0       0 if $t->{check_before_fix};
630              
631 0 0       0 apply_fix($inst) if $t->{apply_fix};
632              
633 0         0 dump_tree ($test_group, $root, 'full', $t->{no_warnings}, $t->{full_dump}, $test_logs, $trace) ;
634              
635 0         0 my $dump = dump_tree ($test_group, $root, 'custom', $t->{no_warnings}, {}, $test_logs, $trace) ;
636              
637 0 0       0 check_data("first", $root, $t->{check}, $t->{no_warnings}) if $t->{check};
638              
639 0 0       0 has_key ( $root, $t->{has_key}, $t->{no_warnings}) if $t->{has_key} ;
640 0 0       0 has_not_key ( $root, $t->{has_not_key}, $t->{no_warnings}) if $t->{has_not_key} ;
641              
642 0 0       0 check_annotation($root,$t) if $t->{verify_annotation};
643              
644 0         0 write_data_back ($test_group, $inst, $t) ;
645              
646 0         0 check_file_content($wr_dir,$t) ;
647              
648 0         0 check_file_mode($wr_dir,$t) ;
649              
650 0 0       0 check_added_or_removed_files ($test_suite_data->{conf_dir}, $wr_dir, $t, @file_list) if $ex_data->is_dir;
651              
652 0         0 my $i2_root = create_second_instance ($model, $test_group, $t_name, $wr_dir, $wr_dir2, $test_suite_data, $t, $config_dir_override);
653              
654 0 0       0 load_instructions ($i2_root,$t->{load2},$trace) if $t->{load2} ;
655              
656 0         0 my $p2_dump = dump_tree("second $test_group", $i2_root, 'custom', $t->{no_warnings},{}, $test_logs, $trace) ;
657              
658 0         0 unified_diff;
659 0         0 eq_or_diff(
660             [ split /\n/,$p2_dump ],
661             [ split /\n/,$dump ],
662             "compare original $test_group custom data with 2nd instance custom data",
663             );
664              
665             ok( -s "$wr_dir2/$test_suite_data->{conf_dir}/$test_suite_data->{conf_file_name}" ,
666             "check that original $test_group file was not clobbered" )
667 0 0       0 if defined $test_suite_data->{conf_file_name} ;
668              
669 0 0       0 check_data("second", $i2_root, $t->{wr_check}, $t->{no_warnings}) if $t->{wr_check} ;
670              
671 0         0 note("End of subtest $test_group $t_name");
672              
673 0         0 $idx++;
674             }
675 0         0 note("End of $test_group test");
676              
677             }
678              
679             sub translate_test_data {
680 0     0 0 0 my $t = shift;
681 0 0       0 map {$t->{full_dump}{$_} = delete $t->{$_} if $t->{$_}; } qw/dump_warnings dump_errors/;
  0         0  
682             }
683              
684             sub create_model_object {
685 0     0 0 0 my $new_model ;
686 0         0 eval { $new_model = Config::Model->new(); } ;
  0         0  
687 0 0       0 if ($@) {
688             # necessary to run smoke test (no Config::Model to avoid dependency loop)
689 0         0 plan skip_all => 'Config::Model is not loaded' ;
690 0         0 return;
691             }
692 0         0 return $new_model;
693             }
694              
695             sub run_tests {
696 1     1 0 140 my ( $test_only_app, $do, $trace, $wr_root );
697 1         0 my $model;
698 1         0 my $test_logs;
699 1 50       5 if (@_) {
700 1         2 my $arg;
701 1         8 note ("Calling run_tests with argument is deprecated");
702 1         942 ( $arg, $test_only_app, $do ) = @_;
703              
704 1         3 my $log = 0;
705              
706 1 50       6 $trace = ($arg =~ /t/) ? 1 : 0;
707 1 50       4 $log = 1 if $arg =~ /l/;
708              
709 1   50     7 my $log4perl_user_conf_file = ($ENV{HOME} || '') . '/.log4config-model';
710              
711 1 50 33     5 if ( $log and -e $log4perl_user_conf_file ) {
712 0         0 Log::Log4perl::init($log4perl_user_conf_file);
713             }
714             else {
715 1 50       12 Log::Log4perl->easy_init( $log ? $WARN : $ERROR );
716             }
717              
718 1 50       5087 Config::Model::Exception::Any->Trace(1) if $arg =~ /e/;
719              
720 1         8 ok( 1, "compiled" );
721              
722             # pseudo root where config files are written by config-model
723 1         622 $wr_root = path('wr_root');
724             }
725             else {
726 0         0 my $opts;
727 0         0 ($model, $trace, $opts) = init_test();
728 0 0       0 $test_logs = $opts->{log} ? 0 : 1;
729 0         0 ( $test_only_app, $do) = @ARGV;
730             # pseudo root where config files are written by config-model
731 0         0 $wr_root = setup_test_dir();
732             }
733              
734 1         155 my @group_of_tests = grep { /-test-conf.pl$/ } glob("t/model_tests.d/*");
  0         0  
735              
736 1         7 foreach my $test_group_conf (@group_of_tests) {
737 0         0 my ($test_group) = ( $test_group_conf =~ m!\.d/([\w\-]+)-test-conf! );
738 0 0 0     0 next if ( $test_only_app and $test_only_app ne $test_group ) ;
739 0         0 $model = create_model_object();
740 0 0       0 return unless $model;
741 0         0 run_model_test($test_group, $test_group_conf, $do, $model, $trace, $wr_root, $test_logs) ;
742             }
743              
744 1         9 memory_cycle_ok($model,"test memory cycle") ;
745              
746 1         346 done_testing;
747              
748             }
749             1;
750              
751             __END__
752              
753             =pod
754              
755             =encoding UTF-8
756              
757             =head1 NAME
758              
759             Config::Model::Tester - Test framework for Config::Model
760              
761             =head1 VERSION
762              
763             version 4.007
764              
765             =head1 SYNOPSIS
766              
767             In your test file (typically C<t/model_test.t>):
768              
769             use warnings;
770             use strict;
771              
772             use Config::Model::Tester ;
773             use ExtUtils::testlib;
774              
775             run_tests() ;
776              
777             Run tests with:
778              
779             perl t/model_test.t [ --log ] [--error] [--trace] [ subtest [ test_case ] ]
780              
781             =head1 DESCRIPTION
782              
783             This class provides a way to test configuration models with tests files.
784             This class was designed to tests several models and run several tests
785             cases per model.
786              
787             A specific layout for test files must be followed.
788              
789             =head2 Sub test specification
790              
791             Each subtest is defined in a file like:
792              
793             t/model_tests.d/<app-name>-test-conf.pl
794              
795             This file specifies that C<app-name> (which is defined in
796             C<lib/Config/Model/*.d> directory) is used for the test cases defined
797             in the C<*-test-conf.pl> file. The model to test is inferred from the
798             application name to test.
799              
800             This file contains a list of test case (explained below) and expects a
801             set of files used as test data. The layout of these test data files is
802             explained in next section.
803              
804             =head2 Simple test file layout
805              
806             Each test case is represented by a configuration file (not
807             a directory) in the C<*-examples> directory. This configuration file
808             is used by the model to test and is copied as
809             C<$confdir/$conf_file_name> using the test data structure explained
810             below.
811              
812             In the example below, we have 1 app model to test: C<lcdproc> and 2 tests
813             cases. The app name matches the file specified in
814             C<lib/Config/Model/*.d> directory. In this case, the app name matches
815             C<lib/Config/Model/system.d/lcdproc>
816              
817             t
818             |-- model_test.t
819             \-- model_tests.d # do not change directory name
820             |-- lcdproc-test-conf.pl # subtest specification for lcdproc app
821             \-- lcdproc-examples
822             |-- t0 # test case t0
823             \-- LCDD-0.5.5 # test case for older LCDproc
824              
825             Subtest specification is written in C<lcdproc-test-conf.pl> file (i.e. this
826             module looks for files named like C<< <app-name>-test-conf.pl> >>).
827              
828             Subtests data is provided in files in directory C<lcdproc-examples> (
829             i.e. this modules looks for test data in directory
830             C<< <model-name>-examples> >>. C<lcdproc-test-conf.pl> contains
831             instructions so that each file is used as a C</etc/LCDd.conf>
832             file during each test case.
833              
834             C<lcdproc-test-conf.pl> can contain specifications for more test
835             cases. Each test case requires a new file in C<lcdproc-examples>
836             directory.
837              
838             See L</Examples> for a link to the actual LCDproc model tests
839              
840             =head2 Test file layout for multi-file configuration
841              
842             When a configuration is spread over several files, each test case is
843             provided in a sub-directory. This sub-directory is copied in
844             C<conf_dir> (a test parameter as explained below)
845              
846             In the example below, the test specification is written in
847             C<dpkg-test-conf.pl>. Dpkg layout requires several files per test case.
848             C<dpkg-test-conf.pl> contains instructions so that each directory
849             under C<dpkg-examples> is used.
850              
851             t/model_tests.d
852             \-- dpkg-test-conf.pl # subtest specification
853             \-- dpkg-examples
854             \-- libversion # example subdir, used as test case name
855             \-- debian # directory for used by test case
856             |-- changelog
857             |-- compat
858             |-- control
859             |-- copyright
860             |-- rules
861             |-- source
862             | \-- format
863             \-- watch
864              
865             See L</Examples> for a link to the (many) Dpkg model tests
866              
867             =head2 More complex file layout
868              
869             Each test case is a sub-directory on the C<*-examples> directory and
870             contains several files. The destination of the test files may depend
871             on the system (e.g. the OS). For instance, system wide C<ssh_config>
872             is stored in C</etc/ssh> on Linux, and directly in C</etc> on MacOS.
873              
874             These files are copied in a test directory using a C<setup> parameter
875             in test case specification.
876              
877             Let's consider this example of 2 tests cases for ssh:
878              
879             t/model_tests.d/
880             |-- ssh-test-conf.pl
881             |-- ssh-examples
882             \-- basic
883             |-- system_ssh_config
884             \-- user_ssh_config
885              
886             Unfortunately, C<user_ssh_config> is a user file, so you need to specify
887             where is located the home directory of the test with another global parameter:
888              
889             home_for_test => '/home/joe' ;
890              
891             For Linux only, the C<setup> parameter is:
892              
893             setup => {
894             system_ssh_config => '/etc/ssh/ssh_config',
895             user_ssh_config => "~/.ssh/config"
896             }
897              
898             On the other hand, system wide config file is different on MacOS and
899             the test file must be copied in the correct location. When the value
900             of the C<setup> hash is another hash, the key of this other hash is
901             used as to specify the target location for other OS (as returned by
902             Perl C<$^O> variable:
903              
904             setup => {
905             'system_ssh_config' => {
906             'darwin' => '/etc/ssh_config',
907             'default' => '/etc/ssh/ssh_config',
908             },
909             'user_ssh_config' => "~/.ssh/config"
910             }
911              
912             C<systemd> is another beast where configuration files can be symlinks
913             to C</dev/null> or other files. To emulate this situation, use an array as setup target:
914              
915             setup => {
916             # test data file => [ link (may be repeated), .. link(s) target contains test data ]
917             'ssh.service' => [ '/etc/systemd/system/sshd.service', '/lib/systemd/system/ssh.service' ]
918             }
919              
920             This will result in a symlink like:
921              
922             wr_root/model_tests/test-sshd-service/etc/systemd/system/sshd.service
923             -> /absolute_path_to/wr_root/model_tests/test-sshd-service/lib/systemd/system/ssh.service
924              
925             See the actual L<Ssh and Sshd model tests|https://github.com/dod38fr/config-model-openssh/tree/master/t/model_tests.d>
926              
927             =head2 Basic test specification
928              
929             Each model subtest is specified in C<< <app>-test-conf.pl >>. This
930             file must return a data structure containing the test
931             specifications. Each test data structure contains global parameters
932             (Applied to all tests cases) and test cases parameters (parameters are
933             applied to the test case)
934              
935             use strict;
936             use warnings;
937             {
938             # global parameters
939              
940             # config file name (used to copy test case into test wr_root/model_tests directory)
941             conf_file_name => "fstab",
942             # config dir where to copy the file (optional)
943             conf_dir => "etc",
944             # home directory for this test
945             home_for_test => '/home/joe'
946              
947             tests => [
948             {
949             # test case 1
950             name => 'my_first_test',
951             # other test case parameters
952             },
953             {
954             # test case 2
955             name => 'my_second_test',
956             # other test case parameters
957             },
958             # ...
959             ],
960             };
961              
962             # do not add 1; at the end of the file
963              
964             In the example below, C<t0> file is copied in C<wr_root/model_tests/test-t0/etc/fstab>.
965              
966             use strict;
967             use warnings;
968             {
969             # list of tests.
970             tests => [
971             {
972             # test name
973             name => 't0',
974             # add optional specification here for t0 test
975             },
976             {
977             name => 't1',
978             # add optional specification here for t1 test
979             },
980             ]
981             };
982              
983             You can suppress warnings by specifying C<< no_warnings => 1 >> in
984             each test case. On the other hand, you may also want to check for
985             warnings specified to your model. In this case, you should avoid
986             specifying C<no_warnings> here and specify warning tests or warning
987             filters as mentioned below.
988              
989             See actual L<fstab test|https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/fstab-test-conf.pl>.
990              
991             =head2 Skip a test
992              
993             A test file can be skipped using C<skip> global test parameter.
994              
995             In this example, test is skipped when not running on a Debian system:
996              
997             eval { require AptPkg::Config; };
998             my $skip = ( $@ or not -r '/etc/debian_version' ) ? 1 : 0;
999              
1000             {
1001             skip => $skip,
1002             tests => [ ] ,
1003             };
1004              
1005             =head2 Internal tests or backend tests
1006              
1007             Some tests require the creation of a configuration class dedicated
1008             for test (typically to test corner cases on a backend).
1009              
1010             This test class can be created directly in the test specification by
1011             specifying tests classes in C<config_classes> global test parameter in an
1012             array ref. Each array element is a data structure that use
1013             L<create_config_class|Config::Model/create_config_class> parameters.
1014             See for instance the
1015             L<layer test|https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/layer-test-conf.pl>
1016             or the
1017             L<test for shellvar backend|https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/backend-shellvar-test-conf.pl>.
1018              
1019             In this case, no application exist for such classes so the model to
1020             test must be specified in a global test parameter:
1021              
1022             return {
1023             config_classes => [ { name => "Foo", element => ... } , ... ],
1024             model_to_test => "Foo",
1025             tests => [ ... ]
1026             };
1027              
1028             =head2 Test specification with arbitrary file names
1029              
1030             In some models, like C<Multistrap>, the config file is chosen by the
1031             user. In this case, the file name must be specified for each tests
1032             case:
1033              
1034             {
1035             tests => [ {
1036             name => 'arm',
1037             config_file => '/home/foo/my_arm.conf',
1038             check => {},
1039             }]
1040             };
1041              
1042             See the actual L<multistrap test|https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/multistrap-test-conf.pl>.
1043              
1044             =head2 Backend argument
1045              
1046             Some application like systemd requires a backend argument specified by
1047             user (e.g. a service name for systemd). The parameter C<backend_arg>
1048             can be specified to emulate this behavior.
1049              
1050             =head2 Re-use test data
1051              
1052             When the input data for test is quite complex (several files), it may
1053             be interesting to re-use these data for other test cases. Knowing that
1054             test names must be unique, you can re-use test data with C<data_from>
1055             parameter. For instance:
1056              
1057             tests => [
1058             {
1059             name => 'some-test',
1060             # ...
1061             },
1062             {
1063             name => 'some-other-test',
1064             data_from => 'some-test', # re-use data from test above
1065             # ...
1066             },
1067             ]
1068              
1069             See
1070             L<plainfile backend test|https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/backend-plainfile-test-conf.pl>
1071             for a real life example.
1072              
1073             Likewise, it may be useful to re-use test data from another group of
1074             test. Lets see this example from C<systemd-service-test-conf.pl>:
1075              
1076             {
1077             name => 'transmission',
1078             data_from_group => 'systemd', # i.e from ../systemd-examples
1079             }
1080              
1081             C<data_from> and C<data_from_group> can be together.
1082              
1083             =head2 Test scenario
1084              
1085             Each subtest follow a sequence explained below. Each step of this
1086             sequence may be altered by adding test case parameters in
1087             C<< <model-to-test>-test-conf.pl >>:
1088              
1089             =over
1090              
1091             =item *
1092              
1093             Setup test in C<< wr_root/model_tests/<subtest name>/ >>. If your configuration file layout depend
1094             on the target system, you will have to specify the path using C<setup> parameter.
1095             See L</"More complex file layout">.
1096              
1097             =item *
1098              
1099             Create configuration instance, load config data and check its validity. Use
1100             C<< load_check => 'no' >> if your file is not valid.
1101              
1102             =item *
1103              
1104             Check for config data warnings. You should pass the list of expected warnings that are
1105             emitted through L<Log::Log4perl>. The array ref is passed as is to the C<expect> function
1106             of L<Test::Log::Lo4Perl/expect>. E.g:
1107              
1108             log4perl_load_warnings => [
1109             [ 'Tree.Node', (warn => qr/deprecated/) x 2 ] ,
1110             [ 'Tree.Element.Value' , ( warn => qr/skipping/) x 2 ]
1111             ]
1112              
1113             The Log classes are specified in C<cme/Logging>.
1114              
1115             Log levels below "warn" are ignored.
1116              
1117             Note that log tests are disabled when C<--log> option is used, hence
1118             all warnings triggered by the tests are shown.
1119              
1120             L<Config::Model> is currently transitioning from traditional "warn" to
1121             warn logs. To avoid breaking all tests based on this module, the
1122             warnings are emitted through L<Log::Log4perl> only when
1123             C<$::_use_log4perl_to_warn> is set. This hack will be removed once all
1124             warnings checks in tests are ported to log4perl checks.
1125              
1126             =item *
1127              
1128             DEPRECATED. Check for config data warning. You should pass the list of expected warnings.
1129             E.g.
1130              
1131             load_warnings => [ qr/Missing/, (qr/deprecated/) x 3 , ],
1132              
1133             Use an empty array_ref to mask load warnings.
1134              
1135             =item *
1136              
1137             Optionally run L<update|App::Cme::Command::update> command:
1138              
1139             update => {
1140             returns => 'foo' , # optional
1141             no_warnings => [ 0 | 1 ], # default 0
1142             quiet => [ 0 | 1], # default 0, passed to update method
1143             load4perl_update_warnings => [ ... ] # Test::Log::Log4perl::expect arguments
1144             }
1145              
1146             Where:
1147              
1148             =over
1149              
1150             =item *
1151              
1152             C<returns> is the expected return value (optional).
1153              
1154             =item *
1155              
1156             C<no_warnings> can be used to suppress the warnings coming from
1157             L<Config::Model::Value>. Note that C<< no_warnings => 1 >> may be
1158             useful for less verbose test.
1159              
1160             =item *
1161              
1162             C<quiet> to suppress progress messages during update.
1163              
1164             =item *
1165              
1166             C<log4perl_update_warnings> is used to check the warnings produced
1167             during update. The argument is passed to C<expect> function of
1168             L<Test::Log::Log4perl>. See C<load_warnings> parameter above for more
1169             details.
1170              
1171             =item *
1172              
1173             DEPRECATED. C<update_warnings> is an array ref of quoted regexp (See qr operator)
1174             to check the warnings produced during update. Please use C<log4perl_update_warnings>
1175             instead.
1176              
1177             =back
1178              
1179             All other arguments are passed to C<update> method.
1180              
1181             =item *
1182              
1183             Optionally load configuration data. You should design this config data to
1184             suppress any error or warning mentioned above. E.g:
1185              
1186             load => 'binary:seaview Synopsis="multiplatform interface for sequence alignment"',
1187              
1188             See L<Config::Model::Loader> for the syntax of the string accepted by C<load> parameter.
1189              
1190             =item *
1191              
1192             Optionally, run a check before running apply_fix (if any). This step is useful to check
1193             warning messages:
1194              
1195             check_before_fix => {
1196             dump_errors => [ ... ] # optional, see below
1197             log4perl_dump_warnings => [ ... ] # optional, see below
1198             }
1199              
1200             Use C<dump_errors> if you expect issues:
1201              
1202             check_before_fix => {
1203             dump_errors => [
1204             # the issues and a way to fix the issue using Config::Model::Node::load
1205             qr/mandatory/ => 'Files:"*" Copyright:0="(c) foobar"',
1206             qr/mandatory/ => ' License:FOO text="foo bar" ! Files:"*" License short_name="FOO" '
1207             ],
1208             }
1209              
1210             Likewise, specify any expected warnings:
1211              
1212             check_before_fix => {
1213             log4perl_dump_warnings => [ ... ],
1214             }
1215              
1216             C<log4perl_dump_warnings> passes the array ref content to C<expect>
1217             function of L<Test::Log::Log4perl>.
1218              
1219             Both C<log4perl_dump_warnings> and C<dump_errors> can be specified in C<check_before_fix> hash.
1220              
1221             =item *
1222              
1223             Optionally, call L<apply_fixes|Config::Model::Instance/apply_fixes>:
1224              
1225             apply_fix => 1,
1226              
1227             =item *
1228              
1229             Call L<dump_tree|Config::Model::Node/dump_tree> to check the validity of the
1230             data after optional C<apply_fix>. This step is not optional.
1231              
1232             As with C<check_before_fix>, both C<dump_errors> or
1233             C<log4perl_dump_warnings> can be specified in C<full_dump> parameter:
1234              
1235             full_dump => {
1236             log4perl_dump_warnings => [ ... ], # optional
1237             dump_errors => [ ... ], # optional
1238             }
1239              
1240             =item *
1241              
1242             Run specific content check to verify that configuration data was retrieved
1243             correctly:
1244              
1245             check => {
1246             'fs:/proc fs_spec' => "proc",
1247             'fs:/proc fs_file' => "/proc",
1248             'fs:/home fs_file' => "/home",
1249             },
1250              
1251             The keys of the hash points to the value to be checked using the
1252             syntax described in L<Config::Model::Role::Grab/grab>.
1253              
1254             Multiple check on the same item can be applied with a array ref:
1255              
1256             check => [
1257             Synopsis => 'fix undefined path_max for st_size zero',
1258             Description => [ qr/^The downstream/, qr/yada yada/ ]
1259             ]
1260              
1261             You can run check using different check modes (See L<Config::Model::Value/fetch>)
1262             by passing a hash ref instead of a scalar :
1263              
1264             check => {
1265             'sections:debian packages:0' => { mode => 'layered', value => 'dpkg-dev' },
1266             'sections:base packages:0' => { mode => 'layered', value => "gcc-4.2-base' },
1267             },
1268              
1269             The whole hash content (except "value") is passed to L<grab|Config::Model::Role::Grab/grab>
1270             and L<fetch|Config::Model::Value/fetch>
1271              
1272             A regexp can also be used to check value:
1273              
1274             check => {
1275             "License text" => qr/gnu/i,
1276             }
1277              
1278             And specification can nest hash or array style:
1279              
1280             check => {
1281             "License:0 text" => qr/gnu/i,
1282             "License:1 text" => [ qr/gnu/i, qr/Stallman/ ],
1283             "License:2 text" => { mode => 'custom', value => [ qr/gnu/i , qr/Stallman/ ] },
1284             "License:3 text" => [ qr/General/], { mode => 'custom', value => [ qr/gnu/i , qr/Stallman/ ] },
1285             }
1286              
1287             =item *
1288              
1289             Verify if a hash contains one or more keys (or keys matching a regexp):
1290              
1291             has_key => [
1292             'sections' => 'debian', # sections must point to a hash element
1293             'control' => [qw/source binary/],
1294             'copyright Files' => qr/.c$/,
1295             'copyright Files' => [qr/\.h$/], qr/\.c$/],
1296             ],
1297              
1298             =item *
1299              
1300             Verify that a hash does B<not> have a key (or a key matching a regexp):
1301              
1302             has_not_key => [
1303             'copyright Files' => qr/.virus$/ # silly, isn't ?
1304             ],
1305              
1306             =item *
1307              
1308             Verify annotation extracted from the configuration file comments:
1309              
1310             verify_annotation => {
1311             'source Build-Depends' => "do NOT add libgtk2-perl to build-deps (see bug #554704)",
1312             'source Maintainer' => "what a fine\nteam this one is",
1313             },
1314              
1315             =item *
1316              
1317             Write back the config data in C<< wr_root/model_tests/<subtest name>/ >>.
1318             Note that write back is forced, so the tested configuration files are
1319             written back even if the configuration values were not changed during the test.
1320              
1321             You can skip warning when writing back with the global :
1322              
1323             no_warnings => 1,
1324              
1325             =item *
1326              
1327             Check the content of the written files(s) with L<Test::File::Contents>. Tests can be grouped
1328             in an array ref:
1329              
1330             file_contents => {
1331             "/home/foo/my_arm.conf" => "really big string" ,
1332             "/home/bar/my_arm.conf" => [ "really big string" , "another"], ,
1333             }
1334              
1335             file_contents_like => {
1336             "/home/foo/my_arm.conf" => [ qr/should be there/, qr/as well/ ] ,
1337             }
1338              
1339             file_contents_unlike => {
1340             "/home/foo/my_arm.conf" => qr/should NOT be there/ ,
1341             }
1342              
1343             =item *
1344              
1345             Check the mode of the written files:
1346              
1347             file_mode => {
1348             "~/.ssh/ssh_config" => oct(600), # better than 0600
1349             "debian/stuff.postinst" => oct(755),
1350             }
1351              
1352             Only the last four octets of the mode are tested. I.e. the test is done with
1353             C< $file_mode & oct(7777) >
1354              
1355             Note: this test is skipped on Windows
1356              
1357             =item *
1358              
1359             Check added or removed configuration files. If you expect changes,
1360             specify a subref to alter the file list:
1361              
1362             file_check_sub => sub {
1363             my $list_ref = shift ;
1364             # file added during tests
1365             push @$list_ref, "/debian/source/format" ;
1366             },
1367              
1368             Note that actual and expected file lists are sorted before check,
1369             adding a file can be done with C<push>.
1370              
1371             =item *
1372              
1373             Copy all config data from C<< wr_root/model_tests/<subtest name>/ >>
1374             to C<< wr_root/model_tests/<subtest name>-w/ >>. This steps is necessary
1375             to check that configuration written back has the same content as
1376             the original configuration.
1377              
1378             =item *
1379              
1380             Create a second configuration instance to read the conf file that was just copied
1381             (configuration data is checked.)
1382              
1383             =item *
1384              
1385             You can skip the load check if the written file still contain errors (e.g.
1386             some errors were ignored and cannot be fixed) with C<< load_check2 => 'no' >>
1387              
1388             =item *
1389              
1390             Optionally load configuration data in the second instance. You should
1391             design this config data to suppress any error or warning that occur in
1392             the step below. E.g:
1393              
1394             load2 => 'binary:seaview',
1395              
1396             See L<Config::Model::Loader> for the syntax of the string accepted by C<load2> parameter.
1397              
1398             =item *
1399              
1400             Compare data read from original data.
1401              
1402             =item *
1403              
1404             Run specific content check on the B<written> config file to verify that
1405             configuration data was written and retrieved correctly:
1406              
1407             wr_check => {
1408             'fs:/proc fs_spec' => "proc" ,
1409             'fs:/proc fs_file' => "/proc",
1410             'fs:/home fs_file' => "/home",
1411             },
1412              
1413             Like the C<check> item explained above, you can run C<wr_check> using
1414             different check modes.
1415              
1416             =back
1417              
1418             =head2 Running the test
1419              
1420             Run all tests with one of these commands:
1421              
1422             prove -l t/model_test.t :: [ --trace ] [ --log ] [ --error ] [ <model_name> [ <regexp> ]]
1423             perl -Ilib t/model_test.t [ --trace ] [ --log ] [ --error ] [ <model_name> [ <regexp> ]]
1424              
1425             By default, all tests are run on all models.
1426              
1427             You can pass arguments to C<t/model_test.t>:
1428              
1429             =over
1430              
1431             =item *
1432              
1433             Optional parameters: C<--trace> to get test traces. C<--error> to get stack trace in case of
1434             errors, C<--log> to have logs. E.g.
1435              
1436             # run with log and error traces
1437             prove -lv t/model_test.t :: --error --logl
1438              
1439             =item *
1440              
1441             The model name to tests. E.g.:
1442              
1443             # run only fstab tests
1444             prove -lv t/model_test.t :: fstab
1445              
1446             =item *
1447              
1448             A regexp to filter subtest E.g.:
1449              
1450             # run only fstab tests foobar subtest
1451             prove -lv t/model_test.t :: fstab foobar
1452              
1453             # run only fstab tests foo subtest
1454             prove -lv t/model_test.t :: fstab '^foo$'
1455              
1456             =back
1457              
1458             =head1 Examples
1459              
1460             Some of these examples may still use global variables (which is
1461             deprecated). Such files may be considered as buggy after Aug
1462             2019. Please warn the author if you find one.
1463              
1464             =over
1465              
1466             =item *
1467              
1468             L<LCDproc|http://lcdproc.org> has a single configuration file:
1469             C</etc/LCDd.conf>. Here's LCDproc test
1470             L<layout|https://github.com/dod38fr/config-model-lcdproc/tree/master/t/model_tests.d>
1471             and the L<test specification|https://github.com/dod38fr/config-model-lcdproc/blob/master/t/model_tests.d/lcdd-test-conf.pl>
1472              
1473             =item *
1474              
1475             Dpkg packages are constructed from several files. These files are handled like
1476             configuration files by L<Config::Model::Dpkg|https://salsa.debian.org/perl-team/modules/packages/libconfig-model-dpkg-perl>. The
1477             L<test layout|https://salsa.debian.org/perl-team/modules/packages/libconfig-model-dpkg-perl/-/tree/master/t/model_tests.d>
1478             features test with multiple file in
1479             L<dpkg-examples|https://salsa.debian.org/perl-team/modules/packages/libconfig-model-dpkg-perl/-/tree/master/t/model_tests.d/dpkg-examples>.
1480             The test is specified in L<https://salsa.debian.org/perl-team/modules/packages/libconfig-model-dpkg-perl/-/blob/master/t/model_tests.d/dpkg-test-conf.pl>
1481              
1482             =item *
1483              
1484             L<multistrap-test-conf.pl|https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/multistrap-test-conf.pl>
1485             and L<multistrap-examples|https://github.com/dod38fr/config-model/tree/master/t/model_tests.d/multistrap-examples>
1486             specify a test where the configuration file name is not imposed by the
1487             application. The file name must then be set in the test specification.
1488              
1489             =item *
1490              
1491             L<backend-shellvar-test-conf.pl|https://github.com/dod38fr/config-model/blob/master/t/model_tests.d/backend-shellvar-test-conf.pl>
1492             is a more complex example showing how to test a backend. The test is done creating a dummy model within the test specification.
1493              
1494             =back
1495              
1496             =head1 CREDITS
1497              
1498             In alphabetical order:
1499              
1500             =over 4
1501              
1502             =item *
1503              
1504             Cyrille Bollu
1505              
1506             =back
1507              
1508             Many thanks for your help.
1509              
1510             =head1 SEE ALSO
1511              
1512             =over 4
1513              
1514             =item *
1515              
1516             L<Config::Model>
1517              
1518             =item *
1519              
1520             L<Test::More>
1521              
1522             =back
1523              
1524             =head1 AUTHOR
1525              
1526             Dominique Dumont
1527              
1528             =head1 COPYRIGHT AND LICENSE
1529              
1530             This software is Copyright (c) 2013-2020 by Dominique Dumont.
1531              
1532             This is free software, licensed under:
1533              
1534             The GNU Lesser General Public License, Version 2.1, February 1999
1535              
1536             =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
1537              
1538             =head1 SUPPORT
1539              
1540             =head2 Websites
1541              
1542             The following websites have more information about this module, and may be of help to you. As always,
1543             in addition to those websites please use your favorite search engine to discover more resources.
1544              
1545             =over 4
1546              
1547             =item *
1548              
1549             CPANTS
1550              
1551             The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution.
1552              
1553             L<http://cpants.cpanauthors.org/dist/Config-Model-Tester>
1554              
1555             =item *
1556              
1557             CPAN Testers
1558              
1559             The CPAN Testers is a network of smoke testers who run automated tests on uploaded CPAN distributions.
1560              
1561             L<http://www.cpantesters.org/distro/C/Config-Model-Tester>
1562              
1563             =item *
1564              
1565             CPAN Testers Matrix
1566              
1567             The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
1568              
1569             L<http://matrix.cpantesters.org/?dist=Config-Model-Tester>
1570              
1571             =item *
1572              
1573             CPAN Testers Dependencies
1574              
1575             The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
1576              
1577             L<http://deps.cpantesters.org/?module=Config::Model::Tester>
1578              
1579             =back
1580              
1581             =head2 Bugs / Feature Requests
1582              
1583             Please report any bugs or feature requests by email to C<ddumont at cpan.org>, or through
1584             the web interface at L<https://github.com/dod38fr/config-model-tester/issues>. You will be automatically notified of any
1585             progress on the request by the system.
1586              
1587             =head2 Source Code
1588              
1589             The code is open to the world, and available for you to hack on. Please feel free to browse it and play
1590             with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull
1591             from your repository :)
1592              
1593             L<http://github.com/dod38fr/config-model-tester.git>
1594              
1595             git clone git://github.com/dod38fr/config-model-tester.git
1596              
1597             =cut