File Coverage

blib/lib/Test2/Tools/Process.pm
Criterion Covered Total %
statement 252 276 91.3
branch 58 74 78.3
condition 27 38 71.0
subroutine 54 56 96.4
pod 5 5 100.0
total 396 449 88.2


line stmt bran cond sub pod time code
1             package Test2::Tools::Process;
2              
3 8     8   901727 use strict;
  8         36  
  8         228  
4 8     8   56 use warnings;
  8         24  
  8         180  
5 8     8   166 use 5.010;
  8         30  
6 8     8   671 use Test2::Tools::Compare ();
  8         117515  
  8         210  
7 8     8   51 use Test2::API qw( context );
  8         23  
  8         465  
8 8     8   3928 use Ref::Util qw( is_plain_arrayref is_ref is_plain_coderef is_plain_hashref );
  8         13518  
  8         630  
9 8     8   69 use Carp qw( croak carp );
  8         16  
  8         406  
10 8     8   56 use Test2::Compare::Array ();
  8         16  
  8         114  
11 8     8   41 use Test2::Compare::Wildcard ();
  8         20  
  8         222  
12 8     8   48 use Test2::Compare::Number ();
  8         19  
  8         123  
13 8     8   42 use Test2::Compare::String ();
  8         26  
  8         121  
14 8     8   38 use Test2::Compare::Custom ();
  8         17  
  8         162  
15 8     8   44 use Test2::Compare ();
  8         16  
  8         193  
16 8     8   3680 use Return::MultiLevel qw( with_return );
  8         24097  
  8         475  
17 8     8   4113 use Capture::Tiny qw( capture_stdout );
  8         190302  
  8         513  
18 8     8   70 use base qw( Exporter );
  8         18  
  8         1902  
