File Coverage

inc/Test2/API.pm
Criterion Covered Total %
statement 117 283 41.3
branch 17 98 17.3
condition 24 92 26.0
subroutine 39 76 51.3
pod 35 39 89.7
total 232 588 39.4


line stmt bran cond sub pod time code
1             #line 1
2 1     1   5 package Test2::API;
  1         1  
  1         20  
3 1     1   4 use strict;
  1         1  
  1         26  
4             use warnings;
5              
6 1   50 1   12 BEGIN {
7 1         50 $ENV{TEST_ACTIVE} ||= 1;
8             $ENV{TEST2_ACTIVE} = 1;
9             }
10              
11             our $VERSION = '1.302073';
12              
13              
14             my $INST;
15 4 100   4 1 40 my $ENDING = 0;
16 0     0 1 0 sub test2_set_is_end { ($ENDING) = @_ ? @_ : (1) }
17             sub test2_get_is_end { $ENDING }
18 1     1   362  
  1         2  
  1         7  
19             use Test2::API::Instance(\$INST);
20             # Set the exit status
21 1     1   4 END {
22 1         6 test2_set_is_end(); # See gh #16
23             $INST->set_exit();
24             }
25              
26             # See gh #16
27 1     1   6 {
  1         1  
  1         46  
28 1 50   1   48 no warnings;
29             INIT { eval 'END { test2_set_is_end() }; 1' or die $@ }
30             }
31              
32 1     1   4 BEGIN {
  1         2  
  1         73  
33 1 50 33 1   6 no warnings 'once';
      0        
34 1         13 if($] ge '5.014' || $ENV{T2_CHECK_DEPTH} || $Test2::API::DO_DEPTH_CHECK) {
35             *DO_DEPTH_CHECK = sub() { 1 };
36             }
37 0         0 else {
38             *DO_DEPTH_CHECK = sub() { 0 };
39             }
40             }
41 1     1   4  
  1         2  
  1         17  
42             use Test2::Util::Trace();
43 1     1   441  
  1         2  
  1         13  
44 1     1   307 use Test2::Hub::Subtest();
  1         2  
  1         14  
45 1     1   4 use Test2::Hub::Interceptor();
  1         1  
  1         9  
46             use Test2::Hub::Interceptor::Terminator();
47 1     1   329  
  1         1  
  1         29  
48 1     1   312 use Test2::Event::Ok();
  1         2  
  1         13  
49 1     1   319 use Test2::Event::Diag();
  1         2  
  1         13  
50 1     1   323 use Test2::Event::Note();
  1         2  
  1         15  
51 1     1   302 use Test2::Event::Plan();
  1         2  
  1         14  
52 1     1   298 use Test2::Event::Bail();
  1         2  
  1         14  
53 1     1   311 use Test2::Event::Exception();
  1         2  
  1         15  
54 1     1   307 use Test2::Event::Waiting();
  1         2  
  1         14  
55 1     1   320 use Test2::Event::Skip();
  1         2  
  1         17  
56             use Test2::Event::Subtest();
57 1     1   5  
  1         1  
  1         41  
58 1     1   4 use Carp qw/carp croak confess longmess/;
  1         1  
  1         32  
59 1     1   3 use Scalar::Util qw/blessed weaken/;
  1         1  
  1         71  
60             use Test2::Util qw/get_tid/;
61              
62             our @EXPORT_OK = qw{
63             context release
64             context_do
65             no_context
66             intercept
67             run_subtest
68              
69             test2_init_done
70             test2_load_done
71              
72             test2_set_is_end
73             test2_get_is_end
74              
75             test2_pid
76             test2_tid
77             test2_stack
78             test2_no_wait
79              
80             test2_add_callback_context_aquire
81             test2_add_callback_context_acquire
82             test2_add_callback_context_init
83             test2_add_callback_context_release
84             test2_add_callback_exit
85             test2_add_callback_post_load
86             test2_list_context_aquire_callbacks
87             test2_list_context_acquire_callbacks
88             test2_list_context_init_callbacks
89             test2_list_context_release_callbacks
90             test2_list_exit_callbacks
91             test2_list_post_load_callbacks
92              
93             test2_ipc
94             test2_ipc_drivers
95             test2_ipc_add_driver
96             test2_ipc_polling
97             test2_ipc_disable_polling
98             test2_ipc_enable_polling
99             test2_ipc_get_pending
100             test2_ipc_set_pending
101             test2_ipc_enable_shm
102              
103             test2_formatter
104             test2_formatters
105             test2_formatter_add
106             test2_formatter_set
107 1     1   5 };
  1         2328  
