File Coverage

blib/lib/Sys/Cmd.pm
Criterion Covered Total %
statement 256 310 82.5
branch 74 120 61.6
condition 8 20 40.0
subroutine 28 31 90.3
pod 6 7 85.7
total 372 488 76.2


line stmt bran cond sub pod time code
1             package # Trick xt/ tests into working
2             Sys::Cmd::Mo;
3              
4             BEGIN {
5             #<<< No perltidy
6             # use Mo qw/build is required default import/;
7             # The following line of code was produced from the previous line by
8             # Mo::Inline version 0.39
9 4 0 66 4   68774 no warnings;my$M=__PACKAGE__.'::';*{$M.Object::new}=sub{my$c=shift;my$s=bless{@_},$c;my%n=%{$c.::.':E'};map{$s->{$_}=$n{$_}->()if!exists$s->{$_}}keys%n;$s};*{$M.import}=sub{import warnings;$^H|=1538;my($P,%e,%o)=caller.'::';shift;eval"no Mo::$_",&{$M.$_.::e}($P,\%e,\%o,\@_)for@_;return if$e{M};%e=(extends,sub{eval"no $_[0]()";@{$P.ISA}=$_[0]},has,sub{my$n=shift;my$m=sub{$#_?$_[0]{$n}=$_[1]:$_[0]{$n}};@_=(default,@_)if!($#_%2);$m=$o{$_}->($m,$n,@_)for sort keys%o;*{$P.$n}=$m},%e,);*{$P.$_}=$e{$_}for keys%e;@{$P.ISA}=$M.Object};*{$M.'build::e'}=sub{my($P,$e)=@_;$e->{new}=sub{$c=shift;my$s=&{$M.Object::new}($c,@_);my@B;do{@B=($c.::BUILD,@B)}while($c)=@{$c.::ISA};exists&$_&&&$_($s)for@B;$s}};*{$M.'is::e'}=sub{my($P,$e,$o)=@_;$o->{is}=sub{my($m,$n,%a)=@_;$a{is}or return$m;sub{$#_&&$a{is}eq'ro'&&caller ne'Mo::coerce'?die$n.' is ro':$m->(@_)}}};*{$M.'required::e'}=sub{my($P,$e,$o)=@_;$o->{required}=sub{my($m,$n,%a)=@_;if($a{required}){my$C=*{$P."new"}{CODE}||*{$M.Object::new}{CODE};no warnings 'redefine';*{$P."new"}=sub{my$s=$C->(@_);my%a=@_[1..$#_];if(!exists$a{$n}){require Carp;Carp::croak($n." required")}$s}}$m}};*{$M.'default::e'}=sub{my($P,$e,$o)=@_;$o->{default}=sub{my($m,$n,%a)=@_;exists$a{default}or return$m;my($d,$r)=$a{default};my$g='HASH'eq($r=ref$d)?sub{+{%$d}}:'ARRAY'eq$r?sub{[@$d]}:'CODE'eq$r?$d:sub{$d};my$i=exists$a{lazy}?$a{lazy}:!${$P.':N'};$i or ${$P.':E'}{$n}=$g and return$m;sub{$#_?$m->(@_):!exists$_[0]{$n}?$_[0]{$n}=$g->(@_):$m->(@_)}}};my$i=\&import;*{$M.import}=sub{(@_==2 and not$_[1])?pop@_:@_==1?push@_,grep!/import/,@f:();goto&$i};@f=qw[build is required default import];use strict;use warnings;
  3 50 33 4   7  
  3 100 50 3   1665  
  4 50 0 3   162  
  3 50 33 3   13  
  3 50   3   1094  
  3 50   3   13  
  3 100   3   11  
  3 100   3   55  
  3 50   461   9  
  3 50       6  
  3 50       127  
  3 50       8  
  3 50       18  
  3 100       38  
  21 50       32  
  21 100       68  
  21 50       32  
  21         111  
  21         44  
  0         0  
  21         53  
  3         12  
  3         8  
  4         50  
  4         10  
  4         19  
  4         5  
  4         242  
  14         78  
  4         15  
  4         39  
  0         0  
  0         0  
  0         0  
  44         42  
  44         87  
  806         435502  
  44         78  
  44         134  
  44         56  
  44         130  
  4         15  
  11         48  
  4         10  
  4         5913  
  3         24  
  3         23  
  3         9  
  3         174  
  20         596  
  20         44  
  20         152  
  20         26  
  20         30  
  40         206  
  40         144  
  20         188  
  17         123  
  3         10  
  3         20  
  4         18  
  4         244  
  44         76  
  44         57  
  44         124  
  884         376725  
  3         11  
  3         25  
  3         8  
  3         185  
  39         88  
  39         53  
  3         3  
  3         8  
  3         9  
  20         76  
  17         110  
  17         79  
  0         0  
  0         0  
  17         96  
  39         57  
  3         10  
  3         22  
  4         10  
  4         31  
  44         68  
  44         115  
  13         11  
  13         46  
  0         0  
  0         0  
  0         0  
  13         21  
  13         31  
  13         23  
  0         0  
  13         52  
  414         1470  
  3         6  
  3         6  
  3         6  
  4         55  
  4         16  
  3         6  
  3         870  
  0         0  
  0         0  
  3         641  
  0         0  
  0         0  
  3         615  
  0         0  
  0         0  
  3         543  
  0         0  
  0         0  
10 3         71 $INC{'Sys/Cmd/Mo.pm'} = __FILE__;
11             #>>>
12             }
13             1;
14              
15             package Sys::Cmd;
16 3     3   11 use strict;
  3         3  
  3         44  
17 3     3   9 use warnings;
  3         5  
  3         50  
18 3     3   49 use 5.006;
  3         6  
19 3     3   21 use Carp;
  3         3  
  3         154  
20 3     3   1714 use Exporter::Tidy all => [qw/spawn run runx/];
  3         17  
  3         15  
21 3     3   127 use File::Spec;
  3         6  
  3         48  
22 3     3   473 use IO::Handle;
  3         4521  
  3         112  
23 3     3   1186 use Log::Any qw/$log/;
  3         31050  
  3         9  
24 3     3   9796 use Sys::Cmd::Mo;
  3         3  
  3         15  
25              
26             our $VERSION = '0.85.4';
27             our $CONFESS;
28              
29             sub run {
30 0     0 1 0 my $proc = spawn(@_);
31 0         0 my @out = $proc->stdout->getlines;
32 0         0 my @err = $proc->stderr->getlines;
33              
34 0         0 $proc->wait_child;
35              
36 0 0       0 if ( $proc->exit != 0 ) {
37 0 0       0 Carp::confess(
38             join( '', @err ) . 'Command exited with value ' . $proc->exit )
39             if $CONFESS;
40 0         0 Carp::croak(
41             join( '', @err ) . 'Command exited with value ' . $proc->exit );
42             }
43              
44 0 0       0 warn @err if @err;
45              
46 0 0       0 if (wantarray) {
47 0         0 return @out;
48             }
49             else {
50 0         0 return join( '', @out );
51             }
52             }
53              
54             sub runx {
55 0     0 1 0 my $proc = spawn(@_);
56 0         0 my @out = $proc->stdout->getlines;
57 0         0 my @err = $proc->stderr->getlines;
58              
59 0         0 $proc->wait_child;
60              
61 0 0       0 if ( $proc->exit != 0 ) {
62 0 0       0 Carp::confess(
63             join( '', @err ) . 'Command exited with value ' . $proc->exit )
64             if $CONFESS;
65 0         0 Carp::croak(
66             join( '', @err ) . 'Command exited with value ' . $proc->exit );
67             }
68              
69 0 0       0 if (wantarray) {
70 0         0 return @out, @err;
71             }
72             else {
73 0         0 return join( '', @out, @err );
74             }
75             }
76              
77             sub spawn {
78 26     26 1 37736 my @cmd = grep { ref $_ ne 'HASH' } @_;
  54         158  
79              
80 26 50       86 defined $cmd[0] || Carp::confess '$cmd must be defined';
81              
82 26 100       74 unless ( ref $cmd[0] eq 'CODE' ) {
83              
84 24 100       248 if ( File::Spec->splitdir( $cmd[0] ) == 1 ) {
85 4         1886 require File::Which;
86 4   33     2570 $cmd[0] = File::Which::which( $cmd[0] )
87             || Carp::confess 'command not found: ' . $cmd[0];
88             }
89              
90 20 100       748 if ( !-x $cmd[0] ) {
91 2         344 Carp::confess 'command not executable: ' . $cmd[0];
92             }
93             }
94              
95 20         42 my @opts = grep { ref $_ eq 'HASH' } @_;
  48         120  
96 20 50       60 if ( @opts > 2 ) {
97 0         0 Carp::confess __PACKAGE__ . ": only a single hashref allowed";
98             }
99              
100 20 100       70 my %args = @opts ? %{ $opts[0] } : ();
  14         80  
101 20         50 $args{cmd} = \@cmd;
102              
103 20         202 return Sys::Cmd->new(%args);
104             }
105              
106             has 'cmd' => (
107             is => 'ro',
108             isa => sub {
109             ref $_[0] eq 'ARRAY' || Carp::confess "cmd must be ARRAYREF";
110             @{ $_[0] } || Carp::confess "Missing cmd elements";
111             if ( grep { !defined $_ } @{ $_[0] } ) {
112             Carp::confess 'cmd array cannot contain undef elements';
113             }
114             },
115             required => 1,
116             );
117              
118             has 'encoding' => (
119             is => 'ro',
120             default => sub { 'utf8' },
121             );
122              
123             has 'env' => (
124             is => 'ro',
125             isa => sub { ref $_[0] eq 'HASH' || Carp::confess "env must be HASHREF" },
126             );
127              
128             has 'dir' => ( is => 'ro', );
129              
130             has 'input' => ( is => 'ro', );
131              
132             has 'pid' => (
133             is => 'rw',
134             init_arg => undef,
135             );
136              
137             has 'stdin' => (
138             is => 'rw',
139             init_arg => undef,
140             default => sub { IO::Handle->new },
141             );
142              
143             has 'stdout' => (
144             is => 'rw',
145             init_arg => undef,
146             default => sub { IO::Handle->new },
147             );
148              
149             has 'stderr' => (
150             is => 'rw',
151             init_arg => undef,
152             default => sub { IO::Handle->new },
153             );
154              
155             has on_exit => (
156             is => 'rw',
157             init_arg => 'on_exit',
158             );
159              
160             has 'exit' => (
161             is => 'rw',
162             init_arg => undef,
163             );
164              
165             has 'signal' => (
166             is => 'rw',
167             init_arg => undef,
168             );
169              
170             has 'core' => (
171             is => 'rw',
172             init_arg => undef,
173             );
174              
175             sub BUILD {
176 20     20 0 26 my $self = shift;
177 20         68 my $dir = $self->dir;
178              
179 20 100       1990 require File::chdir if $dir;
180 20 100       6906 local $File::chdir::CWD = $dir if $dir;
181              
182 18         938 local %ENV = %ENV;
183              
184 18 100       90 if ( defined( my $x = $self->env ) ) {
185 10         74 while ( my ( $key, $val ) = each %$x ) {
186 16 100       38 if ( defined $val ) {
187 12         92 $ENV{$key} = $val;
188             }
189             else {
190 4         22 delete $ENV{$key};
191             }
192             }
193             }
194              
195 18 100       68 if ( ref $self->cmd->[0] eq 'CODE' ) {
196 2         6 $self->_fork;
197             }
198             else {
199 16         48 $self->_spawn;
200             }
201              
202 17         59 $log->debugf( '(PID %d) %s', $self->pid, scalar $self->cmdline );
203              
204 17         334 my $enc = ':encoding(' . $self->encoding . ')';
205 2     2   8 binmode $self->stdin, $enc;
  2         2  
  2         12  
  17         40  
206 17         18242 binmode $self->stdout, $enc;
207 17         380 binmode $self->stderr, $enc;
208              
209             # some input was provided
210 17 100       355 if ( defined( my $input = $self->input ) ) {
211             local $SIG{PIPE} =
212 4     0   106 sub { warn "Broken pipe when writing to:" . $self->cmdline };
  0         0  
213              
214 4 100       26 $self->stdin->print($input) if length $input;
215              
216 4         58 $self->stdin->close;
217             }
218              
219 17         471 return;
220             }
221              
222             sub _spawn {
223 16     16   20 my $self = shift;
224 16         1618 require Proc::FastSpawn;
225              
226             # Get new handles to descriptors 0,1,2
227 16         828 my $fd0 = IO::Handle->new_from_fd( 0, 'r' );
228 16         1226 my $fd1 = IO::Handle->new_from_fd( 1, 'w' );
229 16         640 my $fd2 = IO::Handle->new_from_fd( 2, 'w' );
230              
231             # Backup the original 0,1,2 file descriptors
232 16         818 open my $old_fd0, '<&', 0;
233 16         120 open my $old_fd1, '>&', 1;
234 16         106 open my $old_fd2, '>&', 2;
235              
236             # Pipe our filehandles to new child filehandles
237 16 50       78 pipe( my $child_in, $self->stdin ) || die "pipe: $!";
238 16 50       558 pipe( $self->stdout, my $child_out ) || die "pipe: $!";
239 16 50       428 pipe( $self->stderr, my $child_err ) || die "pipe: $!";
240              
241             # Make sure that 0,1,2 are inherited (probably are anyway)
242 16         456 Proc::FastSpawn::fd_inherit( $_, 1 ) for 0, 1, 2;
243              
244             # But don't inherit the rest
245             Proc::FastSpawn::fd_inherit( fileno($_), 0 )
246 16         50 for $old_fd0, $old_fd1, $old_fd2, $child_in, $child_out, $child_err,
247             $self->stdin, $self->stdout, $self->stderr;
248              
249 16         26 eval {
250             # Re-open 0,1,2 by duping the child pipe ends
251 16         140 open $fd0, '<&', fileno($child_in);
252 16         96 open $fd1, '>&', fileno($child_out);
253 16         82 open $fd2, '>&', fileno($child_err);
254              
255             # Kick off the new process
256             $self->pid(
257             Proc::FastSpawn::spawn(
258             $self->cmd->[0],
259             $self->cmd,
260             [
261 16 50       56 map { $_ . '=' . ( defined $ENV{$_} ? $ENV{$_} : '' ) }
  296         5394  
262             keys %ENV
263             ]
264             )
265             );
266             };
267 16         130 my $err = $@;
268              
269             # Restore our local 0,1,2 to the originals
270 16         204 open $fd0, '<&', fileno($old_fd0);
271 16         104 open $fd1, '>&', fileno($old_fd1);
272 16         90 open $fd2, '>&', fileno($old_fd2);
273              
274             # Complain if the spawn failed for some reason
275 16 50       54 Carp::croak $err if $err;
276 16 50       40 Carp::croak 'Unable to spawn child' unless defined $self->pid;
277              
278             # Parent doesn't need to see the child or backup descriptors anymore
279             close($_)
280 16         120 for $old_fd0, $old_fd1, $old_fd2, $child_in, $child_out, $child_err;
281              
282 16         42 $self->stdin->autoflush(1);
283              
284 16         838 return;
285             }
286              
287             sub _fork {
288 2     2   4 my $self = shift;
289              
290 2 50       10 pipe( my $child_in, $self->stdin ) || die "pipe: $!";
291 2 50       84 pipe( $self->stdout, my $child_out ) || die "pipe: $!";
292 2 50       42 pipe( $self->stderr, my $child_err ) || die "pipe: $!";
293              
294 2         1479 $self->pid( fork() );
295 2 50       28 if ( !defined $self->pid ) {
296 0         0 my $why = $!;
297 0         0 die "fork: $why";
298             }
299              
300 2 100       14 if ( $self->pid == 0 ) { # Child
301 1         16 $self->exit(0); # stop DESTROY() from trying to reap
302              
303 1         57 $child_out->autoflush(1);
304 1         99 $child_err->autoflush(1);
305              
306 1 50       55 if ( !open STDERR, '>&=', fileno($child_err) ) {
307 0         0 print $child_err "open: $! at ", caller, "\n";
308 0         0 die "open: $!";
309             }
310 1   50     18 open STDIN, '<&=', fileno($child_in) || die "open: $!";
311 1   50     19 open STDOUT, '>&=', fileno($child_out) || die "open: $!";
312              
313 1         4 close $self->stdin;
314 1         8 close $self->stdout;
315 1         7 close $self->stderr;
316 1         6 close $child_in;
317 1         2 close $child_out;
318 1         3 close $child_err;
319              
320 1 50       16 if ( ref $self->cmd->[0] eq 'CODE' ) {
321 1         5 my $enc = ':encoding(' . $self->encoding . ')';
322 1         38 binmode STDIN, $enc;
323 1         143 binmode STDOUT, $enc;
324 1         20 binmode STDERR, $enc;
325 1         19 $self->cmd->[0]->();
326 0         0 _exit(0);
327             }
328              
329 0         0 exec( @{ $self->cmd } );
  0         0  
330 0         0 die "exec: $!";
331             }
332              
333             # Parent continues from here
334 1         17 close $child_in;
335 1         10 close $child_out;
336 1         6 close $child_err;
337              
338 1         11 $self->stdin->autoflush(1);
339              
340 1         128 return;
341             }
342              
343             sub cmdline {
344 29     29 1 3415 my $self = shift;
345 29 100       68 if (wantarray) {
346 12         22 return @{ $self->cmd };
  12         36  
347             }
348             else {
349 17         29 return join( ' ', @{ $self->cmd } );
  17         33  
350             }
351             }
352              
353             sub wait_child {
354 37     37 1 59 my $self = shift;
355              
356 37 100       71 return unless defined $self->pid;
357 35 100       129 return $self->exit if defined $self->exit;
358              
359 17         86 local $?;
360 17         51 local $!;
361              
362 17         42 my $pid = waitpid $self->pid, 0;
363 17         49 my $ret = $?;
364              
365 17 50       52 if ( $pid != $self->pid ) {
366 0         0 warn sprintf( 'Could not reap child process %d (waitpid returned: %d)',
367             $self->pid, $pid );
368 0         0 $pid = $self->pid;
369 0         0 $ret = 0;
370             }
371              
372 17 50       52 if ( $ret == -1 ) {
373              
374             # So waitpid returned a PID but then sets $? to this
375             # strange value? (Strange in that tests randomly show it to
376             # be invalid.) Most likely a perl bug; I think that waitpid
377             # got interrupted and when it restarts/resumes the status
378             # is lost.
379             #
380             # See http://www.perlmonks.org/?node_id=641620 for a
381             # possibly related discussion.
382             #
383             # However, since I localised $? and $! above I haven't seen
384             # this problem again, so I hope that is a good enough work
385             # around. Lets warn any way so that we know when something
386             # dodgy is going on.
387 0         0 warn __PACKAGE__
388             . ' received invalid child exit status for pid '
389             . $pid
390             . ' Setting to 0';
391 0         0 $ret = 0;
392              
393             }
394              
395             $log->debugf(
396 17         54 '(PID %d) exit: %d signal: %d core: %d',
397             $pid,
398             $self->exit( $ret >> 8 ),
399             $self->signal( $ret & 127 ),
400             $self->core( $ret & 128 )
401             );
402              
403 17 100       265 if ( my $subref = $self->on_exit ) {
404 2         8 $subref->($self);
405             }
406              
407 17         39 return $self->exit;
408             }
409              
410             sub close {
411 33     33 1 401 my $self = shift;
412              
413 33         72 foreach my $h (qw/stdin stdout stderr/) {
414              
415             # may not be defined during global destruction
416 99 50       999 my $fh = $self->$h or next;
417 99 100       346 $fh->opened or next;
418 47 50       275 $fh->close || Carp::carp "error closing $h: $!";
419             }
420              
421 33         275 return;
422             }
423              
424             sub DESTROY {
425 20     20   11027 my $self = shift;
426 20         66 $self->close;
427 20         50 $self->wait_child;
428 20         342 return;
429             }
430              
431             1;
432              
433             __END__