BEGIN {
my
@ltime
=
localtime
;
$ltime
[5] += 1900;
for
( 3, 4 ) {
$ltime
[4] =
"0$ltime[$_]"
unless
$ltime
[
$_
] > 9;
}
my
$seed
=
$ENV
{FENNEC_SEED} ||
join
(
''
,
@ltime
[5, 4, 3] );
print
"\n*** Seeding random with date ($seed) ***\n"
;
srand
(
$seed
);
}
accessors
qw/pid listener test_classes/
;
my
$SINGLETON
;
my
$listener_class
;
sub
listener_class {
unless
(
$listener_class
) {
if
( $^O eq
'MSWin32'
) {
$listener_class
=
'Fennec::Listener::TBWin32'
;
}
$listener_class
=
'Fennec::Listener::TB2'
;
}
else
{
$listener_class
=
'Fennec::Listener::TB'
;
}
}
return
$listener_class
;
}
sub
init { }
sub
import
{
my
$self
=
shift
->new();
return
unless
@_
;
$self
->_load_guess(
$_
)
for
@_
;
$self
->inject_run(
scalar
caller
);
}
sub
inject_run {
my
$self
=
shift
;
my
(
$caller
) =
@_
;
Fennec::Util::inject_sub(
$caller
,
'run'
,
sub
{
$self
->run }
);
}
sub
new {
my
$class
=
shift
;
return
$SINGLETON
if
$SINGLETON
;
$SINGLETON
=
bless
(
{
test_classes
=> [],
pid
=> $$,
listener
=>
$class
->listener_class->new() || croak
"Could not init listener!"
,
},
$class
);
$SINGLETON
->init(
@_
);
return
$SINGLETON
;
}
sub
_load_guess {
my
$self
=
shift
;
my
(
$item
) =
@_
;
if
(
ref
$item
&&
ref
$item
eq
'CODE'
) {
$self
->_load_guess(
$_
)
for
(
$self
->
$item
);
return
;
}
return
$self
->load_file(
$item
)
if
$item
=~ m/\.(pm|t|pl)$/i
||
$item
=~ m{/};
return
$self
->load_module(
$item
)
if
$item
=~ m/::/
||
$item
=~ m/^\w[\w\d_]+$/;
die
"Not sure how to load '$item'\n"
;
}
sub
load_file {
my
$self
=
shift
;
my
(
$file
) =
@_
;
print
"Loading: $file\n"
;
eval
{
require
$file
; 1 } ||
$self
->exception(
$file
, $@ );
$self
->check_pid();
}
sub
check_pid {
my
$self
=
shift
;
return
unless
$self
->pid != $$;
die
"PID has changed! Did you forget to exit a child process?\n"
;
}
sub
load_module {
my
$self
=
shift
;
my
$module
=
shift
;
print
"Loading: $module\n"
;
eval
"require $module"
||
$self
->exception(
$module
, $@ );
$self
->check_pid();
}
sub
run {
my
$self
=
shift
;
Test::Class->runtests
if
$INC
{
'Test/Class.pm'
} && !
$ENV
{
'FENNEC_TEST'
};
for
my
$class
( @{
$self
->test_classes} ) {
next
unless
$class
&&
$class
->can(
'TEST_WORKFLOW'
);
print
"Running: $class\n"
;
my
$instance
=
$class
->can(
'new'
) ?
$class
->new :
bless
( {},
$class
);
my
$meta
=
$instance
->TEST_WORKFLOW;
$meta
->debug_long_running(
$instance
->FENNEC->debug_long_running );
my
$prunner
;
if
(
my
$max
=
$class
->FENNEC->parallel ) {
if
( $^O eq
'MSWin32'
) {
print
"Parallization unavailable on windows.\n"
;
}
else
{
$prunner
=
$self
->get_prunner(
max
=>
$max
);
$meta
->test_run(
sub
{
my
(
$sub
,
$test
,
$obj
) =
shift
;
$prunner
->run(
sub
{
my
(
$parent
) =
@_
;
$self
->listener->setup_child(
$parent
->write_handle )
if
$parent
;
$sub
->();
},
1,
);
}
);
}
}
Test::Workflow::run_tests(
$instance
);
$prunner
->finish
if
$prunner
;
$meta
->test_run(
undef
);
$self
->check_pid();
}
$self
->listener->terminate();
}
sub
get_prunner {
my
$self
=
shift
;
my
%params
=
@_
;
my
$prunner
= Parallel::Runner->new(
$params
{max},
pipe
=> 1 );
$prunner
->reap_callback(
sub
{
my
(
$status
,
$pid
,
$pid_again
,
$proc
) =
@_
;
while
(
my
$data
=
eval
{
$proc
->
read
() } ) {
$self
->listener->process(
$data
);
}
$self
->listener->flush(
$pid
);
$self
->exception(
"Child process did not exit cleanly"
,
"Status: $status"
)
if
$status
;
}
);
$prunner
->iteration_callback(
sub
{
my
$runner
=
shift
;
for
my
$proc
(
$runner
->children ) {
while
(
my
$data
=
eval
{
$proc
->
read
() } ) {
$self
->listener->process(
$data
);
}
}
}
);
return
$prunner
;
}
sub
exception {
my
$self
=
shift
;
my
(
$name
,
$exception
) =
@_
;
if
(
$exception
=~ m/^FENNEC_SKIP: (.*)\n/ ) {
$self
->listener->ok( 1,
"SKIPPING $name: $1"
);
}
else
{
$self
->listener->ok( 0,
$name
);
$self
->listener->diag(
$exception
);
}
}
1;