File Coverage

blib/lib/Batch/Interpreter/TestSupport.pm
Criterion Covered Total %
statement 122 135 90.3
branch 33 52 63.4
condition 5 8 62.5
subroutine 20 20 100.0
pod 4 9 44.4
total 184 224 82.1


line stmt bran cond sub pod time code
1             package Batch::Interpreter::TestSupport;
2            
3             =head1 NAME
4            
5             Batch::Interpreter::TestSupport - support code for testing Batch::Interpreter
6            
7             =head1 SYNOPSIS
8            
9             The output of runbat is compared with the output of CMD.EXE. On systems with CMD.EXE the batch files can be run with CMD.EXE to store the expected output. The switch --complete combines both steps.
10            
11             =head1 METHODS
12            
13             =cut
14            
15 19     19   373266 use v5.10;
  19         95  
16 19     19   133 use warnings;
  19         51  
  19         669  
17 19     19   128 use strict;
  19         50  
  19         476  
18 19     19   8310 use parent 'Exporter';
  19         5232  
  19         123  
19             our @EXPORT_OK = qw(
20             get_test_attr compare_output
21             read_file
22             );
23            
24             our $VERSION = 0.01;
25            
26 19     19   14670 use Getopt::Long;
  19         230864  
  19         126  
27 19     19   3235 use Test::More;
  19         58  
  19         161  
28 19     19   15875 use Test::Differences;
  19         306313  
  19         1519  
29 19     19   8410 use Data::Dump qw(dump);
  19         81252  
  19         1270  
30 19     19   165 use File::Spec;
  19         50  
  19         464  
31 19     19   12541 use File::Temp;
  19         303319  
  19         1490  
32 19     19   169 use Cwd;
  19         49  
  19         26829  
