File Coverage

blib/lib/Test/Script.pm
Criterion Covered Total %
statement 245 250 98.0
branch 89 104 85.5
condition 28 50 56.0
subroutine 47 47 100.0
pod 21 23 91.3
total 430 474 90.7


line stmt bran cond sub pod time code
1             package Test::Script;
2              
3             # ABSTRACT: Basic cross-platform tests for scripts
4             our $VERSION = '1.28_01'; # TRIAL VERSION
5             $VERSION = eval $VERSION; ## no critic (BuiltinFunctions::ProhibitStringyEval)
6              
7              
8 12     12   2597488 use 5.008001;
  12         103  
9 12     12   71 use strict;
  12         22  
  12         275  
10 12     12   71 use warnings;
  12         20  
  12         480  
11 12     12   83 use Carp qw( croak );
  12         23  
  12         698  
12 12     12   80 use Exporter;
  12         18  
  12         457  
13 12     12   69 use File::Spec;
  12         22  
  12         386  
14 12     12   78 use File::Spec::Unix;
  12         21  
  12         581  
15 12     12   5740 use Probe::Perl;
  12         14671  
  12         432  
16 12     12   6673 use Capture::Tiny qw( capture );
  12         328269  
  12         863  
17 12     12   137 use Test2::API qw( context );
  12         31  
  12         565  
18 12     12   84 use File::Temp qw( tempdir );
  12         26  
  12         471  
19 12     12   67 use IO::Handle;
  12         23  
  12         40738  
