use Carp qw/croak/;
use POSIX qw/SIGTERM/;
-use Test::More;
+use Test::Builder;
use Perl::Destruct::Level level => 3;
=head1 VERSION
-Version 0.04
+Version 0.06
=cut
-our $VERSION = '0.04';
+our $VERSION = '0.06';
=head1 SYNOPSIS
perl -MTest::Valgrind leaky.pl
+Due to the nature of perl's memory allocator, this module can't track leaks of Perl objects. This includes non-mortalized scalars and memory cycles. However, it can track leaks of chunks of memory allocated in XS extensions with C<Newx> and friends or C<malloc>. As such, it's complementary to the other very good leak detectors listed in the L</SEE ALSO> section.
+
=head1 CONFIGURATION
You can pass parameters to C<import> as a list of key / value pairs, where valid keys are :
=over 4
-=item C<< supp => $file >>
+=item *
+
+C<< supp => $file >>
Also use suppressions from C<$file> besides perl's.
-=item C<< no_supp => $bool >>
+=item *
+
+C<< no_supp => $bool >>
If true, do not use any suppressions.
-=item C<< callers => $number >>
+=item *
+
+C<< callers => $number >>
Specify the maximum stack depth studied when valgrind encounters an error. Raising this number improves granularity. Default is 12.
-=item C<< extra => [ @args ] >>
+=item *
+
+C<< extra => [ @args ] >>
Add C<@args> to valgrind parameters.
-=item C<< diag => $bool >>
+=item *
+
+C<< diag => $bool >>
If true, print the raw output of valgrind as diagnostics (may be quite verbose).
-=item C<< no_test => $bool >>
+=item *
+
+C<< no_test => $bool >>
If true, do not actually output the plan and the tests results.
+=item *
+
+C<< cb => sub { my ($val, $name) = @_; ...; return $passed } >>
+
+Specifies a subroutine to execute for each test instead of C<Test::More::is>. It receives the number of bytes leaked in C<$_[0]> and the test name in C<$_[1]>, and is expected to return true if the test passed and false otherwise. Defaults to
+
+ sub {
+ is($_[0], 0, $_[1]);
+ (defined $_[0] and $_[0] == 0) : 1 : 0
+ }
+
=back
=cut
+my $Test = Test::Builder->new;
+
my $run;
+sub _counter {
+ (defined $_[0] and $_[0] == 0) ? 1 : 0;
+}
+
+sub _tester {
+ $Test->is_num($_[0], 0, $_[1]);
+ _counter(@_);
+}
+
sub import {
shift;
croak 'Optional arguments must be passed as key => value pairs' if @_ % 2;
my %args = @_;
if (!defined $args{run} && !$run) {
- my ($file, $next);
+ my ($file, $pm, $next);
my $l = 0;
while ($l < 1000) {
$next = (caller $l++)[1];
last unless defined $next;
- $file = $next;
+ next unless $next ne '-e' and $next !~ /^\s*\(\s*eval\s*\d*\s*\)\s*$/
+ and -f $next;
+ if ($next =~ /\.pm$/) {
+ $pm = $next;
+ } else {
+ $file = $next;
+ }
+ }
+ unless (defined $file) {
+ $file = $pm;
+ return unless defined $pm;
}
- return if not $file or $file eq '-e';
my $callers = $args{callers};
$callers = 12 unless defined $callers;
$callers = int $callers;
}
}
if (!$vg) {
- plan skip_all => 'No valgrind executable could be found in your path';
+ $Test->skip_all('No valgrind executable could be found in your path');
return;
}
}
}
close $wtr or croak "close(\$wtr): $!";
local $SIG{INT} = sub { kill -(SIGTERM) => $pid };
- plan tests => 5 unless $args{no_test};
+ $Test->plan(tests => 5) unless $args{no_test} or defined $Test->has_plan;
my @tests = (
'errors',
'definitely lost', 'indirectly lost', 'possibly lost', 'still reachable'
);
my %res = map { $_ => 0 } @tests;
while (<$rdr>) {
- diag $_ if $args{diag};
+ $Test->diag($_) if $args{diag};
if (/^=+\d+=+\s*FATAL\s*:\s*(.*)/) {
chomp(my $err = $1);
- diag "Valgrind error: $err";
+ $Test->diag("Valgrind error: $err");
$res{$_} = undef for @tests;
}
if (/ERROR\s+SUMMARY\s*:\s+(\d+)/) {
}
}
waitpid $pid, 0;
- my $failed = 0;
+ my $failed = 5;
+ my $cb = ($args{no_test} ? \&_counter
+ : ($args{cb} ? $args{cb} : \&_tester));
for (@tests) {
- is($res{$_}, 0, 'valgrind ' . $_) unless $args{no_test};
- ++$failed if defined $res{$_} and $res{$_} != 0;
+ $failed -= $cb->($res{$_}, 'valgrind ' . $_) ? 1 : 0;
}
exit $failed;
} else {
Valgrind 3.1.0 (L<http://valgrind.org>).
-L<Carp>, L<POSIX> (core modules since perl 5) and L<Test::More> (since 5.6.2).
+L<Carp>, L<POSIX> (core modules since perl 5) and L<Test::Builder> (since 5.6.2).
L<Perl::Destruct::Level>.
+=head1 SEE ALSO
+
+L<Devel::Leak>, L<Devel::LeakTrace>, L<Devel::LeakTrace::Fast>.
+
=head1 AUTHOR
Vincent Pit, C<< <perl at profvince.com> >>, L<http://www.profvince.com>.