19              
20             our @EXPORT = qw( process proc_event named_signal intercept_exit intercept_exec );
21             our @CARP_NOT = qw( Test2::Tools::Process::SystemProc );
22              
23             # ABSTRACT: Unit tests for code that calls exit, exec, system or qx()
24             our $VERSION = '0.07'; # VERSION
25              
26              
27             our %handlers;
28              
29             BEGIN {
30              
31             %handlers = (
32 0         0 exit => sub (;$) { CORE::exit(@_) },
33 0         0 exec => sub { CORE::exec(@_) },
34 0         0 system => sub { CORE::system(@_) },
35 0         0 readpipe => sub (_) { CORE::readpipe(@_) },
36 8     8   128 );
37              
38 8     8   66 no warnings 'redefine';
  8         18  
  8         1554  
39 8     11   92 *CORE::GLOBAL::exit = sub (;$) { $handlers{exit}->(@_) };
  11         82  
40 8     21   53 *CORE::GLOBAL::exec = sub { $handlers{exec}->(@_) };
  21         530  
41 8     15   86 *CORE::GLOBAL::system = sub { $handlers{system}->(@_) };
  15         2783  
42 8     2   16100 *CORE::GLOBAL::readpipe = sub (_) { $handlers{readpipe}->(@_) };
  2         10  
43             }
44              
45              
46             sub process (&;@)
47             {
48 33     33 1 12111 my $sub = shift;
49 33         56 my @expected = ();
50 33         54 my $test_name = 'process ok';
51 33         48 my @events;
52 33         46 my $i = 0;
53              
54 33 100       97 if(is_plain_arrayref $_[0])
55             {
56 31         42 @expected = @{ shift() };
  31         67  
57             }
58              
59 33 100       113 $test_name = shift if defined $_[0];
60              
61             with_return {
62 33     33   421 my($return) = @_;
63              
64 33         161 local %handlers = %handlers;
65              
66             $handlers{exit} = sub {
67 8         17 my $expected = $expected[$i++];
68              
69 8         11 my $status = shift;
70 8 100       19 $status = 0 unless defined $status;
71 8         24 $status = int($status);
72 8         25 push @events, { event_type => 'exit', exit_status => $status };
73              
74 8 100 33     228 if(defined $expected && $expected->is_exit && defined $expected->callback)
      66        
75             {
76 2         17 my $proc = Test2::Tools::Process::Proc->new($return);
77 2         36 my $ret = $expected->callback->($proc, $status);
78 2 50       20 if(exists $proc->{errno})
79             {
80 0         0 $! = $proc->{errno};
81 0         0 return 0;
82             }
83 2         11 return $ret;
84             }
85             else
86             {
87 6         60 $return->();
88             }
89 33         129 };
90              
91             $handlers{exec} = sub {
92 13         28 my $expected = $expected[$i++];
93              
94 13 100 100     77 if(@_ == 1 || @_ == 0)
95             {
96 8         28 push @events, { event_type => 'exec', command => $_[0] };
97             }
98             else
99             {
100 5         21 push @events, { event_type => 'exec', command => [@_] };
101             }
102              
103 13 100 33     388 if(defined $expected && $expected->is_exec && defined $expected->callback)
      66        
104             {
105 1         14 my $proc = Test2::Tools::Process::Proc->new($return);
106 1         31 my $ret = $expected->callback->($proc, @_);
107 1 50       26 if(exists $proc->{errno})
108             {
109 0         0 $! = $proc->{errno};
110 0         0 return 0;
111             }
112 1         8 return $ret;
113             }
114             else
115             {
116 12         111 $return->();
117             }
118 33         135 };
119              
120 33         72 foreach my $type (qw( system readpipe ))
121             {
122             $handlers{$type} = sub {
123 17         33 my $expected = $expected[$i++];
124              
125 17         26 my $event;
126 17         34 my $args = \@_;
127 17 100 100     67 if(@_ == 1 || @_ == 0)
128             {
129 11         42 push @events, $event = { event_type => 'system', command => $_[0] };
130             }
131             else
132             {
133 6         26 push @events, $event = { event_type => 'system', command => [@_] };
134             }
135              
136 17 50 33     473 if(defined $expected && $expected->is_system && defined $expected->callback)
      33        
137             {
138             my $inner = sub {
139 17         32 my($return) = @_;
140 17         66 my $proc = Test2::Tools::Process::SystemProc->new($return, $event, $type);
141 17         369 $expected->callback->($proc, @$args);
142 9         62 $event->{status} = 0;
143 9         32 $? = 0;
144 17         166 };
145 17 100       41 if($type eq 'system')
146             {
147 15         85 with_return { $inner->(@_) };
  15         223  
148 15 100       187 return -1 if exists $event->{errno};
149 14         208 return $?;
150             }
151             else
152             {
153 2         45 return scalar capture_stdout { with_return { $inner->(@_) } };
  2         1966  
  2         29  
154             }
155             }
156             else
157             {
158             local $SIG{__WARN__} = sub {
159 0         0 my($message) = @_;
160 0         0 $message =~ s/ at .*? line [0-9]+\.$//;
161 0         0 chomp $message;
162 0         0 carp($message);
163 0         0 };
164 0 0       0 my $ret = $type eq 'system' ? CORE::system(@_) : CORE::readpipe(@_);
165 0 0       0 if($? == -1)
    0          
166             {
167 0         0 $event->{errno} = $!;
168             }
169             elsif($? & 127)
170             {
171 0         0 $event->{signal} = $? & 127;
172             }
173             else
174             {
175 0         0 $event->{status} = $? >> 8;
176             }
177 0         0 return $ret;
178             }
179 66         267 };
180             }
181              
182 33         111 $sub->();
183 33         224 };
184              
185             @_ = (
186             \@events,
187 33         3820 [ map { $_->to_check } @expected ],
  41         171  
188             $test_name
189             );
190              
191 33         365 goto \&Test2::Tools::Compare::is;
192             }
193              
194              
195             {
196             my $sig;
197             sub named_signal ($)
198             {
199 71     71 1 34698 my($name) = @_;
200              
201             # build hash on demand.
202 71   66     178 $sig ||= do {
203 1         8 require Config;
204 1         3 my %sig;
205 1         145 my @num = split /\s+/, $Config::Config{sig_num};
206 1         45 foreach my $name (split /\s+/, $Config::Config{sig_name})
207             {
208 69         153 $sig{$name} = shift @num;
209             }
210 1         20 \%sig;
211             };
212              
213 71 100       391 croak "no such signal: $name" unless exists $sig->{$name};
214              
215 70         167 $sig->{$name};
216             }
217             }
218              
219              
220             sub intercept_exit (&)
221             {
222 4     4 1 11386 my $sub = shift;
223              
224 4         10 my $ret;
225              
226             with_return {
227 4     4   51 my $return = shift;
228             local $handlers{exit} = sub {
229 3         7 $ret = shift;
230 3 100       11 $ret = 0 unless defined $ret;
231 3         6 $ret = int $ret;
232 3         7 $return->();
233 4         15 };
234 4         20 $sub->();
235 4         30 };
236              
237 4         427 $ret;
238             }
239              
240              
241             sub intercept_exec (&)
242             {
243 3     3 1 401 my $sub = shift;
244              
245 3         7 my $ret;
246              
247             with_return {
248 3     3   38 my $return = shift;
249             local $handlers{exec} = sub {
250 2         6 $ret = \@_;
251 2         7 $return->();
252 3         12 };
253 3         20 $sub->();
254 3         17 };
255              
256 3         399 $ret;
257             }
258              
259              
260             sub proc_event ($;$$$)
261             {
262 41     41 1 186660 my $type = shift;
263 41 50       111 croak("no such process event undef") unless defined $type;
264              
265 41         102 my $check;
266             my $check2;
267 41         0 my $callback;
268              
269 41 100 100     250 $check = shift if defined $_[0] && !is_plain_coderef $_[0] && !is_plain_hashref $_[0];
      100        
270 41 100 100     133 $check2 = shift if defined $_[0] && is_plain_hashref $_[0];
271              
272 41 100       81 if(defined $_[0])
273             {
274 21 50       49 if(is_plain_coderef $_[0])
275             {
276 21         38 $callback = shift;
277             }
278             else
279             {
280 0         0 croak("callback is not a code reference");
281             }
282             }
283              
284 41         140 my @caller = caller;
285              
286 41 100       226 if($type eq 'exit')
    50          
287             {
288 9 100       45 if(defined $check)
289             {
290 6 100       21 unless(is_ref $check)
291             {
292 4         56 $check = Test2::Compare::Number->new(
293             file => $caller[1],
294             lines => [$caller[2]],
295             input => $check,
296             );
297             }
298             }
299             else
300             {
301             $check = Test2::Compare::Custom->new(
302 2 50   2   1757 code => sub { defined $_ ? 1 : 0 },
303 3         32 name => 'DEFINED',
304             operator => 'DEFINED()',
305             file => $caller[1],
306             lines => [$caller[2]],
307             );
308             }
309              
310 9         340 return Test2::Tools::Process::Exit->new(status_check => $check, callback => $callback);
311             }
312              
313             elsif($type =~ /^(exec|system)$/)
314             {
315 32 100       70 if(defined $check)
316             {
317 22 100       64 if(is_plain_arrayref $check)
    100          
318             {
319 5         30 my $array = Test2::Compare::Array->new(
320             called => \@caller,
321             );
322 5         207 foreach my $item (@$check)
323             {
324 11         174 my $wc = Test2::Compare::Wildcard->new(
325             expect => $item,
326             file => $caller[1],
327             lines => [$caller[2]],
328             );
329 11         262 $array->add_item($wc);
330             }
331 5         112 $check = $array;
332             }
333             elsif(!is_ref $check)
334             {
335 4         22 $check = Test2::Compare::String->new(
336             file => $caller[1],
337             lines => [$caller[2]],
338             input => $check,
339             );
340             }
341             }
342             else
343             {
344             $check = Test2::Compare::Custom->new(
345 8 50   8   4707 code => sub { defined $_ ? 1 : 0 },
346 10         56 name => 'DEFINED',
347             operator => 'DEFINED()',
348             file => $caller[1],
349             lines => [$caller[2]],
350             );
351             }
352              
353 32 100       449 if($type eq 'system')
354             {
355 18   100     60 $check2 ||= { status => 0 };
356             }
357              
358 32 100       77 my $class = $type eq 'exec'
359             ? 'Test2::Tools::Process::Exec'
360             : 'Test2::Tools::Process::System';
361 32         183 return $class->new( command_check => $check, result_check => $check2, callback => $callback);
362             }
363              
364 0         0 croak("no such process event $type");
365             }
366              
367             package Test2::Tools::Process::Event;
368              
369 8     8   79 use constant is_exit => 0;
  8         22  
  8         534  
