NAME
Test::Weaken - Test that freed references are, indeed, freed
SYNOPSIS
use Test::Weaken qw(poof);
my $test = sub {
my $obj1 = new Module::Test_me1;
my $obj2 = new Module::Test_me2;
[ $obj1, $obj2 ];
};
my $unfreed_count = Test::Weaken::poof( $test );
my ($weak_count, $strong_count, $weak_unfreed, $strong_unfreed)
= Test::Weaken::poof( $test );
print scalar @$weak_unfreed,
" of $weak_count weak references freed\n";
print scalar @$strong_unfreed,
" of $strong_count strong references freed\n";
print "Weak unfreed references: ",
join(" ", map { "".$_ } @$weak_unfreed), "\n";
print "Strong unfreed references: ",
join(" ", map { "".$_ } @$strong_unfreed), "\n";
DESCRIPTION
Frees memory, and checks that the memory is deallocated. The memory checked includes all memory referred to indirectly, whether through arrays, hashes, weak references or strong references. Arrays, hashes and references are followed recursively and to unlimited depth.
Circular references are handled gracefully. In fact, a major purpose of Test::Weaken
is to test schemes for deallocating circular references.
METHOD
poof
The poof
static method takes one or two closures as arguments. The first, required, argument is the test object constructor for the object to be tested. The second, optional, argument is a test object destructor.
The test object constructor, sometimes simply called the constructor when no confusion with other constructors will arise, should build the test object and create a primary test reference to it. The return value of the test object constructor must be the primary test reference. It is usually best to construct the test object inside the test object constructor as much as possible. That is the easiest way to construct a test object with no references into it from poof
's calling environment. More on this below.
The test object destructor is optional. The test object destructor will be called just before Test::Weaken
frees the primary test reference. The destructor is called with the primary test reference as its only argument, just before the primary test reference is freed. The test object destructor can be used to allow Test::Weaken
to work with objects that require a destructor to be called on them before they are freed. For example, some of the objects created by Gtk2-Perl are of this type. If no test object destructor is specified, no last minute processing will be done on the primary test reference before it is freed.
By recursively following references, arrays, and hashes from the primary test reference, poof
finds all of the references in the test object. These are poof
's test references. In recursing through the test object, poof
keeps track of visited references. poof
never visits the same reference twice, and therefore has no problem when it has to deal with a test object which contains circular references.
In scalar context, poof
returns the number of unfreed test references. If all memory was deallocated successfully, this number will be zero.
In array context, poof
returns a list with four elements. First, the starting count of weak references. Second, the starting count of strong references. Third, a reference to an array containing references to the unfreed weak references. Fourth, a reference to an array containing references to the unfreed strong references.
If @result
is the result array from a call to poof
, then the number of strong references that were freed can be calculated as
$result[1] - @{$result[3]}
that is, the starting count of strong references, less the size of the array containing the unfreed strong references. Similarly, the count of freed weak references will be
$result[0] - @{$result[2]}
The unfreed references in the arrays can be dereferenced and the unfreed data examined. This may offer a clue to locate the source of a memory leak.
Why the Arguments are Closures
Obtaining the primary test reference from a test object constructor may seem roundabout. In fact, this indirect method is the easiest. The test object must not have any strong references to it from outside. It takes some craft to create and pass an object without holding a reference to it. Mistakes are easy to make. If a mistake were made, and poof
's calling environment held a strong reference into the test object, not all memory would be freed. If this happened by accident, so that the programmer was not aware of what was going on, the effect would be a false negative. Errors like this are tricky to detect and hard to debug.
When the test object is constructed completely within the test object constructor, and all the objects used to construct it are also in the scope of the test object constructor, there will be no strong references held from outside the test object once the test object constructor returns. Following this discipline, it is relatively easy for a careful programmer to avoid false negatives.
Tricks and Techniques
In scalar context poof
returns zero if all references were freed. This is the simplest way to use poof
, but sometimes special techniques are required.
Objects Which Refer to External Memory
The object you need to test may have references to "external" memory -- memory consider "outside" the object and not intended to be freed when the object is. When this is the case several techniques are be used.
First, if you can rely there being a fixed count of "external" references, you can call poof
in scalar context and check that the correct count is returned. Be aware that this can cause false positives in cases where equal numbers of references which shouldn't be freed are freed and references which should be freed are not.
Second, you can call poof
in array context, so that it returns lists of the unfreed references. The unfreed references can be then be checked to ensure all and only the correct references are not freed. If references can't be identified using their contents, the addresses of the references can be recorded beforehand and afterward, and compared.
Tracing Memory Leaks
poof
called in array context can also be used to find the source of memory leaks. Pointer address can be used to identified which references are being "leaked". Depending on the application, you can also add elements to arrays and hashes to "tag" them for this purpose.
If You Really Must Test Deallocation of a Global
As explained above, poof
takes its test object as the the return value from a closure because it's tricky to create objects in the global environment without holding references to them which will cause false negatives. If you really have no choice but to do use a reference to an object from the global environment, you can defeat poof
's safeguards by specifying as poof
's constructor argument a closure which return the reference to the global object.
EXPORT
By default, Test::Weaken
exports nothing. Optionally, poof
may be exported.
LIMITATIONS
Test::Weaken
does not look inside code references.
IMPLEMENTATION
poof
's Name
The name poof
is intended to warn the programmer that the test is destructive. I originally called the main subroutine destroy
, but that choice seemed unfortunate because of similarities to DESTROY
, a name reserved for object destructors.
How poof
Works
Test::Weaken
first recurses through the test object. It follows all weak and strong references, arrays and hashes. The test object is explored to unlimited depth. Visited references are tracked, and no reference is visited twice. Two lists of test references into the original data are generated. One list is of strong test references and the other is of weak test references.
As it recurses, Test::Weaken
creates a probe reference for every test reference. The probe references to the strong test references are weakened, so that the probe reference will not interfere with normal deallocation of memory.
When all the probe references have been created, the primary test reference is weakened. Normally, this causes the test object to be deallocated. To check this, Test::Weaken
dereferences the probe references. If the referent of a probe reference was deallocated, the value of that probe reference will be undef
.
AUTHOR
Jeffrey Kegler
BUGS
Please report any bugs or feature requests to bug-test-weaken at rt.cpan.org
, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-Weaken. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Test::Weaken
You can also look for information at:
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
RT: CPAN's request tracker
Search CPAN
SEE ALSO
Potential users will want to compare Test::Memory::Cycle and Devel::Cycle, which examine existing structures non-destructively. Devel::Leak also covers similar ground, although it requires Perl to be compiled with -DDEBUGGING
in order to work. Devel::Cycle looks inside closures if PadWalker is present, a feature Test::Weaken
does not have at present.
ACKNOWLEDGEMENTS
Thanks to jettero, Juerd and perrin of Perlmonks for their advice. Thanks also to Lincoln Stein (developer of Devel::Cycle) for test cases and other ideas.
COPYRIGHT & LICENSE
Copyright 2007-2009 Jeffrey Kegler, all rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl 5.10.