Test::...Growth(3pm)
π NAME π
okay so bestie, this is Test::MemoryGrowth β it asserts that your code does NOT cause growth in memory usage. that's the whole vibe. β¨
πΈ SYNOPSIS πΈ
use Test::More;
use Test::MemoryGrowth;
use Some::Class;
no_growth {
my $obj = Some::Class->new;
} 'Constructing Some::Class does not grow memory';
my $obj = Some::Class->new;
no_growth {
$obj->do_thing;
} 'Some::Class->do_thing does not grow memory';
#### This test will fail ####
my @list;
no_growth {
push @list, "Hello world";
} 'pushing to an array does not grow memory';
done_testing;
π¦ DESCRIPTION π¦
so this module hands you a function that checks a block of code and goes "did this eat extra memory after it was done?" π and like, despite the name, it does NOT β strictly speaking β test for a memory leak. that word is for when memory got allocated and then every single record of it got lost so it can literally never be reclaimed. this module can catch those bugs, but it also catches cases where the memory is still referenced and reachable β the usage just grew way more than you'd expect or need. it's giving "growth", not always "leak". π
the block of code gets run a LOT of times (default? 10,000) and then it compares the memory usage of the process before vs after. if usage went up by more than one byte per call, the test fails. periodt.
and because code needs a lil warmup to load its initial resources, it gets run a few times first (default? 10) β giving it room to load files, AUTOLOADs, caches, whatever it needs. any extra memory used during that burn-in does NOT count against it. π«Ά
ngl bestie, this simple method is NOT a guaranteed indicator that your code has zero memory resource bugs. it can fail in both a false-negative way AND a false-positive way. π
π False Negative π
so it's possible your code grows memory and this module just... doesn't notice. because it only detects growth of at least one byte per call, it can't catch linear memory growth at slower rates than that. most memory growth comes from Perl-level or C-level bugs where memory objects get created on every call and never reclaimed. (these are either genuine memory leaks, or needless allocations of objects that are stored somewhere and never reclaimed.) it's unlikely such a bug would grow slower than one byte per call, but, like, it could.
second failure case: memory usage is taken from the Operating System's measure of the process's Virtual Memory size β this is so it can detect memory growth in C libraries or XS-level wrapping code, not just Perl functions. but because Perl does not aggressively return unused memory to the OS, it's possible a piece of code could grow into un-allocated-but-un-reclaimed memory, so its requirements increase even though it never asked the OS for extra memory. π
π False Positive π
it's also possible the test claims a function grows memory when that behaviour is, in fact, totally normal for the code. example: a function whose job is to store extra state, like adding a new item into a list. in that case, yeah, obviously the process memory usage is gonna increase. that's not a bug, that's the assignment. π
by using this module carefully, false indications can be minimised:
- split your tests across many test scripts β each one starts in a new process state, where most of the memory assigned from the OS is in use by Perl, so anything extra the code needs will have to request more. this should reduce the false negative indications.
- keep in mind that the module simply measures the change in allocated memory size β so to minimise false positives, don't assert that certain pieces of code don't grow in memory when you'd actually expect them to grow. π§
π§ Devel::Gladiator Integration π§
since version 0.04.
if Devel::Gladiator is installed, this test module uses it as a second potential source of detecting memory growth. it takes a walk of the Perl memory heap before running the code, counting how many of every kind of object are present. then it compares that to a second count taken afterward. any object types that increased by at least one per call get reported. π
for example, the output might contain these extra diagnostic lines:
# Growths in arena object counts:
# ARRAY 1735 -> 11735 (1.00 per call)
# HASH 459 -> 10459 (1.00 per call)
# REF 1387 -> 21387 (2.00 per call)
# REF-ARRAY 163 -> 10163 (1.00 per call)
# REF-HASH 66 -> 10066 (1.00 per call)
# WithContainerSlots 10 -> 10010 (1.00 per call)
π Devel::MAT Integration π
if Devel::MAT is installed, this test module uses it to dump the state of the memory after a failure. it creates a .pmat file named the same as the unit test, but with the trailing .t suffix replaced with -TEST.pmat where "TEST" is the number of the test that failed (in case there was more than one). then it runs the code under test one more time, before writing another file whose name is suffixed with -TEST-after.pmat. this pair of files may be useful for differential analysis. π
β¨ FUNCTIONS β¨
π no_growth
no_growth { CODE } %opts, $name;
asserts that the code block does not consume extra memory.
it takes these named arguments:
calls => INTβ the number of times to call the code during growth testing.burn_in => INTβ the number of times to call the code initially, before watching for memory usage.
π TODO π
- More OS portability
rn this module uses OS-specific methods of determining process memory usage (namely, by inspecting /proc/self/status on Linux or calling procstat on FreeBSD). it would be nice to support more OSes, and potentially find a better abstraction for doing so.
currently the author is unaware of a simple portable mechanism to query this. patches very much welcome. :)
π AUTHOR π
Paul Evans <leonerd@leonerd.org.uk>
and that's the whole thing bestie π«Ά this was Test::...Growth(3pm) from the User Contributed Perl Documentation, living in perl v5.40.0, dated 2024-09-21. no cap, you're gonna slay those memory tests now. mwah πβ¨
π¦ where this page comes from
- ubuntu resolute: libtest-memorygrowth-perl 0.05-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libtest-memorygrowth-perl 0.05-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms