our
$VERSION
=
'1.302209'
;
use
Test2::Util
qw/try get_tid pkg_to_file IS_WIN32 ipc_separator do_rename do_unlink/
;
sub
is_viable { 1 }
sub
init {
my
$self
=
shift
;
my
$tmpdir
= File::Temp::tempdir(
$ENV
{T2_TEMPDIR_TEMPLATE} ||
"test2"
. ipc_separator . $$ . ipc_separator .
"XXXXXX"
,
CLEANUP
=> 0,
TMPDIR
=> 1,
);
$self
->abort_trace(
"Could not get a temp dir"
)
unless
$tmpdir
;
$self
->{+TEMPDIR} = File::Spec->canonpath(
$tmpdir
);
print
STDERR
"\nIPC Temp Dir: $tmpdir\n\n"
if
$ENV
{T2_KEEP_TEMPDIR};
$self
->{+EVENT_IDS} = {};
$self
->{+READ_IDS} = {};
$self
->{+TIMEOUTS} = {};
$self
->{+TID} = get_tid();
$self
->{+PID} = $$;
$self
->{+GLOBALS} = {};
return
$self
;
}
sub
hub_file {
my
$self
=
shift
;
my
(
$hid
) =
@_
;
my
$tdir
=
$self
->{+TEMPDIR};
return
File::Spec->catfile(
$tdir
,
"HUB"
. ipc_separator .
$hid
);
}
sub
event_file {
my
$self
=
shift
;
my
(
$hid
,
$e
) =
@_
;
my
$tempdir
=
$self
->{+TEMPDIR};
my
$type
= blessed(
$e
) or
$self
->abort(
"'$e' is not a blessed object!"
);
$self
->abort(
"'$e' is not an event object!"
)
unless
$type
->isa(
'Test2::Event'
);
my
$tid
= get_tid();
my
$eid
=
$self
->{+EVENT_IDS}->{
$hid
}->{$$}->{
$tid
} += 1;
my
@type
=
split
'::'
,
$type
;
my
$name
=
join
(ipc_separator,
$hid
, $$,
$tid
,
$eid
,
@type
);
return
File::Spec->catfile(
$tempdir
,
$name
);
}
sub
add_hub {
my
$self
=
shift
;
my
(
$hid
) =
@_
;
my
$hfile
=
$self
->hub_file(
$hid
);
$self
->abort_trace(
"File for hub '$hid' already exists"
)
if
-e
$hfile
;
open
(
my
$fh
,
'>'
,
$hfile
) or
$self
->abort_trace(
"Could not create hub file '$hid': $!"
);
print
$fh
"$$\n"
. get_tid() .
"\n"
;
close
(
$fh
);
}
sub
drop_hub {
my
$self
=
shift
;
my
(
$hid
) =
@_
;
my
$tdir
=
$self
->{+TEMPDIR};
my
$hfile
=
$self
->hub_file(
$hid
);
$self
->abort_trace(
"File for hub '$hid' does not exist"
)
unless
-e
$hfile
;
open
(
my
$fh
,
'<'
,
$hfile
) or
$self
->abort_trace(
"Could not open hub file '$hid': $!"
);
my
(
$pid
,
$tid
) = <
$fh
>;
close
(
$fh
);
$self
->abort_trace(
"A hub file can only be closed by the process that started it\nExpected $pid, got $$"
)
unless
$pid
== $$;
$self
->abort_trace(
"A hub file can only be closed by the thread that started it\nExpected $tid, got "
. get_tid())
unless
get_tid() ==
$tid
;
if
(
$ENV
{T2_KEEP_TEMPDIR}) {
my
(
$ok
,
$err
) = do_rename(
$hfile
, File::Spec->canonpath(
"$hfile.complete"
));
$self
->abort_trace(
"Could not rename file '$hfile' -> '$hfile.complete': $err"
)
unless
$ok
}
else
{
my
(
$ok
,
$err
) = do_unlink(
$hfile
);
$self
->abort_trace(
"Could not remove file for hub '$hid': $err"
)
unless
$ok
}
opendir
(
my
$dh
,
$tdir
) or
$self
->abort_trace(
"Could not open temp dir!"
);
my
%bad
;
for
my
$file
(
readdir
(
$dh
)) {
next
if
$file
=~ m{\.complete$};
next
unless
$file
=~ m{^
$hid
};
eval
{
$bad
{
$file
} =
$self
->read_event_file(File::Spec->catfile(
$tdir
,
$file
)); 1 } or
$bad
{
$file
} = $@ ||
"Unknown error reading file"
;
}
closedir
(
$dh
);
return
unless
keys
%bad
;
my
$data
;
my
$ok
=
eval
{
local
*UNIVERSAL::TO_JSON
=
sub
{ +{ %{
$_
[0]} } };
my
$json
= JSON::PP->new->ascii->pretty->canonical->allow_unknown->allow_blessed->convert_blessed;
$data
=
$json
->encode(\
%bad
);
1;
};
$ok
||=
eval
{
local
$Data::Dumper::Sortkeys
= 1;
$data
= Data::Dumper::Dumper(\
%bad
);
1;
};
$data
=
"Could not dump data... sorry."
unless
defined
$data
;
$self
->abort_trace(
"Not all files from hub '$hid' have been collected!\nHere is the leftover data:\n========================\n$data\n===================\n"
);
}
sub
send
{
my
$self
=
shift
;
my
(
$hid
,
$e
,
$global
) =
@_
;
my
$tempdir
=
$self
->{+TEMPDIR};
my
$hfile
=
$self
->hub_file(
$hid
);
my
$dest
=
$global
?
'GLOBAL'
:
$hid
;
$self
->abort(
<<" EOT") unless $global || -f $hfile;
hub '$hid' is not available, failed to send event!
There was an attempt to send an event to a hub in a parent process or thread,
but that hub appears to be gone. This can happen if you fork, or start a new
thread from inside subtest, and the parent finishes the subtest before the
child returns.
This can also happen if the parent process is done testing before the child
finishes. Test2 normally waits automatically in the root process, but will not
do so if Test::Builder is loaded for legacy reasons.
EOT
my
$file
=
$self
->event_file(
$dest
,
$e
);
my
$ready
= File::Spec->canonpath(
"$file.ready"
);
if
(
$global
) {
my
$name
=
$ready
;
$name
=~ s{^.*(GLOBAL)}{GLOBAL};
$self
->{+GLOBALS}->{
$hid
}->{
$name
}++;
}
my
(
$ren_ok
,
$ren_err
);
my
(
$ok
,
$err
) = try_sig_mask(
sub
{
Storable::store(
$e
,
$file
);
(
$ren_ok
,
$ren_err
) = do_rename(
"$file"
,
$ready
);
});
if
(
$ok
) {
$self
->abort(
"Could not rename file '$file' -> '$ready': $ren_err"
)
unless
$ren_ok
;
test2_ipc_set_pending(
$file
);
}
else
{
my
$src_file
= __FILE__;
$err
=~ s{ at \Q
$src_file
\E.*$}{};
chomp
(
$err
);
my
$tid
= get_tid();
my
$trace
=
$e
->trace->debug;
my
$type
= blessed(
$e
);
$self
->abort(
<<" EOT");
*******************************************************************************
There was an error writing an event:
Destination: $dest
Origin PID: $$
Origin TID: $tid
Event Type: $type
Event Trace: $trace
File Name: $file
Ready Name: $ready
Error: $err
*******************************************************************************
EOT
}
return
1;
}
sub
driver_abort {
my
$self
=
shift
;
my
(
$msg
) =
@_
;
local
($@, $!, $?, $^E);
eval
{
my
$abort
= File::Spec->catfile(
$self
->{+TEMPDIR},
"ABORT"
);
open
(
my
$fh
,
'>>'
,
$abort
) or
die
"Could not open abort file: $!"
;
print
$fh
$msg
,
"\n"
;
close
(
$fh
) or
die
"Could not close abort file: $!"
;
1;
} or
warn
$@;
}
sub
cull {
my
$self
=
shift
;
my
(
$hid
) =
@_
;
my
$tempdir
=
$self
->{+TEMPDIR};
opendir
(
my
$dh
,
$tempdir
) or
$self
->abort(
"could not open IPC temp dir ($tempdir)!"
);
my
$read
=
$self
->{+READ_IDS};
my
$timeouts
=
$self
->{+TIMEOUTS};
my
@out
;
for
my
$info
(
sort
cmp_events
map
{
$self
->should_read_event(
$hid
,
$_
) }
readdir
(
$dh
)) {
unless
(
$info
->{global}) {
my
$next
=
$self
->{+READ_IDS}->{
$info
->{hid}}->{
$info
->{pid}}->{
$info
->{tid}} ||= 1;
$timeouts
->{
$info
->{file}} ||=
time
;
if
(
$next
!=
$info
->{eid}) {
next
unless
5 <
time
-
$timeouts
->{
$info
->{file}};
$self
->abort(
"Missing event HID: $info->{hid}, PID: $info->{pid}, TID: $info->{tid}, EID: $info->{eid}."
);
}
$self
->{+READ_IDS}->{
$info
->{hid}}->{
$info
->{pid}}->{
$info
->{tid}} =
$info
->{eid} + 1;
}
my
$full
=
$info
->{full_path};
my
$obj
=
$self
->read_event_file(
$full
);
push
@out
=>
$obj
;
next
if
$info
->{global};
if
(
$ENV
{T2_KEEP_TEMPDIR}) {
my
$complete
= File::Spec->canonpath(
"$full.complete"
);
my
(
$ok
,
$err
) = do_rename(
$full
,
$complete
);
$self
->abort(
"Could not rename IPC file '$full', '$complete': $err"
)
unless
$ok
;
}
else
{
my
(
$ok
,
$err
) = do_unlink(
"$full"
);
$self
->abort(
"Could not unlink IPC file '$full': $err"
)
unless
$ok
;
}
}
closedir
(
$dh
);
return
@out
;
}
sub
parse_event_filename {
my
$self
=
shift
;
my
(
$file
) =
@_
;
my
$complete
=
substr
(
$file
, -9, 9) eq
'.complete'
|| 0 and
substr
(
$file
, -9, 9,
""
);
my
$ready
=
substr
(
$file
, -6, 6) eq
'.ready'
|| 0 and
substr
(
$file
, -6, 6,
""
);
my
@parts
=
split
ipc_separator,
$file
;
my
(
$global
,
$hid
) =
$parts
[0] eq
'GLOBAL'
? (1,
shift
@parts
) : (0,
join
ipc_separator,
splice
(
@parts
, 0, 4));
my
(
$pid
,
$tid
,
$eid
) =
splice
(
@parts
, 0, 3);
my
$type
=
join
'::'
=>
@parts
;
return
{
file
=>
$file
,
ready
=> !!
$ready
,
complete
=> !!
$complete
,
global
=>
$global
,
type
=>
$type
,
hid
=>
$hid
,
pid
=>
$pid
,
tid
=>
$tid
,
eid
=>
$eid
,
};
}
sub
should_read_event {
my
$self
=
shift
;
my
(
$hid
,
$file
) =
@_
;
return
if
substr
(
$file
, 0, 1) eq
'.'
;
return
if
substr
(
$file
, 0, 3) eq
'HUB'
;
CORE::
exit
(255)
if
$file
eq
'ABORT'
;
my
$parsed
=
$self
->parse_event_filename(
$file
);
return
if
$parsed
->{complete};
return
unless
$parsed
->{ready};
return
unless
$parsed
->{global} ||
$parsed
->{hid} eq
$hid
;
return
if
$parsed
->{global} &&
$self
->{+GLOBALS}->{
$hid
}->{
$file
}++;
my
$full
= File::Spec->catfile(
$self
->{+TEMPDIR},
$file
);
(
$full
) = (
$full
=~ m/^(.*)$/gs)
if
${^TAINT};
$parsed
->{full_path} =
$full
;
return
$parsed
;
}
sub
cmp_events {
return
-1
if
$a
->{global} && !
$b
->{global};
return
1
if
$b
->{global} && !
$a
->{global};
return
$a
->{pid} <=>
$b
->{pid}
||
$a
->{tid} <=>
$b
->{tid}
||
$a
->{eid} <=>
$b
->{eid};
}
sub
read_event_file {
my
$self
=
shift
;
my
(
$file
) =
@_
;
my
$obj
= Storable::retrieve(
$file
);
$self
->abort(
"Got an unblessed object: '$obj'"
)
unless
blessed(
$obj
);
unless
(
$obj
->isa(
'Test2::Event'
)) {
my
$pkg
= blessed(
$obj
);
my
$mod_file
= pkg_to_file(
$pkg
);
my
(
$ok
,
$err
) =
try
{
require
$mod_file
};
$self
->abort(
"Event has unknown type ($pkg), tried to load '$mod_file' but failed: $err"
)
unless
$ok
;
$self
->abort(
"'$obj' is not a 'Test2::Event' object"
)
unless
$obj
->isa(
'Test2::Event'
);
}
return
$obj
;
}
sub
waiting {
my
$self
=
shift
;
$self
->
send
(
GLOBAL
=> Test2::Event::Waiting->new(
trace
=> Test2::EventFacet::Trace->new(
frame
=> [
caller
()]),
),
'GLOBAL'
);
return
;
}
sub
DESTROY {
my
$self
=
shift
;
return
unless
defined
$self
->pid;
return
unless
defined
$self
->tid;
return
unless
$$ ==
$self
->pid;
return
unless
get_tid() ==
$self
->tid;
my
$tempdir
=
$self
->{+TEMPDIR};
my
$aborted
= 0;
my
$abort_file
= File::Spec->catfile(
$self
->{+TEMPDIR},
"ABORT"
);
if
(-e
$abort_file
) {
$aborted
= 1;
my
(
$ok
,
$err
) = do_unlink(
$abort_file
);
warn
$err
unless
$ok
;
}
opendir
(
my
$dh
,
$tempdir
) or
$self
->abort(
"Could not open temp dir! ($tempdir)"
);
while
(
my
$file
=
readdir
(
$dh
)) {
next
if
$file
=~ m/^\.+$/;
next
if
$file
=~ m/\.complete$/;
my
$full
= File::Spec->catfile(
$tempdir
,
$file
);
my
$sep
= ipc_separator;
if
(
$aborted
||
$file
=~ m/^(GLOBAL|HUB
$sep
)/) {
$full
=~ m/^(.*)$/;
$full
= $1;
next
if
$ENV
{T2_KEEP_TEMPDIR};
my
(
$ok
,
$err
) = do_unlink(
$full
);
$self
->abort(
"Could not unlink IPC file '$full': $err"
)
unless
$ok
;
next
;
}
$self
->abort(
"Leftover files in the directory ($full)!\n"
);
}
closedir
(
$dh
);
if
(
$ENV
{T2_KEEP_TEMPDIR}) {
print
STDERR
"# Not removing temp dir: $tempdir\n"
;
return
;
}
my
$abort
= File::Spec->catfile(
$self
->{+TEMPDIR},
"ABORT"
);
unlink
(
$abort
)
if
-e
$abort
;
rmdir
(
$tempdir
) or
warn
"Could not remove IPC temp dir ($tempdir)"
;
}
1;
Hide Show 72 lines of Pod