Commit | Line | Data |
---|---|---|
b4514920 CG |
1 | package Test2::Hub; |
2 | use strict; | |
3 | use warnings; | |
4 | ||
7358a033 | 5 | our $VERSION = '1.302111'; |
b4514920 CG |
6 | |
7 | ||
8 | use Carp qw/carp croak confess/; | |
fa951d2c | 9 | use Test2::Util qw/get_tid ipc_separator/; |
b4514920 CG |
10 | |
11 | use Scalar::Util qw/weaken/; | |
07bc328a | 12 | use List::Util qw/first/; |
b4514920 CG |
13 | |
14 | use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/; | |
15 | use Test2::Util::HashBase qw{ | |
16 | pid tid hid ipc | |
07bc328a | 17 | nested buffered |
b4514920 CG |
18 | no_ending |
19 | _filters | |
20 | _pre_filters | |
21 | _listeners | |
22 | _follow_ups | |
23 | _formatter | |
24 | _context_acquire | |
25 | _context_init | |
26 | _context_release | |
27 | ||
9fe558dc | 28 | active |
b4514920 CG |
29 | count |
30 | failed | |
31 | ended | |
32 | bailed_out | |
33 | _passing | |
34 | _plan | |
35 | skip_reason | |
36 | }; | |
37 | ||
38 | my $ID_POSTFIX = 1; | |
39 | sub init { | |
40 | my $self = shift; | |
41 | ||
42 | $self->{+PID} = $$; | |
43 | $self->{+TID} = get_tid(); | |
fa951d2c | 44 | $self->{+HID} = join ipc_separator, $self->{+PID}, $self->{+TID}, $ID_POSTFIX++; |
b4514920 | 45 | |
07bc328a SH |
46 | $self->{+NESTED} = 0 unless defined $self->{+NESTED}; |
47 | $self->{+BUFFERED} = 0 unless defined $self->{+BUFFERED}; | |
48 | ||
b4514920 CG |
49 | $self->{+COUNT} = 0; |
50 | $self->{+FAILED} = 0; | |
51 | $self->{+_PASSING} = 1; | |
52 | ||
53 | if (my $formatter = delete $self->{formatter}) { | |
54 | $self->format($formatter); | |
55 | } | |
56 | ||
57 | if (my $ipc = $self->{+IPC}) { | |
58 | $ipc->add_hub($self->{+HID}); | |
59 | } | |
60 | } | |
61 | ||
c59be082 SH |
62 | sub is_subtest { 0 } |
63 | ||
07bc328a SH |
64 | sub _tb_reset { |
65 | my $self = shift; | |
66 | ||
67 | # Nothing to do | |
68 | return if $self->{+PID} == $$ && $self->{+TID} == get_tid(); | |
69 | ||
70 | $self->{+PID} = $$; | |
71 | $self->{+TID} = get_tid(); | |
72 | $self->{+HID} = join ipc_separator, $self->{+PID}, $self->{+TID}, $ID_POSTFIX++; | |
73 | ||
74 | if (my $ipc = $self->{+IPC}) { | |
75 | $ipc->add_hub($self->{+HID}); | |
76 | } | |
77 | } | |
78 | ||
b4514920 CG |
79 | sub reset_state { |
80 | my $self = shift; | |
81 | ||
82 | $self->{+COUNT} = 0; | |
83 | $self->{+FAILED} = 0; | |
84 | $self->{+_PASSING} = 1; | |
85 | ||
86 | delete $self->{+_PLAN}; | |
87 | delete $self->{+ENDED}; | |
88 | delete $self->{+BAILED_OUT}; | |
89 | delete $self->{+SKIP_REASON}; | |
90 | } | |
91 | ||
92 | sub inherit { | |
93 | my $self = shift; | |
94 | my ($from, %params) = @_; | |
95 | ||
07bc328a SH |
96 | $self->{+NESTED} ||= 0; |
97 | ||
b4514920 CG |
98 | $self->{+_FORMATTER} = $from->{+_FORMATTER} |
99 | unless $self->{+_FORMATTER} || exists($params{formatter}); | |
100 | ||
101 | if ($from->{+IPC} && !$self->{+IPC} && !exists($params{ipc})) { | |
102 | my $ipc = $from->{+IPC}; | |
103 | $self->{+IPC} = $ipc; | |
104 | $ipc->add_hub($self->{+HID}); | |
105 | } | |
106 | ||
107 | if (my $ls = $from->{+_LISTENERS}) { | |
108 | push @{$self->{+_LISTENERS}} => grep { $_->{inherit} } @$ls; | |
109 | } | |
110 | ||
35014935 SH |
111 | if (my $pfs = $from->{+_PRE_FILTERS}) { |
112 | push @{$self->{+_PRE_FILTERS}} => grep { $_->{inherit} } @$pfs; | |
113 | } | |
114 | ||
b4514920 CG |
115 | if (my $fs = $from->{+_FILTERS}) { |
116 | push @{$self->{+_FILTERS}} => grep { $_->{inherit} } @$fs; | |
117 | } | |
118 | } | |
119 | ||
120 | sub format { | |
121 | my $self = shift; | |
122 | ||
123 | my $old = $self->{+_FORMATTER}; | |
124 | ($self->{+_FORMATTER}) = @_ if @_; | |
125 | ||
126 | return $old; | |
127 | } | |
128 | ||
129 | sub is_local { | |
130 | my $self = shift; | |
131 | return $$ == $self->{+PID} | |
132 | && get_tid() == $self->{+TID}; | |
133 | } | |
134 | ||
135 | sub listen { | |
136 | my $self = shift; | |
137 | my ($sub, %params) = @_; | |
138 | ||
139 | carp "Useless addition of a listener in a child process or thread!" | |
140 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
141 | ||
142 | croak "listen only takes coderefs for arguments, got '$sub'" | |
143 | unless ref $sub && ref $sub eq 'CODE'; | |
144 | ||
145 | push @{$self->{+_LISTENERS}} => { %params, code => $sub }; | |
146 | ||
147 | $sub; # Intentional return. | |
148 | } | |
149 | ||
150 | sub unlisten { | |
151 | my $self = shift; | |
152 | ||
153 | carp "Useless removal of a listener in a child process or thread!" | |
154 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
155 | ||
156 | my %subs = map {$_ => $_} @_; | |
157 | ||
158 | @{$self->{+_LISTENERS}} = grep { !$subs{$_->{code}} } @{$self->{+_LISTENERS}}; | |
159 | } | |
160 | ||
161 | sub filter { | |
162 | my $self = shift; | |
163 | my ($sub, %params) = @_; | |
164 | ||
165 | carp "Useless addition of a filter in a child process or thread!" | |
166 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
167 | ||
168 | croak "filter only takes coderefs for arguments, got '$sub'" | |
169 | unless ref $sub && ref $sub eq 'CODE'; | |
170 | ||
171 | push @{$self->{+_FILTERS}} => { %params, code => $sub }; | |
172 | ||
173 | $sub; # Intentional Return | |
174 | } | |
175 | ||
176 | sub unfilter { | |
177 | my $self = shift; | |
178 | carp "Useless removal of a filter in a child process or thread!" | |
179 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
180 | my %subs = map {$_ => $_} @_; | |
181 | @{$self->{+_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_FILTERS}}; | |
182 | } | |
183 | ||
184 | sub pre_filter { | |
185 | my $self = shift; | |
186 | my ($sub, %params) = @_; | |
187 | ||
188 | croak "pre_filter only takes coderefs for arguments, got '$sub'" | |
189 | unless ref $sub && ref $sub eq 'CODE'; | |
190 | ||
191 | push @{$self->{+_PRE_FILTERS}} => { %params, code => $sub }; | |
192 | ||
193 | $sub; # Intentional Return | |
194 | } | |
195 | ||
196 | sub pre_unfilter { | |
197 | my $self = shift; | |
198 | my %subs = map {$_ => $_} @_; | |
199 | @{$self->{+_PRE_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_PRE_FILTERS}}; | |
200 | } | |
201 | ||
202 | sub follow_up { | |
203 | my $self = shift; | |
204 | my ($sub) = @_; | |
205 | ||
206 | carp "Useless addition of a follow-up in a child process or thread!" | |
207 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
208 | ||
209 | croak "follow_up only takes coderefs for arguments, got '$sub'" | |
210 | unless ref $sub && ref $sub eq 'CODE'; | |
211 | ||
212 | push @{$self->{+_FOLLOW_UPS}} => $sub; | |
213 | } | |
214 | ||
215 | *add_context_aquire = \&add_context_acquire; | |
216 | sub add_context_acquire { | |
217 | my $self = shift; | |
218 | my ($sub) = @_; | |
219 | ||
220 | croak "add_context_acquire only takes coderefs for arguments, got '$sub'" | |
221 | unless ref $sub && ref $sub eq 'CODE'; | |
222 | ||
223 | push @{$self->{+_CONTEXT_ACQUIRE}} => $sub; | |
224 | ||
225 | $sub; # Intentional return. | |
226 | } | |
227 | ||
228 | *remove_context_aquire = \&remove_context_acquire; | |
229 | sub remove_context_acquire { | |
230 | my $self = shift; | |
231 | my %subs = map {$_ => $_} @_; | |
232 | @{$self->{+_CONTEXT_ACQUIRE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_ACQUIRE}}; | |
233 | } | |
234 | ||
235 | sub add_context_init { | |
236 | my $self = shift; | |
237 | my ($sub) = @_; | |
238 | ||
239 | croak "add_context_init only takes coderefs for arguments, got '$sub'" | |
240 | unless ref $sub && ref $sub eq 'CODE'; | |
241 | ||
242 | push @{$self->{+_CONTEXT_INIT}} => $sub; | |
243 | ||
244 | $sub; # Intentional return. | |
245 | } | |
246 | ||
247 | sub remove_context_init { | |
248 | my $self = shift; | |
249 | my %subs = map {$_ => $_} @_; | |
250 | @{$self->{+_CONTEXT_INIT}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_INIT}}; | |
251 | } | |
252 | ||
253 | sub add_context_release { | |
254 | my $self = shift; | |
255 | my ($sub) = @_; | |
256 | ||
257 | croak "add_context_release only takes coderefs for arguments, got '$sub'" | |
258 | unless ref $sub && ref $sub eq 'CODE'; | |
259 | ||
260 | push @{$self->{+_CONTEXT_RELEASE}} => $sub; | |
261 | ||
262 | $sub; # Intentional return. | |
263 | } | |
264 | ||
265 | sub remove_context_release { | |
266 | my $self = shift; | |
267 | my %subs = map {$_ => $_} @_; | |
268 | @{$self->{+_CONTEXT_RELEASE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_RELEASE}}; | |
269 | } | |
270 | ||
271 | sub send { | |
272 | my $self = shift; | |
273 | my ($e) = @_; | |
274 | ||
275 | if ($self->{+_PRE_FILTERS}) { | |
276 | for (@{$self->{+_PRE_FILTERS}}) { | |
277 | $e = $_->{code}->($self, $e); | |
278 | return unless $e; | |
279 | } | |
280 | } | |
281 | ||
282 | my $ipc = $self->{+IPC} || return $self->process($e); | |
283 | ||
284 | if($e->global) { | |
285 | $ipc->send($self->{+HID}, $e, 'GLOBAL'); | |
286 | return $self->process($e); | |
287 | } | |
288 | ||
289 | return $ipc->send($self->{+HID}, $e) | |
290 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
291 | ||
292 | $self->process($e); | |
293 | } | |
294 | ||
295 | sub process { | |
296 | my $self = shift; | |
297 | my ($e) = @_; | |
298 | ||
299 | if ($self->{+_FILTERS}) { | |
300 | for (@{$self->{+_FILTERS}}) { | |
301 | $e = $_->{code}->($self, $e); | |
302 | return unless $e; | |
303 | } | |
304 | } | |
305 | ||
07bc328a | 306 | # Optimize the most common case |
b4514920 | 307 | my $type = ref($e); |
07bc328a SH |
308 | if ($type eq 'Test2::Event::Pass' || ($type eq 'Test2::Event::Ok' && $e->{pass})) { |
309 | my $count = ++($self->{+COUNT}); | |
310 | $self->{+_FORMATTER}->write($e, $count) if $self->{+_FORMATTER}; | |
b4514920 | 311 | |
07bc328a SH |
312 | if ($self->{+_LISTENERS}) { |
313 | $_->{code}->($self, $e, $count) for @{$self->{+_LISTENERS}}; | |
314 | } | |
315 | ||
316 | return $e; | |
317 | } | |
318 | ||
319 | my $f = $e->facet_data; | |
b4514920 | 320 | |
07bc328a SH |
321 | my $fail = 0; |
322 | $fail = 1 if $f->{assert} && !$f->{assert}->{pass}; | |
95db2efb | 323 | $fail = 1 if $f->{errors} && grep { $_->{fail} } @{$f->{errors}}; |
07bc328a | 324 | $fail = 0 if $f->{amnesty}; |
b4514920 | 325 | |
07bc328a SH |
326 | $self->{+COUNT}++ if $f->{assert}; |
327 | $self->{+FAILED}++ if $fail && $f->{assert}; | |
328 | $self->{+_PASSING} = 0 if $fail; | |
329 | ||
330 | my $code = $f->{control}->{terminate}; | |
b4514920 CG |
331 | my $count = $self->{+COUNT}; |
332 | ||
07bc328a SH |
333 | if (my $plan = $f->{plan}) { |
334 | if ($plan->{skip}) { | |
335 | $self->plan('SKIP'); | |
336 | $self->set_skip_reason($plan->{details} || 1); | |
337 | $code ||= 0; | |
338 | } | |
339 | elsif ($plan->{none}) { | |
340 | $self->plan('NO PLAN'); | |
341 | } | |
342 | else { | |
343 | $self->plan($plan->{count}); | |
344 | } | |
345 | } | |
346 | ||
347 | $e->callback($self) if $f->{control}->{has_callback}; | |
348 | ||
349 | $self->{+_FORMATTER}->write($e, $count, $f) if $self->{+_FORMATTER}; | |
b4514920 CG |
350 | |
351 | if ($self->{+_LISTENERS}) { | |
07bc328a | 352 | $_->{code}->($self, $e, $count, $f) for @{$self->{+_LISTENERS}}; |
b4514920 CG |
353 | } |
354 | ||
07bc328a SH |
355 | if ($f->{control}->{halt}) { |
356 | $code ||= 255; | |
357 | $self->set_bailed_out($e); | |
358 | } | |
b4514920 | 359 | |
c59be082 | 360 | if (defined $code) { |
07bc328a SH |
361 | $self->{+_FORMATTER}->terminate($e, $f) if $self->{+_FORMATTER}; |
362 | $self->terminate($code, $e, $f); | |
c59be082 | 363 | } |
b4514920 CG |
364 | |
365 | return $e; | |
366 | } | |
367 | ||
368 | sub terminate { | |
369 | my $self = shift; | |
370 | my ($code) = @_; | |
371 | exit($code); | |
372 | } | |
373 | ||
374 | sub cull { | |
375 | my $self = shift; | |
376 | ||
377 | my $ipc = $self->{+IPC} || return; | |
378 | return if $self->{+PID} != $$ || $self->{+TID} != get_tid(); | |
379 | ||
380 | # No need to do IPC checks on culled events | |
381 | $self->process($_) for $ipc->cull($self->{+HID}); | |
382 | } | |
383 | ||
384 | sub finalize { | |
385 | my $self = shift; | |
386 | my ($trace, $do_plan) = @_; | |
387 | ||
388 | $self->cull(); | |
389 | ||
390 | my $plan = $self->{+_PLAN}; | |
391 | my $count = $self->{+COUNT}; | |
392 | my $failed = $self->{+FAILED}; | |
9fe558dc | 393 | my $active = $self->{+ACTIVE}; |
b4514920 | 394 | |
07bc328a SH |
395 | # return if NOTHING was done. |
396 | unless ($active || $do_plan || defined($plan) || $count || $failed) { | |
397 | $self->{+_FORMATTER}->finalize($plan, $count, $failed, 0, $self->is_subtest) if $self->{+_FORMATTER}; | |
398 | return; | |
399 | } | |
b4514920 CG |
400 | |
401 | unless ($self->{+ENDED}) { | |
402 | if ($self->{+_FOLLOW_UPS}) { | |
403 | $_->($trace, $self) for reverse @{$self->{+_FOLLOW_UPS}}; | |
404 | } | |
405 | ||
406 | # These need to be refreshed now | |
407 | $plan = $self->{+_PLAN}; | |
408 | $count = $self->{+COUNT}; | |
409 | $failed = $self->{+FAILED}; | |
410 | ||
411 | if (($plan && $plan eq 'NO PLAN') || ($do_plan && !$plan)) { | |
412 | $self->send( | |
413 | Test2::Event::Plan->new( | |
414 | trace => $trace, | |
415 | max => $count, | |
416 | ) | |
417 | ); | |
418 | } | |
419 | $plan = $self->{+_PLAN}; | |
420 | } | |
421 | ||
422 | my $frame = $trace->frame; | |
423 | if($self->{+ENDED}) { | |
424 | my (undef, $ffile, $fline) = @{$self->{+ENDED}}; | |
425 | my (undef, $sfile, $sline) = @$frame; | |
426 | ||
427 | die <<" EOT" | |
428 | Test already ended! | |
429 | First End: $ffile line $fline | |
430 | Second End: $sfile line $sline | |
431 | EOT | |
432 | } | |
433 | ||
434 | $self->{+ENDED} = $frame; | |
c59be082 SH |
435 | my $pass = $self->is_passing(); # Generate the final boolean. |
436 | ||
07bc328a | 437 | $self->{+_FORMATTER}->finalize($plan, $count, $failed, $pass, $self->is_subtest) if $self->{+_FORMATTER}; |
c59be082 SH |
438 | |
439 | return $pass; | |
b4514920 CG |
440 | } |
441 | ||
442 | sub is_passing { | |
443 | my $self = shift; | |
444 | ||
445 | ($self->{+_PASSING}) = @_ if @_; | |
446 | ||
447 | # If we already failed just return 0. | |
fa951d2c | 448 | my $pass = $self->{+_PASSING} or return 0; |
b4514920 CG |
449 | return $self->{+_PASSING} = 0 if $self->{+FAILED}; |
450 | ||
451 | my $count = $self->{+COUNT}; | |
452 | my $ended = $self->{+ENDED}; | |
453 | my $plan = $self->{+_PLAN}; | |
454 | ||
455 | return $pass if !$count && $plan && $plan =~ m/^SKIP$/; | |
456 | ||
457 | return $self->{+_PASSING} = 0 | |
458 | if $ended && (!$count || !$plan); | |
459 | ||
460 | return $pass unless $plan && $plan =~ m/^\d+$/; | |
461 | ||
462 | if ($ended) { | |
463 | return $self->{+_PASSING} = 0 if $count != $plan; | |
464 | } | |
465 | else { | |
466 | return $self->{+_PASSING} = 0 if $count > $plan; | |
467 | } | |
468 | ||
469 | return $pass; | |
470 | } | |
471 | ||
472 | sub plan { | |
473 | my $self = shift; | |
474 | ||
475 | return $self->{+_PLAN} unless @_; | |
476 | ||
477 | my ($plan) = @_; | |
478 | ||
479 | confess "You cannot unset the plan" | |
480 | unless defined $plan; | |
481 | ||
482 | confess "You cannot change the plan" | |
483 | if $self->{+_PLAN} && $self->{+_PLAN} !~ m/^NO PLAN$/; | |
484 | ||
485 | confess "'$plan' is not a valid plan! Plan must be an integer greater than 0, 'NO PLAN', or 'SKIP'" | |
486 | unless $plan =~ m/^(\d+|NO PLAN|SKIP)$/; | |
487 | ||
488 | $self->{+_PLAN} = $plan; | |
489 | } | |
490 | ||
491 | sub check_plan { | |
492 | my $self = shift; | |
493 | ||
494 | return undef unless $self->{+ENDED}; | |
495 | my $plan = $self->{+_PLAN} || return undef; | |
496 | ||
497 | return 1 if $plan !~ m/^\d+$/; | |
498 | ||
499 | return 1 if $plan == $self->{+COUNT}; | |
500 | return 0; | |
501 | } | |
502 | ||
503 | sub DESTROY { | |
504 | my $self = shift; | |
505 | my $ipc = $self->{+IPC} || return; | |
506 | return unless $$ == $self->{+PID}; | |
507 | return unless get_tid() == $self->{+TID}; | |
b4514920 CG |
508 | $ipc->drop_hub($self->{+HID}); |
509 | } | |
510 | ||
511 | 1; | |
512 | ||
513 | __END__ | |
514 | ||
515 | =pod | |
516 | ||
517 | =encoding UTF-8 | |
518 | ||
519 | =head1 NAME | |
520 | ||
521 | Test2::Hub - The conduit through which all events flow. | |
522 | ||
523 | =head1 SYNOPSIS | |
524 | ||
525 | use Test2::Hub; | |
526 | ||
527 | my $hub = Test2::Hub->new(); | |
528 | $hub->send(...); | |
529 | ||
530 | =head1 DESCRIPTION | |
531 | ||
532 | The hub is the place where all events get processed and handed off to the | |
58818a66 | 533 | formatter. The hub also tracks test state, and provides several hooks into the |
b4514920 CG |
534 | event pipeline. |
535 | ||
536 | =head1 COMMON TASKS | |
537 | ||
538 | =head2 SENDING EVENTS | |
539 | ||
540 | $hub->send($event) | |
541 | ||
542 | The C<send()> method is used to issue an event to the hub. This method will | |
543 | handle thread/fork sync, filters, listeners, TAP output, etc. | |
544 | ||
545 | =head2 ALTERING OR REMOVING EVENTS | |
546 | ||
e7e8a349 | 547 | You can use either C<filter()> or C<pre_filter()>, depending on your |
b4514920 CG |
548 | needs. Both have identical syntax, so only C<filter()> is shown here. |
549 | ||
550 | $hub->filter(sub { | |
551 | my ($hub, $event) = @_; | |
552 | ||
553 | my $action = get_action($event); | |
554 | ||
555 | # No action should be taken | |
556 | return $event if $action eq 'none'; | |
557 | ||
558 | # You want your filter to remove the event | |
559 | return undef if $action eq 'delete'; | |
560 | ||
561 | if ($action eq 'do_it') { | |
562 | my $new_event = copy_event($event); | |
563 | ... Change your copy of the event ... | |
564 | return $new_event; | |
565 | } | |
566 | ||
567 | die "Should not happen"; | |
568 | }); | |
569 | ||
e7e8a349 | 570 | By default, filters are not inherited by child hubs. That means if you start a |
b4514920 CG |
571 | subtest, the subtest will not inherit the filter. You can change this behavior |
572 | with the C<inherit> parameter: | |
573 | ||
574 | $hub->filter(sub { ... }, inherit => 1); | |
575 | ||
576 | =head2 LISTENING FOR EVENTS | |
577 | ||
578 | $hub->listen(sub { | |
579 | my ($hub, $event, $number) = @_; | |
580 | ||
581 | ... do whatever you want with the event ... | |
582 | ||
583 | # return is ignored | |
584 | }); | |
585 | ||
e7e8a349 | 586 | By default listeners are not inherited by child hubs. That means if you start a |
b4514920 CG |
587 | subtest, the subtest will not inherit the listener. You can change this behavior |
588 | with the C<inherit> parameter: | |
589 | ||
590 | $hub->listen(sub { ... }, inherit => 1); | |
591 | ||
592 | ||
593 | =head2 POST-TEST BEHAVIORS | |
594 | ||
595 | $hub->follow_up(sub { | |
596 | my ($trace, $hub) = @_; | |
597 | ||
598 | ... do whatever you need to ... | |
599 | ||
600 | # Return is ignored | |
601 | }); | |
602 | ||
e7e8a349 | 603 | follow_up subs are called only once, either when done_testing is called, or in |
b4514920 CG |
604 | an END block. |
605 | ||
606 | =head2 SETTING THE FORMATTER | |
607 | ||
608 | By default an instance of L<Test2::Formatter::TAP> is created and used. | |
609 | ||
610 | my $old = $hub->format(My::Formatter->new); | |
611 | ||
612 | Setting the formatter will REPLACE any existing formatter. You may set the | |
613 | formatter to undef to prevent output. The old formatter will be returned if one | |
e7e8a349 | 614 | was already set. Only one formatter is allowed at a time. |
b4514920 CG |
615 | |
616 | =head1 METHODS | |
617 | ||
618 | =over 4 | |
619 | ||
620 | =item $hub->send($event) | |
621 | ||
622 | This is where all events enter the hub for processing. | |
623 | ||
624 | =item $hub->process($event) | |
625 | ||
626 | This is called by send after it does any IPC handling. You can use this to | |
627 | bypass the IPC process, but in general you should avoid using this. | |
628 | ||
629 | =item $old = $hub->format($formatter) | |
630 | ||
631 | Replace the existing formatter instance with a new one. Formatters must be | |
632 | objects that implement a C<< $formatter->write($event) >> method. | |
633 | ||
634 | =item $sub = $hub->listen(sub { ... }, %optional_params) | |
635 | ||
636 | You can use this to record all events AFTER they have been sent to the | |
637 | formatter. No changes made here will be meaningful, except possibly to other | |
638 | listeners. | |
639 | ||
640 | $hub->listen(sub { | |
641 | my ($hub, $event, $number) = @_; | |
642 | ||
643 | ... do whatever you want with the event ... | |
644 | ||
645 | # return is ignored | |
646 | }); | |
647 | ||
648 | Normally listeners are not inherited by child hubs such as subtests. You can | |
649 | add the C<< inherit => 1 >> parameter to allow a listener to be inherited. | |
650 | ||
651 | =item $hub->unlisten($sub) | |
652 | ||
653 | You can use this to remove a listen callback. You must pass in the coderef | |
654 | returned by the C<listen()> method. | |
655 | ||
656 | =item $sub = $hub->filter(sub { ... }, %optional_params) | |
657 | ||
658 | =item $sub = $hub->pre_filter(sub { ... }, %optional_params) | |
659 | ||
660 | These can be used to add filters. Filters can modify, replace, or remove events | |
661 | before anything else can see them. | |
662 | ||
663 | $hub->filter( | |
664 | sub { | |
665 | my ($hub, $event) = @_; | |
666 | ||
667 | return $event; # No Changes | |
668 | return; # Remove the event | |
669 | ||
670 | # Or you can modify an event before returning it. | |
671 | $event->modify; | |
672 | return $event; | |
673 | } | |
674 | ); | |
675 | ||
676 | If you are not using threads, forking, or IPC then the only difference between | |
677 | a C<filter> and a C<pre_filter> is that C<pre_filter> subs run first. When you | |
678 | are using threads, forking, or IPC, pre_filters happen to events before they | |
679 | are sent to their destination proc/thread, ordinary filters happen only in the | |
680 | destination hub/thread. | |
681 | ||
682 | You cannot add a regular filter to a hub if the hub was created in another | |
683 | process or thread. You can always add a pre_filter. | |
684 | ||
685 | =item $hub->unfilter($sub) | |
686 | ||
687 | =item $hub->pre_unfilter($sub) | |
688 | ||
689 | These can be used to remove filters and pre_filters. The C<$sub> argument is | |
690 | the reference returned by C<filter()> or C<pre_filter()>. | |
691 | ||
692 | =item $hub->follow_op(sub { ... }) | |
693 | ||
694 | Use this to add behaviors that are called just before the hub is finalized. The | |
07bc328a | 695 | only argument to your codeblock will be a L<Test2::EventFacet::Trace> instance. |
b4514920 CG |
696 | |
697 | $hub->follow_up(sub { | |
698 | my ($trace, $hub) = @_; | |
699 | ||
700 | ... do whatever you need to ... | |
701 | ||
702 | # Return is ignored | |
703 | }); | |
704 | ||
705 | follow_up subs are called only once, ether when done_testing is called, or in | |
706 | an END block. | |
707 | ||
708 | =item $sub = $hub->add_context_acquire(sub { ... }); | |
709 | ||
710 | Add a callback that will be called every time someone tries to acquire a | |
58818a66 | 711 | context. It gets a single argument, a reference of the hash of parameters |
b4514920 CG |
712 | being used the construct the context. This is your chance to change the |
713 | parameters by directly altering the hash. | |
714 | ||
715 | test2_add_callback_context_acquire(sub { | |
716 | my $params = shift; | |
717 | $params->{level}++; | |
718 | }); | |
719 | ||
720 | This is a very scary API function. Please do not use this unless you need to. | |
721 | This is here for L<Test::Builder> and backwards compatibility. This has you | |
722 | directly manipulate the hash instead of returning a new one for performance | |
723 | reasons. | |
724 | ||
725 | B<Note> Using this hook could have a huge performance impact. | |
726 | ||
727 | The coderef you provide is returned and can be used to remove the hook later. | |
728 | ||
729 | =item $hub->remove_context_acquire($sub); | |
730 | ||
731 | This can be used to remove a context acquire hook. | |
732 | ||
733 | =item $sub = $hub->add_context_init(sub { ... }); | |
734 | ||
735 | This allows you to add callbacks that will trigger every time a new context is | |
736 | created for the hub. The only argument to the sub will be the | |
737 | L<Test2::API::Context> instance that was created. | |
738 | ||
739 | B<Note> Using this hook could have a huge performance impact. | |
740 | ||
741 | The coderef you provide is returned and can be used to remove the hook later. | |
742 | ||
743 | =item $hub->remove_context_init($sub); | |
744 | ||
745 | This can be used to remove a context init hook. | |
746 | ||
747 | =item $sub = $hub->add_context_release(sub { ... }); | |
748 | ||
749 | This allows you to add callbacks that will trigger every time a context for | |
750 | this hub is released. The only argument to the sub will be the | |
751 | L<Test2::API::Context> instance that was released. These will run in reverse | |
752 | order. | |
753 | ||
754 | B<Note> Using this hook could have a huge performance impact. | |
755 | ||
756 | The coderef you provide is returned and can be used to remove the hook later. | |
757 | ||
758 | =item $hub->remove_context_release($sub); | |
759 | ||
760 | This can be used to remove a context release hook. | |
761 | ||
762 | =item $hub->cull() | |
763 | ||
764 | Cull any IPC events (and process them). | |
765 | ||
766 | =item $pid = $hub->pid() | |
767 | ||
768 | Get the process id under which the hub was created. | |
769 | ||
770 | =item $tid = $hub->tid() | |
771 | ||
772 | Get the thread id under which the hub was created. | |
773 | ||
774 | =item $hud = $hub->hid() | |
775 | ||
776 | Get the identifier string of the hub. | |
777 | ||
778 | =item $ipc = $hub->ipc() | |
779 | ||
780 | Get the IPC object used by the hub. | |
781 | ||
782 | =item $hub->set_no_ending($bool) | |
783 | ||
784 | =item $bool = $hub->no_ending | |
785 | ||
786 | This can be used to disable auto-ending behavior for a hub. The auto-ending | |
787 | behavior is triggered by an end block and is used to cull IPC events, and | |
788 | output the final plan if the plan was 'no_plan'. | |
789 | ||
9fe558dc SH |
790 | =item $bool = $hub->active |
791 | ||
792 | =item $hub->set_active($bool) | |
793 | ||
794 | These are used to get/set the 'active' attribute. When true this attribute will | |
795 | force C<< hub->finalize() >> to take action even if there is no plan, and no | |
796 | tests have been run. This flag is useful for plugins that add follow-up | |
797 | behaviors that need to run even if no events are seen. | |
798 | ||
b4514920 CG |
799 | =back |
800 | ||
801 | =head2 STATE METHODS | |
802 | ||
803 | =over 4 | |
804 | ||
805 | =item $hub->reset_state() | |
806 | ||
807 | Reset all state to the start. This sets the test count to 0, clears the plan, | |
808 | removes the failures, etc. | |
809 | ||
810 | =item $num = $hub->count | |
811 | ||
812 | Get the number of tests that have been run. | |
813 | ||
814 | =item $num = $hub->failed | |
815 | ||
816 | Get the number of failures (Not all failures come from a test fail, so this | |
817 | number can be larger than the count). | |
818 | ||
819 | =item $bool = $hub->ended | |
820 | ||
821 | True if the testing has ended. This MAY return the stack frame of the tool that | |
822 | ended the test, but that is not guaranteed. | |
823 | ||
824 | =item $bool = $hub->is_passing | |
825 | ||
826 | =item $hub->is_passing($bool) | |
827 | ||
828 | Check if the overall test run is a failure. Can also be used to set the | |
829 | pass/fail status. | |
830 | ||
831 | =item $hub->plan($plan) | |
832 | ||
833 | =item $plan = $hub->plan | |
834 | ||
835 | Get or set the plan. The plan must be an integer larger than 0, the string | |
836 | 'no_plan', or the string 'skip_all'. | |
837 | ||
838 | =item $bool = $hub->check_plan | |
839 | ||
840 | Check if the plan and counts match, but only if the tests have ended. If tests | |
58818a66 | 841 | have not ended this will return undef, otherwise it will be a true/false. |
b4514920 CG |
842 | |
843 | =back | |
844 | ||
845 | =head1 THIRD PARTY META-DATA | |
846 | ||
847 | This object consumes L<Test2::Util::ExternalMeta> which provides a consistent | |
848 | way for you to attach meta-data to instances of this class. This is useful for | |
58818a66 | 849 | tools, plugins, and other extensions. |
b4514920 CG |
850 | |
851 | =head1 SOURCE | |
852 | ||
853 | The source code repository for Test2 can be found at | |
854 | F<http://github.com/Test-More/test-more/>. | |
855 | ||
856 | =head1 MAINTAINERS | |
857 | ||
858 | =over 4 | |
859 | ||
860 | =item Chad Granum E<lt>exodist@cpan.orgE<gt> | |
861 | ||
862 | =back | |
863 | ||
864 | =head1 AUTHORS | |
865 | ||
866 | =over 4 | |
867 | ||
868 | =item Chad Granum E<lt>exodist@cpan.orgE<gt> | |
869 | ||
870 | =back | |
871 | ||
872 | =head1 COPYRIGHT | |
873 | ||
07bc328a | 874 | Copyright 2017 Chad Granum E<lt>exodist@cpan.orgE<gt>. |
b4514920 CG |
875 | |
876 | This program is free software; you can redistribute it and/or | |
877 | modify it under the same terms as Perl itself. | |
878 | ||
879 | See F<http://dev.perl.org/licenses/> | |
880 | ||
881 | =cut |