PERL_TRACE_CALLS=1 prove -l t/sometest.t #### { "Local::Example" : { "quux" : 1 }, "Local::Example::Module1" : { "bar" : 1 }, "Local::Example::Module2" : { "bar" : 1, "foo" : 1 } } #### use strict; use warnings; package Devel::TraceCalls; our $AUTHORITY = 'cpan:TOBYINK'; our $VERSION = '0.001'; use constant ACTIVE => $ENV{'PERL_TRACE_CALLS'}; BEGIN { eval q{ use match::simple (); use Carp (); use File::Spec (); use FindBin (); use Hook::AfterRuntime (); use JSON::PP (); use Sub::Util (); 1; } || die($@) if ACTIVE; }; our $JSON; our %CALL; $JSON = 'JSON::PP'->new->pretty(1)->canonical(1) if ACTIVE; sub import { my $me = shift; my $caller = caller; my (%opts) = @_; &Hook::AfterRuntime::after_runtime( sub { $me->setup_for($caller, %opts) }, ) if ACTIVE; } sub setup_for { my $me = shift; my ($caller, %opts) = @_; $opts{match} = sub { local $_ = shift; !/^_/ and /\p{Ll}/; } unless exists $opts{match}; no strict 'refs'; my @names = grep match::simple::match($_, $opts{match}), grep !/::$/, sort keys %{"$caller\::"}; $me->wrap_sub($caller, $_) for @names; } sub wrap_sub { my $me = shift; no strict 'refs'; no warnings 'redefine'; my ($package, $sub) = @_; ($package, $sub) = (/^(.+)::([^:]+)$/ =~ $package) if !defined $sub; my $code = \&{"$package\::$sub"}; my $subname = Sub::Util::subname($code); my $newcode = Sub::Util::set_prototype prototype($code), Sub::Util::set_subname $subname, sub { ++$CALL{$package}{$sub}; goto $code }; *{"$package\::$sub"} = $newcode; } END { if (ACTIVE) { my $map = $JSON->encode(\%CALL); my $outfile = 'File::Spec'->catfile( $FindBin::RealBin, $FindBin::RealScript . ".map", ); my $already = 0; if (-f $outfile) { my $slurped = do { local $/; my $fh; open($fh, '<', $outfile) ? <$fh> : undef; }; $already++ if $slurped eq $map; } if (!$already) { open my $outfh, '>', $outfile or Carp::croak("Cannot open $outfile for output: $!"); print {$outfh} $map or Carp::croak("Cannot write to $outfile: $!"); close $outfh or Carp::croak("Cannot close $outfile: $!"); } }; } 1;