File Coverage

blib/lib/Test2/Tools/Command.pm
Criterion Covered Total %
statement 104 104 100.0
branch 55 56 98.2
condition 23 30 76.6
subroutine 12 12 100.0
pod 2 2 100.0
total 196 204 96.0


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # run simple unix commands for expected results given particular inputs
4              
5             package Test2::Tools::Command;
6             our $VERSION = '0.20';
7              
8 3     3   475202 use 5.10.0;
  3         31  
9 3     3   16 use strict;
  3         6  
  3         60  
10 3     3   14 use warnings;
  3         6  
  3         91  
11 3     3   1614 use File::chdir; # $CWD
  3         8592  
  3         555  
12 3     3   1670 use IPC::Open3 'open3';
  3         12522  
  3         182  
13 3     3   22 use Symbol 'gensym';
  3         16  
  3         114  
14 3     3   16 use Test2::API 'context';
  3         6  
  3         127  
15              
16 3     3   16 use base 'Exporter';
  3         6  
  3         3925  
17             our @EXPORT = qw(command is_exit);
18              
19             our @command; # prefixed on each run, followed by any ->{args}
20             our $timeout = 30; # seconds, for alarm()
21              
22             sub command ($) {
23 9 100   9 1 18075 local $CWD = $_[0]->{chdir} if defined $_[0]->{chdir};
24 8         161 local @ENV{ keys %{ $_[0]->{env} } } = values %{ $_[0]->{env} };
  8         52  
  8         43  
25              
26             # what to run, and possibly also a string to include in the test name
27 8 100       37 my @cmd = ( @command, exists $_[0]->{args} ? @{ $_[0]->{args} } : () );
  7         28  
28              
29 8         29 my ( $stdout, $stderr );
30 8 100       18 eval {
31 8     1   268 local $SIG{ALRM} = sub { die "timeout\n" };
  1         235  
32 8   66     155 alarm( $_[0]->{timeout} || $timeout );
33              
34 8         109 my $pid = open3( my $in, my $out, my $err = gensym, @cmd );
35 8 100       45787 if ( defined $_[0]->{binmode} ) {
36 1     1   31 for my $fh ( $in, $out, $err ) { binmode $fh, $_[0]->{binmode} }
  3         2587  
  1         51  
  1         7  
  1         46  
37             }
38 8 100       134 if ( exists $_[0]->{stdin} ) {
39 1         46 print $in $_[0]->{stdin};
40 1         26 close $in;
41             }
42             # this may be bad if the utility produces too much output
43 8         31 $stdout = do { local $/; readline $out };
  8         125  
  8         1033782  
44 7         349 $stderr = do { local $/; readline $err };
  7         46  
  7         324  
45 7         272 waitpid $pid, 0;
46 7         100 alarm 0;
47 7         921 1;
48             } or die $@;
49 7         220 my $orig_status = $?;
50             # the exit status is broken out into a hashref for exact tests on
51             # the various components of the 16-bit word (an alternative might be
52             # to mangle it into a number like the shell does)
53 7 50       131 my $status = {
54             code => $? >> 8,
55             signal => $? & 127,
56             iscore => $? & 128 ? 1 : 0
57             };
58             # the munge are for when the code or signal vary (for portability or
59             # for reasons out of your control) and you only want to know if the
60             # value was 0 or not. lots of CPAN Tester systems did not set the
61             # iscore flag following a CORE::dump by a test program...
62 7 100       57 $status->{code} = $status->{code} ? 1 : 0 if $_[0]->{munge_status};
    100          
63 7 100       68 $status->{signal} = $status->{signal} ? 1 : 0 if $_[0]->{munge_signal};
    100          
64              
65             # default exit status word is 0, but need it in hashref form
66 7 100       47 if ( exists $_[0]->{status} ) {
67 3 100       76 if ( !defined $_[0]->{status} ) {
    100          
68 1         20 $_[0]->{status} = { code => 0, signal => 0, iscore => 0 };
69             } elsif ( ref $_[0]->{status} eq '' ) {
70 1         15 $_[0]->{status} = { code => $_[0]->{status}, signal => 0, iscore => 0 };
71             }
72             # assume that ->{status} is a hashref
73             } else {
74 4         82 $_[0]->{status} = { code => 0, signal => 0, iscore => 0 };
75             }
76              
77 7   66     226 my ( $ctx, $name, $result ) = ( context(), $_[0]->{name} // "@cmd", 1 );
78              
79 7 100 66     3114 if ( $_[0]->{status}{code} == $status->{code}
      66        
80             and $_[0]->{status}{signal} == $status->{signal}
81             and $_[0]->{status}{iscore} == $status->{iscore} ) {
82 5         115 $ctx->pass("exit - $name");
83             } else {
84             $ctx->fail(
85             "exit - $name",
86             sprintf(
87             "code\t%d\tsignal\t%d\tiscore\t%d want",
88             $_[0]->{status}{code},
89             $_[0]->{status}{signal},
90             $_[0]->{status}{iscore}
91             ),
92             sprintf(
93             "code\t%d\tsignal\t%d\tiscore\t%d got",
94             $status->{code}, $status->{signal}, $status->{iscore}
95             )
96 2         66 );
97 2         1387 $result = 0;
98             }
99             # qr// or assume it's a string
100 7 100 100     2426 if ( defined $_[0]->{stdout} and ref $_[0]->{stdout} eq 'Regexp' ) {
101 2 100       47 if ( $stdout =~ m/$_[0]->{stdout}/ ) {
102 1         16 $ctx->pass("stdout - $name");
103             } else {
104 1         39 $ctx->fail( "stdout - $name", "expected match on $_[0]->{stdout}" );
105 1         187 $result = 0;
106             }
107             } else {
108 5   100     96 my $want = $_[0]->{stdout} // '';
109 5 100       18 if ( $stdout eq $want ) {
110 4         31 $ctx->pass("stdout - $name");
111             } else {
112 1         25 $ctx->fail( "stdout - $name", "expected equality with q{$want}" );
113 1         210 $result = 0;
114             }
115             }
116 7 100 100     785 if ( defined $_[0]->{stderr} and ref $_[0]->{stderr} eq 'Regexp' ) {
117 2 100       30 if ( $stderr =~ m/$_[0]->{stderr}/ ) {
118 1         10 $ctx->pass("stderr - $name");
119             } else {
120 1         24 $ctx->fail( "stderr - $name", "expected match on $_[0]->{stderr}" );
121 1         164 $result = 0;
122             }
123             } else {
124 5   100     75 my $want = $_[0]->{stderr} // '';
125 5 100       20 if ( $stderr eq $want ) {
126 4         22 $ctx->pass("stderr - $name");
127             } else {
128 1         20 $ctx->fail( "stderr - $name", "expected equality with q{$want}" );
129 1         214 $result = 0;
130             }
131             }
132 7         747 $ctx->release;
133 7         459 return $result, $orig_status, \$stdout, \$stderr;
134             }
135              
136             sub is_exit ($;$$) {
137 9     9 1 6757 my ( $exit, $expect, $name ) = @_;
138              
139 9   100     43 $name //= 'exit status';
140              
141 9 100       30 if ( !defined $expect ) {
    100          
142 1         5 $expect = { code => 0, signal => 0, iscore => 0 };
143             } elsif ( ref $expect eq '' ) {
144 1         5 $expect = { code => $expect, signal => 0, iscore => 0 };
145             }
146              
147 9 100       32 my $status = {
148             code => $exit >> 8,
149             signal => $exit & 127,
150             iscore => $exit & 128 ? 1 : 0
151             };
152 9 100       25 $status->{code} = $status->{code} ? 1 : 0 if $expect->{munge_status};
    100          
153 9 100       30 $status->{signal} = $status->{signal} ? 1 : 0 if $expect->{munge_signal};
    100          
154              
155 9         24 my ( $ctx, $result ) = ( context(), 1 );
156              
157 9 100 33     781 if ( $expect->{code} == $status->{code}
      66        
158             and $expect->{signal} == $status->{signal}
159             and $expect->{iscore} == $status->{iscore} ) {
160 8         27 $ctx->pass($name);
161             } else {
162             $ctx->fail(
163             $name,
164             sprintf(
165             "code\t%d\tsignal\t%d\tiscore\t%d want",
166             $expect->{code}, $expect->{signal},
167             $expect->{iscore}
168             ),
169             sprintf(
170             "code\t%d\tsignal\t%d\tiscore\t%d got",
171             $status->{code}, $status->{signal}, $status->{iscore}
172             )
173 1         20 );
174 1         288 $result = 0;
175             }
176              
177 9         907 $ctx->release;
178 9         234 return $result;
179             }
180              
181             1;
182             __END__