File Coverage

blib/lib/Command/Runner.pm
Criterion Covered Total %
statement 146 184 79.3
branch 55 82 67.0
condition 12 18 66.6
subroutine 21 25 84.0
pod 3 3 100.0
total 237 312 75.9


line stmt bran cond sub pod time code
1             package Command::Runner;
2 12     12   797284 use strict;
  12         124  
  12         352  
3 12     12   64 use warnings;
  12         19  
  12         286  
4              
5 12     12   6251 use Capture::Tiny ();
  12         332947  
  12         358  
6 12     12   4976 use Command::Runner::Format ();
  12         42  
  12         268  
7 12     12   4614 use Command::Runner::LineBuffer;
  12         30  
  12         347  
8 12     12   78 use Command::Runner::Quote ();
  12         24  
  12         156  
9 12     12   61 use Config ();
  12         23  
  12         256  
10 12     12   5437 use IO::Select;
  12         19308  
  12         539  
11 12     12   6286 use POSIX ();
  12         74033  
  12         340  
12 12     12   78 use Time::HiRes ();
  12         25  
  12         299  
13              
14 12     12   62 use constant WIN32 => $^O eq 'MSWin32';
  12         18  
  12         2233  
15              
16             our $VERSION = '0.102';
17             our $TICK = 0.02;
18              
19             sub new {
20 55     55 1 243192 my ($class, %option) = @_;
21 55         162 my $command = delete $option{command};
22 55         131 my $commandf = delete $option{commandf};
23 55 50 66     314 die "Cannot specify both command and commandf" if $command && $commandf;
24 55 100 100     370 if (!$command && $commandf) {
25 2         24 $command = Command::Runner::Format::commandf @$commandf;
26             }
27             bless {
28 55 100       627 keep => 1,
29             _buffer => {},
30             %option,
31             ($command ? (command => $command) : ()),
32             }, $class;
33             }
34              
35             for my $attr (qw(command redirect timeout keep stdout stderr env)) {
36 12     12   84 no strict 'refs';
  12         24  
  12         19962  
37             *$attr = sub {
38 140     140   623 my $self = shift;
39 140         375 $self->{$attr} = $_[0];
40 140         794 $self;
41             };
42             }
43              
44             sub commandf {
45 7     7 1 44 my ($self, $format, @args) = @_;
46 7         115 $self->{command} = Command::Runner::Format::commandf $format, @args;
47 7         70 $self;
48             }
49              
50             sub run {
51 55     55 1 170 my $self = shift;
52 55 50       169 local %ENV = %{$self->{env}} if $self->{env};
  0         0  
53 55         123 my $command = $self->{command};
54 55 100       273 if (ref $command eq 'CODE') {
55 21     21   203 $self->_wrap(sub { $self->_run_code($command) });
  21         33284  
56             } elsif (WIN32) {
57 0     0   0 $self->_wrap(sub { $self->_system_win32($command) });
58             } else {
59 34         133 $self->_exec($command);
60             }
61             }
62              
63             sub _wrap {
64 21     21   66 my ($self, $code) = @_;
65              
66 21         46 my ($stdout, $stderr, $res);
67 21 100       61 if ($self->{redirect}) {
68 6         174 ($stdout, $res) = &Capture::Tiny::capture_merged($code);
69             } else {
70 15         557 ($stdout, $stderr, $res) = &Capture::Tiny::capture($code);
71             }
72              
73 21 50 33     18565 if (length $stdout and my $sub = $self->{stdout}) {
74 21         230 my $buffer = Command::Runner::LineBuffer->new(buffer => $stdout);
75 21         95 my @line = $buffer->get(1);
76 21         97 $sub->($_) for @line;
77             }
78 21 50 66     461 if (!$self->{redirect} and length $stderr and my $sub = $self->{stderr}) {
      66        
79 15         75 my $buffer = Command::Runner::LineBuffer->new(buffer => $stderr);
80 15         69 my @line = $buffer->get(1);
81 15         86 $sub->($_) for @line;
82             }
83              
84 21 100       158 if ($self->{keep}) {
85 17         90 $res->{stdout} = $stdout;
86 17         75 $res->{stderr} = $stderr;
87             }
88              
89 21         135 return $res;
90             }
91              
92             sub _run_code {
93 21     21   252 my ($self, $code) = @_;
94              
95 21 100       118 if (!$self->{timeout}) {
96 19         160 my $result = $code->();
97 19         2363 return { pid => $$, result => $result };
98             }
99              
100 2         10 my ($result, $err);
101             {
102 2         10 local $SIG{__DIE__} = 'DEFAULT';
  2         30  
103 2     2   54 local $SIG{ALRM} = sub { die "__TIMEOUT__\n" };
  2         2000624  
104 2         8 eval {
105 2         32 alarm $self->{timeout};
106 2         10 $result = $code->();
107             };
108 2         12 $err = $@;
109 2         64 alarm 0;
110             }
111 2 50       40 if (!$err) {
    50          
112 0         0 return { pid => $$, result => $result, };
113             } elsif ($err eq "__TIMEOUT__\n") {
114 2         94 return { pid => $$, result => $result, timeout => 1 };
115             } else {
116 0         0 die $err;
117             }
118             }
119              
120             sub _system_win32 {
121 0     0   0 my ($self, $command) = @_;
122              
123 0         0 my $pid;
124 0 0       0 if (ref $command) {
125 0         0 my @cmd = map { Command::Runner::Quote::quote_win32($_) } @$command;
  0         0  
126 0         0 $pid = system { $command->[0] } 1, @cmd;
  0         0  
127             } else {
128 0         0 $pid = system 1, $command;
129             }
130              
131 0 0       0 my $timeout_at = $self->{timeout} ? Time::HiRes::time() + $self->{timeout} : undef;
132 0     0   0 my $INT; local $SIG{INT} = sub { $INT++ };
  0         0  
  0         0  
133 0         0 my ($result, $timeout);
134 0         0 while (1) {
135 0 0       0 if ($INT) {
136 0         0 kill INT => $pid;
137 0         0 $INT = 0;
138             }
139              
140 0         0 my $res = waitpid $pid, POSIX::WNOHANG();
141 0 0       0 if ($res == -1) {
    0          
142 0         0 warn "waitpid($pid, POSIX::WNOHANG()) returns unexpectedly -1";
143 0         0 last;
144             } elsif ($res > 0) {
145 0         0 $result = $?;
146 0         0 last;
147             } else {
148 0 0       0 if ($timeout_at) {
149 0         0 my $now = Time::HiRes::time();
150 0 0       0 if ($timeout_at <= $now) {
151 0         0 $timeout = 1;
152 0         0 kill TERM => $pid;
153             }
154             }
155 0         0 Time::HiRes::sleep($TICK);
156             }
157             }
158 0         0 return { pid => $pid, result => $result, timeout => $timeout };
159             }
160              
161             sub _exec {
162 34     34   111 my ($self, $command) = @_;
163              
164 34         1424 pipe my $stdout_read, my $stdout_write;
165 34         474 $self->{_buffer}{stdout} = Command::Runner::LineBuffer->new(keep => $self->{keep});
166              
167 34         87 my ($stderr_read, $stderr_write);
168 34 100       129 if (!$self->{redirect}) {
169 26         736 pipe $stderr_read, $stderr_write;
170 26         213 $self->{_buffer}{stderr} = Command::Runner::LineBuffer->new(keep => $self->{keep});
171             }
172              
173 34         33352 my $pid = fork;
174 34 50       1725 die "fork: $!" unless defined $pid;
175 34 100       723 if ($pid == 0) {
176 9         1083 close $_ for grep $_, $stdout_read, $stderr_read;
177 9         741 open STDOUT, ">&", $stdout_write;
178 9 100       375 if ($self->{redirect}) {
179 2         80 open STDERR, ">&", \*STDOUT;
180             } else {
181 7         271 open STDERR, ">&", $stderr_write;
182             }
183 9 50       1203 if ($Config::Config{d_setpgrp}) {
184 9 50       989 POSIX::setpgid(0, 0) or die "setpgid: $!";
185             }
186              
187 9 100       304 if (ref $command) {
188 6         74 exec { $command->[0] } @$command;
  6         0  
189             } else {
190 3         0 exec $command;
191             }
192 0         0 exit 127;
193             }
194 25         2653 close $_ for grep $_, $stdout_write, $stderr_write;
195              
196 25 50       5149 my $signal_pid = $Config::Config{d_setpgrp} ? -$pid : $pid;
197              
198 25     0   211 my $INT; local $SIG{INT} = sub { $INT++ };
  25         2750  
  0         0  
199 25         186 my $timeout;
200 25 100       353 my $timeout_at = $self->{timeout} ? Time::HiRes::time() + $self->{timeout} : undef;
201 25         1804 my $select = IO::Select->new(grep $_, $stdout_read, $stderr_read);
202              
203 25         4912 while ($select->count) {
204 530 50       9629463 if ($INT) {
205 0         0 kill INT => $signal_pid;
206 0         0 $INT = 0;
207             }
208 530 100 66     2876 if ($timeout_at and !$timeout) {
209 131         822 my $now = Time::HiRes::time();
210 131 100       626 if ($now > $timeout_at) {
211 3         67 $timeout++;
212 3         475 kill TERM => $signal_pid;
213             }
214             }
215              
216 530         2152 for my $ready ($select->can_read($TICK)) {
217 87 100       149844 my $type = $ready == $stdout_read ? "stdout" : "stderr";
218 87         1409 my $len = sysread $ready, my $buf, 64*1024;
219 87 100       341 if ($len) {
220 43         214 my $buffer = $self->{_buffer}{$type};
221 43         825 $buffer->add($buf);
222 43 100       212 next unless my @line = $buffer->get;
223 40 100       242 next unless my $sub = $self->{$type};
224 38         378 $sub->($_) for @line;
225             } else {
226 44 50       145 warn "sysread $type pipe failed: $!" unless defined $len;
227 44         266 $select->remove($ready);
228 44         3080 close $ready;
229             }
230             }
231             }
232 25         293 for my $type (qw(stdout stderr)) {
233 50 100       250 next unless my $sub = $self->{$type};
234 46 100       180 my $buffer = $self->{_buffer}{$type} or next;
235 42 100       259 my @line = $buffer->get(1) or next;
236 5         109 $sub->($_) for @line;
237             }
238 25         102 close $_ for $select->handles;
239 25         1316 waitpid $pid, 0;
240             my $res = {
241             pid => $pid,
242             result => $?,
243             timeout => $timeout,
244             stdout => $self->{_buffer}{stdout} ? $self->{_buffer}{stdout}->raw : "",
245 25 50       252 stderr => $self->{_buffer}{stderr} ? $self->{_buffer}{stderr}->raw : "",
    100          
246             };
247 25         480 $self->{_buffer} = +{}; # cleanup
248 25         3479 return $res;
249             }
250              
251             1;
252             __END__