our
@EXPORT
=
qw(TEST TEST_STDOUT TEST_EXCEPTION GIVES perhaps_run_tests)
;
our
@EXPORT_OK
=
qw(run_tests run_tests_ no_tests)
;
our
%EXPORT_TAGS
= (
all
=> [
@EXPORT
,
@EXPORT_OK
]);
sub
run_tests_style {
if
(
my
$rt
=
$ENV
{RUN_TESTS}) {
(
$rt
=~ /(old|pod_snippets)/i ?
"old"
:
"tap"
)
}
else
{
"old"
}
}
our
$run_tests_style
;
sub
style_switch {
my
$choices
=
shift
;
my
$handler
=
$choices
->{
$run_tests_style
}
or
die
"missing choice for style '$run_tests_style'"
;
goto
$handler
}
sub
import
{
my
$class
=
shift
;
my
(
$package
,
$filename
,
$line
) =
caller
;
my
@args
;
for
(
my
$i
= 0;
$i
<
@_
;
$i
++) {
my
$v
=
$_
[
$i
];
if
(
$v
eq
"use"
or
$v
eq
"require"
) {
my
$val
=
$_
[
$i
+ 1];
defined
$val
or croak
"undef given as 'require' parameter"
;
my
(
$module
,
@args
) =
do
{
if
(
ref
(
$val
) eq
"ARRAY"
) {
@$val
}
elsif
(
length
$val
) {
(
$val
)
}
else
{
croak
"value given as 'require' parameter must be a string or array"
;
}
};
my
$smallcode
= (
"$v $module "
.
join
(
","
,
map
{ singlequote
$_
}
@args
));
$filename
=~ /[\r\n]/
and
die
"possible security issue"
;
my
$code
=
"#line $line $filename\npackage $package; $smallcode; 1"
;
if
(
eval
$code
) {
}
else
{
if
(
my
$rt
=
$ENV
{RUN_TESTS}) {
if
(
$rt
=~ /pod_snippets/i) {
die
"TEST use<$module> failed: $smallcode"
;
}
else
{
Test::More::plan(
skip_all
=>
"could not $smallcode"
);
exit
1;
}
}
else
{
die
$@
}
}
$i
++;
}
else
{
push
@args
,
$v
}
}
my
$sub
=
$class
->can(
"SUPER::import"
)
or
die
"$class does not have an 'import' procedure"
;
@_
= (
$class
,
@args
);
goto
&$sub
;
}
our
$tests_by_package
= {};
our
$num_by_package
= {};
our
$dropped_tests
= 0;
sub
no_tests {
@_
== 0 or fp_croak_arity 0;
exists
$ENV
{TEST} and !
$ENV
{TEST}
}
sub
_TEST {
my
(
$proc
,
$res
) =
@_
;
if
(no_tests) {
$dropped_tests
++;
}
else
{
my
(
$package
,
$filename
,
$line
) =
caller
(1);
$$num_by_package
{
$package
}++;
push
@{
$$tests_by_package
{
$package
} },
[
$proc
,
$res
,
$$num_by_package
{
$package
}, (
$package
,
$filename
,
$line
)
];
}
}
sub
TEST (&$) {
_TEST(
@_
)
}
sub
TEST_STDOUT (&$) {
my
(
$proc
,
$res
) =
@_
;
_TEST(
sub
{ capture_stdout_(
$proc
) },
$res
);
}
sub
TEST_EXCEPTION (&$) {
my
(
$proc
,
$res
) =
@_
;
_TEST(
sub
{
if
(
eval
{
&$proc
();
1
}
)
{
undef
}
else
{
my
$msg
=
"$@"
;
$msg
=~ s| at .*? line \d*.*||s;
$msg
}
},
$res
);
}
sub
GIVES (&) {
my
(
$thunk
) =
@_
;
bless
$thunk
,
"Chj::TEST::GIVES"
;
}
our
@ISA
=
qw(Test::Builder)
;
our
$fake_caller
;
sub
caller
{
my
(
$self
,
$height
) =
@_
;
if
(
$fake_caller
) {
wantarray
?
@$fake_caller
:
$$fake_caller
[0]
}
else
{
my
$m
=
$self
->can(
"SUPER::caller"
) or
die
"bug"
;
goto
$m
}
}
}
sub
eval_test {
@_
== 2 or fp_croak_arity 2;
my
(
$t
,
$stat
) =
@_
;
my
(
$proc
,
$res
,
$num
,
$package
,
$filename
,
$line
) =
@$t
;
style_switch {
old
=>
sub
{
print
"running test $num.."
;
},
tap
=>
sub
{
},
};
my
(
$got
,
$maybe_e
);
my
$action
=
sub
{
$got
=
&$proc
;
if
(
ref
(
$res
) eq
'Chj::TEST::GIVES'
) {
$res
=
&$res
;
}
};
style_switch {
old
=>
$action
,
tap
=>
sub
{
eval
{
&$action
;
1
} ||
do
{
$maybe_e
= [$@];
}
},
};
my
$location
=
"at $filename line $line"
;
my
$nicelocation
=
"line $line"
;
if
(!
$maybe_e
and relaxedequal(
$got
,
$res
)) {
style_switch {
old
=>
sub
{
print
"ok\n"
;
},
tap
=>
sub
{
pass(
$nicelocation
);
},
};
$$stat
{successes}++
}
else
{
my
$gotstr
= show
$got
;
my
$resstr
= show
$res
;
style_switch {
old
=>
sub
{
die
"bug, shouldn't happen in this mode"
if
defined
$maybe_e
;
print
"FAIL $location:\n"
;
print
" got: $gotstr\n"
;
print
" expected: $resstr\n"
;
},
tap
=>
sub
{
my
$tb
= Test::More->builder;
(
ref
(
$tb
) eq
'Test::Builder'
or
ref
(
$tb
) eq
'Chj::TEST::Test::Builder'
or
die
"unexpected class of: $tb"
);
bless
$tb
,
'Chj::TEST::Test::Builder'
;
local
$Chj::TEST::Test::Builder::fake_caller
= [
$package
,
$filename
,
$line
];
$tb
->ok(0,
$location
);
if
(
defined
$maybe_e
) {
diag(
"Exception: $$maybe_e[0]"
);
}
else
{
diag(
" got: $gotstr\n"
.
" expected: $resstr\n"
);
}
},
};
$$stat
{failures}++
}
}
sub
run_tests_for_package {
my
(
$package
,
$stat
,
$maybe_testnumbers
) =
@_
;
my
$action
=
sub
{
if
(
my
$tests
=
$$tests_by_package
{
$package
}) {
if
(
defined
$maybe_testnumbers
) {
style_switch {
old
=>
sub
{
print
"=== running selected tests in package '$package'\n"
;
},
tap
=>
sub
{
warn
"=== running selected tests in package '$package'\n"
;
},
};
for
my
$number
(
@$maybe_testnumbers
) {
if
(
$number
=~ /^\d+\z/
and
$number
> 0
and (
my
$test
=
$$tests
[
$number
- 1]))
{
eval_test
$test
,
$stat
}
else
{
warn
"ignoring invalid test number '$number'"
;
}
}
}
else
{
my
$action
=
sub
{
for
my
$test
(
@$tests
) {
eval_test
$test
,
$stat
}
};
style_switch {
old
=>
sub
{
print
"=== running tests in package '$package'\n"
;
&$action
;
},
tap
=>
sub
{
plan(
tests
=>
scalar
@$tests
);
&$action
;
done_testing();
}
};
}
}
else
{
style_switch {
old
=>
sub
{
print
"=== no tests for package '$package'\n"
;
},
tap
=>
sub
{
plan(
tests
=> 1);
pass(
"no tests for package '$package'"
);
done_testing();
},
};
}
};
style_switch {
old
=>
$action
,
tap
=>
sub
{
subtest(
"Package $package"
=>
$action
);
},
};
}
sub
unify_values {
my
$maybe_values
;
for
(
@_
) {
if
(
ref
$_
) {
push
@$maybe_values
,
@$_
}
elsif
(
defined
$_
) {
push
@$maybe_values
,
$_
}
}
$maybe_values
}
my
$accessor
=
sub
{
my
(
$field
) =
@_
;
sub
{
my
$s
=
shift
;
$$s
{
$field
} }
};
*failures
=
$accessor
->(
"failures"
);
*successes
=
$accessor
->(
"successes"
);
}
sub
run_tests_ {
@_
% 2 and
die
"need even number of arguments"
;
my
$args
= +{
@_
};
my
$maybe_packages
= unify_values
delete
$$args
{packages},
delete
$$args
{
package
};
my
$maybe_testnumbers
= unify_values
delete
$$args
{numbers},
delete
$$args
{number},
delete
$$args
{
no
};
for
(
sort
keys
%$args
) {
warn
"run_tests_: unknown argument '$_'"
}
local
$run_tests_style
//= run_tests_style;
local
$| = 1;
style_switch {
old
=>
sub
{
print
"==== run_tests in $run_tests_style style ====\n"
;
},
tap
=>
sub
{
import
Test::More;
},
};
my
$stat
=
bless
{
successes
=> 0,
failures
=> 0 },
"Chj::TEST::Result"
;
my
$packages
=
do
{
if
(
defined
$maybe_packages
and
@$maybe_packages
) {
$maybe_packages
;
}
else
{
[
sort
keys
%$tests_by_package
]
}
};
my
$action
=
sub
{
run_tests_for_package
$_
,
$stat
,
$maybe_testnumbers
for
@$packages
;
};
style_switch {
old
=>
sub
{
&$action
;
print
"===\n"
;
print
" => $$stat{successes} success(es), "
.
"$$stat{failures} failure(s)\n"
;
},
tap
=>
sub
{
plan(
tests
=>
scalar
@$packages
);
&$action
;
done_testing();
},
};
$stat
}
sub
run_tests {
my
$packages
= [
@_
];
run_tests_
packages
=>
$packages
;
}
sub
perhaps_run_tests {
my
$args
= [
@_
];
if
(
$ENV
{RUN_TESTS}) {
die
"Tests were dropped due to the TEST environmental "
.
"variable being set to false"
if
$dropped_tests
;
local
$run_tests_style
//= run_tests_style;
import
Test::More;
style_switch {
old
=>
sub
{
is(
eval
{ run_tests(
@$args
)->failures }
//
do
{ diag($@);
undef
},
0,
"run_tests"
);
done_testing();
},
tap
=>
sub
{
run_tests(
@$args
);
},
};
1
}
else
{
()
}
}
1