File Coverage

blib/lib/Test2/Tools/MemoryCycle.pm
Criterion Covered Total %
statement 54 54 100.0
branch 18 20 90.0
condition 2 2 100.0
subroutine 9 9 100.0
pod 1 1 100.0
total 84 86 97.6


line stmt bran cond sub pod time code
1             package Test2::Tools::MemoryCycle;
2              
3 1     1   191485 use strict;
  1         5  
  1         25  
4 1     1   4 use warnings;
  1         2  
  1         17  
5 1     1   19 use 5.008004;
  1         3  
6 1     1   426 use Devel::Cycle qw( find_cycle );
  1         2730  
  1         4  
7 1     1   132 use Test2::API qw( context );
  1         2  
  1         36  
8 1     1   4 use Exporter qw( import );
  1         2  
  1         350  
9              
10             # ABSTRACT: Check for memory leaks and circular memory references
11             our $VERSION = '0.01'; # VERSION
12              
13              
14             our @EXPORT = qw( memory_cycle_ok );
15              
16             # Adapted from Test::Memory::Cycle for Test2::API
17             sub memory_cycle_ok {
18 3     3 1 37890 my $ref = shift;
19 3         6 my $msg = shift;
20              
21 3   100     18 $msg ||= 'no memory cycle';
22              
23 3         5 my $cycle_no = 0;
24 3         5 my @diags;
25              
26             # Callback function that is called once for each memory cycle found.
27             my $callback = sub {
28 1     1   134 my $path = shift;
29 1         1 $cycle_no++;
30 1         18 push( @diags, "Cycle #$cycle_no" );
31 1         5 foreach (@$path) {
32 3         5 my ($type,$index,$ref,$value) = @$_;
33              
34 3         5 my $str = 'Unknown! This should never happen!';
35 3         6 my $refdisp = _ref_shortname( $ref );
36 3         5 my $valuedisp = _ref_shortname( $value );
37              
38 3 100       9 $str = sprintf( ' %s => %s', $refdisp, $valuedisp ) if $type eq 'SCALAR';
39 3 50       5 $str = sprintf( ' %s => %s', "${refdisp}->[$index]", $valuedisp ) if $type eq 'ARRAY';
40 3 100       7 $str = sprintf( ' %s => %s', "${refdisp}->{$index}", $valuedisp ) if $type eq 'HASH';
41 3 100       7 $str = sprintf( ' closure %s => %s', "${refdisp}, $index", $valuedisp ) if $type eq 'CODE';
42              
43 3         7 push( @diags, $str );
44             }
45 3         12 };
46              
47 3         9 find_cycle( $ref, $callback );
48 3         137 my $ok = !$cycle_no;
49              
50 3         7 my $ctx = context();
51 3 100       231 if($ok) {
52 2         11 $ctx->pass_and_release($msg);
53             } else {
54 1         4 $ctx->fail_and_release($msg, @diags);
55             }
56              
57 3         475 return $ok;
58             } # memory_cycle_ok
59              
60             my %shortnames;
61             my $new_shortname = "A";
62              
63             sub _ref_shortname {
64 6     6   8 my $ref = shift;
65 6         9 my $refstr = "$ref";
66 6         8 my $refdisp = $shortnames{ $refstr };
67 6 100       11 if ( !$refdisp ) {
68 3         7 my $sigil = ref($ref) . " ";
69 3 100       6 $sigil = '%' if $sigil eq "HASH ";
70 3 50       6 $sigil = '@' if $sigil eq "ARRAY ";
71 3 100       4 $sigil = '$' if $sigil eq "REF ";
72 3 100       15 $sigil = '&' if $sigil eq "CODE ";
73 3         8 $refdisp = $shortnames{ $refstr } = $sigil . $new_shortname++;
74             }
75              
76 6         10 return $refdisp;
77             }
78              
79             1;
80              
81             __END__