File Coverage

blib/lib/Test/Memory/Cycle.pm
Criterion Covered Total %
statement 109 109 100.0
branch 27 30 90.0
condition n/a
subroutine 15 15 100.0
pod 4 4 100.0
total 155 158 98.1


line stmt bran cond sub pod time code
1             package Test::Memory::Cycle;
2              
3 8     8   314603 use strict;
  8         15  
  8         214  
4 8     8   60 use warnings;
  8         12  
  8         401  
5              
6             =head1 NAME
7              
8             Test::Memory::Cycle - Check for memory leaks and circular memory references
9              
10             =head1 VERSION
11              
12             Version 1.06
13              
14             =cut
15              
16             our $VERSION = '1.06';
17              
18             =head1 SYNOPSIS
19              
20             Perl's garbage collection has one big problem: Circular references
21             can't get cleaned up. A circular reference can be as simple as two
22             references that refer to each other:
23              
24             my $mom = {
25             name => "Marilyn Lester",
26             };
27              
28             my $me = {
29             name => "Andy Lester",
30             mother => $mom,
31             };
32             $mom->{son} = $me;
33              
34             C is built on top of C to give
35             you an easy way to check for these circular references.
36              
37             use Test::Memory::Cycle;
38              
39             my $object = new MyObject;
40             # Do stuff with the object.
41             memory_cycle_ok( $object );
42              
43             You can also use C to make sure that you have a
44             cycle where you expect to have one.
45              
46             =cut
47              
48 8     8   3530 use Devel::Cycle qw( find_cycle find_weakened_cycle );
  8         20356  
  8         34  
49 8     8   1202 use Test::Builder;
  8         14  
  8         354  
