File Coverage

blib/lib/Command/Runner.pm
Criterion Covered Total %
statement 146 200 73.0
branch 53 100 53.0
condition 13 27 48.1
subroutine 22 28 78.5
pod 2 3 66.6
total 236 358 65.9


line stmt bran cond sub pod time code
1             package Command::Runner;
2 10     10   618221 use strict;
  10         106  
  10         280  
3 10     10   49 use warnings;
  10         16  
  10         247  
4              
5 10     10   4456 use Capture::Tiny ();
  10         258782  
  10         283  
6 10     10   4169 use Command::Runner::Format ();
  10         27  
  10         227  
7 10     10   3992 use Command::Runner::LineBuffer;
  10         25  
  10         276  
8 10     10   55 use Command::Runner::Quote ();
  10         20  
  10         140  
9 10     10   3408 use Command::Runner::Timeout;
  10         24  
  10         270  
10 10     10   54 use Config ();
  10         16  
  10         111  
11 10     10   4422 use File::pushd ();
  10         10733  
  10         219  
12 10     10   4123 use IO::Select;
  10         14769  
  10         553  
13 10     10   5009 use POSIX ();
  10         59081  
  10         276  
14 10     10   77 use Time::HiRes ();
  10         20  
  10         222  
15              
16 10     10   40 use constant WIN32 => $^O eq 'MSWin32';
  10         25  
  10         1770  
