File Coverage

blib/lib/Config/Model/Tester.pm
Criterion Covered Total %
statement 74 448 16.5
branch 6 206 2.9
condition 2 77 2.6
subroutine 18 47 38.3
pod 0 24 0.0
total 100 802 12.4


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