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__ |