15759b3d2Safresh1package Test2::Hub; 25759b3d2Safresh1use strict; 35759b3d2Safresh1use warnings; 45759b3d2Safresh1 5*5486feefSafresh1our $VERSION = '1.302199'; 65759b3d2Safresh1 75759b3d2Safresh1 85759b3d2Safresh1use Carp qw/carp croak confess/; 9f3efcd01Safresh1use Test2::Util qw/get_tid gen_uid/; 105759b3d2Safresh1 115759b3d2Safresh1use Scalar::Util qw/weaken/; 125759b3d2Safresh1use List::Util qw/first/; 135759b3d2Safresh1 145759b3d2Safresh1use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/; 155759b3d2Safresh1use Test2::Util::HashBase qw{ 165759b3d2Safresh1 pid tid hid ipc 175759b3d2Safresh1 nested buffered 185759b3d2Safresh1 no_ending 195759b3d2Safresh1 _filters 205759b3d2Safresh1 _pre_filters 215759b3d2Safresh1 _listeners 225759b3d2Safresh1 _follow_ups 235759b3d2Safresh1 _formatter 245759b3d2Safresh1 _context_acquire 255759b3d2Safresh1 _context_init 265759b3d2Safresh1 _context_release 275759b3d2Safresh1 285759b3d2Safresh1 uuid 295759b3d2Safresh1 active 305759b3d2Safresh1 count 315759b3d2Safresh1 failed 325759b3d2Safresh1 ended 335759b3d2Safresh1 bailed_out 345759b3d2Safresh1 _passing 355759b3d2Safresh1 _plan 365759b3d2Safresh1 skip_reason 375759b3d2Safresh1}; 385759b3d2Safresh1 395759b3d2Safresh1my $UUID_VIA; 405759b3d2Safresh1 415759b3d2Safresh1sub init { 425759b3d2Safresh1 my $self = shift; 435759b3d2Safresh1 445759b3d2Safresh1 $self->{+PID} = $$; 455759b3d2Safresh1 $self->{+TID} = get_tid(); 46f3efcd01Safresh1 $self->{+HID} = gen_uid(); 475759b3d2Safresh1 485759b3d2Safresh1 $UUID_VIA ||= Test2::API::_add_uuid_via_ref(); 495759b3d2Safresh1 $self->{+UUID} = ${$UUID_VIA}->('hub') if $$UUID_VIA; 505759b3d2Safresh1 515759b3d2Safresh1 $self->{+NESTED} = 0 unless defined $self->{+NESTED}; 525759b3d2Safresh1 $self->{+BUFFERED} = 0 unless defined $self->{+BUFFERED}; 535759b3d2Safresh1 545759b3d2Safresh1 $self->{+COUNT} = 0; 555759b3d2Safresh1 $self->{+FAILED} = 0; 565759b3d2Safresh1 $self->{+_PASSING} = 1; 575759b3d2Safresh1 585759b3d2Safresh1 if (my $formatter = delete $self->{formatter}) { 595759b3d2Safresh1 $self->format($formatter); 605759b3d2Safresh1 } 615759b3d2Safresh1 625759b3d2Safresh1 if (my $ipc = $self->{+IPC}) { 635759b3d2Safresh1 $ipc->add_hub($self->{+HID}); 645759b3d2Safresh1 } 655759b3d2Safresh1} 665759b3d2Safresh1 675759b3d2Safresh1sub is_subtest { 0 } 685759b3d2Safresh1 695759b3d2Safresh1sub _tb_reset { 705759b3d2Safresh1 my $self = shift; 715759b3d2Safresh1 725759b3d2Safresh1 # Nothing to do 735759b3d2Safresh1 return if $self->{+PID} == $$ && $self->{+TID} == get_tid(); 745759b3d2Safresh1 755759b3d2Safresh1 $self->{+PID} = $$; 765759b3d2Safresh1 $self->{+TID} = get_tid(); 77f3efcd01Safresh1 $self->{+HID} = gen_uid(); 785759b3d2Safresh1 795759b3d2Safresh1 if (my $ipc = $self->{+IPC}) { 805759b3d2Safresh1 $ipc->add_hub($self->{+HID}); 815759b3d2Safresh1 } 825759b3d2Safresh1} 835759b3d2Safresh1 845759b3d2Safresh1sub reset_state { 855759b3d2Safresh1 my $self = shift; 865759b3d2Safresh1 875759b3d2Safresh1 $self->{+COUNT} = 0; 885759b3d2Safresh1 $self->{+FAILED} = 0; 895759b3d2Safresh1 $self->{+_PASSING} = 1; 905759b3d2Safresh1 915759b3d2Safresh1 delete $self->{+_PLAN}; 925759b3d2Safresh1 delete $self->{+ENDED}; 935759b3d2Safresh1 delete $self->{+BAILED_OUT}; 945759b3d2Safresh1 delete $self->{+SKIP_REASON}; 955759b3d2Safresh1} 965759b3d2Safresh1 975759b3d2Safresh1sub inherit { 985759b3d2Safresh1 my $self = shift; 995759b3d2Safresh1 my ($from, %params) = @_; 1005759b3d2Safresh1 1015759b3d2Safresh1 $self->{+NESTED} ||= 0; 1025759b3d2Safresh1 1035759b3d2Safresh1 $self->{+_FORMATTER} = $from->{+_FORMATTER} 1045759b3d2Safresh1 unless $self->{+_FORMATTER} || exists($params{formatter}); 1055759b3d2Safresh1 1065759b3d2Safresh1 if ($from->{+IPC} && !$self->{+IPC} && !exists($params{ipc})) { 1075759b3d2Safresh1 my $ipc = $from->{+IPC}; 1085759b3d2Safresh1 $self->{+IPC} = $ipc; 1095759b3d2Safresh1 $ipc->add_hub($self->{+HID}); 1105759b3d2Safresh1 } 1115759b3d2Safresh1 1125759b3d2Safresh1 if (my $ls = $from->{+_LISTENERS}) { 1135759b3d2Safresh1 push @{$self->{+_LISTENERS}} => grep { $_->{inherit} } @$ls; 1145759b3d2Safresh1 } 1155759b3d2Safresh1 1165759b3d2Safresh1 if (my $pfs = $from->{+_PRE_FILTERS}) { 1175759b3d2Safresh1 push @{$self->{+_PRE_FILTERS}} => grep { $_->{inherit} } @$pfs; 1185759b3d2Safresh1 } 1195759b3d2Safresh1 1205759b3d2Safresh1 if (my $fs = $from->{+_FILTERS}) { 1215759b3d2Safresh1 push @{$self->{+_FILTERS}} => grep { $_->{inherit} } @$fs; 1225759b3d2Safresh1 } 1235759b3d2Safresh1} 1245759b3d2Safresh1 1255759b3d2Safresh1sub format { 1265759b3d2Safresh1 my $self = shift; 1275759b3d2Safresh1 1285759b3d2Safresh1 my $old = $self->{+_FORMATTER}; 1295759b3d2Safresh1 ($self->{+_FORMATTER}) = @_ if @_; 1305759b3d2Safresh1 1315759b3d2Safresh1 return $old; 1325759b3d2Safresh1} 1335759b3d2Safresh1 1345759b3d2Safresh1sub is_local { 1355759b3d2Safresh1 my $self = shift; 1365759b3d2Safresh1 return $$ == $self->{+PID} 1375759b3d2Safresh1 && get_tid() == $self->{+TID}; 1385759b3d2Safresh1} 1395759b3d2Safresh1 1405759b3d2Safresh1sub listen { 1415759b3d2Safresh1 my $self = shift; 1425759b3d2Safresh1 my ($sub, %params) = @_; 1435759b3d2Safresh1 1445759b3d2Safresh1 carp "Useless addition of a listener in a child process or thread!" 1455759b3d2Safresh1 if $$ != $self->{+PID} || get_tid() != $self->{+TID}; 1465759b3d2Safresh1 1475759b3d2Safresh1 croak "listen only takes coderefs for arguments, got '$sub'" 1485759b3d2Safresh1 unless ref $sub && ref $sub eq 'CODE'; 1495759b3d2Safresh1 1505759b3d2Safresh1 push @{$self->{+_LISTENERS}} => { %params, code => $sub }; 1515759b3d2Safresh1 1525759b3d2Safresh1 $sub; # Intentional return. 1535759b3d2Safresh1} 1545759b3d2Safresh1 1555759b3d2Safresh1sub unlisten { 1565759b3d2Safresh1 my $self = shift; 1575759b3d2Safresh1 1585759b3d2Safresh1 carp "Useless removal of a listener in a child process or thread!" 1595759b3d2Safresh1 if $$ != $self->{+PID} || get_tid() != $self->{+TID}; 1605759b3d2Safresh1 1615759b3d2Safresh1 my %subs = map {$_ => $_} @_; 1625759b3d2Safresh1 1635759b3d2Safresh1 @{$self->{+_LISTENERS}} = grep { !$subs{$_->{code}} } @{$self->{+_LISTENERS}}; 1645759b3d2Safresh1} 1655759b3d2Safresh1 1665759b3d2Safresh1sub filter { 1675759b3d2Safresh1 my $self = shift; 1685759b3d2Safresh1 my ($sub, %params) = @_; 1695759b3d2Safresh1 1705759b3d2Safresh1 carp "Useless addition of a filter in a child process or thread!" 1715759b3d2Safresh1 if $$ != $self->{+PID} || get_tid() != $self->{+TID}; 1725759b3d2Safresh1 1735759b3d2Safresh1 croak "filter only takes coderefs for arguments, got '$sub'" 1745759b3d2Safresh1 unless ref $sub && ref $sub eq 'CODE'; 1755759b3d2Safresh1 1765759b3d2Safresh1 push @{$self->{+_FILTERS}} => { %params, code => $sub }; 1775759b3d2Safresh1 1785759b3d2Safresh1 $sub; # Intentional Return 1795759b3d2Safresh1} 1805759b3d2Safresh1 1815759b3d2Safresh1sub unfilter { 1825759b3d2Safresh1 my $self = shift; 1835759b3d2Safresh1 carp "Useless removal of a filter in a child process or thread!" 1845759b3d2Safresh1 if $$ != $self->{+PID} || get_tid() != $self->{+TID}; 1855759b3d2Safresh1 my %subs = map {$_ => $_} @_; 1865759b3d2Safresh1 @{$self->{+_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_FILTERS}}; 1875759b3d2Safresh1} 1885759b3d2Safresh1 1895759b3d2Safresh1sub pre_filter { 1905759b3d2Safresh1 my $self = shift; 1915759b3d2Safresh1 my ($sub, %params) = @_; 1925759b3d2Safresh1 1935759b3d2Safresh1 croak "pre_filter only takes coderefs for arguments, got '$sub'" 1945759b3d2Safresh1 unless ref $sub && ref $sub eq 'CODE'; 1955759b3d2Safresh1 1965759b3d2Safresh1 push @{$self->{+_PRE_FILTERS}} => { %params, code => $sub }; 1975759b3d2Safresh1 1985759b3d2Safresh1 $sub; # Intentional Return 1995759b3d2Safresh1} 2005759b3d2Safresh1 2015759b3d2Safresh1sub pre_unfilter { 2025759b3d2Safresh1 my $self = shift; 2035759b3d2Safresh1 my %subs = map {$_ => $_} @_; 2045759b3d2Safresh1 @{$self->{+_PRE_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_PRE_FILTERS}}; 2055759b3d2Safresh1} 2065759b3d2Safresh1 2075759b3d2Safresh1sub follow_up { 2085759b3d2Safresh1 my $self = shift; 2095759b3d2Safresh1 my ($sub) = @_; 2105759b3d2Safresh1 2115759b3d2Safresh1 carp "Useless addition of a follow-up in a child process or thread!" 2125759b3d2Safresh1 if $$ != $self->{+PID} || get_tid() != $self->{+TID}; 2135759b3d2Safresh1 2145759b3d2Safresh1 croak "follow_up only takes coderefs for arguments, got '$sub'" 2155759b3d2Safresh1 unless ref $sub && ref $sub eq 'CODE'; 2165759b3d2Safresh1 2175759b3d2Safresh1 push @{$self->{+_FOLLOW_UPS}} => $sub; 2185759b3d2Safresh1} 2195759b3d2Safresh1 2205759b3d2Safresh1*add_context_aquire = \&add_context_acquire; 2215759b3d2Safresh1sub add_context_acquire { 2225759b3d2Safresh1 my $self = shift; 2235759b3d2Safresh1 my ($sub) = @_; 2245759b3d2Safresh1 2255759b3d2Safresh1 croak "add_context_acquire only takes coderefs for arguments, got '$sub'" 2265759b3d2Safresh1 unless ref $sub && ref $sub eq 'CODE'; 2275759b3d2Safresh1 2285759b3d2Safresh1 push @{$self->{+_CONTEXT_ACQUIRE}} => $sub; 2295759b3d2Safresh1 2305759b3d2Safresh1 $sub; # Intentional return. 2315759b3d2Safresh1} 2325759b3d2Safresh1 2335759b3d2Safresh1*remove_context_aquire = \&remove_context_acquire; 2345759b3d2Safresh1sub remove_context_acquire { 2355759b3d2Safresh1 my $self = shift; 2365759b3d2Safresh1 my %subs = map {$_ => $_} @_; 2375759b3d2Safresh1 @{$self->{+_CONTEXT_ACQUIRE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_ACQUIRE}}; 2385759b3d2Safresh1} 2395759b3d2Safresh1 2405759b3d2Safresh1sub add_context_init { 2415759b3d2Safresh1 my $self = shift; 2425759b3d2Safresh1 my ($sub) = @_; 2435759b3d2Safresh1 2445759b3d2Safresh1 croak "add_context_init only takes coderefs for arguments, got '$sub'" 2455759b3d2Safresh1 unless ref $sub && ref $sub eq 'CODE'; 2465759b3d2Safresh1 2475759b3d2Safresh1 push @{$self->{+_CONTEXT_INIT}} => $sub; 2485759b3d2Safresh1 2495759b3d2Safresh1 $sub; # Intentional return. 2505759b3d2Safresh1} 2515759b3d2Safresh1 2525759b3d2Safresh1sub remove_context_init { 2535759b3d2Safresh1 my $self = shift; 2545759b3d2Safresh1 my %subs = map {$_ => $_} @_; 2555759b3d2Safresh1 @{$self->{+_CONTEXT_INIT}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_INIT}}; 2565759b3d2Safresh1} 2575759b3d2Safresh1 2585759b3d2Safresh1sub add_context_release { 2595759b3d2Safresh1 my $self = shift; 2605759b3d2Safresh1 my ($sub) = @_; 2615759b3d2Safresh1 2625759b3d2Safresh1 croak "add_context_release only takes coderefs for arguments, got '$sub'" 2635759b3d2Safresh1 unless ref $sub && ref $sub eq 'CODE'; 2645759b3d2Safresh1 2655759b3d2Safresh1 push @{$self->{+_CONTEXT_RELEASE}} => $sub; 2665759b3d2Safresh1 2675759b3d2Safresh1 $sub; # Intentional return. 2685759b3d2Safresh1} 2695759b3d2Safresh1 2705759b3d2Safresh1sub remove_context_release { 2715759b3d2Safresh1 my $self = shift; 2725759b3d2Safresh1 my %subs = map {$_ => $_} @_; 2735759b3d2Safresh1 @{$self->{+_CONTEXT_RELEASE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_RELEASE}}; 2745759b3d2Safresh1} 2755759b3d2Safresh1 2765759b3d2Safresh1sub send { 2775759b3d2Safresh1 my $self = shift; 2785759b3d2Safresh1 my ($e) = @_; 2795759b3d2Safresh1 280f3efcd01Safresh1 $e->eid; 281f3efcd01Safresh1 2825759b3d2Safresh1 $e->add_hub( 2835759b3d2Safresh1 { 2845759b3d2Safresh1 details => ref($self), 2855759b3d2Safresh1 2865759b3d2Safresh1 buffered => $self->{+BUFFERED}, 2875759b3d2Safresh1 hid => $self->{+HID}, 2885759b3d2Safresh1 nested => $self->{+NESTED}, 2895759b3d2Safresh1 pid => $self->{+PID}, 2905759b3d2Safresh1 tid => $self->{+TID}, 2915759b3d2Safresh1 uuid => $self->{+UUID}, 2925759b3d2Safresh1 2935759b3d2Safresh1 ipc => $self->{+IPC} ? 1 : 0, 2945759b3d2Safresh1 } 2955759b3d2Safresh1 ); 2965759b3d2Safresh1 2975759b3d2Safresh1 $e->set_uuid(${$UUID_VIA}->('event')) if $$UUID_VIA; 2985759b3d2Safresh1 2995759b3d2Safresh1 if ($self->{+_PRE_FILTERS}) { 3005759b3d2Safresh1 for (@{$self->{+_PRE_FILTERS}}) { 3015759b3d2Safresh1 $e = $_->{code}->($self, $e); 3025759b3d2Safresh1 return unless $e; 3035759b3d2Safresh1 } 3045759b3d2Safresh1 } 3055759b3d2Safresh1 3065759b3d2Safresh1 my $ipc = $self->{+IPC} || return $self->process($e); 3075759b3d2Safresh1 3085759b3d2Safresh1 if($e->global) { 3095759b3d2Safresh1 $ipc->send($self->{+HID}, $e, 'GLOBAL'); 3105759b3d2Safresh1 return $self->process($e); 3115759b3d2Safresh1 } 3125759b3d2Safresh1 3135759b3d2Safresh1 return $ipc->send($self->{+HID}, $e) 3145759b3d2Safresh1 if $$ != $self->{+PID} || get_tid() != $self->{+TID}; 3155759b3d2Safresh1 3165759b3d2Safresh1 $self->process($e); 3175759b3d2Safresh1} 3185759b3d2Safresh1 3195759b3d2Safresh1sub process { 3205759b3d2Safresh1 my $self = shift; 3215759b3d2Safresh1 my ($e) = @_; 3225759b3d2Safresh1 3235759b3d2Safresh1 if ($self->{+_FILTERS}) { 3245759b3d2Safresh1 for (@{$self->{+_FILTERS}}) { 3255759b3d2Safresh1 $e = $_->{code}->($self, $e); 3265759b3d2Safresh1 return unless $e; 3275759b3d2Safresh1 } 3285759b3d2Safresh1 } 3295759b3d2Safresh1 3305759b3d2Safresh1 # Optimize the most common case 3315759b3d2Safresh1 my $type = ref($e); 3325759b3d2Safresh1 if ($type eq 'Test2::Event::Pass' || ($type eq 'Test2::Event::Ok' && $e->{pass})) { 3335759b3d2Safresh1 my $count = ++($self->{+COUNT}); 3345759b3d2Safresh1 $self->{+_FORMATTER}->write($e, $count) if $self->{+_FORMATTER}; 3355759b3d2Safresh1 3365759b3d2Safresh1 if ($self->{+_LISTENERS}) { 3375759b3d2Safresh1 $_->{code}->($self, $e, $count) for @{$self->{+_LISTENERS}}; 3385759b3d2Safresh1 } 3395759b3d2Safresh1 3405759b3d2Safresh1 return $e; 3415759b3d2Safresh1 } 3425759b3d2Safresh1 3435759b3d2Safresh1 my $f = $e->facet_data; 3445759b3d2Safresh1 3455759b3d2Safresh1 my $fail = 0; 3465759b3d2Safresh1 $fail = 1 if $f->{assert} && !$f->{assert}->{pass}; 3475759b3d2Safresh1 $fail = 1 if $f->{errors} && grep { $_->{fail} } @{$f->{errors}}; 3485759b3d2Safresh1 $fail = 0 if $f->{amnesty}; 3495759b3d2Safresh1 3505759b3d2Safresh1 $self->{+COUNT}++ if $f->{assert}; 3515759b3d2Safresh1 $self->{+FAILED}++ if $fail && $f->{assert}; 3525759b3d2Safresh1 $self->{+_PASSING} = 0 if $fail; 3535759b3d2Safresh1 354256a93a4Safresh1 my $code = $f->{control} ? $f->{control}->{terminate} : undef; 3555759b3d2Safresh1 my $count = $self->{+COUNT}; 3565759b3d2Safresh1 3575759b3d2Safresh1 if (my $plan = $f->{plan}) { 3585759b3d2Safresh1 if ($plan->{skip}) { 3595759b3d2Safresh1 $self->plan('SKIP'); 3605759b3d2Safresh1 $self->set_skip_reason($plan->{details} || 1); 3615759b3d2Safresh1 $code ||= 0; 3625759b3d2Safresh1 } 3635759b3d2Safresh1 elsif ($plan->{none}) { 3645759b3d2Safresh1 $self->plan('NO PLAN'); 3655759b3d2Safresh1 } 3665759b3d2Safresh1 else { 3675759b3d2Safresh1 $self->plan($plan->{count}); 3685759b3d2Safresh1 } 3695759b3d2Safresh1 } 3705759b3d2Safresh1 371256a93a4Safresh1 $e->callback($self) if $f->{control} && $f->{control}->{has_callback}; 3725759b3d2Safresh1 3735759b3d2Safresh1 $self->{+_FORMATTER}->write($e, $count, $f) if $self->{+_FORMATTER}; 3745759b3d2Safresh1 3755759b3d2Safresh1 if ($self->{+_LISTENERS}) { 3765759b3d2Safresh1 $_->{code}->($self, $e, $count, $f) for @{$self->{+_LISTENERS}}; 3775759b3d2Safresh1 } 3785759b3d2Safresh1 379256a93a4Safresh1 if ($f->{control} && $f->{control}->{halt}) { 3805759b3d2Safresh1 $code ||= 255; 3815759b3d2Safresh1 $self->set_bailed_out($e); 3825759b3d2Safresh1 } 3835759b3d2Safresh1 3845759b3d2Safresh1 if (defined $code) { 3855759b3d2Safresh1 $self->{+_FORMATTER}->terminate($e, $f) if $self->{+_FORMATTER}; 3865759b3d2Safresh1 $self->terminate($code, $e, $f); 3875759b3d2Safresh1 } 3885759b3d2Safresh1 3895759b3d2Safresh1 return $e; 3905759b3d2Safresh1} 3915759b3d2Safresh1 3925759b3d2Safresh1sub terminate { 3935759b3d2Safresh1 my $self = shift; 3945759b3d2Safresh1 my ($code) = @_; 3955759b3d2Safresh1 exit($code); 3965759b3d2Safresh1} 3975759b3d2Safresh1 3985759b3d2Safresh1sub cull { 3995759b3d2Safresh1 my $self = shift; 4005759b3d2Safresh1 4015759b3d2Safresh1 my $ipc = $self->{+IPC} || return; 4025759b3d2Safresh1 return if $self->{+PID} != $$ || $self->{+TID} != get_tid(); 4035759b3d2Safresh1 4045759b3d2Safresh1 # No need to do IPC checks on culled events 4055759b3d2Safresh1 $self->process($_) for $ipc->cull($self->{+HID}); 4065759b3d2Safresh1} 4075759b3d2Safresh1 4085759b3d2Safresh1sub finalize { 4095759b3d2Safresh1 my $self = shift; 4105759b3d2Safresh1 my ($trace, $do_plan) = @_; 4115759b3d2Safresh1 4125759b3d2Safresh1 $self->cull(); 4135759b3d2Safresh1 4145759b3d2Safresh1 my $plan = $self->{+_PLAN}; 4155759b3d2Safresh1 my $count = $self->{+COUNT}; 4165759b3d2Safresh1 my $failed = $self->{+FAILED}; 4175759b3d2Safresh1 my $active = $self->{+ACTIVE}; 4185759b3d2Safresh1 4195759b3d2Safresh1 # return if NOTHING was done. 4205759b3d2Safresh1 unless ($active || $do_plan || defined($plan) || $count || $failed) { 4215759b3d2Safresh1 $self->{+_FORMATTER}->finalize($plan, $count, $failed, 0, $self->is_subtest) if $self->{+_FORMATTER}; 4225759b3d2Safresh1 return; 4235759b3d2Safresh1 } 4245759b3d2Safresh1 4255759b3d2Safresh1 unless ($self->{+ENDED}) { 4265759b3d2Safresh1 if ($self->{+_FOLLOW_UPS}) { 4275759b3d2Safresh1 $_->($trace, $self) for reverse @{$self->{+_FOLLOW_UPS}}; 4285759b3d2Safresh1 } 4295759b3d2Safresh1 4305759b3d2Safresh1 # These need to be refreshed now 4315759b3d2Safresh1 $plan = $self->{+_PLAN}; 4325759b3d2Safresh1 $count = $self->{+COUNT}; 4335759b3d2Safresh1 $failed = $self->{+FAILED}; 4345759b3d2Safresh1 435*5486feefSafresh1 if ((defined($plan) && $plan eq 'NO PLAN') || ($do_plan && !defined($plan))) { 4365759b3d2Safresh1 $self->send( 4375759b3d2Safresh1 Test2::Event::Plan->new( 4385759b3d2Safresh1 trace => $trace, 4395759b3d2Safresh1 max => $count, 4405759b3d2Safresh1 ) 4415759b3d2Safresh1 ); 4425759b3d2Safresh1 } 4435759b3d2Safresh1 $plan = $self->{+_PLAN}; 4445759b3d2Safresh1 } 4455759b3d2Safresh1 4465759b3d2Safresh1 my $frame = $trace->frame; 4475759b3d2Safresh1 if($self->{+ENDED}) { 4485759b3d2Safresh1 my (undef, $ffile, $fline) = @{$self->{+ENDED}}; 4495759b3d2Safresh1 my (undef, $sfile, $sline) = @$frame; 4505759b3d2Safresh1 4515759b3d2Safresh1 die <<" EOT" 4525759b3d2Safresh1Test already ended! 4535759b3d2Safresh1First End: $ffile line $fline 4545759b3d2Safresh1Second End: $sfile line $sline 4555759b3d2Safresh1 EOT 4565759b3d2Safresh1 } 4575759b3d2Safresh1 4585759b3d2Safresh1 $self->{+ENDED} = $frame; 4595759b3d2Safresh1 my $pass = $self->is_passing(); # Generate the final boolean. 4605759b3d2Safresh1 4615759b3d2Safresh1 $self->{+_FORMATTER}->finalize($plan, $count, $failed, $pass, $self->is_subtest) if $self->{+_FORMATTER}; 4625759b3d2Safresh1 4635759b3d2Safresh1 return $pass; 4645759b3d2Safresh1} 4655759b3d2Safresh1 4665759b3d2Safresh1sub is_passing { 4675759b3d2Safresh1 my $self = shift; 4685759b3d2Safresh1 4695759b3d2Safresh1 ($self->{+_PASSING}) = @_ if @_; 4705759b3d2Safresh1 4715759b3d2Safresh1 # If we already failed just return 0. 4725759b3d2Safresh1 my $pass = $self->{+_PASSING} or return 0; 4735759b3d2Safresh1 return $self->{+_PASSING} = 0 if $self->{+FAILED}; 4745759b3d2Safresh1 4755759b3d2Safresh1 my $count = $self->{+COUNT}; 4765759b3d2Safresh1 my $ended = $self->{+ENDED}; 4775759b3d2Safresh1 my $plan = $self->{+_PLAN}; 4785759b3d2Safresh1 4795759b3d2Safresh1 return $pass if !$count && $plan && $plan =~ m/^SKIP$/; 4805759b3d2Safresh1 4815759b3d2Safresh1 return $self->{+_PASSING} = 0 4825759b3d2Safresh1 if $ended && (!$count || !$plan); 4835759b3d2Safresh1 4845759b3d2Safresh1 return $pass unless $plan && $plan =~ m/^\d+$/; 4855759b3d2Safresh1 4865759b3d2Safresh1 if ($ended) { 4875759b3d2Safresh1 return $self->{+_PASSING} = 0 if $count != $plan; 4885759b3d2Safresh1 } 4895759b3d2Safresh1 else { 4905759b3d2Safresh1 return $self->{+_PASSING} = 0 if $count > $plan; 4915759b3d2Safresh1 } 4925759b3d2Safresh1 4935759b3d2Safresh1 return $pass; 4945759b3d2Safresh1} 4955759b3d2Safresh1 4965759b3d2Safresh1sub plan { 4975759b3d2Safresh1 my $self = shift; 4985759b3d2Safresh1 4995759b3d2Safresh1 return $self->{+_PLAN} unless @_; 5005759b3d2Safresh1 5015759b3d2Safresh1 my ($plan) = @_; 5025759b3d2Safresh1 5035759b3d2Safresh1 confess "You cannot unset the plan" 5045759b3d2Safresh1 unless defined $plan; 5055759b3d2Safresh1 5065759b3d2Safresh1 confess "You cannot change the plan" 5075759b3d2Safresh1 if $self->{+_PLAN} && $self->{+_PLAN} !~ m/^NO PLAN$/; 5085759b3d2Safresh1 5095759b3d2Safresh1 confess "'$plan' is not a valid plan! Plan must be an integer greater than 0, 'NO PLAN', or 'SKIP'" 5105759b3d2Safresh1 unless $plan =~ m/^(\d+|NO PLAN|SKIP)$/; 5115759b3d2Safresh1 5125759b3d2Safresh1 $self->{+_PLAN} = $plan; 5135759b3d2Safresh1} 5145759b3d2Safresh1 5155759b3d2Safresh1sub check_plan { 5165759b3d2Safresh1 my $self = shift; 5175759b3d2Safresh1 5185759b3d2Safresh1 return undef unless $self->{+ENDED}; 5195759b3d2Safresh1 my $plan = $self->{+_PLAN} || return undef; 5205759b3d2Safresh1 5215759b3d2Safresh1 return 1 if $plan !~ m/^\d+$/; 5225759b3d2Safresh1 5235759b3d2Safresh1 return 1 if $plan == $self->{+COUNT}; 5245759b3d2Safresh1 return 0; 5255759b3d2Safresh1} 5265759b3d2Safresh1 5275759b3d2Safresh1sub DESTROY { 5285759b3d2Safresh1 my $self = shift; 5295759b3d2Safresh1 my $ipc = $self->{+IPC} || return; 5305759b3d2Safresh1 return unless $$ == $self->{+PID}; 5315759b3d2Safresh1 return unless get_tid() == $self->{+TID}; 5325759b3d2Safresh1 $ipc->drop_hub($self->{+HID}); 5335759b3d2Safresh1} 5345759b3d2Safresh1 5355759b3d2Safresh11; 5365759b3d2Safresh1 5375759b3d2Safresh1__END__ 5385759b3d2Safresh1 5395759b3d2Safresh1=pod 5405759b3d2Safresh1 5415759b3d2Safresh1=encoding UTF-8 5425759b3d2Safresh1 5435759b3d2Safresh1=head1 NAME 5445759b3d2Safresh1 5455759b3d2Safresh1Test2::Hub - The conduit through which all events flow. 5465759b3d2Safresh1 5475759b3d2Safresh1=head1 SYNOPSIS 5485759b3d2Safresh1 5495759b3d2Safresh1 use Test2::Hub; 5505759b3d2Safresh1 5515759b3d2Safresh1 my $hub = Test2::Hub->new(); 5525759b3d2Safresh1 $hub->send(...); 5535759b3d2Safresh1 5545759b3d2Safresh1=head1 DESCRIPTION 5555759b3d2Safresh1 5565759b3d2Safresh1The hub is the place where all events get processed and handed off to the 5575759b3d2Safresh1formatter. The hub also tracks test state, and provides several hooks into the 5585759b3d2Safresh1event pipeline. 5595759b3d2Safresh1 5605759b3d2Safresh1=head1 COMMON TASKS 5615759b3d2Safresh1 5625759b3d2Safresh1=head2 SENDING EVENTS 5635759b3d2Safresh1 5645759b3d2Safresh1 $hub->send($event) 5655759b3d2Safresh1 5665759b3d2Safresh1The C<send()> method is used to issue an event to the hub. This method will 5675759b3d2Safresh1handle thread/fork sync, filters, listeners, TAP output, etc. 5685759b3d2Safresh1 5695759b3d2Safresh1=head2 ALTERING OR REMOVING EVENTS 5705759b3d2Safresh1 5715759b3d2Safresh1You can use either C<filter()> or C<pre_filter()>, depending on your 5725759b3d2Safresh1needs. Both have identical syntax, so only C<filter()> is shown here. 5735759b3d2Safresh1 5745759b3d2Safresh1 $hub->filter(sub { 5755759b3d2Safresh1 my ($hub, $event) = @_; 5765759b3d2Safresh1 5775759b3d2Safresh1 my $action = get_action($event); 5785759b3d2Safresh1 5795759b3d2Safresh1 # No action should be taken 5805759b3d2Safresh1 return $event if $action eq 'none'; 5815759b3d2Safresh1 5825759b3d2Safresh1 # You want your filter to remove the event 5835759b3d2Safresh1 return undef if $action eq 'delete'; 5845759b3d2Safresh1 5855759b3d2Safresh1 if ($action eq 'do_it') { 5865759b3d2Safresh1 my $new_event = copy_event($event); 5875759b3d2Safresh1 ... Change your copy of the event ... 5885759b3d2Safresh1 return $new_event; 5895759b3d2Safresh1 } 5905759b3d2Safresh1 5915759b3d2Safresh1 die "Should not happen"; 5925759b3d2Safresh1 }); 5935759b3d2Safresh1 5945759b3d2Safresh1By default, filters are not inherited by child hubs. That means if you start a 5955759b3d2Safresh1subtest, the subtest will not inherit the filter. You can change this behavior 5965759b3d2Safresh1with the C<inherit> parameter: 5975759b3d2Safresh1 5985759b3d2Safresh1 $hub->filter(sub { ... }, inherit => 1); 5995759b3d2Safresh1 6005759b3d2Safresh1=head2 LISTENING FOR EVENTS 6015759b3d2Safresh1 6025759b3d2Safresh1 $hub->listen(sub { 6035759b3d2Safresh1 my ($hub, $event, $number) = @_; 6045759b3d2Safresh1 6055759b3d2Safresh1 ... do whatever you want with the event ... 6065759b3d2Safresh1 6075759b3d2Safresh1 # return is ignored 6085759b3d2Safresh1 }); 6095759b3d2Safresh1 6105759b3d2Safresh1By default listeners are not inherited by child hubs. That means if you start a 6115759b3d2Safresh1subtest, the subtest will not inherit the listener. You can change this behavior 6125759b3d2Safresh1with the C<inherit> parameter: 6135759b3d2Safresh1 6145759b3d2Safresh1 $hub->listen(sub { ... }, inherit => 1); 6155759b3d2Safresh1 6165759b3d2Safresh1 6175759b3d2Safresh1=head2 POST-TEST BEHAVIORS 6185759b3d2Safresh1 6195759b3d2Safresh1 $hub->follow_up(sub { 6205759b3d2Safresh1 my ($trace, $hub) = @_; 6215759b3d2Safresh1 6225759b3d2Safresh1 ... do whatever you need to ... 6235759b3d2Safresh1 6245759b3d2Safresh1 # Return is ignored 6255759b3d2Safresh1 }); 6265759b3d2Safresh1 6275759b3d2Safresh1follow_up subs are called only once, either when done_testing is called, or in 6285759b3d2Safresh1an END block. 6295759b3d2Safresh1 6305759b3d2Safresh1=head2 SETTING THE FORMATTER 6315759b3d2Safresh1 6325759b3d2Safresh1By default an instance of L<Test2::Formatter::TAP> is created and used. 6335759b3d2Safresh1 6345759b3d2Safresh1 my $old = $hub->format(My::Formatter->new); 6355759b3d2Safresh1 6365759b3d2Safresh1Setting the formatter will REPLACE any existing formatter. You may set the 6375759b3d2Safresh1formatter to undef to prevent output. The old formatter will be returned if one 6385759b3d2Safresh1was already set. Only one formatter is allowed at a time. 6395759b3d2Safresh1 6405759b3d2Safresh1=head1 METHODS 6415759b3d2Safresh1 6425759b3d2Safresh1=over 4 6435759b3d2Safresh1 6445759b3d2Safresh1=item $hub->send($event) 6455759b3d2Safresh1 6465759b3d2Safresh1This is where all events enter the hub for processing. 6475759b3d2Safresh1 6485759b3d2Safresh1=item $hub->process($event) 6495759b3d2Safresh1 6505759b3d2Safresh1This is called by send after it does any IPC handling. You can use this to 6515759b3d2Safresh1bypass the IPC process, but in general you should avoid using this. 6525759b3d2Safresh1 6535759b3d2Safresh1=item $old = $hub->format($formatter) 6545759b3d2Safresh1 6555759b3d2Safresh1Replace the existing formatter instance with a new one. Formatters must be 6565759b3d2Safresh1objects that implement a C<< $formatter->write($event) >> method. 6575759b3d2Safresh1 6585759b3d2Safresh1=item $sub = $hub->listen(sub { ... }, %optional_params) 6595759b3d2Safresh1 6605759b3d2Safresh1You can use this to record all events AFTER they have been sent to the 6615759b3d2Safresh1formatter. No changes made here will be meaningful, except possibly to other 6625759b3d2Safresh1listeners. 6635759b3d2Safresh1 6645759b3d2Safresh1 $hub->listen(sub { 6655759b3d2Safresh1 my ($hub, $event, $number) = @_; 6665759b3d2Safresh1 6675759b3d2Safresh1 ... do whatever you want with the event ... 6685759b3d2Safresh1 6695759b3d2Safresh1 # return is ignored 6705759b3d2Safresh1 }); 6715759b3d2Safresh1 6725759b3d2Safresh1Normally listeners are not inherited by child hubs such as subtests. You can 6735759b3d2Safresh1add the C<< inherit => 1 >> parameter to allow a listener to be inherited. 6745759b3d2Safresh1 6755759b3d2Safresh1=item $hub->unlisten($sub) 6765759b3d2Safresh1 6775759b3d2Safresh1You can use this to remove a listen callback. You must pass in the coderef 6785759b3d2Safresh1returned by the C<listen()> method. 6795759b3d2Safresh1 6805759b3d2Safresh1=item $sub = $hub->filter(sub { ... }, %optional_params) 6815759b3d2Safresh1 6825759b3d2Safresh1=item $sub = $hub->pre_filter(sub { ... }, %optional_params) 6835759b3d2Safresh1 6845759b3d2Safresh1These can be used to add filters. Filters can modify, replace, or remove events 6855759b3d2Safresh1before anything else can see them. 6865759b3d2Safresh1 6875759b3d2Safresh1 $hub->filter( 6885759b3d2Safresh1 sub { 6895759b3d2Safresh1 my ($hub, $event) = @_; 6905759b3d2Safresh1 6915759b3d2Safresh1 return $event; # No Changes 6925759b3d2Safresh1 return; # Remove the event 6935759b3d2Safresh1 6945759b3d2Safresh1 # Or you can modify an event before returning it. 6955759b3d2Safresh1 $event->modify; 6965759b3d2Safresh1 return $event; 6975759b3d2Safresh1 } 6985759b3d2Safresh1 ); 6995759b3d2Safresh1 7005759b3d2Safresh1If you are not using threads, forking, or IPC then the only difference between 7015759b3d2Safresh1a C<filter> and a C<pre_filter> is that C<pre_filter> subs run first. When you 7025759b3d2Safresh1are using threads, forking, or IPC, pre_filters happen to events before they 7035759b3d2Safresh1are sent to their destination proc/thread, ordinary filters happen only in the 7045759b3d2Safresh1destination hub/thread. 7055759b3d2Safresh1 7065759b3d2Safresh1You cannot add a regular filter to a hub if the hub was created in another 7075759b3d2Safresh1process or thread. You can always add a pre_filter. 7085759b3d2Safresh1 7095759b3d2Safresh1=item $hub->unfilter($sub) 7105759b3d2Safresh1 7115759b3d2Safresh1=item $hub->pre_unfilter($sub) 7125759b3d2Safresh1 7135759b3d2Safresh1These can be used to remove filters and pre_filters. The C<$sub> argument is 7145759b3d2Safresh1the reference returned by C<filter()> or C<pre_filter()>. 7155759b3d2Safresh1 7165759b3d2Safresh1=item $hub->follow_op(sub { ... }) 7175759b3d2Safresh1 7185759b3d2Safresh1Use this to add behaviors that are called just before the hub is finalized. The 7195759b3d2Safresh1only argument to your codeblock will be a L<Test2::EventFacet::Trace> instance. 7205759b3d2Safresh1 7215759b3d2Safresh1 $hub->follow_up(sub { 7225759b3d2Safresh1 my ($trace, $hub) = @_; 7235759b3d2Safresh1 7245759b3d2Safresh1 ... do whatever you need to ... 7255759b3d2Safresh1 7265759b3d2Safresh1 # Return is ignored 7275759b3d2Safresh1 }); 7285759b3d2Safresh1 7295759b3d2Safresh1follow_up subs are called only once, ether when done_testing is called, or in 7305759b3d2Safresh1an END block. 7315759b3d2Safresh1 7325759b3d2Safresh1=item $sub = $hub->add_context_acquire(sub { ... }); 7335759b3d2Safresh1 7345759b3d2Safresh1Add a callback that will be called every time someone tries to acquire a 7355759b3d2Safresh1context. It gets a single argument, a reference of the hash of parameters 7365759b3d2Safresh1being used the construct the context. This is your chance to change the 7375759b3d2Safresh1parameters by directly altering the hash. 7385759b3d2Safresh1 7395759b3d2Safresh1 test2_add_callback_context_acquire(sub { 7405759b3d2Safresh1 my $params = shift; 7415759b3d2Safresh1 $params->{level}++; 7425759b3d2Safresh1 }); 7435759b3d2Safresh1 7445759b3d2Safresh1This is a very scary API function. Please do not use this unless you need to. 7455759b3d2Safresh1This is here for L<Test::Builder> and backwards compatibility. This has you 7465759b3d2Safresh1directly manipulate the hash instead of returning a new one for performance 7475759b3d2Safresh1reasons. 7485759b3d2Safresh1 7495759b3d2Safresh1B<Note> Using this hook could have a huge performance impact. 7505759b3d2Safresh1 7515759b3d2Safresh1The coderef you provide is returned and can be used to remove the hook later. 7525759b3d2Safresh1 7535759b3d2Safresh1=item $hub->remove_context_acquire($sub); 7545759b3d2Safresh1 7555759b3d2Safresh1This can be used to remove a context acquire hook. 7565759b3d2Safresh1 7575759b3d2Safresh1=item $sub = $hub->add_context_init(sub { ... }); 7585759b3d2Safresh1 7595759b3d2Safresh1This allows you to add callbacks that will trigger every time a new context is 7605759b3d2Safresh1created for the hub. The only argument to the sub will be the 7615759b3d2Safresh1L<Test2::API::Context> instance that was created. 7625759b3d2Safresh1 7635759b3d2Safresh1B<Note> Using this hook could have a huge performance impact. 7645759b3d2Safresh1 7655759b3d2Safresh1The coderef you provide is returned and can be used to remove the hook later. 7665759b3d2Safresh1 7675759b3d2Safresh1=item $hub->remove_context_init($sub); 7685759b3d2Safresh1 7695759b3d2Safresh1This can be used to remove a context init hook. 7705759b3d2Safresh1 7715759b3d2Safresh1=item $sub = $hub->add_context_release(sub { ... }); 7725759b3d2Safresh1 7735759b3d2Safresh1This allows you to add callbacks that will trigger every time a context for 7745759b3d2Safresh1this hub is released. The only argument to the sub will be the 7755759b3d2Safresh1L<Test2::API::Context> instance that was released. These will run in reverse 7765759b3d2Safresh1order. 7775759b3d2Safresh1 7785759b3d2Safresh1B<Note> Using this hook could have a huge performance impact. 7795759b3d2Safresh1 7805759b3d2Safresh1The coderef you provide is returned and can be used to remove the hook later. 7815759b3d2Safresh1 7825759b3d2Safresh1=item $hub->remove_context_release($sub); 7835759b3d2Safresh1 7845759b3d2Safresh1This can be used to remove a context release hook. 7855759b3d2Safresh1 7865759b3d2Safresh1=item $hub->cull() 7875759b3d2Safresh1 7885759b3d2Safresh1Cull any IPC events (and process them). 7895759b3d2Safresh1 7905759b3d2Safresh1=item $pid = $hub->pid() 7915759b3d2Safresh1 7925759b3d2Safresh1Get the process id under which the hub was created. 7935759b3d2Safresh1 7945759b3d2Safresh1=item $tid = $hub->tid() 7955759b3d2Safresh1 7965759b3d2Safresh1Get the thread id under which the hub was created. 7975759b3d2Safresh1 7985759b3d2Safresh1=item $hud = $hub->hid() 7995759b3d2Safresh1 8005759b3d2Safresh1Get the identifier string of the hub. 8015759b3d2Safresh1 8025759b3d2Safresh1=item $uuid = $hub->uuid() 8035759b3d2Safresh1 8045759b3d2Safresh1If UUID tagging is enabled (see L<Test2::API>) then the hub will have a UUID. 8055759b3d2Safresh1 8065759b3d2Safresh1=item $ipc = $hub->ipc() 8075759b3d2Safresh1 8085759b3d2Safresh1Get the IPC object used by the hub. 8095759b3d2Safresh1 8105759b3d2Safresh1=item $hub->set_no_ending($bool) 8115759b3d2Safresh1 8125759b3d2Safresh1=item $bool = $hub->no_ending 8135759b3d2Safresh1 8145759b3d2Safresh1This can be used to disable auto-ending behavior for a hub. The auto-ending 8155759b3d2Safresh1behavior is triggered by an end block and is used to cull IPC events, and 816de8cc8edSafresh1output the final plan if the plan was 'NO PLAN'. 8175759b3d2Safresh1 8185759b3d2Safresh1=item $bool = $hub->active 8195759b3d2Safresh1 8205759b3d2Safresh1=item $hub->set_active($bool) 8215759b3d2Safresh1 8225759b3d2Safresh1These are used to get/set the 'active' attribute. When true this attribute will 8235759b3d2Safresh1force C<< hub->finalize() >> to take action even if there is no plan, and no 8245759b3d2Safresh1tests have been run. This flag is useful for plugins that add follow-up 8255759b3d2Safresh1behaviors that need to run even if no events are seen. 8265759b3d2Safresh1 8275759b3d2Safresh1=back 8285759b3d2Safresh1 8295759b3d2Safresh1=head2 STATE METHODS 8305759b3d2Safresh1 8315759b3d2Safresh1=over 4 8325759b3d2Safresh1 8335759b3d2Safresh1=item $hub->reset_state() 8345759b3d2Safresh1 8355759b3d2Safresh1Reset all state to the start. This sets the test count to 0, clears the plan, 8365759b3d2Safresh1removes the failures, etc. 8375759b3d2Safresh1 8385759b3d2Safresh1=item $num = $hub->count 8395759b3d2Safresh1 8405759b3d2Safresh1Get the number of tests that have been run. 8415759b3d2Safresh1 8425759b3d2Safresh1=item $num = $hub->failed 8435759b3d2Safresh1 8445759b3d2Safresh1Get the number of failures (Not all failures come from a test fail, so this 8455759b3d2Safresh1number can be larger than the count). 8465759b3d2Safresh1 8475759b3d2Safresh1=item $bool = $hub->ended 8485759b3d2Safresh1 8495759b3d2Safresh1True if the testing has ended. This MAY return the stack frame of the tool that 8505759b3d2Safresh1ended the test, but that is not guaranteed. 8515759b3d2Safresh1 8525759b3d2Safresh1=item $bool = $hub->is_passing 8535759b3d2Safresh1 8545759b3d2Safresh1=item $hub->is_passing($bool) 8555759b3d2Safresh1 8565759b3d2Safresh1Check if the overall test run is a failure. Can also be used to set the 8575759b3d2Safresh1pass/fail status. 8585759b3d2Safresh1 8595759b3d2Safresh1=item $hub->plan($plan) 8605759b3d2Safresh1 8615759b3d2Safresh1=item $plan = $hub->plan 8625759b3d2Safresh1 8635759b3d2Safresh1Get or set the plan. The plan must be an integer larger than 0, the string 864de8cc8edSafresh1'NO PLAN', or the string 'SKIP'. 8655759b3d2Safresh1 8665759b3d2Safresh1=item $bool = $hub->check_plan 8675759b3d2Safresh1 8685759b3d2Safresh1Check if the plan and counts match, but only if the tests have ended. If tests 8695759b3d2Safresh1have not ended this will return undef, otherwise it will be a true/false. 8705759b3d2Safresh1 8715759b3d2Safresh1=back 8725759b3d2Safresh1 8735759b3d2Safresh1=head1 THIRD PARTY META-DATA 8745759b3d2Safresh1 8755759b3d2Safresh1This object consumes L<Test2::Util::ExternalMeta> which provides a consistent 8765759b3d2Safresh1way for you to attach meta-data to instances of this class. This is useful for 8775759b3d2Safresh1tools, plugins, and other extensions. 8785759b3d2Safresh1 8795759b3d2Safresh1=head1 SOURCE 8805759b3d2Safresh1 8815759b3d2Safresh1The source code repository for Test2 can be found at 882*5486feefSafresh1L<https://github.com/Test-More/test-more/>. 8835759b3d2Safresh1 8845759b3d2Safresh1=head1 MAINTAINERS 8855759b3d2Safresh1 8865759b3d2Safresh1=over 4 8875759b3d2Safresh1 8885759b3d2Safresh1=item Chad Granum E<lt>exodist@cpan.orgE<gt> 8895759b3d2Safresh1 8905759b3d2Safresh1=back 8915759b3d2Safresh1 8925759b3d2Safresh1=head1 AUTHORS 8935759b3d2Safresh1 8945759b3d2Safresh1=over 4 8955759b3d2Safresh1 8965759b3d2Safresh1=item Chad Granum E<lt>exodist@cpan.orgE<gt> 8975759b3d2Safresh1 8985759b3d2Safresh1=back 8995759b3d2Safresh1 9005759b3d2Safresh1=head1 COPYRIGHT 9015759b3d2Safresh1 902256a93a4Safresh1Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<gt>. 9035759b3d2Safresh1 9045759b3d2Safresh1This program is free software; you can redistribute it and/or 9055759b3d2Safresh1modify it under the same terms as Perl itself. 9065759b3d2Safresh1 907*5486feefSafresh1See L<https://dev.perl.org/licenses/> 9085759b3d2Safresh1 9095759b3d2Safresh1=cut 910