50              
51             my $Test = Test::Builder->new;
52              
53             sub import {
54 7     7   59 my $self = shift;
55 7         13 my $caller = caller;
56 8     8   31 no strict 'refs';
  8         11  
  8         5562  
57 7         8 *{$caller.'::memory_cycle_ok'} = \&memory_cycle_ok;
  7         38  
58 7         8 *{$caller.'::memory_cycle_exists'} = \&memory_cycle_exists;
  7         26  
59              
60 7         8 *{$caller.'::weakened_memory_cycle_ok'} = \&weakened_memory_cycle_ok;
  7         65  
61 7         9 *{$caller.'::weakened_memory_cycle_exists'} = \&weakened_memory_cycle_exists;
  7         45  
62 7         8 *{$caller.'::memory_cycle_exists'} = \&memory_cycle_exists;
  7         15  
63              
64 7         8 *{$caller.'::weakened_memory_cycle_ok'} = \&weakened_memory_cycle_ok;
  7         16  
65 7         8 *{$caller.'::weakened_memory_cycle_exists'} = \&weakened_memory_cycle_exists;
  7         12  
66              
67 7         28 $Test->exported_to($caller);
68 7         72 $Test->plan(@_);
69              
70 7         89 return;
71             }
72              
73             =head1 FUNCTIONS
74              
75             =head2 C, I<$msg> )>
76              
77             Checks that I<$reference> doesn't have any circular memory references.
78              
79             =cut
80              
81             sub memory_cycle_ok {
82 11     11 1 386752 my $ref = shift;
83 11         16 my $msg = shift;
84              
85 11         16 my $cycle_no = 0;
86 11         13 my @diags;
87              
88             # Callback function that is called once for each memory cycle found.
89             my $callback = sub {
90 12     12   1297 my $path = shift;
91 12         14 $cycle_no++;
92 12         24 push( @diags, "Cycle #$cycle_no" );
93 12         21 foreach (@$path) {
94 36         54 my ($type,$index,$ref,$value) = @$_;
95              
96 36         37 my $str = 'Unknown! This should never happen!';
97 36         50 my $refdisp = _ref_shortname( $ref );
98 36         38 my $valuedisp = _ref_shortname( $value );
99              
100 36 100       70 $str = sprintf( ' %s => %s', $refdisp, $valuedisp ) if $type eq 'SCALAR';
101 36 100       71 $str = sprintf( ' %s => %s', "${refdisp}->[$index]", $valuedisp ) if $type eq 'ARRAY';
102 36 100       88 $str = sprintf( ' %s => %s', "${refdisp}->{$index}", $valuedisp ) if $type eq 'HASH';
103 36 50       45 $str = sprintf( ' closure %s => %s', "${refdisp}, $index", $valuedisp ) if $type eq 'CODE';
104              
105 36         70 push( @diags, $str );
106             }
107 11         53 };
108              
109 11         39 find_cycle( $ref, $callback );
110 11         843 my $ok = !$cycle_no;
111 11         39 $Test->ok( $ok, $msg );
112 11 100       3687 $Test->diag( join( "\n", @diags, '' ) ) unless $ok;
113              
114 11         479 return $ok;
115             } # memory_cycle_ok
116              
117             =head2 C, I<$msg> )>
118              
119             Checks that I<$reference> B have any circular memory references.
120              
121             =cut
122              
123             sub memory_cycle_exists {
124 13     13 1 79387 my $ref = shift;
125 13         19 my $msg = shift;
126              
127 13         15 my $cycle_no = 0;
128              
129             # Callback function that is called once for each memory cycle found.
130 13     11   42 my $callback = sub { $cycle_no++ };
  11         999  
131              
132 13         40 find_cycle( $ref, $callback );
133 13         976 my $ok = $cycle_no;
134 13         33 $Test->ok( $ok, $msg );
135              
136 13         3454 return $ok;
137             } # memory_cycle_exists
138              
139             =head2 C, I<$msg> )>
140              
141             Checks that I<$reference> doesn't have any circular memory references, but unlike
142             C this will also check for weakened cycles produced with
143             Scalar::Util's C.
144              
145             =cut
146              
147             sub weakened_memory_cycle_ok {
148 3     3 1 1894 my $ref = shift;
149 3         4 my $msg = shift;
150              
151 3         6 my $cycle_no = 0;
152 3         3 my @diags;
153              
154             # Callback function that is called once for each memory cycle found.
155             my $callback = sub {
156 6     6   408 my $path = shift;
157 6         5 $cycle_no++;
158 6         14 push( @diags, "Cycle #$cycle_no" );
159 6         64 foreach (@$path) {
160 19         34 my ($type,$index,$ref,$value,$is_weakened) = @$_;
161              
162 19         20 my $str = "Unknown! This should never happen!";
163 19         22 my $refdisp = _ref_shortname( $ref );
164 19         22 my $valuedisp = _ref_shortname( $value );
165 19 100       36 my $weak = $is_weakened ? 'w->' : '';
166              
167 19 100       34 $str = sprintf( ' %s%s => %s', $weak, $refdisp, $valuedisp ) if $type eq 'SCALAR';
168 19 100       43 $str = sprintf( ' %s%s => %s', $weak, "${refdisp}->[$index]", $valuedisp ) if $type eq 'ARRAY';
169 19 100       43 $str = sprintf( ' %s%s => %s', $weak, "${refdisp}->{$index}", $valuedisp ) if $type eq 'HASH';
170              
171 19         33 push( @diags, $str );
172             }
173 3         16 };
174              
175 3         12 find_weakened_cycle( $ref, $callback );
176 3         117 my $ok = !$cycle_no;
177 3         10 $Test->ok( $ok, $msg );
178 3 50       1164 $Test->diag( join( "\n", @diags, "" ) ) unless $ok;
179              
180 3         186 return $ok;
181             } # weakened_memory_cycle_ok
182              
183             =head2 C, I<$msg> )>
184              
185             Checks that I<$reference> B have any circular memory references, but unlike
186             C this will also check for weakened cycles produced with
187             Scalar::Util's C.
188              
189             =cut
190              
191             sub weakened_memory_cycle_exists {
192 3     3 1 1700 my $ref = shift;
193 3         6 my $msg = shift;
194              
195 3         6 my $cycle_no = 0;
196              
197             # Callback function that is called once for each memory cycle found.
198 3     6   13 my $callback = sub { $cycle_no++ };
  6         519  
199              
200 3         10 find_weakened_cycle( $ref, $callback );
201 3         172 my $ok = $cycle_no;
202 3         10 $Test->ok( $ok, $msg );
203              
204 3         589 return $ok;
205             } # weakened_memory_cycle_exists
206              
207              
208             my %shortnames;
209             my $new_shortname = "A";
210              
211             sub _ref_shortname {
212 110     110   84 my $ref = shift;
213 110         118 my $refstr = "$ref";
214 110         90 my $refdisp = $shortnames{ $refstr };
215 110 100       152 if ( !$refdisp ) {
216 17         24 my $sigil = ref($ref) . " ";
217 17 100       31 $sigil = '%' if $sigil eq "HASH ";
218 17 100       39 $sigil = '@' if $sigil eq "ARRAY ";
219 17 100       28 $sigil = '$' if $sigil eq "REF ";
220 17 50       32 $sigil = '&' if $sigil eq "CODE ";
221 17         41 $refdisp = $shortnames{ $refstr } = $sigil . $new_shortname++;
222             }
223              
224 110         129 return $refdisp;
225             }
226              
227             =head1 AUTHOR
228              
229             Written by Andy Lester, C<< >>.
230              
231             =head1 BUGS
232              
233             Please report any bugs or feature requests to
234             C, or through the web interface at
235             L.
236             I will be notified, and then you'll automatically be notified of progress on
237             your bug as I make changes.
238              
239             =head1 SUPPORT
240              
241             You can find documentation for this module with the perldoc command.
242              
243             perldoc Test::Memory::Cycle
244              
245             You can also look for information at:
246              
247             =over 4
248              
249             =item * AnnoCPAN: Annotated CPAN documentation
250              
251             L
252              
253             =item * CPAN Ratings
254              
255             L
256              
257             =item * RT: CPAN's request tracker
258              
259             L
260              
261             =item * Search CPAN
262              
263             L
264              
265             =back
266              
267             =head1 ACKNOWLEDGEMENTS
268              
269             Thanks to the contributions of Stevan Little, and to Lincoln Stein for writing Devel::Cycle.
270              
271             =head1 COPYRIGHT
272              
273             Copyright 2003-2016 Andy Lester.
274              
275             This program is free software; you can redistribute it and/or modify
276             it under the terms of the Artistic License v2.0.
277              
278             See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE
279             file that comes with the Test::Memory::Cycle distribution.
280              
281             =cut
282              
283             1;