File Coverage

blib/lib/Sys/Cmd.pm
Criterion Covered Total %
statement 249 298 83.5
branch 81 130 62.3
condition 7 19 36.8
subroutine 27 30 90.0
pod 6 7 85.7
total 370 484 76.4


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