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.271'; # TRIAL VERSION
5              
6              
7 12     12   2632652 use 5.008001;
  12         110  
8 12     12   69 use strict;
  12         22  
  12         264  
9 12     12   58 use warnings;
  12         32  
  12         462  
10 12     12   87 use Carp qw( croak );
  12         37  
  12         722  
11 12     12   74 use Exporter;
  12         27  
  12         607  
12 12     12   75 use File::Spec;
  12         20  
  12         356  
13 12     12   68 use File::Spec::Unix;
  12         33  
  12         502  
14 12     12   6223 use Probe::Perl;
  12         15201  
  12         422  
15 12     12   6843 use Capture::Tiny qw( capture );
  12         336119  
  12         910  
16 12     12   116 use Test2::API qw( context );
  12         38  
  12         592  
17 12     12   74 use File::Temp qw( tempdir );
  12         28  
  12         462  
18 12     12   69 use IO::Handle;
  12         35  
  12         41376  
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   52893 my $self = shift;
48 13         40 my $pack = caller;
49 13 100 66     124 if(defined $_[0] && $_[0] =~ /^(?:no_plan|skip_all|tests)$/)
50             {
51             # icky back compat.
52             # do not use.
53 3         13 my $ctx = context();
54 3 100       266 if($_[0] eq 'tests')
    100          
55             {
56 1         7 $ctx->plan($_[1]);
57             }
58             elsif($_[0] eq 'skip_all')
59             {
60 1         8 $ctx->plan(0, 'SKIP', $_[1]);
61             }
62             else
63             {
64 1         6 $ctx->hub->plan('NO PLAN');
65             }
66 2         415 $ctx->release;
67             }
68 12         129 foreach ( @EXPORT ) {
69 264         21076 $self->export_to_level(1, $self, $_);
70             }
71             }
72              
73             my $perl = undef;
74              
75             sub perl () {
76 26 100   26 0 215 $perl or
77             $perl = Probe::Perl->find_perl_interpreter;
78             }
79              
80             sub path ($) {
81 26     26 0 65 my $path = shift;
82 26 50       88 unless ( defined $path ) {
83 0         0 croak("Did not provide a script name");
84             }
85 26 50       875 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         915 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 113727 my $args = _script(shift);
100 8         23 my $unix = shift @$args;
101 8         27 my $path = path( $unix );
102 8         39 my $pargs = _perl_args($path);
103 8         69 my $dir = _preload_module();
104 8         69 my $cmd = [ perl, @$pargs, "-I$dir", '-M__TEST_SCRIPT__', '-c', $path, @$args ];
105 8     8   531 my ($stdout, $stderr) = capture { system(@$cmd) };
  8         96209  
106 8         13377 my $error = $@;
107 8 100       93 my $exit = $? ? ($? >> 8) : 0;
108 8 100       53 my $signal = $? ? ($? & 127) : 0;
109 8   66     315 my $ok = !! (
110             $error eq '' and $exit == 0 and $signal == 0 and $stderr =~ /syntax OK\s+\z/si
111             );
112              
113 8         222 my $ctx = context();
114 8   66     3352 $ctx->ok( $ok, $_[0] || "Script $unix compiles" );
115 8 100       5150 $ctx->diag( "$exit - $stderr" ) unless $ok;
116 8 50       1182 $ctx->diag( "exception: $error" ) if $error;
117 8 100       54 $ctx->diag( "signal: $signal" ) if $signal;
118 8         391 $ctx->release;
119              
120 8         332 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   259 my @opts = ( '.test-script-XXXXXXXX', CLEANUP => 1);
130 26 50       607 if(-w File::Spec->curdir)
131 26         165 { push @opts, DIR => File::Spec->curdir }
132             else
133 0         0 { push @opts, DIR => File::Spec->tmpdir }
134 26         254 my $dir = tempdir(@opts);
135 26         16027 $dir = File::Spec->rel2abs($dir);
136             # this is hopefully a pm file that nobody would use
137 26         225 my $filename = File::Spec->catfile($dir, '__TEST_SCRIPT__.pm');
138 26         99 my $fh;
139 26 50       1789 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         958 map { '"' . quotemeta($_) . '"' }
146 26 50       239 grep { ! ref } @INC)
  286         1561  
