File Coverage

blib/lib/Test2/Tools/Command.pm
Criterion Covered Total %
statement 89 89 100.0
branch 39 40 97.5
condition 18 22 81.8
subroutine 11 11 100.0
pod 1 1 100.0
total 158 163 96.9


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.11';
7              
8 2     2   225979 use 5.10.0;
  2         13  
9 2     2   10 use strict;
  2         4  
  2         51  
10 2     2   11 use warnings;
  2         4  
  2         72  
11 2     2   944 use File::chdir; # $CWD
  2         4971  
  2         252  
12 2     2   1035 use IPC::Open3 'open3';
  2         7874  
  2         122  
13 2     2   15 use Symbol 'gensym';
  2         5  
  2         78  
14 2     2   11 use Test2::API 'context';
  2         5  
  2         84  
15              
16 2     2   12 use base 'Exporter';
  2         4  
  2         2131  
17             our @EXPORT = qw(command);
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 19479 local $CWD = $_[0]->{chdir} if defined $_[0]->{chdir};
24 8         195 local @ENV{ keys %{ $_[0]->{env} } } = values %{ $_[0]->{env} };
  8         58  
  8         68  
25              
26             # what to run, and possibly also a string to include in the test name
27 8 100       64 my @cmd = ( @command, exists $_[0]->{args} ? @{ $_[0]->{args} } : () );
  7         41  
28              
29 8         42 my ( $stdout, $stderr );
30 8 100       24 eval {
31 8     1   366 local $SIG{ALRM} = sub { die "timeout\n" };
  1         253  
32 8   66     163 alarm( $_[0]->{timeout} || $timeout );
33              
34 8         59 my $pid = open3( my $in, my $out, my $err = gensym, @cmd );
35 8 100       43375 if ( defined $_[0]->{binmode} ) {
36 1     1   20 for my $fh ( $in, $out, $err ) { binmode $fh, $_[0]->{binmode} }
  3         2062  
  1         32  
  1         9  
  1         32  
37             }
38 8 100       86 if ( exists $_[0]->{stdin} ) {
39 1         27 print $in $_[0]->{stdin};
40 1         24 close $in;
41             }
42             # this may be bad if the utility produces too much output
43 8         50 $stdout = do { local $/; readline $out };
  8         120  
  8         1037267  
44 7         556 $stderr = do { local $/; readline $err };
  7         65  
  7         351  
45 7         371 waitpid $pid, 0;
46 7         100 alarm 0;
47 7         1047 1;
48             } or die $@;
49 7         190 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       242 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       92 $status->{code} = $status->{code} ? 1 : 0 if $_[0]->{munge_status};
    100          
63 7 100       96 $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       55 if ( exists $_[0]->{status} ) {
67 3 100       96 if ( !defined $_[0]->{status} ) {
    100          
68 1         15 $_[0]->{status} = { code => 0, signal => 0, iscore => 0 };
69             } elsif ( ref $_[0]->{status} eq '' ) {
70 1         22 $_[0]->{status} = { code => $_[0]->{status}, signal => 0, iscore => 0 };
71             }
72             # assume that ->{status} is a hashref
73             } else {
74 4         55 $_[0]->{status} = { code => 0, signal => 0, iscore => 0 };
75             }
76              
77 7   66     161 my ( $ctx, $name, $result ) = ( context(), $_[0]->{name} // "@cmd", 1 );
78              
79 7 100 66     3300 if ( $_[0]->{status}{code} == $status->{code}
      66        
80             and $_[0]->{status}{signal} == $status->{signal}
81             and $_[0]->{status}{iscore} == $status->{iscore} ) {
82 5         112 $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         101 );
97 2         1420 $result = 0;
98             }
99             # qr// or assume it's a string
100 7 100 100     2765 if ( defined $_[0]->{stdout} and ref $_[0]->{stdout} eq 'Regexp' ) {
101 2 100       75 if ( $stdout =~ m/$_[0]->{stdout}/ ) {
102 1         15 $ctx->pass("stdout - $name");
103             } else {
104 1         25 $ctx->fail( "stdout - $name", "expected match on $_[0]->{stdout}" );
105 1         239 $result = 0;
106             }
107             } else {
108 5   100     75 my $want = $_[0]->{stdout} // '';
109 5 100       21 if ( $stdout eq $want ) {
110 4         31 $ctx->pass("stdout - $name");
111             } else {
112 1         17 $ctx->fail( "stdout - $name", "expected equality with q{$want}" );
113 1         167 $result = 0;
114             }
115             }
116 7 100 100     1249 if ( defined $_[0]->{stderr} and ref $_[0]->{stderr} eq 'Regexp' ) {
117 2 100       57 if ( $stderr =~ m/$_[0]->{stderr}/ ) {
118 1         16 $ctx->pass("stderr - $name");
119             } else {
120 1         30 $ctx->fail( "stderr - $name", "expected match on $_[0]->{stderr}" );
121 1         227 $result = 0;
122             }
123             } else {
124 5   100     94 my $want = $_[0]->{stderr} // '';
125 5 100       27 if ( $stderr eq $want ) {
126 4         35 $ctx->pass("stderr - $name");
127             } else {
128 1         41 $ctx->fail( "stderr - $name", "expected equality with q{$want}" );
129 1         315 $result = 0;
130             }
131             }
132 7         1014 $ctx->release;
133 7         609 return $result, $orig_status, \$stdout, \$stderr;
134             }
135              
136             1;
137             __END__