Trying to convert some code from use Test::more
to use Test::Builder
I got this problem when trying to use is_deeply
(perl 5.18.2):
Cannot run test (h_c: is_deeply($n, $s)) with active children at /usr/lib/perl5/5.18.2/Test/More.pm line 1020
The manual for Test::Builder
states this example for explain
:
is_deeply($have, $want) || diag explain $have;
However Test::Builder
does not have the is_deeply
method, but Test::More
has it (I used use Test::More 'import' => [qw(is_deeply)];
to import only that).
Unfortunately when trying to use it from a sub-test ($tb->child()
), the program dies with the message cited above.
Is there a way to use is_deeply
in a sub-test?
Message from within the Perl debugger:
Cannot run test (h_c: is_deeply($n, $s)) with active children at /usr/lib/perl5/5.18.2/Test/More.pm line 1020.
at /usr/lib/perl5/5.18.2/Test/Builder.pm line 1969.
Test::Builder::croak('Test::Builder=HASH(0x1b6e3c0)', 'Cannot run test (h_c: is_deeply($n, $s)) with active children') called at /usr/lib/perl5/5.18.2/Test/Builder.pm line 769
Test::Builder::ok('Test::Builder=HASH(0x1b6e3c0)', 1, 'h_c: is_deeply($n, $s)') called at /usr/lib/perl5/5.18.2/Test/More.pm line 1020
Test::More::is_deeply('HOTP::Core=ARRAY(0x31131c8)', 'HOTP::Core=ARRAY(0x310ab10)', 'h_c: is_deeply($n, $s)') called at otptest.pl line 96
As I failed to extract a minimum example, I dug into the code I have.
Line 1020 of Test::More
is $ok = $tb->ok( 1, $name );
(so the actual test succeeded).
The interesting fact is $tb
that is not the Test::Builder
child instance used in the caller, but a local instance created in is_deeply
as my $tb = Test::More->builder
.
That instance has a child that is the first-level child of my Test::Builder
(that's not finished yet, of course), so the test $self->{Child_Name}
in line 766 of Test::Builder::ok
fails.
I'm tempted to make a copy of is_deeply
passing the correct Test::Builder
instance instead of creating a local one.
Anyway, here's the best approximation of the failure scenario I could make:
#!/usr/bin/perl
use strict;
use warnings;
use Test::Builder;
use Test::More 'import' => [qw(is_deeply)];
sub t2($$$)
{
my ($TB, $a, $b) = @_;
$TB->plan('tests' => 4);
$TB->ok(1, 't2.1');
$TB->ok(1, 't2.2');
is_deeply($a, $b, 't2.is_deeply');
$TB->ok(1, 't2.4');
$TB->finalize();
}
sub t1($$$)
{
my ($TB, $a, $b) = @_;
$TB->plan('tests' => 2);
t2($TB->child('t2'), $a, $b);
$TB->ok(1, 't1.2');
$TB->finalize();
}
sub main()
{
my ($a, $b) = ([1], [1]);
my $TB = Test::Builder->create();
my $subtest = sub($) { $TB->diag($_[0]); return $TB->child($_[0]); };
$TB->plan('tests' => 1);
t1($subtest->('t1.1'), $a, $b);
#t1($subtest->('t1.2'), $a, $b);
$TB->done_testing();
}
main();
And this is the output I got:
DB<0> c
1..1
# t1.1
1..2
1..4
ok 1 - t2.1
ok 2 - t2.2
ok 1 - t2.is_deeply
ok 3 - t2.4
# Looks like you planned 4 tests but ran 3.
not ok 1 - t2
# Failed test 't2'
# at foo.pl line 72.
ok 2 - t1.2
# Looks like you failed 1 test of 2.
not ok 1 - t1.1
# Failed test 't1.1'
# at foo.pl line 84.
# Tests were run but no plan was declared and done_testing() was not seen.
Debugged program terminated. Use q to quit or R to restart,
When debugging the code, I realized that my actual program used Test::Builder->new
instead of Test::Builder->create
(as in the minimal example). That made the difference (also see my answer https://stackoverflow.com/a/78159022/6607497):
#!/usr/bin/perl
use strict;
use warnings;
use Test::Builder;
use Test::More 'import' => [qw(is_deeply)];
sub t2($$$)
{
my ($TB, $a, $b) = @_;
$TB->plan('tests' => 4);
$TB->ok(1, 't2.1');
$TB->ok(1, 't2.2');
#local $Test::Builder::Test = $TB; # ugly and dangerous!
is_deeply($a, $b, 't2.is_deeply');
$TB->ok(1, 't2.4');
$TB->finalize();
}
sub t1($$$)
{
my ($TB, $a, $b) = @_;
$TB->plan('tests' => 2);
t2($TB->child('t2'), $a, $b);
$TB->ok(1, 't1.2');
$TB->finalize();
}
sub main()
{
my ($a, $b) = ([1], [1]);
my $TB = Test::Builder->new();
my $subtest = sub($) { $TB->diag($_[0]); return $TB->child($_[0]); };
$TB->plan('tests' => 1);
t1($subtest->('t1.1'), $a, $b);
#t1($subtest->('t1.2'), $a, $b);
$TB->done_testing();
}
main();
Loading DB routines from perl5db.pl version 1.39_10
Editor support available.
Enter h or 'h h' for help, or 'man perldebug' for more help.
main::(foo.pl:42): main();
DB<0> c
1..1
# t1.1
1..2
1..4
ok 1 - t2.1
ok 2 - t2.2
Cannot run test (t2.is_deeply) with active children at /usr/lib/perl5/5.18.2/Test/More.pm line 1020.
at /usr/lib/perl5/5.18.2/Test/Builder.pm line 1969.
Test::Builder::croak('Test::Builder=HASH(0x2003c58)', 'Cannot run test (t2.is_deeply) with active children') called at /usr/lib/perl5/5.18.2/Test/Builder.pm line 769
Test::Builder::ok('Test::Builder=HASH(0x2003c58)', 1, 't2.is_deeply') called at /usr/lib/perl5/5.18.2/Test/More.pm line 1020
Test::More::is_deeply('ARRAY(0x1d12770)', 'ARRAY(0x2476030)', 't2.is_deeply') called at foo.pl line 15
main::t2('Test::Builder=HASH(0x2508560)', 'ARRAY(0x1d12770)', 'ARRAY(0x2476030)') called at foo.pl line 25
main::t1('Test::Builder=HASH(0x25116e0)', 'ARRAY(0x1d12770)', 'ARRAY(0x2476030)') called at foo.pl line 37
main::main() called at foo.pl line 42
# Child (t2) exited without calling finalize()
not ok 1 - t2
# Failed test 't2'
# at /usr/lib/perl5/5.18.2/Test/Builder.pm line 1969.
# Child (t1.1) exited without calling finalize()
not ok 1 - t1.1
# Failed test 't1.1'
# at /usr/lib/perl5/5.18.2/Test/Builder.pm line 1969.
# Looks like you failed 1 test of 1.
# Looks like your test exited with 29 just after 1.
Debugged program terminated. Use q to quit or R to restart,
When un-commenting the local
line, the output is instead:
main::(foo.pl:42): main();
DB<0> c
1..1
# t1.1
1..2
1..4
ok 1 - t2.1
ok 2 - t2.2
ok 3 - t2.is_deeply
ok 4 - t2.4
ok 1 - t2
ok 2 - t1.2
ok 1 - t1.1
Debugged program terminated. Use q to quit or R to restart,
I believe the effect is caused by several bugs in Test::Builder
:
Test::Builder->new()
, a "copy" of the returned objects is saved as $Test::Builder::Test
, but that "copy" is actually a copied reference, so that setting the number of tests in the object(s) returned by new
also changes the number of tests in $Test::Builder::Test
.is_deeply
from Test::More
should be a method of Test::Builder
actually, but it uses its own lexical instance of Test::More->builder
. That, in turn. returns the value of $Test::Builder::Test
. Naturally that won't work for nested tests (i.e.: children of Test::Builder
).My solution was to add local $Test::Builder::Test = $TB;
just before calling is_deeply
.
In addition I could have used Test::Builder->create()
instead of Test::Builder->new()
, but that still would not fix the output of is_deeply
.