New memory testing tools

petdance on 2003-12-15T06:18:29

Here are a couple of slick new tools that make it easy to find circular references in your Perl structures that foul up Perl's reference counting.

Lincoln Stein just released Devel::Cycle the other day, and I just noticed it today. It checks a given reference for circular references (what Lincoln calls "memory cycles"), and thus memory leaks. "What a great tool," I thought, "I'll make a Test:: module out of it!" So I just now released the 0.01 version of Test::Memory::Cycle. I named it in the Test::Memory:: namespace in planning for future memory-testing modules.

The two work together really well. For instance, WWW::Mechanize has always been plagued with circular references. It keeps a queue of pages that have been visited, implemented with references back to prior objects. I'd sure like some way to verify that I haven't fouled anything. So I started adding memory_cycle_ok() calls in the t/*.t files, like so:

use Test::More tests => 18; use_ok( 'WWW::Mechanize' );

my $agent = WWW::Mechanize->new(); isa_ok( $agent, "WWW::Mechanize" );

# ... do a whole bunch of Mech testing # and then at the end ...

SKIP: { eval "use Test::Memory::Cycle"; skip "Test::Memory::Cycle not installed", 1 if $@;

memory_cycle_ok( $agent, "No memory cycles found" ); }


The output format is pretty cool, too, if I do say so myself. Here's some sample memory cycle-creating code: my $mom = { name => "Marilyn Lester", };

my $me = { name => "Andy Lester", mother => $mom, }; $mom->{son} = $me;

my $quinn = { name => "Quinn Lester", father => $me, grandmother => $mom, }; $mom->{grandchild} = [ $quinn ];
Here, my mom and I each point at each other, and Mom and Quinn point at each other. Mom's pointing at Quinn is an anonymous list, to handle more grandchildren (my sister is 7 months pregnant), but Quinn only has the one grandma (in this example).



In Lincoln's module, you run find_cycle($me) and it prints to standard output: Cycle (1): HASH(0x8004a0)->{mother} => HASH(0x800368) HASH(0x800368)->{grandchild} => ARRAY(0x804c6c) ARRAY(0x804c6c)->[0] => HASH(0x8049f0) HASH(0x8049f0)->{grandmother} => HASH(0x800368)

Cycle (2): HASH(0x8004a0)->{mother} => HASH(0x800368) HASH(0x800368)->{grandchild} => ARRAY(0x804c6c) ARRAY(0x804c6c)->[0] => HASH(0x8049f0) HASH(0x8049f0)->{father} => HASH(0x8004a0)

Cycle (3): HASH(0x8004a0)->{mother} => HASH(0x800368) HASH(0x800368)->{son} => HASH(0x8004a0)


In mine, you call memory_cycle_ok($me) and it prints in the test diagnostic:

# Cycle (1) # %A->{mother} => %B # %B->{grandchild} => @C # @C->[0] => %D # %D->{grandmother} => %B # Cycle (2) # %A->{mother} => %B # %B->{grandchild} => @C # @C->[0] => %D # %D->{father} => %A # Cycle (3) # %A->{mother} => %B # %B->{son} => %A

I'm hoping that'll make debugging easier.

As always, your comments and suggestions are welcome!


Very neat!

samtregar on 2003-12-15T06:23:44

But brain is throwing warnings about this syntax:

%A->{mother}

Maybe change that too:

$A{mother}

-sam