20              
21             our @ISA = 'Exporter';
22             our @EXPORT = qw{
23             script_compiles
24             script_compiles_ok
25             script_fails
26             script_runs
27             script_stdout_is
28             script_stdout_isnt
29             script_stdout_like
30             script_stdout_unlike
31             script_stderr_is
32             script_stderr_isnt
33             script_stderr_like
34             script_stderr_unlike
35             program_fails
36             program_runs
37             program_stdout_is
38             program_stdout_isnt
39             program_stdout_like
40             program_stdout_unlike
41             program_stderr_is
42             program_stderr_isnt
43             program_stderr_like
44             program_stderr_unlike
45             };
46              
47             sub import {
48 13     13   51305 my $self = shift;
49 13         39 my $pack = caller;
50 13 100 66     118 if(defined $_[0] && $_[0] =~ /^(?:no_plan|skip_all|tests)$/)
51             {
52             # icky back compat.
53             # do not use.
54 3         13 my $ctx = context();
55 3 100       628 if($_[0] eq 'tests')
    100          
56             {
57 1         8 $ctx->plan($_[1]);
58             }
59             elsif($_[0] eq 'skip_all')
60             {
61 1         19 $ctx->plan(0, 'SKIP', $_[1]);
62             }
63             else
64             {
65 1         5 $ctx->hub->plan('NO PLAN');
66             }
67 2         406 $ctx->release;
68             }
69 12         135 foreach ( @EXPORT ) {
70 264         20611 $self->export_to_level(1, $self, $_);
71             }
72             }
73              
74             my $perl = undef;
75              
76             sub perl () {
77 26 100   26 0 234 $perl or
78             $perl = Probe::Perl->find_perl_interpreter;
79             }
80              
81             sub path ($) {
82 26     26 0 67 my $path = shift;
83 26 50       101 unless ( defined $path ) {
84 0         0 croak("Did not provide a script name");
85             }
86 26 50       994 if ( File::Spec::Unix->file_name_is_absolute($path) ) {
87 0         0 croak("Script name must be relative");
88             }
89             File::Spec->catfile(
90 26         1061 File::Spec->curdir,
91             split /\//, $path
92             );
93             }
94              
95             #####################################################################
96             # Test Functions for Scripts
97              
98              
99             sub script_compiles {
100 8     8 1 119244 my $args = _script(shift);
101 8         21 my $unix = shift @$args;
102 8         39 my $path = path( $unix );
103 8         48 my $pargs = _perl_args($path);
104 8         80 my $dir = _preload_module();
105 8         88 my $cmd = [ perl, @$pargs, "-I$dir", '-M__TEST_SCRIPT__', '-c', $path, @$args ];
106 8     8   682 my ($stdout, $stderr) = capture { system(@$cmd) };
  8         105422  
107 8         15859 my $error = $@;
108 8 100       133 my $exit = $? ? ($? >> 8) : 0;
109 8 100       60 my $signal = $? ? ($? & 127) : 0;
110 8   66     372 my $ok = !! (
111             $error eq '' and $exit == 0 and $signal == 0 and $stderr =~ /syntax OK\s+\z/si
112             );
113              
114 8         310 my $ctx = context();
115 8   66     3563 $ctx->ok( $ok, $_[0] || "Script $unix compiles" );
116 8 100       5730 $ctx->diag( "$exit - $stderr" ) unless $ok;
117 8 50       854 $ctx->diag( "exception: $error" ) if $error;
118 8 100       54 $ctx->diag( "signal: $signal" ) if $signal;
119 8         385 $ctx->release;
120              
121 8         422 return $ok;
122             }
123              
124             # this is noticeably slower for long @INC lists (sometimes present in cpantesters
125             # boxes) than the previous implementation, which added a -I for every element in
126             # @INC. (also slower for more reasonable @INCs, but not noticeably). But it is
127             # safer as very long argument lists can break calls to system
128             sub _preload_module
129             {
130 26     26   267 my @opts = ( '.test-script-XXXXXXXX', CLEANUP => 1);
131 26 50       661 if(-w File::Spec->curdir)
132 26         172 { push @opts, DIR => File::Spec->curdir }
133             else
134 0         0 { push @opts, DIR => File::Spec->tmpdir }
135 26         318 my $dir = tempdir(@opts);
136 26         16496 $dir = File::Spec->rel2abs($dir);
137             # this is hopefully a pm file that nobody would use
138 26         240 my $filename = File::Spec->catfile($dir, '__TEST_SCRIPT__.pm');
139 26         142 my $fh;
140 26 50       2039 open($fh, '>', $filename)
141             || die "unable to open $filename: $!";
142             print($fh 'unshift @INC, ',
143             join ',',
144             # quotemeta is overkill, but it will make sure that characters
145             # like " are quoted
146 286         1054 map { '"' . quotemeta($_) . '"' }
147 26 50       176 grep { ! ref } @INC)
  286         1990  
148             || die "unable to write $filename: $!";
149 26 50       1173 close($fh) || die "unable to close $filename: $!";;
150 26         246 $dir;
151             }
152              
153              
154             my $stdout;
155             my $stderr;
156              
157             sub script_runs {
158 18     18 1 115138 my $args = _script(shift);
159 18         101 my $opt = _options(\$stdout, \$stderr, 1, \@_);
160 18         77 my $unix = shift @$args;
161 18         119 my $path = path( $unix );
162 18         58 my $pargs = [ @{ _perl_args($path) }, @{ $opt->{interpreter_options} } ];
  18         47  
  18         86  
163 18         136 my $dir = _preload_module();
164 18         141 my $cmd = [ perl, @$pargs, "-I$dir", '-M__TEST_SCRIPT__', $path, @$args ];
165 18         111 $stdout = '';
166 18         42 $stderr = '';
167              
168 18 100       84 unshift @_, "Script $unix runs" unless $_[0];
169 18         51 unshift @_, $cmd, $opt;
170 18         223 goto &_run;
171             }
172              
173              
174             sub script_fails {
175 3     3 1 51499 my $args = _script(shift);
176 3         15 my ( $opt, $testname ) = @_;
177 3 100       31 $testname = "Script $args->[0] fails" unless defined $testname;
178             die "exit is a mandatory option for script_fails"
179 3 100       8 unless eval{ defined $opt->{exit} };
  3         24  
180 2         9 my $ctx = context();
181 2         196 return release $ctx, script_runs( $args, $opt, $testname );
182             }
183              
184             # Run a script or program and provide test events corresponding to the results.
185             # Call as _run(\@cmd, \%opt, "Test description")
186             sub _run {
187 36     36   151 my ($cmd, $opt, $description) = @_;
188              
189 36 100       140 if($opt->{stdin})
190             {
191 4         21 my $filename;
192              
193 4 100       43 if(ref($opt->{stdin}) eq 'SCALAR')
    50          
194             {
195 2         36 $filename = File::Spec->catfile(
196             tempdir(CLEANUP => 1),
197             'stdin.txt',
198             );
199 2         1307 my $tmp;
200 2 50       190 open($tmp, '>', $filename) || die "unable to write to $filename";
201 2         16 print $tmp ${ $opt->{stdin} };
  2         61  
202 2         86 close $tmp;
203             }
204             elsif(ref($opt->{stdin}) eq '')
205             {
206 2         49 $filename = $opt->{stdin};
207             }
208             else
209             {
210 0         0 croak("stdin MUST be either a scalar reference or a string filename");
211             }
212              
213 4         26 my $fh;
214 4 50       232 open($fh, '<', $filename) || die "unable to open $filename $!";
215 4 50       162 STDIN->fdopen( $fh, 'r' ) or die "unable to reopen stdin to $filename $!";
216             }
217              
218 36     36   3627 (${$opt->{stdout}}, ${$opt->{stderr}}) = capture { system(@$cmd) };
  36         60707  
  36         549  
  36         373303  
219              
220 36         1136 my $error = $@;
221 36 100       322 my $exit = $? ? ($? >> 8) : 0;
222 36 100       314 my $signal = $? ? ($? & 127) : 0;
223 36   100     1066 my $ok = !! ( $error eq '' and $exit == $opt->{exit} and $signal == $opt->{signal} );
224              
225 36         789 my $ctx = context();
226 36         46195 $ctx->ok( $ok, $description );
227 36 100       19144 $ctx->diag( "$exit - " . ${$opt->{stderr}} ) unless $ok;
  12         146  
228 36 50       2747 $ctx->diag( "exception: $error" ) if $error;
229 36 100       253 $ctx->diag( "signal: $signal" ) unless $signal == $opt->{signal};
230 36         1133 $ctx->release;
231              
232 36         2072 return $ok;
233             }
234              
235             sub _like
236             {
237 36     36   151 my($text, $pattern, $regex, $not, $name) = @_;
238              
239 36 100       316 my $ok = $regex ? $text =~ $pattern : $text eq $pattern;
240 36 100       151 $ok = !$ok if $not;
241              
242 36         159 my $ctx = context;
243 36         3414 $ctx->ok( $ok, $name );
244 36 100       9046 unless($ok) {
245 16         55 $ctx->diag( "The output" );
246 16         2934 $ctx->diag( " $_") for split /\n/, $text;
247 16 100       5886 $ctx->diag( $not ? "does match" : "does not match" );
248 16 100       2915 if($regex) {
249 8         75 $ctx->diag( " $pattern" );
250             } else {
251 8         49 $ctx->diag( " $_" ) for split /\n/, $pattern;
252             }
253             }
254 36         3710 $ctx->release;
255              
256 36         1005 $ok;
257             }
258              
259              
260             sub script_stdout_is
261             {
262 2     2 1 2484 my($pattern, $name) = @_;
263 2   50     23 @_ = ($stdout, $pattern, 0, 0, $name || 'stdout matches' );
264 2         28 goto &_like;
265             }
266              
267              
268             sub script_stdout_isnt
269             {
270 2     2 1 45996 my($pattern, $name) = @_;
271 2   50     23 @_ = ($stdout, $pattern, 0, 1, $name || 'stdout does not match' );
272 2         18 goto &_like;
273             }
274              
275              
276             sub script_stdout_like
277             {
278 4     4 1 7136 my($pattern, $name) = @_;
279 4   50     91 @_ = ($stdout, $pattern, 1, 0, $name || 'stdout matches' );
280 4         44 goto &_like;
281             }
282              
283              
284             sub script_stdout_unlike
285             {
286 2     2 1 6494 my($pattern, $name) = @_;
287 2   50     23 @_ = ($stdout, $pattern, 1, 1, $name || 'stdout does not match' );
288 2         10 goto &_like;
289             }
290              
291              
292             sub script_stderr_is
293             {
294 2     2 1 2544 my($pattern, $name) = @_;
295 2   50     27 @_ = ($stderr, $pattern, 0, 0, $name || 'stderr matches' );
296 2         17 goto &_like;
297             }
298              
299              
300             sub script_stderr_isnt
301             {
302 2     2 1 45858 my($pattern, $name) = @_;
303 2   50     29 @_ = ($stderr, $pattern, 0, 1, $name || 'stderr does not match' );
304 2         11 goto &_like;
305             }
306              
307              
308             sub script_stderr_like
309             {
310 2     2 1 6962 my($pattern, $name) = @_;
311 2   50     21 @_ = ($stderr, $pattern, 1, 0, $name || 'stderr matches' );
312 2         9 goto &_like;
313             }
314              
315              
316             sub script_stderr_unlike
317             {
318 2     2 1 6472 my($pattern, $name) = @_;
319 2   50     27 @_ = ($stderr, $pattern, 1, 1, $name || 'stderr does not match' );
320 2         9 goto &_like;
321             }
322              
323             #####################################################################
324             # Test Functions for Programs
325              
326             my $program_stdout;
327             my $program_stderr;
328              
329              
330             sub program_runs {
331 19     19 1 107851 my $cmd = _script(shift);
332 19         98 my $opt = _options(\$program_stdout, \$program_stderr, 0, \@_);
333 18         65 $program_stdout = '';
334 18         49 $program_stderr = '';
335              
336 18 100       84 unshift @_, "Program $$cmd[0] runs" unless $_[0];
337 18         49 unshift @_, $cmd, $opt;
338 18         153 goto &_run;
339             }
340              
341              
342             sub program_fails {
343 6     6 1 10131 my $cmd = _script(shift);
344 6         31 my ( $opt, $testname ) = @_;
345 6 100       33 $testname = 'program_fails' unless defined $testname;
346             die "exit is a mandatory option for program_fails"
347 6 100       18 unless eval{ defined $opt->{exit} };
  6         39  
348 5         51 my $ctx = context();
349 5         547 return release $ctx, program_runs( $cmd, $opt, $testname );
350             }
351              
352              
353             sub program_stdout_is
354             {
355 2     2 1 2844 my($pattern, $name) = @_;
356 2   50     30 @_ = ($program_stdout, $pattern, 0, 0, $name || 'stdout matches' );
357 2         20 goto &_like;
358             }
359              
360              
361             sub program_stdout_isnt
362             {
363 2     2 1 49093 my($pattern, $name) = @_;
364 2   50     55 @_ = ($program_stdout, $pattern, 0, 1, $name || 'stdout does not match' );
365 2         12 goto &_like;
366             }
367              
368              
369             sub program_stdout_like
370             {
371 4     4 1 7624 my($pattern, $name) = @_;
372 4   50     99 @_ = ($program_stdout, $pattern, 1, 0, $name || 'stdout matches' );
373 4         42 goto &_like;
374             }
375              
376              
377             sub program_stdout_unlike
378             {
379 2     2 1 6935 my($pattern, $name) = @_;
380 2   50     23 @_ = ($program_stdout, $pattern, 1, 1, $name || 'stdout does not match' );
381 2         11 goto &_like;
382             }
383              
384              
385             sub program_stderr_is
386             {
387 2     2 1 3250 my($pattern, $name) = @_;
388 2   50     30 @_ = ($program_stderr, $pattern, 0, 0, $name || 'stderr matches' );
389 2         15 goto &_like;
390             }
391              
392              
393             sub program_stderr_isnt
394             {
395 2     2 1 46508 my($pattern, $name) = @_;
396 2   50     38 @_ = ($program_stderr, $pattern, 0, 1, $name || 'stderr does not match' );
397 2         11 goto &_like;
398             }
399              
400              
401             sub program_stderr_like
402             {
403 2     2 1 7181 my($pattern, $name) = @_;
404 2   50     20 @_ = ($program_stderr, $pattern, 1, 0, $name || 'stderr matches' );
405 2         10 goto &_like;
406             }
407              
408              
409             sub program_stderr_unlike
410             {
411 2     2 1 6738 my($pattern, $name) = @_;
412 2   50     14 @_ = ($program_stderr, $pattern, 1, 1, $name || 'stderr does not match' );
413 2         14 goto &_like;
414             }
415              
416              
417             ######################################################################
418             # Support Functions
419              
420             # Script params must be either a simple non-null string with the script
421             # name, or an array reference with one or more non-null strings.
422             sub _script {
423 55     55   6209 my $in = shift;
424 55 100       243 if ( defined _STRING($in) ) {
425 26         130 return [ $in ];
426             }
427 29 50       94 if ( _ARRAY($in) ) {
428 29 50       74 unless ( scalar grep { not defined _STRING($_) } @$in ) {
  66         123  
429 29         101 return [ @$in ];
430             }
431             }
432 0         0 croak("Invalid command parameter");
433             }
434              
435             # Determine any extra arguments that need to be passed into Perl.
436             # ATM this is just -T.
437             sub _perl_args {
438 26     26   93 my($script) = @_;
439 26         47 my $fh;
440 26         69 my $first_line = '';
441 26 100       2114 if(open($fh, '<', $script))
442             {
443 24         723 $first_line = <$fh>;
444 24         430 close $fh;
445             }
446 26 100       729 (grep /^-.*T/, split /\s+/, $first_line) ? ['-T'] : [];
447             }
448              
449             # Inline some basic Params::Util functions
450              
451             sub _options {
452 37     37   95 my $ref_stdout = shift;
453 37         72 my $ref_stderr = shift;
454 37         61 my $permit_interpreter_options = shift;
455 37 100       172 my %options = ref($_[0]->[0]) eq 'HASH' ? %{ shift @{ $_[0] } }: ();
  20         43  
  20         117  
456              
457 37 100       146 $options{exit} = 0 unless defined $options{exit};
458 37 100       130 $options{signal} = 0 unless defined $options{signal};
459 37         219 my $stdin = '';
460             #$options{stdin} = \$stdin unless defined $options{stdin};
461 37 100       143 $options{stdout} = $ref_stdout unless defined $options{stdout};
462 37 100       130 $options{stderr} = $ref_stderr unless defined $options{stderr};
463              
464 37 100       98 if(defined $options{interpreter_options})
465             {
466 3 100       56 die "interpreter_options not supported" unless $permit_interpreter_options;
467 2 100       24 unless(ref $options{interpreter_options} eq 'ARRAY')
468             {
469 1         1393 require Text::ParseWords;
470 1         2267 $options{interpreter_options} = [ Text::ParseWords::shellwords($options{interpreter_options}) ];
471             }
472             }
473             else
474             {
475 34         90 $options{interpreter_options} = [];
476             }
477              
478 36         440 \%options;
479             }
480              
481             sub _ARRAY ($) {
482 29 50 33 29   99 (ref $_[0] eq 'ARRAY' and @{$_[0]}) ? $_[0] : undef;
483             }
484              
485             sub _STRING ($) {
486 121 100 66 121   1268 (defined $_[0] and ! ref $_[0] and length($_[0])) ? $_[0] : undef;
487             }
488              
489             BEGIN {
490             # Alias to old name
491 12     12   518 *script_compiles_ok = *script_compiles;
492             }
493              
494             1;
495              
496             __END__