Code outside named subroutines is invisible to Devel::Cover #51

Open
DrHyde opened this Issue Mar 21, 2013 · 9 comments

Comments

Projects
None yet
5 participants
Contributor

DrHyde commented Mar 21, 2013

For example, if you generate a coverage report for CPU::Emulator::Z80 (there's a t/coverage.sh script; see git@github.com:DrHyde/perl-modules-CPU-Emulator-Z80.git) and look at cover_db/blib-lib-CPU-Emulator-Z80-pm.html, it doesn't say anything at all for lines like this:

10: $VERSION = '1.0';

...

32: my @registers8 = qw(A B C D E F R W Z I);

although they obviously got compiled - and in this case I know that they got executed too.

From looking at various other examples, eg http://lists.preshweb.co.uk/pipermail/dancer-users/2011-October/001990.html, it appears that the general rule is that code gets reported on if it is within a named subroutine. Anonymous subs' code appear in reports if the anonysub is defined within a named subroutine (see eg CPU::Emulator::Z80 line 209) but not if it is defined outside any named sub (see eg line 7 in TestApp/lib/TestApp.pm as generated by Michael Dorman in his email to dancer-users).

Contributor

jkeenan commented Mar 21, 2013

This is not a new issue. Devel::Cover has never, in my nine years' use of it, provided data on anonymous subs. I suspect that if pjcj hasn't done that already, it's because it's very hard.

My feeling is that what we should do in the short-run is simply to document the fact than data is provided only on named subroutines.

Thank you very much.
Jim Keenan

Owner

pjcj commented Mar 30, 2013

You're quite correct that this is rather tricky, but I have been making some progress on it recently and I have a local branch for it. But documentation of the limitiations is good.

It's not that only named subroutines are covered but that within a module, only code within named subroutines can be reported on.

A workaround is to put all code in a module into a named subroutine and call it.

Contributor

DrHyde commented Apr 2, 2013

For the particular situation at work that prompted me to raise this issue, I've got a work-around that does exactly that. It looks to see if Devel::Cover is loaded, and if it is, it puts a sub-ref in @inc so that the affected files get wrapped in:

sub main { ... } main();

which is EVIL. When your branch is ready for real-world testing, do please let me know, because I'd love to delete my hideous voodoo code.

Contributor

jkeenan commented Apr 29, 2013

Today I made my first attempt at $job to use Devel::Cover. The package whose coverage I was most concerned with is a Moose/Catalyst class in which the particular code I was concerned with was inside a Moose around method modifier. Pseudo-code:

around 'my_hook' => sub {
  my $first = shift;
  my $self = shift;
  my ($c, $form) = @_;
### lots of code
};

I got no coverage of this, notwithstanding the fact that manual debugging indicates the code is hit hundreds of times.

Is this a particular instance of the "only visible in named subroutines" problem?

Thank you very much.
Jim Keenan

Contributor

DrHyde commented Apr 30, 2013

Yup. I expect that this will fix it ...

sub _my_hook { my($first, $self, $c, $form) = @_; ... }
around my_hook => \&_my_hook;

or you could adapt my nasty (and potentially a bit fragile) solution, which looks something like this ...

push @INC, sub {
    my(undef, $filename) = @_;

    if(my $found = (grep { -e $_ } map { "$_/$filename" } grep { !ref } @INC)[0]) {
        # $found is the full path to the file
        local $/ = undef;
        open(my $fh, '<', $found) || die("Can't read module file $found\n");
        my $module_text = <$fh>;
        close($fh);

        # define everything in a sub, so Devel::Cover will DTRT
        # NB this introduces no extra linefeeds so D::C's line numbers
        # in reports match the file on disk
        $module_text =~ s/(.*?package\s+\S+)(.*)/$1sub main {$2}main();/s;

        # filehandle on the scalar
        open ($fh, '<', \$module_text);

        # and put it into %INC too so that it looks like we loaded the code
        # from the file directly
        $INC{$filename} = $found;
        return $fh;
    } else {
        return ();
    }
};
Contributor

jkeenan commented May 1, 2013

Dr. Hyde, your workaround worked for me as well. By making the Moose method modifier a thin wrapper around a named subroutine, I was able to get coverage data on that named subroutine. That was my practical objective and it confirmed the results I got by temporarily stuffing the code with say STDERR statements.

Let's hope that we can get coverage of anonymous subroutines into Devel::Cover so that we don't have to resort to such workarounds. A colleague notes that much event-loop-based programming relies heavily on closures, so that programming would also benefit from an expansion of Devel::Cover's capabilities.

The issue remains open.

Contributor

dolmen commented May 2, 2013

@jkeenan You can also use Sub::Name to give a name to your subroutine:

use Sub::Name;

around my_hook => subname _my_hook {
  my $first = shift;
    ...
};
Contributor

bor commented Mar 18, 2016

Role::Tiny (Class::Method::Modifiers under-hood) also is affected.

Guys, thanks for sharing workarounds!

Contributor

dolmen commented Mar 20, 2016

See also Sub::Util::set_subname as an alternative to Sub::Name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment