File Coverage

blib/lib/Test/Script.pm
Criterion Covered Total %
statement 247 252 98.0
branch 89 104 85.5
condition 29 50 58.0
subroutine 47 47 100.0
pod 21 23 91.3
total 433 476 90.9


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