370 8     8   65 use constant is_exec => 0;
  8         17  
  8         403  
371 8     8   51 use constant is_system => 0;
  8         88  
  8         455  
372 8     8   4434 use Class::Tiny qw( callback );
  8         14837  
  8         36  
373              
374             package Test2::Tools::Process::Exit;
375              
376 8     8   1829 use constant is_exit => 1;
  8         18  
  8         758  
377 8     8   63 use base qw( Test2::Tools::Process::Event );
  8         16  
  8         2728  
378 8     8   61 use Class::Tiny qw( status_check );
  8         21  
  8         32  
379              
380             sub to_check
381             {
382 9     9   19 my($self) = @_;
383 9         175 { event_type => 'exit', exit_status => $self->status_check };
384             }
385              
386             package Test2::Tools::Process::Exec;
387              
388 8     8   2150 use constant is_exec => 1;
  8         20  
  8         563  
389 8     8   56 use base qw( Test2::Tools::Process::Event );
  8         15  
  8         2105  
390 8     8   79 use Class::Tiny qw( command_check );
  8         17  
  8         47  
391              
392             sub to_check
393             {
394 14     14   54 my($self) = @_;
395 14         291 { event_type => 'exec', command => $self->command_check };
396             }
397              
398             package Test2::Tools::Process::System;
399              
400 8     8   2147 use constant is_system => 1;
  8         29  
  8         499  
