File Coverage

blib/lib/Test/Script/Async.pm
Criterion Covered Total %
statement 202 216 93.5
branch 56 68 82.3
condition 17 27 62.9
subroutine 36 38 94.7
pod 17 17 100.0
total 328 366 89.6


line stmt bran cond sub pod time code
1             package Test::Script::Async;
2              
3 14     14   2241668 use strict;
  14         34  
  14         418  
4 14     14   210 use warnings;
  14         29  
  14         403  
5 14     14   401 use 5.008001;
  14         44  
6 14     14   78 use Carp ();
  14         38  
  14         364  
7 14     14   11262 use AnyEvent::Open3::Simple 0.86;
  14         178687  
  14         434  
8 14     14   100 use File::Spec ();
  14         28  
  14         295  
9 14     14   10475 use Probe::Perl;
  14         27306  
  14         504  
10 14     14   3037 use Test::Stream::Context qw( context );
  14         149721  
  14         147  
11 14     14   1231 use Test::Stream::Exporter;
  14         28  
  14         100  
12             default_exports qw( script_compiles script_runs );
13 14     14   1717 no Test::Stream::Exporter;
  14         29  
  14         76  
14              
15             # ABSTRACT: Non-blocking friendly tests for scripts
16             our $VERSION = '0.01'; # VERSION
17              
18              
19             sub _path ($)
20             {
21 53     53   329 my $path = shift;
22 53 50       201 Carp::croak("Did not provide a script name") unless $path;
23 53 50       805 Carp::croak("Script name must be relative") if File::Spec::Unix->file_name_is_absolute($path);
24 53         1625 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   325 $perl ||= Probe::Perl->find_perl_interpreter;
35             }
36              
37             sub _detect
38             {
39 90 50   90   13767 if(grep /^(Mojo|Mojolicious)(\/.*)?\.pm?$/, keys %INC)
40 0         0 { 'mojo' }
41             else
42 90         708 { return undef }
43             }
44              
45             sub _is_mojo
46             {
47 40     40   221 my $detect = _detect();
48 40 50       1047 defined $detect && $detect eq 'mojo';
49             }
50              
51              
52             sub script_compiles
53             {
54 5     5 1 19780 my($script, $test_name) = @_;
55 5         30 my @libs = map { "-I$_" } grep { !ref($_) } @INC;
  55         177  
  55         145  
56 5         28 my @cmd = ( _perl, @libs, '-c', _path $script );
57            
58 5   66     49 $test_name ||= "Script $script compiles";
59            
60             # TODO: also work with mojo
61 5         11 my $done;
62 5 50       19 unless(_detect())
63             {
64 5         1475 require AE;
65 5         14664 $done = AE::cv();
66             }
67 5         9926 my @stderr;
68              
69 5         21 my $ctx = context();
70              
71             my $ipc = AnyEvent::Open3::Simple->new(
72             on_stderr => sub {
73 6     6   30497 my($proc, $line) = @_;
74 6         95 push @stderr, $line;
75             },
76             on_exit => sub {
77 5     5   1535 my($proc, $exit, $sig) = @_;
78            
79 5   66     150 my $ok = $exit == 0 && $sig == 0 && grep / syntax OK$/, @stderr;
80            
81 5         97 $ctx->ok($ok, $test_name);
82 5 100       1609 $ctx->diag(@stderr) unless $ok;
83 5 100       347 $ctx->diag("exit - $exit") if $exit;
84 5 50       234 $ctx->diag("signal - $sig") if $sig;
85            
86 5         53 $done->send($ok);
87            
88             },
89             on_error => sub {
90 0     0   0 my($error) = @_;
91            
92 0         0 $ctx->ok(0, $test_name);
93 0         0 $ctx->diag("error compiling script: $error");
94            
95 0         0 $done->send(0);
96             },
97 5         497 );
98            
99 5         334 $ipc->run(@cmd);
100 5         26769 my $ok = $done->recv;
101 5         668 $ctx->release;
102            
103 5         448 $ok;
104             }
105              
106              
107             # TODO: support stdin input
108              
109             sub script_runs
110             {
111 24     24 1 61802 my($script, $test_name) = @_;
112 24         114 my @libs = map { "-I$_" } grep { !ref($_) } @INC;
  264         788  
  264         637  
113 24 100       153 $script = [ $script ] unless ref $script;
114 24         71 my @args;
115 24         83 ($script, @args) = @$script;
116 24         124 my @cmd = ( _perl, @libs, _path $script, @args );
117            
118 24 100 66     327 $test_name ||= @args ? "Script $script runs with arguments @args" : "Script $script runs";
119            
120             # TODO: also work with mojo
121 24         48 my $done;
122 24 50       76 unless(_detect())
123             {
124 24         6651 require AE;
125 24         66630 $done = AE::cv();
126             }
127 24         44053 my $run = bless {
128             script => _path $script,
129             args => [@args],
130             out => [],
131             err => [],
132             ok => 0,
133             }, __PACKAGE__;
134 24         149 my $ctx = context();
135              
136 24 100       2441 unless(-f $script)
137             {
138 4         41 $ctx->ok(0, $test_name);
139 4         749 $ctx->diag("script does not exist");
140 4         571 $run->{fail} = 'script not found';
141 4         30 $ctx->release;
142 4         103 return $run;
143             }
144              
145             my $ipc = AnyEvent::Open3::Simple->new(
146             implementation => _detect(),
147             on_stderr => sub {
148 20     20   21540 my(undef, $line) = @_;
149 20         63 push @{ $run->{err} }, $line;
  20         221  
150             },
151             on_stdout => sub {
152 25     25   114718 my(undef, $line) = @_;
153 25         77 push @{ $run->{out} }, $line;
  25         284  
154             },
155             on_exit => sub {
156 20     20   115652 (undef, $run->{exit}, $run->{signal}) = @_;
157              
158 20         107 $run->{ok} = 1;
159 20         395 $ctx->ok(1, $test_name);
160            
161 20 50       10337 _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->ok(0, $test_name);
170 0         0 $ctx->diag("error running script: $error");
171 0 0       0 _is_mojo() ? $done = 1 : $done->send;
172             },
173 20         93 );
174            
175 20         1358 $ipc->run(@cmd);
176 20 50       122799 if(_is_mojo())
177             {
178 0         0 Mojo::IOLoop->one_tick until $done;
179             }
180             else
181             {
182 20         387 $done->recv;
183             }
184 20         2787 $ctx->release;
185            
186 20         1827 $run;
187             }
188              
189              
190 7     7 1 347 sub out { shift->{out} }
191 5     5 1 122 sub err { shift->{err} }
192 43     43 1 531 sub exit { shift->{exit} }
193 54     54 1 317 sub signal { shift->{signal} }
194              
195              
196             our $reverse = 0;
197             our $level = 0;
198              
199             sub exit_is
200             {
201 12     12 1 9372 my($self, $value, $test_message) = @_;
202 12         61 my $ctx = context( level => $level );
203              
204 12 100 66     1001 $test_message ||= $reverse ? "script exited with a value other than $value" : "script exited with value $value";
205 12   66     45 my $ok = defined $self->exit && !$self->{signal} && ($reverse ? $self->exit != $value : $self->exit == $value);
206              
207 12         49 $ctx->ok($ok, $test_message);
208 12 100       1549 if(!defined $self->exit)
    100          
    100          
209             {
210 2         11 $ctx->diag("script did not run so did not exit");
211             }
212             elsif($self->signal)
213             {
214 2         6 $ctx->diag("script killed with signal @{[ $self->signal ]}");
  2         9  
215             }
216             elsif(!$ok)
217             {
218 3         7 $ctx->diag("script exited with value @{[ $self->exit ]}");
  3         9  
219             }
220            
221 12 100       775 $self->{ok} = 0 unless $ok;
222              
223 12         38 $ctx->release;
224 12         198 $self;
225             }
226              
227              
228             sub exit_isnt
229             {
230 5     5 1 74 local $reverse = 1;
231 5         13 local $level = 1;
232 5         11 shift->exit_is(@_);
233             }
234              
235              
236             sub signal_is
237             {
238 10     10 1 10949 my($self, $value, $test_message) = @_;
239 10         30 my $ctx = context(level => $level);
240              
241 10 100 66     698 $test_message ||= $reverse ? "script not killed by signal $value" : "script killed by signal $value";
242 10   66     32 my $ok = $self->signal && ($reverse ? $self->signal != $value : $self->signal == $value);
243              
244 10         34 $ctx->ok($ok, $test_message);
245 10 100       1093 if(!defined $self->signal)
    100          
    100          
246             {
247 2         11 $ctx->diag("script did not run so was not killed");
248             }
249             elsif(!$self->signal)
250             {
251 2         5 $ctx->diag("script exited with value @{[ $self->exit ]}");
  2         12  
252             }
253             elsif(!$ok)
254             {
255 2         4 $ctx->diag("script killed with signal @{[ $self->signal ]}");
  2         6  
256             }
257              
258 10 100       540 $self->{ok} = 0 unless $ok;
259              
260 10         31 $ctx->release;
261 10         176 $self;
262             }
263              
264              
265             sub signal_isnt
266             {
267 5     5 1 15692 local $reverse = 1;
268 5         12 local $level = 1;
269 5         17 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 6632 my($self, $regex, $test_name) = @_;
279            
280 9         45 my $ctx = context(level => $level);
281 9 100 33     654 $test_name ||= $reverse ? "$stream_name does not match $regex" : "$stream_name matches $regex";
282            
283 9         16 my $ok;
284             my @diag;
285            
286 9 100       25 if($reverse)
287             {
288 4         6 $ok = 1;
289 4         7 my $num = 1;
290 4         7 foreach my $line (@{ $self->{$stream} })
  4         13  
291             {
292 16 100       68 if($line =~ $regex)
293             {
294 2         6 $ok = 0;
295 2         9 push @diag, "line $num of $stream_name matches: $line";
296             }
297 16         27 $num++;
298             }
299             }
300             else
301             {
302 5         16 $ok = 0;
303 5         11 foreach my $line (@{ $self->{$stream} })
  5         23  
304             {
305 15 100       86 if($line =~ $regex)
306             {
307 3         9 $ok = 1;
308 3         12 last;
309             }
310             }
311             }
312            
313 9         34 $ctx->ok($ok, $test_name);
314 9         1128 $ctx->diag($_) for @diag;
315            
316 9         224 $ctx->release;
317 9 100       170 $self->{ok} = 0 unless $ok;
318            
319 9         55 $self;
320             }
321              
322              
323             sub out_unlike
324             {
325 2     2 1 6362 local $reverse = 1;
326 2         6 local $level = 1;
327 2         9 shift->out_like(@_);
328             }
329              
330              
331             sub err_like
332             {
333 2     2 1 5585 local $stream = 'err';
334 2         7 local $stream_name = 'standard error';
335 2         3 local $level = 1;
336 2         8 shift->out_like(@_);
337             }
338              
339              
340             sub err_unlike
341             {
342 2     2 1 6401 local $stream = 'err';
343 2         4 local $stream_name = 'standard error';
344 2         10 local $reverse = 1;
345 2         4 local $level = 1;
346 2         7 shift->out_like(@_);
347             }
348              
349              
350             our $diag = 'diag';
351              
352             sub diag
353             {
354 4     4 1 24 my($self) = @_;
355              
356 4         43 my $ctx = context(level => $level);
357            
358 4         605 $ctx->$diag("script: @{[ $self->{script} ]}");
  4         73  
359 4 100       997 $ctx->$diag("arguments: @{[ join ' ', @{ $self->{args} } ]}") if @{ $self->{args} };
  3         7  
  3         31  
  4         37  
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         7 $ctx->$diag("signal: @{[ $self->signal ]}");
  1         10  
367             }
368             else
369             {
370 3         11 $ctx->$diag("exit: @{[ $self->exit ]}");
  3         20  
371             }
372 4         505 $ctx->$diag("[out] $_") for @{ $self->out };
  4         29  
373 4         1009 $ctx->$diag("[err] $_") for @{ $self->err };
  4         32  
374            
375 4         1070 $ctx->release;
376            
377 4         101 $self;
378             }
379              
380              
381             sub note
382             {
383 3     3 1 275 local $diag = 'note';
384 3         26 local $level = 1;
385 3         17 shift->diag;
386             }
387              
388              
389             sub diag_if_fail
390             {
391 4     4 1 4439 my($self) = @_;
392 4 100       43 return if $self->{ok};
393 1         4 local $level = 1;
394 1         11 $self->diag;
395 1         28 $self;
396             }
397              
398             1;
399              
400             __END__