Skip to content

Commit

Permalink
Merge pull request 'Made modify_files more robust, added module struc…
Browse files Browse the repository at this point in the history
…ture, added doc' (#1) from cpan into master

Reviewed-on: https://gitea.oetiker.ch/OP/perl_coverage_reports/pulls/1
  • Loading branch information
Tobias Bossert committed Jun 24, 2021
2 parents 1bba130 + 2760ea5 commit b4dfd33
Show file tree
Hide file tree
Showing 17 changed files with 282 additions and 63 deletions.
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.idea
cover_db
*.iml
_Deparsed_XSubs.pm
_Deparsed_XSubs.pm
examples/cover_db
*.tar
Makefile.old
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Tobias Bossert <tobib at cpan.org>
Tobias Oetiker <tobi at oetiker.ch>
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
0.1.0 2021-23-23 Tobias Bossert (tobib at cpan.org)

- initial release
6 changes: 6 additions & 0 deletions COPYRIGHT
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Devel::Deanonymize

Copyright (c) 2020 Tobias Bossert and the other people listed in the
AUTHORS file.

All rights reserved.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Tobias Bossert, OETIKER+PARTNER AG Switzerland

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
14 changes: 14 additions & 0 deletions MANIFEST
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
AUTHORS
CHANGES
COPYRIGHT
examples/lib/Fancy/Module.pm
examples/my_program.pl
examples/runit.sh
examples/t/number_test.t
lib/Devel/Deanonymize.pm
LICENSE
Makefile.PL
MANIFEST This list of files
MANIFEST.SKIP
Readme.md
VERSION
18 changes: 18 additions & 0 deletions MANIFEST.SKIP
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.*\.old$
\.tar\.gz$
^Makefile$
^MYMETA\.
^blib
^pm_to_blib
\.idea
\.debian
\.git
_build
\.gitignore
cpanfile
examples/cover_db/
\.perl-version
configure\.ac
^_Deparsed_XSubs\.pm
.*.bak$
.*.iml$
48 changes: 48 additions & 0 deletions Makefile.PL
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use v5.20.0;

use strict;
use warnings;
use ExtUtils::MakeMaker;
use FindBin;

sub get_version() {
open my $fh, 'VERSION';
chomp(my $v = <$fh>);
close $fh;
return $v;
}

my $version = get_version();

WriteMakefile(
NAME => 'Devel::Deanonymize',
VERSION => $version,
ABSTRACT => 'A tool do make anonymous sub visible to Devel::Cover',
AUTHOR => 'Tobias Bossert <tobib at cpan.org>',
LICENSE => 'mit',
PREREQ_PM => {},
BUILD_REQUIRES => {},
MAKE => 'gmake',
EXE_FILES => [],
META_MERGE => {
requires => { perl => '5.020000' },
resources => {
license => 'https://opensource.org/licenses/mit',
repository => 'https://github.com/sirtoobii/wg-meta',
bugtracker => 'https://github.com/sirtoobii/wg-meta/issues'
},
no_index => { directory => [ 't' ] }
},
test => { TESTS => 't/*.t' }
);

sub MY::postamble {
my $self = shift;
return <<"EOF";
VERSION_FILES := \$(shell grep -Rl 'our \$\$VERSION = ' .)
\$(VERSION_FILES): VERSION
\$(PERL) -i -p -e 's/\$VERSION\\s*=\\s*"[\\d|\.]*[A-z]*"/\$VERSION = "\$(VERSION)"/;' \$\@
EOF
}
28 changes: 10 additions & 18 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
# Perl tests with coverage statistics
# Devel::Deanonymize

A quick guide on capturing coverage statistics in perl programs
A small tool to make anonymous sub visible to Devel::Coverage (and possibly similar Modules)

## Synopsys
## Synopsys

```bash
# delete old coverage data (optional)
cover -delete

# Perl scripts
perl -I lib/ -MDevel::Cover=-ignore,^t/,Deanonymize -MDevel::Deanonymize=Fancy runit.pl
perl -MDevel::Cover=-ignore,^t/,Deanonymize -MDevel::Deanonymize=<inculde_pattern> your_script.pl

# Perl tests
HARNESS_PERL_SWITCHES="-MDevel::Cover=-ignore,^t/,Deanonymize -MDevel::Deanonymize=Fancy" prove -I lib/ t
HARNESS_PERL_SWITCHES="-MDevel::Cover=-ignore,^t/,Deanonymize -MDevel::Deanonymize=<include_pattern" prove t/

# generate report
cover -report html
```

## Anonymous subs

Unfortunately, anonymous subs are invisible to the Coverage package. The solution for this problem is `Devel::Deanoymize` (available in this repo)

```bash
# perl scripts
perl -I lib/ -MDevel::Cover=<options> -MDevel::Deanonymize=<include_pattern> your_script.pl

# perl tests
HARNESS_PERL_SWITCHES="-MDevel::Cover=<options> -MDevel::Deanonymize=<include_pattern>" prove -I lib/ t
```

## Reports
## Coverage Reports

Per default, `Devel::Cover` creates a folder named `cover_db` inside the project root. To visualize the result, we have to
generate a report:
Expand All @@ -42,6 +30,10 @@ cover -report html
The html report (or any other report type) is then stored under `cover_db` as well.


## Examples

See separate subdirectory [examples/runit.sh](examples/runit.sh)

## Important notes

- Make sure your script (the one under test) always ends with `__END__`, otherwise the regex to modify it fails silently
Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
41 changes: 41 additions & 0 deletions examples/lib/Fancy/Module.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package Fancy::Module;
use strict;
use warnings FATAL => 'all';
use experimental 'signatures';

use base 'Exporter';
our @EXPORT = qw(is_it_the_number is_the_sum_the_number);