401 8     8   68 use base qw( Test2::Tools::Process::Event );
  8         35  
  8         2086  
402 8     8   58 use Class::Tiny qw( command_check result_check );
  8         16  
  8         43  
403              
404             sub to_check
405             {
406 18     18   35 my($self) = @_;
407 18         341 { event_type => 'system', command => $self->command_check, %{ $self->result_check } };
  18         332  
408             }
409              
410             package Test2::Tools::Process::Proc;
411              
412             sub new
413             {
414 3     3   12 my($class, $return) = @_;
415 3         13 bless {
416             return => $return,
417             }, $class;
418             }
419              
420 0     0   0 sub terminate { shift->{return}->() }
421              
422             sub errno
423             {
424 0     0   0 my($self, $errno) = @_;
425 0         0 $self->{errno} = $errno;
426             }
427              
428             package Test2::Tools::Process::SystemProc;
429              
430             sub new
431             {
432 17     17   39 my($class, $return, $result, $type) = @_;
433 17         65 bless {
434             return => $return,
435             result => $result,
436             type => $type,
437             }, $class;
438             }
439              
440 2     2   1139 sub type { shift->{type} }
441              
442             sub exit
443             {
444 5     5   7501 my($self, $status) = @_;
445 5 100       19 $status = 0 unless defined $status;
446 5         13 $status = int $status;
447 5         15 $self->{result}->{status} = $status;
448 5         14 $? = $status << 8;
449 5         16 $self->{return}->();
450             }
451              
452             sub signal
453             {
454 3     3   490 my($self, $signal) = @_;
455 3 50       9 $signal = 0 unless defined $signal;
456 3 100       12 if($signal =~ /^[A-Z]/i)
457             {
458 2         10 $signal = Test2::Tools::Process::named_signal($signal);
459             }
460             else
461             {
462 1         2 $signal = int $signal;
463             }
464 2         5 $self->{result}->{signal} = $signal;
465 2         5 $? = $signal;
466 2         6 $self->{return}->();
467             }
468              
469             sub errno
470             {
471 1     1   30 my($self, $errno) = @_;
472 1 50       16 $errno = 0 unless defined $errno;
473 1         3 $errno = int $errno;
474 1         13 $self->{result}->{errno} = $! = $errno;
475 1         4 $self->{return}->();
476             }
477              
478             1;
479              
480             __END__