/usr/share/perl5/Test/Refcount.pm is in libtest-refcount-perl 0.08-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 | # You may distribute under the terms of either the GNU General Public License
# or the Artistic License (the same terms as Perl itself)
#
# (C) Paul Evans, 2008-2014 -- leonerd@leonerd.org.uk
package Test::Refcount;
use strict;
use warnings;
use base qw( Test::Builder::Module );
use Scalar::Util qw( weaken refaddr );
use B qw( svref_2object );
our $VERSION = '0.08';
our @EXPORT = qw(
is_refcount
is_oneref
);
use constant HAVE_DEVEL_MAT_DUMPER => defined eval { require Devel::MAT::Dumper };
=head1 NAME
C<Test::Refcount> - assert reference counts on objects
=head1 SYNOPSIS
use Test::More tests => 2;
use Test::Refcount;
use Some::Class;
my $object = Some::Class->new();
is_oneref( $object, '$object has a refcount of 1' );
my $otherref = $object;
is_refcount( $object, 2, '$object now has 2 references' );
=head1 DESCRIPTION
The Perl garbage collector uses simple reference counting during the normal
execution of a program. This means that cycles or unweakened references in
other parts of code can keep an object around for longer than intended. To
help avoid this problem, the reference count of a new object from its class
constructor ought to be 1. This way, the caller can know the object will be
properly DESTROYed when it drops all of its references to it.
This module provides two test functions to help ensure this property holds
for an object class, so as to be polite to its callers.
If the assertion fails; that is, if the actual reference count is different to
what was expected, either of the following two modules may be used to assist
the developer in finding where the references are.
=over 4
=item *
If L<Devel::FindRef> module is installed, a reverse-references trace is
printed to the test output.
=item *
If L<Devel::MAT> is installed, this test module will use it to dump the state
of the memory after a failure. It will create a F<.pmat> file named the same
as the unit test, but with the trailing F<.t> suffix replaced with
F<-TEST.pmat> where C<TEST> is the number of the test that failed (in case
there was more than one).
=back
See the examples below for more information.
=cut
=head1 FUNCTIONS
=cut
=head2 is_refcount( $object, $count, $name )
Test that $object has $count references to it.
=cut
sub is_refcount($$;$)
{
my ( $object, $count, $name ) = @_;
@_ = ();
my $tb = __PACKAGE__->builder;
if( !ref $object ) {
my $ok = $tb->ok( 0, $name );
$tb->diag( " expected a reference, was not given one" );
return $ok;
}
weaken $object; # So this reference itself doesn't show up
my $REFCNT = svref_2object($object)->REFCNT;
my $ok = $tb->ok( $REFCNT == $count, $name );
unless( $ok ) {
$tb->diag( " expected $count references, found $REFCNT" );
if( eval { require Devel::FindRef } ) {
$tb->diag( Devel::FindRef::track( $object ) );
}
elsif( HAVE_DEVEL_MAT_DUMPER ) {
my $file = $0;
my $num = $tb->current_test;
# Trim the .t off first then append -$num.pmat, in case $0 wasn't a .t file
$file =~ s/\.(?:t|pm|pl)$//;
$file .= "-$num\.pmat";
$tb->diag( sprintf "SV address is 0x%x", refaddr $object );
$tb->diag( "Writing heap dump to $file" );
Devel::MAT::Dumper::dump( $file );
}
}
return $ok;
}
=head2 is_oneref( $object, $name )
Assert that the $object has only 1 reference to it.
=cut
sub is_oneref($;$)
{
splice( @_, 1, 0, ( 1 ) );
goto &is_refcount;
}
=head1 EXAMPLE
Suppose, having written a new class C<MyBall>, you now want to check that its
constructor and methods are well-behaved, and don't leak references. Consider
the following test script:
use Test::More tests => 2;
use Test::Refcount;
use MyBall;
my $ball = MyBall->new();
is_oneref( $ball, 'One reference after construct' );
$ball->bounce;
# Any other code here that might be part of the test script
is_oneref( $ball, 'One reference just before EOF' );
The first assertion is just after the constructor, to check that the reference
returned by it is the only reference to that object. This fact is important if
we ever want C<DESTROY> to behave properly. The second call is right at the
end of the file, just before the main scope closes. At this stage we expect
the reference count also to be one, so that the object is properly cleaned up.
Suppose, when run, this produces the following output (presuming
C<Devel::FindRef> is available):
1..2
ok 1 - One reference after construct
not ok 2 - One reference just before EOF
# Failed test 'One reference just before EOF'
# at demo.pl line 16.
# expected 1 references, found 2
# MyBall=ARRAY(0x817f880) is
# +- referenced by REF(0x82c1fd8), which is
# | in the member 'self' of HASH(0x82c1f68), which is
# | referenced by REF(0x81989d0), which is
# | in the member 'cycle' of HASH(0x82c1f68), which was seen before.
# +- referenced by REF(0x82811d0), which is
# in the lexical '$ball' in CODE(0x817fa00), which is
# the main body of the program.
# Looks like you failed 1 test of 2.
From this output, we can see that the constructor was well-behaved, but that a
reference was leaked by the end of the script - the reference count was 2,
when we expected just 1. Reading the trace output, we can see that there were
2 references that C<Devel::FindRef> could find - one stored in the $ball
lexical in the main program, and one stored in a HASH. Since we expected to
find the $ball lexical variable, we know we are now looking for a leak in a
hash somewhere in the code. From reading the test script, we can guess this
leak is likely to be in the bounce() method. Furthermore, we know that the
reference to the object will be stored in a HASH in a member called C<self>.
By reading the code which implements the bounce() method, we can see this is
indeed the case:
sub bounce
{
my $self = shift;
my $cycle = { self => $self };
$cycle->{cycle} = $cycle;
}
From reading the C<Devel::FindRef> output, we find that the HASH this object
is referenced in also contains a reference to itself, in a member called
C<cycle>. This comes from the last line in this function, a line that
purposely created a cycle, to demonstrate the point. While a real program
probably wouldn't do anything quite this obvious, the trace would still be
useful in finding the likely cause of the leak.
If C<Devel::FindRef> is unavailable, then these detailed traces will not be
produced. The basic reference count testing will still take place, but a
smaller message will be produced:
1..2
ok 1 - One reference after construct
not ok 2 - One reference just before EOF
# Failed test 'One reference just before EOF'
# at demo.pl line 16.
# expected 1 references, found 2
# Looks like you failed 1 test of 2.
=head1 BUGS
=over 4
=item * Temporaries created on the stack
Code which creates temporaries on the stack, to be released again when the
called function returns does not work correctly on perl 5.8 (and probably
before). Examples such as
is_oneref( [] );
may fail and claim a reference count of 2 instead.
Passing a variable such as
my $array = [];
is_oneref( $array );
works fine. Because of the intention of this test module; that is, to assert
reference counts on some object stored in a variable during the lifetime of
the test script, this is unlikely to cause any problems.
=back
=head1 ACKNOWLEDGEMENTS
Peter Rabbitson <ribasushi@cpan.org> - for suggesting using core's C<B>
instead of C<Devel::Refcount> to obtain refcounts
=head1 AUTHOR
Paul Evans <leonerd@leonerd.org.uk>
=cut
0x55AA;
|