108             BEGIN { require Exporter; our @ISA = qw(Exporter) }
109              
110             my $STACK = $INST->stack;
111             my $CONTEXTS = $INST->contexts;
112             my $INIT_CBS = $INST->context_init_callbacks;
113             my $ACQUIRE_CBS = $INST->context_acquire_callbacks;
114 1     1 1 3  
115 1     1 1 4 sub test2_init_done { $INST->finalized }
116             sub test2_load_done { $INST->loaded }
117 0     0 0 0  
118 0     0 0 0 sub test2_pid { $INST->pid }
119 1     1 1 3 sub test2_tid { $INST->tid }
120             sub test2_stack { $INST->stack }
121 0 0   0 1 0 sub test2_no_wait {
122 0         0 $INST->set_no_wait(@_) if @_;
123             $INST->no_wait;
124             }
125 0     0 1 0  
126 1     1 0 5 sub test2_add_callback_context_acquire { $INST->add_context_acquire_callback(@_) }
127 0     0 1 0 sub test2_add_callback_context_aquire { $INST->add_context_acquire_callback(@_) }
128 0     0 1 0 sub test2_add_callback_context_init { $INST->add_context_init_callback(@_) }
129 1     1 1 4 sub test2_add_callback_context_release { $INST->add_context_release_callback(@_) }
130 0     0 1 0 sub test2_add_callback_exit { $INST->add_exit_callback(@_) }
131 0     0 0 0 sub test2_add_callback_post_load { $INST->add_post_load_callback(@_) }
  0         0  
132 0     0 1 0 sub test2_list_context_aquire_callbacks { @{$INST->context_acquire_callbacks} }
  0         0  
133 0     0 1 0 sub test2_list_context_acquire_callbacks { @{$INST->context_acquire_callbacks} }
  0         0  
134 0     0 1 0 sub test2_list_context_init_callbacks { @{$INST->context_init_callbacks} }
  0         0  
135 0     0 1 0 sub test2_list_context_release_callbacks { @{$INST->context_release_callbacks} }
  0         0  
136 0     0 1 0 sub test2_list_exit_callbacks { @{$INST->exit_callbacks} }
  0         0  
137             sub test2_list_post_load_callbacks { @{$INST->post_load_callbacks} }
138 1     1 1 6  
139 0     0 1 0 sub test2_ipc { $INST->ipc }
140 0     0 1 0 sub test2_ipc_add_driver { $INST->add_ipc_driver(@_) }
  0         0  
141 0     0 1 0 sub test2_ipc_drivers { @{$INST->ipc_drivers} }
142 0     0 1 0 sub test2_ipc_polling { $INST->ipc_polling }
143 0     0 1 0 sub test2_ipc_enable_polling { $INST->enable_ipc_polling }
144 0     0 1 0 sub test2_ipc_disable_polling { $INST->disable_ipc_polling }
145 0     0 1 0 sub test2_ipc_get_pending { $INST->get_ipc_pending }
146 0     0 1 0 sub test2_ipc_set_pending { $INST->set_ipc_pending(@_) }
147             sub test2_ipc_enable_shm { $INST->ipc_enable_shm }
148 1     1 1 3  
149 0     0 1 0 sub test2_formatter { $INST->formatter }
  0         0  