my $anon = sub($number) {
if ($number != 42) {
return "No, it's not";
}
};

sub is_it_the_number($number) {
if ($number == 42) {
return "It is the number";
}
else {
&{$anon}($number);
}
}

sub is_the_sum_the_number($number1, $number2) {
# extra complicated check
if ($number1 > 42 or $number2 > 42) {
return "No, its not";
}
elsif (($number2 == 42 or $number1 == 42) and ($number1 + $number2 == 0)) {
return "It is the number";
}
elsif ($number1 + $number2 == 42) {
return "It is the number"
}
else {
&{$anon}($number1 + $number2);
}

}

__END__
19 changes: 19 additions & 0 deletions examples/my_program.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/perl
use strict;
use warnings FATAL => 'all';
use Fancy::Module;

is_it_the_number(21);
is_it_the_number(42);
is_the_sum_the_number(21,21);
is_the_sum_the_number(42,0);
is_the_sum_the_number(0,42);
is_the_sum_the_number(42,42);
is_the_sum_the_number(23,42);
is_the_sum_the_number(42,23);
is_the_sum_the_number(43,43);
is_the_sum_the_number(43,21);
is_the_sum_the_number(21,43);
is_the_sum_the_number(21,21);

print("done\n");
18 changes: 18 additions & 0 deletions examples/runit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash

#PERL_DIR=/home/tbossert/.plenv/versions/5.32.1/bin
PERL_DIR=/usr/bin/

# Delete old coverage Data
#$PERL_DIR/cover -delete

# Run tests
#HARNESS_PERL_SWITCHES="-MDevel::Cover=-ignore,^t/,Deanonymize -MDevel::Deanonymize=Fancy" $PERL_DIR/prove t/ -I lib/ -I ../lib
#HARNESS_PERL_SWITCHES="-MDevel::Cover=-ignore,^t/,Deanonymize" $PERL_DIR/prove t/ -I lib/ -I ../lib



# Run script
$PERL_DIR/perl -MDevel::Cover=-ignore,^t/,Deanonymize -MDevel::Deanonymize=Fancy -I lib/ -I ../lib my_program.pl

$PERL_DIR/cover -report html
File renamed without changes.
84 changes: 77 additions & 7 deletions lib/Devel/Deanonymize.pm
Original file line number Diff line number Diff line change
@@ -1,20 +1,85 @@
=head1 NAME
Devel::Deanonymize - A small tool to make anonymous sub visible
=head1 DESCRIPTION
When collecting Coverage statistics with L<Devel::Cover> a construct like below appear to be invisible and is simply ignored
by the statistic:
my $sub = sub handle{
print "hello";
}
This script aims to solve this problem by wrapping each file in a sub and thus making these subs I<visible>.
=head1 SYNOPSIS
# Perl scripts
perl -MDevel::Cover=-ignore,^t/,Deanonymize -MDevel::Deanonymize=<inculde_pattern> your_script.pl
# Perl tests
HARNESS_PERL_SWITCHES="-MDevel::Cover=-ignore,^t/,Deanonymize -MDevel::Deanonymize=<include_pattern" prove t/
=head1 EXAMPLES
Please referer to the files provided in the I<examples/> directory
=head1 AUTHORS
Since there is a lot of spam flooding my mailbox, I had to put spam filtering in place. If you want to make sure
that your email gets delivered into my mailbox, include C<#im_not_a_bot#> in the B<subject!>
S<Tobias Bossert E<lt>tobib at cpan.orgE<gt>>
=head1 COPYRIGHT AND LICENSE
MIT License
Copyright (c) 2021 Tobias Bossert, OETIKER+PARTNER AG Switzerland
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=cut

package Devel::Deanonymize;
use strict;
use warnings FATAL => 'all';

our $VERSION = "0.1.0";

my $include_pattern;

sub import {
# capture input parameters
$include_pattern = $_[1];
$include_pattern = $_[1] ? $_[1] : die("Devel::Deanonymize: An include Pattern must be specified \n");
}

sub modify_files{
# Internal note:
sub modify_files {
# Internal notes:
# Basically, this code replaces every file path in @INC with a reference to an anonymous sub which wraps each
# file in sub classWrapper { $orig_content } classWrapper(); However, this sub is **not** necessarily run at INIT or UNITCHECK stage!
# NB, this also explains why its is possible to have $include_pattern "defined" at UNITCHECK even if its run **before** import()
# Also do not forget the `__END__` at each file
# Also make sure each file either ends with __DATA__, __END__, or 1;
unshift @INC, sub {
my (undef, $filename) = @_;
return () if ($filename !~ /$include_pattern/);
Expand All @@ -24,10 +89,15 @@ sub modify_files{
my $module_text = <$fh>;
close $fh;

if (not $module_text =~ /(__END__|1;|__DATA__)/) {
warn("Devel::Deanonymize: Found no endmarker in file `$filename` - skipping\n");
return ();
}

# 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+)(.*)__END__/$1sub classWrapper {$2} classWrapper();/s;
$module_text =~ s/(.*?package\s+\S+)(.*)(__END__|1;|__DATA__)/$1sub classWrapper {$2} classWrapper();/s;

# unhide private methods to avoid "Variable will not stay shared"
# warnings that appear due to change of applicable scoping rules
Expand All @@ -51,9 +121,9 @@ sub modify_files{


# We call modify_files twice since depending on how a module is loaded (use or required) it is present in @INC at different stages
# A "double-modification" is not possible because we only edit non references
# Also, "double-modification" is not possible because we only alter non references
INIT {
modify_files();
modify_files();
}

UNITCHECK {
Expand Down
Loading

0 comments on commit b4dfd33

Please sign in to comment.