17              
18             our $VERSION = '0.200';
19             our $TICK = 0.02;
20              
21             sub new {
22 39     39 1 188891 my ($class, %option) = @_;
23 39         140 my $command = delete $option{command};
24 39         104 my $commandf = delete $option{commandf};
25 39 50 66     250 die "Cannot specify both command and commandf" if $command && $commandf;
26 39 100       264 my $self = bless {
27             keep => 1,
28             _buffer => {},
29             %option,
30             ($command ? (command => $command) : ()),
31             }, $class;
32 39 50       125 $self->commandf(@$commandf) if $commandf;
33 39         380 $self;
34             }
35              
36             for my $attr (qw(command cwd redirect timeout keep stdout stderr env)) {
37 10     10   60 no strict 'refs';
  10         15  
  10         15579  
38             *$attr = sub {
39 106     106   440 my $self = shift;
40 106         316 $self->{$attr} = $_[0];
41 106         493 $self;
42             };
43             }
44              
45             # NOTE: commandf is derecated; do not use this. will be removed in the future version
46             sub commandf {
47 0     0 0 0 my ($self, $format, @args) = @_;
48 0         0 require Command::Runner::Format;
49 0         0 $self->{command} = Command::Runner::Format::commandf($format, @args);
50 0         0 $self;
51             }
52              
53             sub run {
54 39     39 1 145 my $self = shift;
55 39         106 my $command = $self->{command};
56 39 100       223 if (ref $command eq 'CODE') {
57 16     16   158 $self->_wrap(sub { $self->_run_code($command) });
  16         24096  
58             } elsif (WIN32) {
59 0     0   0 $self->_wrap(sub { $self->_system_win32($command) });
60             } else {
61 23         175 $self->_exec($command);
62             }
63             }
64              
65             sub _wrap {
66 16     16   48 my ($self, $code) = @_;
67              
68 16         41 my ($stdout, $stderr, $res);
69 16 100       58 if ($self->{redirect}) {
70 5         435 ($stdout, $res) = &Capture::Tiny::capture_merged($code);
71             } else {
72 11         389 ($stdout, $stderr, $res) = &Capture::Tiny::capture($code);
73             }
74              
75 16 50 33     13203 if (length $stdout and my $sub = $self->{stdout}) {
76 16         172 my $buffer = Command::Runner::LineBuffer->new(buffer => $stdout);
77 16         115 my @line = $buffer->get(1);
78 16         78 $sub->($_) for @line;
79             }
80 16 50 66     299 if (!$self->{redirect} and length $stderr and my $sub = $self->{stderr}) {
      66        
81 11         42 my $buffer = Command::Runner::LineBuffer->new(buffer => $stderr);
82 11         40 my @line = $buffer->get(1);
83 11         37 $sub->($_) for @line;
84             }
85              
86 16 100       108 if ($self->{keep}) {
87 13         53 $res->{stdout} = $stdout;
88 13         34 $res->{stderr} = $stderr;
89             }
90              
91 16         109 return $res;
92             }
93              
94             sub _run_code {
95 16     16   58 my ($self, $code) = @_;
96              
97 16         35 my $wrap_code;
98 16 50 33     172 if ($self->{env} || $self->{cwd}) {
99             $wrap_code = sub {
100 0 0   0   0 local %ENV = %{$self->{env}} if $self->{env};
  0         0  
101 0 0       0 my $guard = File::pushd::pushd($self->{cwd}) if $self->{cwd};
102 0         0 $code->();
103 0         0 };
104             }
105              
106 16 100       57 if (!$self->{timeout}) {
107 15 50       88 my $result = $wrap_code ? $wrap_code->() : $code->();
108 15         2049 return { pid => $$, result => $result };
109             }
110              
111 1         7 my ($result, $err);
112             {
113 1         4 local $SIG{__DIE__} = 'DEFAULT';
  1         12  
114 1     1   31 local $SIG{ALRM} = sub { die "__TIMEOUT__\n" };
  1         1000173  
115 1         3 eval {
116 1         18 alarm $self->{timeout};
117 1 50       12 $result = $wrap_code ? $wrap_code->() : $code->();
118             };
119 1         6 $err = $@;
120 1         30 alarm 0;
121             }
122 1 50       9 if (!$err) {
    50          
123 0         0 return { pid => $$, result => $result, };
124             } elsif ($err eq "__TIMEOUT__\n") {
125 1         18 return { pid => $$, result => $result, timeout => 1 };
126             } else {
127 0         0 die $err;
128             }
129             }
130              
131             sub _system_win32 {
132 0     0   0 my ($self, $command) = @_;
133              
134 0         0 my $pid;
135 0 0       0 if (ref $command) {
136 0         0 my @cmd = map { Command::Runner::Quote::quote_win32($_) } @$command;
  0         0  
137 0 0       0 local %ENV = %{$self->{env}} if $self->{env};
  0         0  
138 0 0       0 my $guard = File::pushd::pushd($self->{cwd}) if $self->{cwd};
139 0         0 $pid = system { $command->[0] } 1, @cmd;
  0         0  
140             } else {
141 0 0       0 local %ENV = %{$self->{env}} if $self->{env};
  0         0  
142 0 0       0 my $guard = File::pushd::pushd($self->{cwd}) if $self->{cwd};
143 0         0 $pid = system 1, $command;
144             }
145              
146 0 0       0 my $timeout = $self->{timeout} ? Command::Runner::Timeout->new($self->{timeout}, 1) : undef;
147 0     0   0 my $INT; local $SIG{INT} = sub { $INT++ };
  0         0  
  0         0  
148 0         0 my $result;
149 0         0 while (1) {
150 0 0       0 if ($INT) {
151 0         0 kill INT => $pid;
152 0         0 $INT = 0;
153             }
154              
155 0         0 my $res = waitpid $pid, POSIX::WNOHANG();
156 0 0       0 if ($res == -1) {
    0          
157 0         0 warn "waitpid($pid, POSIX::WNOHANG()) returns unexpectedly -1";
158 0         0 last;
159             } elsif ($res > 0) {
160 0         0 $result = $?;
161 0         0 last;
162             } else {
163 0 0 0     0 if ($timeout and my $signal = $timeout->signal) {
164 0         0 kill $signal => $pid;
165             }
166 0         0 Time::HiRes::sleep($TICK);
167             }
168             }
169 0   0     0 return { pid => $pid, result => $result, timeout => $timeout && $timeout->signaled };
170             }
171              
172             sub _exec {
173 23     23   213 my ($self, $command) = @_;
174              
175 23         1457 pipe my $stdout_read, my $stdout_write;
176 23         397 $self->{_buffer}{stdout} = Command::Runner::LineBuffer->new(keep => $self->{keep});
177              
178 23         79 my ($stderr_read, $stderr_write);
179 23 100       87 if (!$self->{redirect}) {
180 19         535 pipe $stderr_read, $stderr_write;
181 19         171 $self->{_buffer}{stderr} = Command::Runner::LineBuffer->new(keep => $self->{keep});
182             }
183              
184 23         33848 my $pid = fork;
185 23 50       1233 die "fork: $!" unless defined $pid;
186 23 100       565 if ($pid == 0) {
187 7         699 close $_ for grep $_, $stdout_read, $stderr_read;
188 7         526 open STDOUT, ">&", $stdout_write;
189 7 100       229 if ($self->{redirect}) {
190 1         43 open STDERR, ">&", \*STDOUT;
191             } else {
192 6         223 open STDERR, ">&", $stderr_write;
193             }
194 7 50       829 if ($Config::Config{d_setpgrp}) {
195 7 50       650 POSIX::setpgid(0, 0) or die "setpgid: $!";
196             }
197              
198 7 50       219 if ($self->{cwd}) {
199 0 0       0 chdir $self->{cwd} or die "chdir $self->{cwd}: $!";
200             }
201 7 50       80 if ($self->{env}) {
202 0         0 %ENV = %{$self->{env}};
  0         0  
203             }
204              
205 7 50       115 if (ref $command) {
206 7         42 exec { $command->[0] } @$command;
  7         0  
207             } else {
208 0         0 exec $command;
209             }
210 0         0 exit 127;
211             }
212 16         1745 close $_ for grep $_, $stdout_write, $stderr_write;
213              
214 16 50       3658 my $signal_pid = $Config::Config{d_setpgrp} ? -$pid : $pid;
215              
216 16     0   151 my $INT; local $SIG{INT} = sub { $INT++ };
  16         1613  
  0         0  
217 16 100       527 my $timeout = $self->{timeout} ? Command::Runner::Timeout->new($self->{timeout}, 1) : undef;
218 16         1048 my $select = IO::Select->new(grep $_, $stdout_read, $stderr_read);
219              
220 16         2922 while ($select->count) {
221 386 50       7160691 if ($INT) {
222 0         0 kill INT => $signal_pid;
223 0         0 $INT = 0;
224             }
225 386 100 100     2438 if ($timeout and my $signal = $timeout->signal) {
226 5         326 kill $signal => $signal_pid;
227             }
228 386         2052 for my $ready ($select->can_read($TICK)) {
229 58 100       147704 my $type = $ready == $stdout_read ? "stdout" : "stderr";
230 58         1260 my $len = sysread $ready, my $buf, 64*1024;
231 58 100       272 if ($len) {
232 29         132 my $buffer = $self->{_buffer}{$type};
233 29         544 $buffer->add($buf);
234 29 50       224 next unless my @line = $buffer->get;
235 29 100       233 next unless my $sub = $self->{$type};
236 23         243 $sub->($_) for @line;
237             } else {
238 29 50       393 warn "sysread $type pipe failed: $!" unless defined $len;
239 29         251 $select->remove($ready);
240 29         2836 close $ready;
241             }
242             }
243             }
244 16         164 for my $type (qw(stdout stderr)) {
245 32 100       180 next unless my $sub = $self->{$type};
246 26 100       134 my $buffer = $self->{_buffer}{$type} or next;
247 23 50       145 my @line = $buffer->get(1) or next;
248 0         0 $sub->($_) for @line;
249             }
250 16         99 close $_ for $select->handles;
251 16         1245 waitpid $pid, 0;
252             my $res = {
253             pid => $pid,
254             result => $?,
255             timeout => $timeout && $timeout->signaled,
256             stdout => $self->{_buffer}{stdout} ? $self->{_buffer}{stdout}->raw : "",
257 16 50 66     354 stderr => $self->{_buffer}{stderr} ? $self->{_buffer}{stderr}->raw : "",
    100          
258             };
259 16         384 $self->{_buffer} = +{}; # cleanup
260 16         1818 return $res;
261             }
262              
263             1;
264             __END__