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