Reputation: 33
Test::LeakTrace
says code leaking. I don't understand.
I do not understand output of Test::LeakTrace
, and it is too long for this post. Some leaks from Test system, but others? No.
This is the code.
use 5.26.0;
use warnings;
use Test::More;
use Test::LeakTrace;
sub spawn {
my %methods = @_;
state $spawned = 1;
my $object = bless {}, "Spawned::Class$spawned";
$spawned++;
while ( my ( $method, $value ) = each %methods ) {
no strict 'refs';
*{ join '::', ref($object), $method } = sub { $value };
}
return $object;
}
no_leaks_ok {
my $spawn = spawn( this => 2 );
is( $spawn->this, 2 );
}
'no leaks';
done_testing;
I get weird things like this:
# leaked SCALAR(0x7f9b41a069c0) from leak.pl line 11.
# 10: $spawned++;
# 11: while ( my ( $method, $value ) = each %methods ) {
# 12: no strict 'refs';
# SV = IV(0x7f9b41a069b0) at 0x7f9b41a069c0
# REFCNT = 1
# FLAGS = (IOK,pIOK)
# IV = 2
And this:
# leaked GLOB(0x7f9b411b22a0) from leak.pl line 9.
# 8: state $spawned = 1;
# 9: my $object = bless {}, "Spawned::Class$spawned";
# 10: $spawned++;
# SV = PVGV(0x7f9b41a29530) at 0x7f9b411b22a0
# REFCNT = 1
# FLAGS = (MULTI)
# NAME = "Class4::"
# NAMELEN = 8
# GvSTASH = 0x7f9b4081a0a8 "Spawned"
# FLAGS = 0x2
# GP = 0x7f9b40534720
# SV = 0x0
# REFCNT = 1
# IO = 0x0
# FORM = 0x0
# AV = 0x0
# HV = 0x7f9b41a24730
# CV = 0x0
# CVGEN = 0x0
# GPFLAGS = 0x0 ()
# LINE = 9
# FILE = "leak.pl"
# EGV = 0x7f9b411b22a0 "Class4::"
Nothing makes sense to me. Reference counts are 1.
Upvotes: 3
Views: 178
Reputation: 385897
Your code leaks. They are intentional leaks, but leaks nonetheless.
You create a package that is never freed.[1] In it you create a glob that is never freed. To this glob you assign a sub that is never freed. The sub captures a variable, so it's never freed.
The module is doing its job and telling you about this.
I encountered a few surprises confirming the above was what was happening. The rest of this answer identifies them and explains them.
I'll be using this program (a.pl
):
use 5.010;
use Test::More tests => 1;
use Test::LeakTrace;
sub f {
state $spawned = 1;
my $object = bless {}, "Spawned::Class$spawned" if $ARGV[0] & 1;
$spawned++ if $ARGV[0] & 2;
delete $Spawned::{"Class".($spawned-1)."::"} if $ARGV[0] & 4;
}
If we do $spawned++;
but not the bless:
$ perl a.pl 1
1..1
ok 1 - leaks 0 <= 0
Expected.
If we do the bless but not $spawned++;
:
$ perl a.pl 2
1..1
ok 1 - leaks 0 <= 0
huh!? We created global symbols. Shouldn't those be considered leaks? So why did the OP produce leaks, then? I'll come back to this.
If we do both:
$ perl a.pl 3
1..1
not ok 1 - leaks 8 <= 0
# Failed test 'leaks 8 <= 0'
# at a.pl line 11.
# '8'
# <=
# '0'
#
# [snip]
huh?! Why is it suddenly mentioning the global symbols we created?! I mean, it's what we expect, but we expected it above too. I'll come back to this.
Finally, we'll also undo the changes we made.
$ perl a.pl 7
1..1
ok 1 - leaks 0 <= 0
As expected, if we release the additions we made to the global symbol table, it no longer reports any leaks.
Now let's address the questions I raised.
Imagine if you had done something like
state $cache = { };
You wouldn't want that hash to be reported as a leak even though it's never freed. To that end, Test::LeakTrace evaluates the test block twice, ignoring leaks from the first call.
Leaked SVs are SVs which are not released after the end of the scope they have been created. These SVs include global variables and internal caches. For example, if you call a method in a tracing block, perl might prepare a cache for the method. Thus, to trace true leaks,
no_leaks_ok()
andleaks_cmp_ok()
executes a block more than once.
That's why perl a.pl 2
didn't result in any reported leaks.
But perl a.pl 3
and the OP's code (intentionally) leak every time they are called, not just the first. Test::LeakTrace has no way to know those leaks are intentional, so you get what I suppose you could call false positives.
Upvotes: 5