147             || die "unable to write $filename: $!";
148 26 50       1062 close($fh) || die "unable to close $filename: $!";;
149 26         228 $dir;
150             }
151              
152              
153             my $stdout;
154             my $stderr;
155              
156             sub script_runs {
157 18     18 1 106606 my $args = _script(shift);
158 18         104 my $opt = _options(\$stdout, \$stderr, 1, \@_);
159 18         55 my $unix = shift @$args;
160 18         144 my $path = path( $unix );
161 18         51 my $pargs = [ @{ _perl_args($path) }, @{ $opt->{interpreter_options} } ];
  18         61  
  18         79  
162 18         130 my $dir = _preload_module();
163 18         154 my $cmd = [ perl, @$pargs, "-I$dir", '-M__TEST_SCRIPT__', $path, @$args ];
164 18         114 $stdout = '';
165 18         31 $stderr = '';
166              
167 18 100       79 unshift @_, "Script $unix runs" unless $_[0];
168 18         55 unshift @_, $cmd, $opt;
169 18         217 goto &_run;
170             }
171              
172              
173             sub script_fails {
174 3     3 1 50784 my $args = _script(shift);
175 3         14 my ( $opt, $testname ) = @_;
176 3 100       17 $testname = "Script $args->[0] fails" unless defined $testname;
177             die "exit is a mandatory option for script_fails"
178 3 100       5 unless eval{ defined $opt->{exit} };
  3         25  
179 2         8 my $ctx = context();
180 2         190 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   148 my ($cmd, $opt, $description) = @_;
187              
188 36 100       145 if($opt->{stdin})
189             {
190 4         15 my $filename;
191              
192 4 100       46 if(ref($opt->{stdin}) eq 'SCALAR')
    50          
193             {
194 2         36 $filename = File::Spec->catfile(
195             tempdir(CLEANUP => 1),
196             'stdin.txt',
197             );
198 2         1311 my $tmp;
199 2 50       183 open($tmp, '>', $filename) || die "unable to write to $filename";
200 2         8 print $tmp ${ $opt->{stdin} };
  2         26  
201 2         107 close $tmp;
202             }
203             elsif(ref($opt->{stdin}) eq '')
204             {
205 2         13 $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         22 my $fh;
213 4 50       230 open($fh, '<', $filename) || die "unable to open $filename $!";
214 4 50       152 STDIN->fdopen( $fh, 'r' ) or die "unable to reopen stdin to $filename $!";
215             }
216              
217 36     36   3548 (${$opt->{stdout}}, ${$opt->{stderr}}) = capture { system(@$cmd) };
  36         57671  
  36         589  
  36         360522  
218              
219 36         1018 my $error = $@;
220 36 100       302 my $exit = $? ? ($? >> 8) : 0;
221 36 100       320 my $signal = $? ? ($? & 127) : 0;
222 36   100     996 my $ok = !! ( $error eq '' and $exit == $opt->{exit} and $signal == $opt->{signal} );
223              
224 36         778 my $ctx = context();
225 36         47576 $ctx->ok( $ok, $description );
226 36 100       18849 $ctx->diag( "$exit - " . ${$opt->{stderr}} ) unless $ok;
  12         152  
227 36 50       2576 $ctx->diag( "exception: $error" ) if $error;
228 36 100       275 $ctx->diag( "signal: $signal" ) unless $signal == $opt->{signal};
229 36         1231 $ctx->release;
230              
231 36         1668 return $ok;
232             }
233              
234             sub _like
235             {
236 36     36   148 my($text, $pattern, $regex, $not, $name) = @_;
237              
238 36 100       330 my $ok = $regex ? $text =~ $pattern : $text eq $pattern;
239 36 100       138 $ok = !$ok if $not;
240              
241 36         165 my $ctx = context;
242 36         3271 $ctx->ok( $ok, $name );
243 36 100       9637 unless($ok) {
244 16         58 $ctx->diag( "The output" );
245 16         2972 $ctx->diag( " $_") for split /\n/, $text;
246 16 100       5870 $ctx->diag( $not ? "does match" : "does not match" );
247 16 100       3016 if($regex) {
248 8         66 $ctx->diag( " $pattern" );
249             } else {
250 8         58 $ctx->diag( " $_" ) for split /\n/, $pattern;
251             }
252             }
253 36         3996 $ctx->release;
254              
255 36         1131 $ok;
256             }
257              
258              
259             sub script_stdout_is
260             {
261 2     2 1 2647 my($pattern, $name) = @_;
262 2   50     22 @_ = ($stdout, $pattern, 0, 0, $name || 'stdout matches' );
263 2         19 goto &_like;
264             }
265              
266              
267             sub script_stdout_isnt
268             {
269 2     2 1 46494 my($pattern, $name) = @_;
270 2   50     22 @_ = ($stdout, $pattern, 0, 1, $name || 'stdout does not match' );
271 2         16 goto &_like;
272             }
273              
274              
275             sub script_stdout_like
276             {
277 4     4 1 7119 my($pattern, $name) = @_;
278 4   50     59 @_ = ($stdout, $pattern, 1, 0, $name || 'stdout matches' );
279 4         48 goto &_like;
280             }
281              
282              
283             sub script_stdout_unlike
284             {
285 2     2 1 6580 my($pattern, $name) = @_;
286 2   50     23 @_ = ($stdout, $pattern, 1, 1, $name || 'stdout does not match' );
287 2         10 goto &_like;
288             }
289              
290              
291             sub script_stderr_is
292             {
293 2     2 1 2745 my($pattern, $name) = @_;
294 2   50     38 @_ = ($stderr, $pattern, 0, 0, $name || 'stderr matches' );
295 2         18 goto &_like;
296             }
297              
298              
299             sub script_stderr_isnt
300             {
301 2     2 1 45330 my($pattern, $name) = @_;
302 2   50     22 @_ = ($stderr, $pattern, 0, 1, $name || 'stderr does not match' );
303 2         9 goto &_like;
304             }
305              
306              
307             sub script_stderr_like
308             {
309 2     2 1 6877 my($pattern, $name) = @_;
310 2   50     25 @_ = ($stderr, $pattern, 1, 0, $name || 'stderr matches' );
311 2         9 goto &_like;
312             }
313              
314              
315             sub script_stderr_unlike
316             {
317 2     2 1 6381 my($pattern, $name) = @_;
318 2   50     21 @_ = ($stderr, $pattern, 1, 1, $name || 'stderr does not match' );
319 2         14 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 119650 my $cmd = _script(shift);
331 19         125 my $opt = _options(\$program_stdout, \$program_stderr, 0, \@_);
332 18         69 $program_stdout = '';
333 18         46 $program_stderr = '';
334              
335 18 100       96 unshift @_, "Program $$cmd[0] runs" unless $_[0];
336 18         44 unshift @_, $cmd, $opt;
337 18         208 goto &_run;
338             }
339              
340              
341             sub program_fails {
342 6     6 1 10098 my $cmd = _script(shift);
343 6         33 my ( $opt, $testname ) = @_;
344 6 100       47 $testname = 'program_fails' unless defined $testname;
345             die "exit is a mandatory option for program_fails"
346 6 100       19 unless eval{ defined $opt->{exit} };
  6         49  
347 5         46 my $ctx = context();
348 5         491 return release $ctx, program_runs( $cmd, $opt, $testname );
349             }
350              
351              
352             sub program_stdout_is
353             {
354 2     2 1 3469 my($pattern, $name) = @_;
355 2   50     27 @_ = ($program_stdout, $pattern, 0, 0, $name || 'stdout matches' );
356 2         28 goto &_like;
357             }
358              
359              
360             sub program_stdout_isnt
361             {
362 2     2 1 53562 my($pattern, $name) = @_;
363 2   50     48 @_ = ($program_stdout, $pattern, 0, 1, $name || 'stdout does not match' );
364 2         17 goto &_like;
365             }
366              
367              
368             sub program_stdout_like
369             {
370 4     4 1 10045 my($pattern, $name) = @_;
371 4   50     120 @_ = ($program_stdout, $pattern, 1, 0, $name || 'stdout matches' );
372 4         56 goto &_like;
373             }
374              
375              
376             sub program_stdout_unlike
377             {
378 2     2 1 9154 my($pattern, $name) = @_;
379 2   50     36 @_ = ($program_stdout, $pattern, 1, 1, $name || 'stdout does not match' );
380 2         16 goto &_like;
381             }
382              
383              
384             sub program_stderr_is
385             {
386 2     2 1 2508 my($pattern, $name) = @_;
387 2   50     33 @_ = ($program_stderr, $pattern, 0, 0, $name || 'stderr matches' );
388 2         14 goto &_like;
389             }
390              
391              
392             sub program_stderr_isnt
393             {
394 2     2 1 46600 my($pattern, $name) = @_;
395 2   50     31 @_ = ($program_stderr, $pattern, 0, 1, $name || 'stderr does not match' );
396 2         10 goto &_like;
397             }
398              
399              
400             sub program_stderr_like
401             {
402 2     2 1 7484 my($pattern, $name) = @_;
403 2   50     20 @_ = ($program_stderr, $pattern, 1, 0, $name || 'stderr matches' );
404 2         9 goto &_like;
405             }
406              
407              
408             sub program_stderr_unlike
409             {
410 2     2 1 6733 my($pattern, $name) = @_;
411 2   50     29 @_ = ($program_stderr, $pattern, 1, 1, $name || 'stderr does not match' );
412 2         9 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   6291 my $in = shift;
423 55 100       231 if ( defined _STRING($in) ) {
424 26         125 return [ $in ];
425             }
426 29 50       109 if ( _ARRAY($in) ) {
427 29 50       81 unless ( scalar grep { not defined _STRING($_) } @$in ) {
  66         136  
428 29         119 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   71 my($script) = @_;
438 26         45 my $fh;
439 26         109 my $first_line = '';
440 26 100       1550 if(open($fh, '<', $script))
441             {
442 24         613 $first_line = <$fh>;
443 24         428 close $fh;
444             }
445 26 100       680 (grep /^-.*T/, split /\s+/, $first_line) ? ['-T'] : [];
446             }
447              
448             # Inline some basic Params::Util functions
449              
450             sub _options {
451 37     37   83 my $ref_stdout = shift;
452 37         73 my $ref_stderr = shift;
453 37         88 my $permit_interpreter_options = shift;
454 37 100       159 my %options = ref($_[0]->[0]) eq 'HASH' ? %{ shift @{ $_[0] } }: ();
  20         35  
  20         119  
455              
456 37 100       175 $options{exit} = 0 unless defined $options{exit};
457 37 100       180 $options{signal} = 0 unless defined $options{signal};
458 37         210 my $stdin = '';
459             #$options{stdin} = \$stdin unless defined $options{stdin};
460 37 100       175 $options{stdout} = $ref_stdout unless defined $options{stdout};
461 37 100       144 $options{stderr} = $ref_stderr unless defined $options{stderr};
462              
463 37 100       111 if(defined $options{interpreter_options})
464             {
465 3 100       124 die "interpreter_options not supported" unless $permit_interpreter_options;
466 2 100       16 unless(ref $options{interpreter_options} eq 'ARRAY')
467             {
468 1         977 require Text::ParseWords;
469 1         1826 $options{interpreter_options} = [ Text::ParseWords::shellwords($options{interpreter_options}) ];
470             }
471             }
472             else
473             {
474 34         108 $options{interpreter_options} = [];
475             }
476              
477 36         411 \%options;
478             }
479              
480             sub _ARRAY ($) {
481 29 50 33 29   118 (ref $_[0] eq 'ARRAY' and @{$_[0]}) ? $_[0] : undef;
482             }
483              
484             sub _STRING ($) {
485 121 100 66 121   1285 (defined $_[0] and ! ref $_[0] and length($_[0])) ? $_[0] : undef;
486             }
487              
488             BEGIN {
489             # Alias to old name
490 12     12   556 *script_compiles_ok = *script_compiles;
491             }
492              
493             1;
494              
495             __END__