Open DrHyde opened 11 years ago
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
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.
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.
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
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 ...
(code updated on 2020-03-04 to be a lot more reliable)
unshift @INC, sub {
my(undef, $filename) = @_;
if(my $found = (grep { -e $_ } map { "$_/$filename" } grep { !ref } @INC)[0]) {
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 minimal extra line feeds to avoid any
# horrible confusion between what's on disk and in memory.
# The two linefeeds are to avoid putting } after a closing
# =cut and to make sure __DATA__ or __END__ is at the start
# of a line
$module_text =~ s/
(.*?package\s+\S+;) # package declaration
(.*?) # body
((__END__|__DATA__).*|$) # trailer
/$1sub main {$2\n};main();\n$3/sx;
# 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 ();
}
};
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.
@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;
...
};
Role::Tiny (Class::Method::Modifiers under-hood) also is affected.
Guys, thanks for sharing workarounds!
See also Sub::Util::set_subname
as an alternative to Sub::Name
.
@pjcj @DrHyde @jkeenan etc., if you write a CPAN class using Moose, you might be interested to know that there is a CPAN module named MooseX::CoverableModifiers that can help with this issue. I was able to get the code to cover all unnamed subroutine references in around, after, and before.
But there is one style of code that would still not be covered properly:
my %callback_function = (
opt1 => sub {
## do something
},
opt2 => sub {
## something else
},
);
sub run_funcs {
my $callback = shift;
$callback_function{$callback}->()
}
After extensive research by Aaron Crane, it is all but impossible to implement this without core support. So we're going to go for core support.
Is that coming in Perl 5?
With luck, it will be in Perl 5.32 in about a year or so
That's good news, thanks!
Did this land?
I tried MooseX::CoverableModifiers and it completely exploded with Perl 5.32. I found out that, on the other hand, replacing before 'update' => sub { ...
with before 'update' => *before_update = sub { ...
had a similar effect, and it was a nice workaround.
Nice! @mat813 @pjcj Do you think a doc patch would be a good idea?
Just FYI: I wrapped the code from @DrHyde in a perl module: https://metacpan.org/pod/Devel::Deanonymize. Feedback is highly appreciated
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).