Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get list of methods/functions defined explicitly in a module

After realizing the sad state of code coverage on our unit tests at work I am trying to create a utility that will scan our code base and flag files that don't have 100%. I found two approaches that get all of the methods:

Access symbol table directly:

for my $classname ( @ARGV ) {
   eval "require $classname";
   die "Can't load $classname $EVAL_ERROR"
      if $EVAL_ERROR; 

    no strict 'refs';
    METHODS:
    for my $sym ( keys %{ "${classname}::" } ) {
       next METHODS unless defined &{"${classname}::${sym}"};
       print "$sym\n";
   }
}

Use the Class::Inspector module from CPAN:

for my $classname ( @ARGV ) {
   my @methods = Class::Inspector->methods($classname, 'public');
   print Dumper \@methods;
}

these two approaches produce similar results; The problem with these is that they show all of the methods available to the entire module, not just the methods defined inside of that module.

Is there some way to distinguish between methods accessible to a module and methods defined explicitly inside of a module?

Note: I am not attempting to create a full code coverage test, for my use case I just want to test that all of the methods have been called at least once. Complete coverage tests like Devel::Cover are overkill for us.

like image 269
Hunter McMillen Avatar asked Oct 02 '22 09:10

Hunter McMillen


1 Answers

Each sub (or more specifically, each CV), remembers which package it was originally declared in. Test case:

Foo.pm:

package Foo;
sub import {
  *{caller . "::foo"} = sub{};
}
1;

Bar.pm:

package Bar;
use Foo;

our $bar;  # introduces *Bar::bar which does not have a CODE slot
sub baz {}
1;

Accessing the symbol table now gives both foo and baz. By the way, I'd write that code like this (for reasons that will become clear in a moment):

my $classname = 'Bar';
for my $glob (values %{ "${classname}::" }) {
   my $sub = *$glob{CODE} or next;
   say *$glob{NAME};
}

Next, we have to look into the B module to introspect the underlying C data structure. We do this with the B::svref_2object function. This will produce a B::CV object which has the convenient STASH field (which returns a B::HV object which has a NAME field):

use B ();
my $classname = 'Bar';
for my $glob (values %{ "${classname}::" }) {
   my $sub = *$glob{CODE} or next;
   my $cv = B::svref_2object($sub);
   $cv->STASH->NAME eq $classname or next;
   say *$glob{NAME};
}

Add a few sanity checks, and this should work quite well.

Dynamic class/module loading should not be done via string eval. Instead I recommend Module::Runtime:

Module::Runtime::require_module($classname);
like image 125
amon Avatar answered Oct 13 '22 11:10

amon