150 1     1 1 12 sub test2_formatters { @{$INST->formatters} }
151             sub test2_formatter_add { $INST->add_formatter(@_) }
152 0     0 1 0 sub test2_formatter_set {
153 0 0       0 my ($formatter) = @_;
154 0 0       0 croak "No formatter specified" unless $formatter;
155 0         0 croak "Global Formatter already set" if $INST->formatter_set;
156             $INST->set_formatter($formatter);
157             }
158              
159 1     1   2 # Private, for use in Test2::API::Context
160 0     0   0 sub _contexts_ref { $INST->contexts }
161 0     0   0 sub _context_acquire_callbacks_ref { $INST->context_acquire_callbacks }
162 1     1   5 sub _context_init_callbacks_ref { $INST->context_init_callbacks }
163             sub _context_release_callbacks_ref { $INST->context_release_callbacks }
164              
165 0     0   0 # Private, for use in Test2::IPC
166             sub _set_ipc { $INST->set_ipc(@_) }
167              
168 0     0 1 0 sub context_do(&;@) {
169 0         0 my $code = shift;
170             my @args = @_;
171 0         0  
172             my $ctx = context(level => 1);
173 0         0  
174             my $want = wantarray;
175 0         0  
176 0         0 my @out;
177 0 0       0 my $ok = eval {
    0          
178             $want ? @out = $code->($ctx, @args) :
179             defined($want) ? $out[0] = $code->($ctx, @args) :
180 0         0 $code->($ctx, @args) ;
181             1;
182 0         0 };
183             my $err = $@;
184 0         0  
185             $ctx->release;
186 0 0       0  
187             die $err unless $ok;
188 0 0       0  
189 0 0       0 return @out if $want;
190 0         0 return $out[0] if defined $want;
191             return;
192             }
193              
194 0     0 1 0 sub no_context(&;$) {
195 0   0     0 my ($code, $hid) = @_;
196             $hid ||= $STACK->top->hid;
197 0         0  
198 0         0 my $ctx = $CONTEXTS->{$hid};
199 0         0 delete $CONTEXTS->{$hid};
  0         0  
  0         0  
200 0         0 my $ok = eval { $code->(); 1 };
201             my $err = $@;
202 0         0  
203 0         0 $CONTEXTS->{$hid} = $ctx;
204             weaken($CONTEXTS->{$hid});
205 0 0       0  
206             die $err unless $ok;
207 0         0  
208             return;
209             };
210              
211             sub context {
212             # We need to grab these before anything else to ensure they are not
213 132     132 1 872 # changed.
214             my ($errno, $eval_error, $child_error) = (0 + $!, $@, $?);
215 132         685  
216             my %params = (level => 0, wrapped => 0, @_);
217              
218             # If something is getting a context then the sync system needs to be
219 132 100       326 # considered loaded...
220             $INST->load unless $INST->{loaded};
221 132 50       304  
222             croak "context() called, but return value is ignored"
223             unless defined wantarray;
224 132   66     353  
225 132   33     487 my $stack = $params{stack} || $STACK;
226 132         210 my $hub = $params{hub} || (@$stack ? $stack->[-1] : $stack->top);
227 132         203 my $hid = $hub->{hid};
228             my $current = $CONTEXTS->{$hid};
229 132         390  
230 132 50       237 $_->(\%params) for @$ACQUIRE_CBS;
  0         0  
231             map $_->(\%params), @{$hub->{_context_acquire}} if $hub->{_context_acquire};
232              
233             # This is for https://github.com/Test-More/test-more/issues/16
234 132   50     333 # and https://rt.perl.org/Public/Bug/Display.html?id=127774
235 132   33     460 my $phase = ${^GLOBAL_PHASE} || 'NA';
236             my $end_phase = $ENDING || $phase eq 'END' || $phase eq 'DESTRUCT';
237 132         215  
238 132 50       711 my $level = 1 + $params{level};
239 132 50 33     296 my ($pkg, $file, $line, $sub) = $end_phase ? caller(0) : caller($level);
240 0 0       0 unless ($pkg || $end_phase) {
241 0   0     0 confess "Could not find context at depth $level" unless $params{fudge};
242             ($pkg, $file, $line, $sub) = caller(--$level) while ($level >= 0 && !$pkg);
243             }
244 132         138  
245 132   100     805 my $depth = $level;
      66        
      100        
246 132         206 $depth++ while DO_DEPTH_CHECK && !$end_phase && (!$current || $depth <= $current->{_depth} + $params{wrapped}) && caller($depth + 1);
247 132   66     729 $depth -= $params{wrapped};
248             my $depth_ok = !DO_DEPTH_CHECK || $end_phase || !$current || $current->{_depth} < $depth;
249 132 50 66     244  
      33        
250 0   0     0 if ($current && $params{on_release} && $depth_ok) {
251 0         0 $current->{_on_release} ||= [];
  0         0  
252             push @{$current->{_on_release}} => $params{on_release};
253             }
254              
255 132 100 50     339 # I know this is ugly....
      66        
256             ($!, $@, $?) = ($errno, $eval_error, $child_error) and return bless(
257             {
258             %$current,
259             _is_canon => undef,
260             errno => $errno,
261             eval_error => $eval_error,
262             child_error => $child_error,
263             _is_spawn => [$pkg, $file, $line, $sub],
264             },
265             'Test2::API::Context'
266             ) if $current && $depth_ok;
267              
268 125 50       202 # Handle error condition of bad level
269 0 0       0 if ($current) {
  0         0  
270             unless (${$current->{_aborted}}) {
271 0 0       0 _canon_error($current, [$pkg, $file, $line, $sub, $depth])
272             unless $current->{_is_canon};
273 0 0       0  
274             _depth_error($current, [$pkg, $file, $line, $sub, $depth])
275             unless $depth_ok;
276             }
277 0 0       0  
278             $current->release if $current->{_is_canon};
279 0         0  
280             delete $CONTEXTS->{$hid};
281             }
282              
283             # Directly bless the object here, calling new is a noticeable performance
284 125         634 # hit with how often this needs to be called.
285             my $trace = bless(
286             {
287             frame => [$pkg, $file, $line, $sub],
288             pid => $$,
289             tid => get_tid(),
290             },
291             'Test2::Util::Trace'
292             );
293              
294             # Directly bless the object here, calling new is a noticeable performance
295 125         186 # hit with how often this needs to be called.
296             my $aborted = 0;
297             $current = bless(
298             {
299             _aborted => \$aborted,
300             stack => $stack,
301             hub => $hub,
302             trace => $trace,
303             _is_canon => 1,
304             _depth => $depth,
305             errno => $errno,
306             eval_error => $eval_error,
307 125 50       808 child_error => $child_error,
308             $params{on_release} ? (_on_release => [$params{on_release}]) : (),
309             },
310             'Test2::API::Context'
311             );
312 125         228  
313 125         444 $CONTEXTS->{$hid} = $current;
314             weaken($CONTEXTS->{$hid});
315 125         223  
316 125 50       321 $_->($current) for @$INIT_CBS;
  0         0  
317             map $_->($current), @{$hub->{_context_init}} if $hub->{_context_init};
318 125 50       187  
319             $params{on_init}->($current) if $params{on_init};
320 125         375  
321             ($!, $@, $?) = ($errno, $eval_error, $child_error);
322 125         425  
323             return $current;
324             }
325              
326 0     0   0 sub _depth_error {
327             _existing_error(@_, <<" EOT");
328             context() was called to retrieve an existing context, however the existing
329             context was created in a stack frame at the same, or deeper level. This usually
330             means that a tool failed to release the context when it was finished.
331             EOT
332             }
333              
334 0     0   0 sub _canon_error {
335             _existing_error(@_, <<" EOT");
336             context() was called to retrieve an existing context, however the existing
337             context has an invalid internal state (!_canon_count). This should not normally
338             happen unless something is mucking about with internals...
339             EOT
340             }
341              
342 0     0   0 sub _existing_error {
343 0         0 my ($ctx, $details, $msg) = @_;
344             my ($pkg, $file, $line, $sub, $depth) = @$details;
345 0         0  
346 0         0 my $oldframe = $ctx->{trace}->frame;
347             my $olddepth = $ctx->{_depth};
348 0         0  
349             my $mess = longmess();
350 0         0  
351             warn <<" EOT";
352             $msg
353             Old context details:
354             File: $oldframe->[1]
355             Line: $oldframe->[2]
356             Tool: $oldframe->[3]
357             Depth: $olddepth
358              
359             New context details:
360             File: $file
361             Line: $line
362             Tool: $sub
363             Depth: $depth
364              
365             Trace: $mess
366              
367             Removing the old context and creating a new one...
368             EOT
369             }
370              
371 4     4 1 13 sub release($;$) {
372 4         11 $_[0]->release;
373             return $_[1];
374             }
375              
376 0     0 1   sub intercept(&) {
377             my $code = shift;
378 0            
379             my $ctx = context();
380 0            
381 0 0         my $ipc;
382 0           if (my $global_ipc = test2_ipc()) {
383 0           my $driver = blessed($global_ipc);
384             $ipc = $driver->new;
385             }
386 0            
387             my $hub = Test2::Hub::Interceptor->new(
388             ipc => $ipc,
389             no_ending => 1,
390             );
391 0            
392 0     0     my @events;
  0            
393             $hub->listen(sub { push @events => $_[1] });
394 0            
395 0           $ctx->stack->top; # Make sure there is a top hub before we begin.
396             $ctx->stack->push($hub);
397 0            
398             my ($ok, $err) = (1, undef);
399             T2_SUBTEST_WRAPPER: {
400 0           # Do not use 'try' cause it localizes __DIE__
  0            
  0            
  0            
401 0           $ok = eval { $code->(hub => $hub, context => $ctx->snapshot); 1 };
402             $err = $@;
403              
404 0 0 0       # They might have done 'BEGIN { skip_all => "whatever" }'
      0        
      0        
405 0           if (!$ok && $err =~ m/Label not found for "last T2_SUBTEST_WRAPPER"/ || (blessed($err) && $err->isa('Test2::Hub::Interceptor::Terminator'))) {
406 0           $ok = 1;
407             $err = undef;
408             }
409             }
410 0            
411 0           $hub->cull;
412             $ctx->stack->pop($hub);
413 0            
414 0           my $trace = $ctx->trace;
415             $ctx->release;
416 0 0          
417             die $err unless $ok;
418 0 0 0        
      0        
419             $hub->finalize($trace, 1)
420             if $ok
421             && !$hub->no_ending
422             && !$hub->ended;
423 0            
424             return \@events;
425             }
426              
427 0     0 1   sub run_subtest {
428             my ($name, $code, $params, @args) = @_;
429 0 0          
430 0           $params = {buffered => $params} unless ref $params;
431 0           my $buffered = delete $params->{buffered};
432             my $inherit_trace = delete $params->{inherit_trace};
433 0            
434             my $ctx = context();
435 0 0          
436             $ctx->note($name) unless $buffered;
437 0            
438             my $parent = $ctx->hub;
439 0   0        
440 0           my $stack = $ctx->stack || $STACK;
441             my $hub = $stack->new_hub(
442             class => 'Test2::Hub::Subtest',
443             %$params,
444             );
445 0            
446 0 0         my @events;
447 0     0     $hub->set_nested( $parent->isa('Test2::Hub::Subtest') ? $parent->nested + 1 : 1 );
  0            
448             $hub->listen(sub { push @events => $_[1] });
449 0 0          
    0          
450 0 0         if ($buffered) {
451 0 0         if (my $format = $hub->format) {
452 0 0         my $hide = $format->can('hide_buffered') ? $format->hide_buffered : 1;
453             $hub->format(undef) if $hide;
454             }
455             }
456             elsif (! $parent->format) {
457             # If our parent has no format that means we're in a buffered subtest
458             # and now we're trying to run a streaming subtest. There's really no
459             # way for that to work, so we need to force the use of a buffered
460             # subtest here as
461 0           # well. https://github.com/Test-More/test-more/issues/721
462             $buffered = 1;
463             }
464 0 0          
465 0           if ($inherit_trace) {
466             my $orig = $code;
467 0     0     $code = sub {
468             my $st_ctx = Test2::API::Context->new(
469             trace => $ctx->trace,
470             hub => $hub,
471 0           );
472 0           $st_ctx->do_in_context($orig, @args);
473             };
474             }
475 0            
476             my ($ok, $err, $finished);
477             T2_SUBTEST_WRAPPER: {
478 0           # Do not use 'try' cause it localizes __DIE__
  0            
  0            
  0            
479 0           $ok = eval { $code->(@args); 1 };
480             $err = $@;
481              
482 0 0 0       # They might have done 'BEGIN { skip_all => "whatever" }'
      0        
      0        
483 0           if (!$ok && $err =~ m/Label not found for "last T2_SUBTEST_WRAPPER"/ || (blessed($err) && blessed($err) eq 'Test::Builder::Exception')) {
484 0           $ok = undef;
485             $err = undef;
486             }
487 0           else {
488             $finished = 1;
489             }
490 0           }
491             $stack->pop($hub);
492 0            
493             my $trace = $ctx->trace;
494 0 0          
495 0 0         if (!$finished) {
496 0           if(my $bailed = $hub->bailed_out) {
497             $ctx->bail($bailed->reason);
498 0           }
499 0           my $code = $hub->exit_code;
500 0 0         $ok = !$code;
501             $err = "Subtest ended with exit code $code" if $code;
502             }
503 0 0 0        
      0        
504             $hub->finalize($trace, 1)
505             if $ok
506             && !$hub->no_ending
507             && !$hub->ended;
508 0   0        
509 0           my $pass = $ok && $hub->is_passing;
510             my $e = $ctx->build_event(
511             'Subtest',
512             pass => $pass,
513             name => $name,
514             subtest_id => $hub->id,
515             buffered => $buffered,
516             subevents => \@events,
517             );
518 0            
519             my $plan_ok = $hub->check_plan;
520 0            
521             $ctx->hub->send($e);
522 0 0          
523             $ctx->failure_diag($e) unless $e->pass;
524 0 0          
525             $ctx->diag("Caught exception in subtest: $err") unless $ok;
526 0 0 0        
527             $ctx->diag("Bad subtest plan, expected " . $hub->plan . " but ran " . $hub->count)
528             if defined($plan_ok) && !$plan_ok;
529 0            
530 0           $ctx->release;
531             return $pass;
532             }
533              
534             # There is a use-cycle between API and API/Context. Context needs to use some
535             # API functions as the package is compiling. Test2::API::context() needs
536             # Test2::API::Context to be loaded, but we cannot 'require' the module there as
537             # it causes a very noticeable performance impact with how often context() is
538             # called.
539             require Test2::API::Context;
540              
541             1;
542              
543             __END__