File Coverage

blib/lib/Test/Script/Async.pm
Criterion Covered Total %
statement 199 213 93.4
branch 56 68 82.3
condition 17 27 62.9
subroutine 35 37 94.5
pod 17 17 100.0
total 324 362 89.5


line stmt bran cond sub pod time code
1             package Test::Script::Async;
2              
3 14     14   1074368 use strict;
  14         22  
  14         332  
4 14     14   47 use warnings;
  14         15  
  14         260  
5 14     14   296 use 5.008001;
  14         31  
6 14     14   48 use Carp ();
  14         22  
  14         252  
7 14     14   6425 use AnyEvent::Open3::Simple 0.86;
  14         325806  
  14         418  
8 14     14   102 use File::Spec ();
  14         19  
  14         233  
9 14     14   5723 use Probe::Perl;
  14         16505  
  14         373  
10 14     14   1486 use Test2::API qw( context );
  14         68175  
  14         525  
11 14     14   7792 use base qw( Exporter );
  14         16  
  14         24140  
12              
13             # ABSTRACT: Non-blocking friendly tests for scripts
14             our $VERSION = '0.02'; # VERSION
15              
16             our @EXPORT = qw( script_compiles script_runs );
17              
18              
19             sub _path ($)
20             {
21 53     53   264 my $path = shift;
22 53 50       125 Carp::croak("Did not provide a script name") unless $path;
23 53 50       539 Carp::croak("Script name must be relative") if File::Spec::Unix->file_name_is_absolute($path);
24 53         1069 File::Spec->catfile(
25             File::Spec->curdir,
26             split /\//, $path
27             );
28             }
29              
30             my $perl;
31              
32             sub _perl ()
33             {
34 29   66 29   227 $perl ||= Probe::Perl->find_perl_interpreter;
35             }
36              
37             sub _detect
38             {
39 90 50   90   7196 if(grep /^(Mojo|Mojolicious)(\/.*)?\.pm?$/, keys %INC)
40 0         0 { 'mojo' }
41             else
42 90         541 { return undef }
43             }
44              
45             sub _is_mojo
46             {
47 40     40   131 my $detect = _detect();
48 40 50       620 defined $detect && $detect eq 'mojo';
49             }
50              
51              
52             sub script_compiles
53             {
54 5     5 1 13810 my($script, $test_name) = @_;
55 5         17 my @libs = map { "-I$_" } grep { !ref($_) } @INC;
  55         82  
  55         56  
56 5         18 my @cmd = ( _perl, @libs, '-c', _path $script );
57            
58 5   66     30 $test_name ||= "Script $script compiles";
59            
60             # TODO: also work with mojo
61 5         4 my $done;
62 5 50       11 unless(_detect())
63             {
64 5         1006 require AE;
65 5         10632 $done = AE::cv();
66             }
67 5         6938 my @stderr;
68              
69 5         20 my $ctx = context();
70              
71             my $ipc = AnyEvent::Open3::Simple->new(
72             on_stderr => sub {
73 6     6   16557 my($proc, $line) = @_;
74 6         142 push @stderr, $line;
75             },
76             on_exit => sub {
77 5     5   7589 my($proc, $exit, $sig) = @_;
78            
79 5   66     144 my $ok = $exit == 0 && $sig == 0 && grep / syntax OK$/, @stderr;
80            
81 5         72 $ctx->send_event('Ok', pass => $ok, name => $test_name);
82 5 100       1199 $ctx->diag(@stderr) unless $ok;
83 5 100       196 $ctx->diag("exit - $exit") if $exit;
84 5 50       170 $ctx->diag("signal - $sig") if $sig;
85            
86 5         40 $done->send($ok);
87            
88             },
89             on_error => sub {
90 0     0   0 my($error) = @_;
91            
92 0         0 $ctx->send_event('Ok', pass => 0, name => $test_name);
93 0         0 $ctx->diag("error compiling script: $error");
94            
95 0         0 $done->send(0);
96             },
97 5         424 );
98            
99 5         232 $ipc->run(@cmd);
100 5         19025 my $ok = $done->recv;
101 5         480 $ctx->release;
102            
103 5         321 $ok;
104             }
105              
106              
107             # TODO: support stdin input
108              
109             sub script_runs
110             {
111 24     24 1 54702 my($script, $test_name) = @_;
112 24         80 my @libs = map { "-I$_" } grep { !ref($_) } @INC;
  264         400  
  264         283  
113 24 100       100 $script = [ $script ] unless ref $script;
114 24         30 my @args;
115 24         58 ($script, @args) = @$script;
116 24         65 my @cmd = ( _perl, @libs, _path $script, @args );
117            
118 24 100 66     202 $test_name ||= @args ? "Script $script runs with arguments @args" : "Script $script runs";
119            
120             # TODO: also work with mojo
121 24         30 my $done;
122 24 50       54 unless(_detect())
123             {
124 24         3703 require AE;
125 24         42487 $done = AE::cv();
126             }
127 24         27404 my $run = bless {
128             script => _path $script,
129             args => [@args],
130             out => [],
131             err => [],
132             ok => 0,
133             }, __PACKAGE__;
134 24         105 my $ctx = context();
135              
136 24 100       1984 unless(-f $script)
137             {
138 4         30 $ctx->send_event('Ok', pass => 0, name => $test_name);
139 4         224 $ctx->diag("script does not exist");
140 4         215 $run->{fail} = 'script not found';
141 4         20 $ctx->release;
142 4         64 return $run;
143             }
144              
145             my $ipc = AnyEvent::Open3::Simple->new(
146             implementation => _detect(),
147             on_stderr => sub {
148 20     20   11556 my(undef, $line) = @_;
149 20         26 push @{ $run->{err} }, $line;
  20         130  
150             },
151             on_stdout => sub {
152 25     25   69030 my(undef, $line) = @_;
153 25         38 push @{ $run->{out} }, $line;
  25         237  
154             },
155             on_exit => sub {
156 20     20   72949 (undef, $run->{exit}, $run->{signal}) = @_;
157              
158 20         68 $run->{ok} = 1;
159 20         235 $ctx->send_event('Ok', pass => 1, name => $test_name);
160            
161 20 50       6878 _is_mojo() ? $done = 1 : $done->send;
162            
163             },
164             on_error => sub {
165 0     0   0 my($error) = @_;
166            
167 0         0 $run->{ok} = 0;
168 0         0 $run->{fail} = $error;
169 0         0 $ctx->send_event('Ok', pass => 0, name => $test_name);
170 0         0 $ctx->diag("error running script: $error");
171 0 0       0 _is_mojo() ? $done = 1 : $done->send;
172             },
173 20         228 );
174            
175 20         1042 $ipc->run(@cmd);
176 20 50       58177 if(_is_mojo())
177             {
178 0         0 Mojo::IOLoop->one_tick until $done;
179             }
180             else
181             {
182 20         208 $done->recv;
183             }
184 20         1903 $ctx->release;
185            
186 20         1384 $run;
187             }
188              
189              
190 7     7 1 143 sub out { shift->{out} }
191 5     5 1 78 sub err { shift->{err} }
192 43     43 1 311 sub exit { shift->{exit} }
193 54     54 1 221 sub signal { shift->{signal} }
194              
195              
196             our $reverse = 0;
197             our $level = 0;
198              
199             sub exit_is
200             {
201 12     12 1 4412 my($self, $value, $test_name) = @_;
202 12         49 my $ctx = context( level => $level );
203              
204 12 100 66     941 $test_name ||= $reverse ? "script exited with a value other than $value" : "script exited with value $value";
205 12   66     113 my $ok = defined $self->exit && !$self->{signal} && ($reverse ? $self->exit != $value : $self->exit == $value);
206              
207 12         43 $ctx->send_event('Ok', pass => $ok, name => $test_name);
208 12 100       678 if(!defined $self->exit)
    100          
    100          
209             {
210 2         10 $ctx->diag("script did not run so did not exit");
211             }
212             elsif($self->signal)
213             {
214 2         5 $ctx->diag("script killed with signal @{[ $self->signal ]}");
  2         3  
215             }
216             elsif(!$ok)
217             {
218 3         5 $ctx->diag("script exited with value @{[ $self->exit ]}");
  3         19  
219             }
220            
221 12 100       362 $self->{ok} = 0 unless $ok;
222              
223 12         32 $ctx->release;
224 12         213 $self;
225             }
226              
227              
228             sub exit_isnt
229             {
230 5     5 1 44 local $reverse = 1;
231 5         8 local $level = 1;
232 5         12 shift->exit_is(@_);
233             }
234              
235              
236             sub signal_is
237             {
238 10     10 1 6303 my($self, $value, $test_name) = @_;
239 10         19 my $ctx = context(level => $level);
240              
241 10 100 66     444 $test_name ||= $reverse ? "script not killed by signal $value" : "script killed by signal $value";
242 10   66     17 my $ok = $self->signal && ($reverse ? $self->signal != $value : $self->signal == $value);
243              
244 10         25 $ctx->send_event('Ok', pass => $ok, name => $test_name);
245 10 100       405 if(!defined $self->signal)
    100          
    100          
246             {
247 2         5 $ctx->diag("script did not run so was not killed");
248             }
249             elsif(!$self->signal)
250             {
251 2         3 $ctx->diag("script exited with value @{[ $self->exit ]}");
  2         5  
252             }
253             elsif(!$ok)
254             {
255 2         3 $ctx->diag("script killed with signal @{[ $self->signal ]}");
  2         3  
256             }
257              
258 10 100       235 $self->{ok} = 0 unless $ok;
259              
260 10         21 $ctx->release;
261 10         137 $self;
262             }
263              
264              
265             sub signal_isnt
266             {
267 5     5 1 5651 local $reverse = 1;
268 5         4 local $level = 1;
269 5         11 shift->signal_is(@_);
270             }
271              
272              
273             our $stream = 'out';
274             our $stream_name = 'standard output';
275              
276             sub out_like
277             {
278 9     9 1 2482 my($self, $regex, $test_name) = @_;
279            
280 9         26 my $ctx = context(level => $level);
281 9 100 33     517 $test_name ||= $reverse ? "$stream_name does not match $regex" : "$stream_name matches $regex";
282            
283 9         9 my $ok;
284             my @diag;
285            
286 9 100       16 if($reverse)
287             {
288 4         4 $ok = 1;
289 4         5 my $num = 1;
290 4         4 foreach my $line (@{ $self->{$stream} })
  4         9  
291             {
292 16 100       56 if($line =~ $regex)
293             {
294 2         4 $ok = 0;
295 2         7 push @diag, "line $num of $stream_name matches: $line";
296             }
297 16         17 $num++;
298             }
299             }
300             else
301             {
302 5         8 $ok = 0;
303 5         7 foreach my $line (@{ $self->{$stream} })
  5         17  
304             {
305 15 100       58 if($line =~ $regex)
306             {
307 3         7 $ok = 1;
308 3         8 last;
309             }
310             }
311             }
312            
313 9         27 $ctx->send_event('Ok', pass => $ok, name => $test_name);
314 9         567 $ctx->diag($_) for @diag;
315            
316 9         133 $ctx->release;
317 9 100       148 $self->{ok} = 0 unless $ok;
318            
319 9         27 $self;
320             }
321              
322              
323             sub out_unlike
324             {
325 2     2 1 2855 local $reverse = 1;
326 2         3 local $level = 1;
327 2         6 shift->out_like(@_);
328             }
329              
330              
331             sub err_like
332             {
333 2     2 1 2527 local $stream = 'err';
334 2         5 local $stream_name = 'standard error';
335 2         4 local $level = 1;
336 2         5 shift->out_like(@_);
337             }
338              
339              
340             sub err_unlike
341             {
342 2     2 1 2488 local $stream = 'err';
343 2         4 local $stream_name = 'standard error';
344 2         6 local $reverse = 1;
345 2         3 local $level = 1;
346 2         4 shift->out_like(@_);
347             }
348              
349              
350             our $diag = 'diag';
351              
352             sub diag
353             {
354 4     4 1 10 my($self) = @_;
355              
356 4         34 my $ctx = context(level => $level);
357            
358 4         366 $ctx->$diag("script: @{[ $self->{script} ]}");
  4         38  
359 4 100       786 $ctx->$diag("arguments: @{[ join ' ', @{ $self->{args} } ]}") if @{ $self->{args} };
  3         101  
  3         27  
  4         21  
360 4 50       506 if(defined $self->{fail})
    100          
361             {
362 0         0 $ctx->$diag("error: @{[ $self->{fail} ]}");
  0         0  
363             }
364             elsif($self->signal)
365             {
366 1         5 $ctx->$diag("signal: @{[ $self->signal ]}");
  1         5  
367             }
368             else
369             {
370 3         6 $ctx->$diag("exit: @{[ $self->exit ]}");
  3         12  
371             }
372 4         485 $ctx->$diag("[out] $_") for @{ $self->out };
  4         114  
373 4         702 $ctx->$diag("[err] $_") for @{ $self->err };
  4         13  
374            
375 4         685 $ctx->release;
376            
377 4         69 $self;
378             }
379              
380              
381             sub note
382             {
383 3     3 1 125 local $diag = 'note';
384 3         12 local $level = 1;
385 3         13 shift->diag;
386             }
387              
388              
389             sub diag_if_fail
390             {
391 4     4 1 427 my($self) = @_;
392 4 100       17 return if $self->{ok};
393 1         4 local $level = 1;
394 1         6 $self->diag;
395 1         5 $self;
396             }
397              
398             1;
399              
400             __END__