33            
34             =head2 ->get_test_attr()
35            
36             Read C<@ARGV> to generate a C<$test_attr> HashRef, that is returned for (possibly modified) use in ->compare_output().
37            
38             =cut
39             sub get_test_attr {
40 17     17 1 247 my ($record, $compare, $complete);
41 17         0 my $dump;
42 17         0 my $verbose;
43 17         0 my $help;
44 17         131 GetOptions(
45             'record' => \$record,
46             'compare' => \$compare,
47             'complete' => \$complete,
48             'dump' => \$dump,
49             'verbose!' => \$verbose,
50             'help|h|?!' => \$help,
51             );
52 17 50       8053 if ($help) {
53 0         0 say <<"EOH";
54             usage: $0 [--record|--compare|--complete] [--dump] [--[no-]verbose] [--help|-h|-?] [-- ]
55            
56             --record
57             Run the test script with the system shell and store the output.
58             Only available under Win32.
59             --compare
60             Run the test script with runbat and compare the output.
61             This is the default.
62             --complete
63             First --record, then --compare.
64            
65             --dump Dump the outputs before comparison.
66            
67             --verbose
68             Be verbose.
69            
70             --help
71             This help.
72             EOH
73 0         0 exit 1;
74             }
75 17 50       98 my $mode = $complete ? 'record,compare'
    50          
76             : $record ? 'record'
77             : 'compare';
78             return {
79 17         135 mode => $mode,
80             dump => $dump,
81             argv => [ @ARGV ],
82             verbose => $verbose,
83             number => 0,
84             };
85             }
86            
87             # TODO: use a prepackaged implementation
88             sub quote_argument {
89 234     234 0 432 $_ = shift;
90 234         566 s/([\\\"])/\\$1/gi;
91 234 100       963 return /[\s\\\"]/ ? "\"$_\"" : $_;
92             }
93            
94             # Some archivers may restore test files with the wrong newlines, so ship the
95             # files in hex and decode before use.
96             sub decode_file {
97 117     117 0 436 my ($filename) = @_;
98 117         409 my $hexname = "$filename.hex";
99 117 100 100     3827 if (-e $hexname && !-e $filename) {
100 69 50       2340 open my $in, '<', $hexname or die "$hexname: $!";
101 69 50       33463 open my $out, '>:raw', $filename or die "$filename: $!";
102 69         482 local $/;
103 69         2180 print $out pack 'H*', <$in>;
104 69         2426 close $_ for $in, $out;
105            
106             # file may need some time to be visible on network filesystems
107 69         1745 while (!-e $filename) {
108 0         0 say STDERR "Waiting for creation of $filename...";
109 0         0 sleep 1;
110             }
111             }
112             }
113            
114             =head2 read_file($filename)
115            
116             Read C<$filename> in the same way as compare_output() reads it (including possible decoding) and return the content as a binary string.
117             =cut
118             sub read_file {
119 58     58 1 261 my ($filename) = @_;
120 58         358 decode_file $filename;
121 58 50       1693 open my $fh, '<:raw', $filename
122             or die "open '$filename': $!";
123 58         295 local $/;
124 58         1546 return scalar <$fh>;
125             }
126            
127            
128             sub filter_log {
129 49     49 1 327 my ($type, $stream, $attr, $content) = @_;
130            
131             # system and emulated CMD may have different CWDs (virtual mount
132             # points) and they may be differently slashed.
133             # the tested program maps t/ to B:/test/, but we have to translate
134             # the output of CMD.EXE, where at least the absolute paths are
135             # easily matched
136 49 50       261 $type eq 'cmd' and
137             $content =~
138             s([A-Za-z]\:[\\\/][^\:]*\bBatch-Interpreter.*[\\\/]t\b)
139             (B:\\test)gm;
140            
141             $content =~ s/\r//g
142 49 100       264 if $attr->{unix_nl};
143            
144             $content = $attr->{filter_log}->($type, $stream, $content)
145 49 100       234 if $attr->{filter_log};
146            
147 49         316 return $content;
148             }
149            
150             sub read_content {
151 49     49 0 203 my ($fh) = @_;
152 49 50       459 seek $fh, 0, SEEK_SET
153             or die $!;
154 49         191 binmode $fh;
155 49         410 local $/;
156 49         1950 return scalar <$fh>;
157             }
158            
159             sub store_content {
160 49     49 0 216 my ($output, $content) = @_;
161            
162 49 50       261 if ('SCALAR' eq ref $output) {
163 49         267 $$output = $content;
164             } else {
165 0         0 open my $out, '>:raw', $output;
166 0         0 print $out $content;
167             }
168             }
169            
170             sub run_redirected {
171 25     25 0 141 my ($type, $attr, @commandline) = @_;
172 25         130 my $cmd = join ' ', map quote_argument($_), @commandline;
173            
174             say STDERR "COMMAND: $cmd"
175 25 50       125 if $attr->{verbose};
176            
177 25         107 my ($old_stdout, $old_stderr);
178 25         0 my ($stdout, $stderr);
179 25 50       105 if ($attr->{stdout}) {
180 25         255 $stdout = File::Temp->new();
181 25         15870 open $old_stdout, '>&', \*STDOUT;
182 25         188 open \*STDOUT, '>&', $stdout;
183             }
184 25 100       804 if ($attr->{stderr}) {
185 24         336 $stderr = File::Temp->new();
186 24         9115 open $old_stderr, '>&', \*STDERR;
187 24         149 open \*STDERR, '>&', $stderr;
188             }
189            
190 25         3620851 system $cmd;
191            
192 25 100       1008 if ($stderr) {
193 24         1301 open \*STDERR, '>&', $old_stderr;
194             store_content $attr->{stderr},
195 24         345 filter_log $type, 'stderr', $attr,
196             read_content $stderr;
197             }
198 25 50       177 if ($stdout) {
199 25         758 open \*STDOUT, '>&', $old_stdout;
200             store_content $attr->{stdout},
201 25         178 filter_log $type, 'stdout', $attr,
202             read_content $stdout;
203             }
204            
205 25         375 return !($? & 127);
206             }
207            
208             =head2 compare_output($test_attr, $subtest_name, @commandline)
209            
210             Compare the output of running C<@commandline> with CMD.EXE and Batch::Interpreter.
211            
212             C<$subtest_name> can be given as undef, in which case the subtests are simply numbered.
213            
214             Attributes that can be added to C<$test_attr> are:
215            
216             =over
217            
218             =item verbose
219            
220             Be verbose.
221            
222             =item filter_log
223            
224             A callback CodeRef, that is used to filter the output, which is called as
225            
226             $content = $attr->{filter_log}->($type, $stream, $content)
227            
228             C<$type> can be 'cmd' or 'lib', C<$stream> can be 'stdout' or 'stderr'.
229            
230             =item unix_nl
231            
232             Normalize the data to unix newlines before comparison. Note 'before comparison' includes the point in time the CMD.EXE output is saved.
233            
234             =item skip_stderr
235            
236             Only STDOUT is compared, not STDERR.
237            
238             =item in_dir
239            
240             The command is run in the given directory.
241            
242             =back
243            
244             =cut
245             sub compare_output {
246 25     25 1 290 my ($test_attr, $subtest_name, @commandline) = @_;
247            
248 25         73 my $runbat = 'bin/runbat';
249 25         66 my $lib = 'lib';
250 25         65 my $t = 't';
251            
252 25         56 my $olddir;
253 25 100       122 if ($test_attr->{in_dir}) {
254 1         47 $_ = File::Spec->rel2abs($_) for $runbat, $lib, $t;
255            
256 1         4 $olddir = getcwd;
257 1         12 chdir $test_attr->{in_dir};
258             }
259            
260             decode_file $_
261 25         143 for @commandline;
262            
263 25         114 my $mode = $test_attr->{mode};
264            
265 25         995 my $basename = (File::Spec->splitpath($0))[2];
266             #$basename =~ s/^\d+_//;
267 25         240 $basename =~ s/\.t$//;
268            
269 25   33     370 $subtest_name //= sprintf '%02d', $test_attr->{number}++;
270            
271 25         103 my $base = join '_', $basename, $subtest_name;
272 25         207 my $digest = "$base: @commandline";
273            
274 25         126 my @stream = qw(stdout stderr);
275             $test_attr->{skip_stderr}
276 25 100       116 and @stream = grep $_ ne 'stderr', @stream;
277            
278 25         190 my %attr = map +($_ => \(my $data)), @stream;
279 25         231 my %cmd_attr = map +($_ => "$t/$base.cmd.$_"), @stream;
280            
281 25 50       145 if ($mode =~ /\brecord\b/) {
282 0 0       0 $^O =~ /Win/ or die "record mode only possible under windows";
283            
284             # run with CMD
285             my $ok = run_redirected('cmd', { %$test_attr, %cmd_attr },
286 0         0 map { y/\//\\/; $_ } @commandline
  0         0  
  0         0  
287             );
288             # suppress tests for complete mode
289 0 0       0 if ($mode eq 'record') {
290 0         0 ok $ok, "record $digest";
291 0         0 pass "no comparison: $digest";
292             }
293             }
294            
295 25 50       174 if ($mode =~ /\bcompare\b/) {
296             ok run_redirected('lib', { %$test_attr, %attr },
297             $^X, '-I', $lib,
298             $runbat,
299             '--mount', "B:/test=$t",
300 25   50     234 @{$test_attr->{argv} // []},
  25         183  
301             '--',
302             @commandline
303             ), "run $digest";
304            
305 25         41751 my %result = map +($_ => ${$attr{$_}}), @stream;
  49         362  
306 25         222 my %cmd_result = map +($_ => read_file $cmd_attr{$_}), @stream;
307            
308             $test_attr->{dump} and
309 25 50       181 dump [\%result, \%cmd_attr, \%cmd_result];
310            
311             eq_or_diff $result{$_}, $cmd_result{$_}, "result($_) $digest"
312 25         477 for @stream;
313            
314 25         78954 unlink @attr{@stream};
315             }
316            
317 25 100       1362 defined $olddir and chdir $olddir;
318             }
319            
320             1;
321            
322             __END__