App-Test-Generator
view release on metacpan or search on metacpan
lib/App/Test/Generator/CoverageGuidedFuzzer.pm view on Meta::CPAN
if($self->{_cover_available}) {
$coverage = $self->_run_with_cover($input, \$result, \$error);
} else {
$coverage = {};
# Include instance as invocant for method calls
my @call_args = defined($self->{instance})
? ($self->{instance}, $input)
: ($input);
my @warnings;
eval {
local $SIG{__WARN__} = sub { push @warnings, @_ };
local $SIG{__DIE__};
$result = $self->{target_sub}->(@call_args);
};
$error = $@ if $@;
# Treat unexpected warnings matching known bad patterns as soft bugs
if(!defined($error) && @warnings) {
my $w = join '', @warnings;
$error = "warning: $w"
if $w =~ /uninitialized|undefined|blessed|invalid/i;
}
}
# Record bugs â only when the input was valid per the schema.
# A die on invalid input is correct behaviour, not a bug.
if($error && $self->_input_is_valid($input)) {
push @{ $self->{bugs} }, { input => $input, error => "$error" };
$self->{stats}{bugs}++;
}
# Keep the input in the corpus if it exercised new branches
if($self->_is_interesting($coverage)) {
push @{ $self->{corpus} }, { input => $input, coverage => $coverage };
$self->_update_covered($coverage);
$self->{stats}{interesting}++;
}
}
# --------------------------------------------------
# _run_with_cover
#
# Purpose: Run the target sub with Devel::Cover
# active and return the set of newly hit
# branches as a hashref.
#
# Entry: $input - value to pass to target_sub.
# $result_ref - scalar ref to store result.
# $error_ref - scalar ref to store error.
#
# Exit: Returns a hashref of newly hit branch
# keys ("file:line:branch").
#
# Side effects: Calls Devel::Cover::start/stop.
# Sets $$result_ref and $$error_ref.
#
# Notes: Snapshot comparison is imprecise for
# concurrent use but correct for single-
# threaded fuzzing. Instance is passed
# as invocant when set.
# --------------------------------------------------
sub _run_with_cover {
my ($self, $input, $result_ref, $error_ref) = @_;
Devel::Cover::start() if Devel::Cover->can('start');
my %before = $self->_snapshot_cover();
# Include instance as invocant for method calls
my @call_args = defined($self->{instance})
? ($self->{instance}, $input)
: ($input);
eval {
local $SIG{__DIE__};
$$result_ref = $self->{target_sub}->(@call_args);
};
$$error_ref = $@ if $@;
my %after = $self->_snapshot_cover();
Devel::Cover::stop() if Devel::Cover->can('stop');
# Return only branches newly hit in this call
my %delta;
for my $key (keys %after) {
$delta{$key} = 1 unless exists $before{$key};
}
return \%delta;
}
# --------------------------------------------------
# _snapshot_cover
#
# Purpose: Take a lightweight snapshot of the
# currently hit branches from Devel::Cover.
#
# Entry: None beyond $self.
# Exit: Returns a hash of "file:line:branch" keys.
#
# Side effects: Reads Devel::Cover internal state.
#
# Notes: Falls back to empty hash if the
# Devel::Cover API is not accessible.
# All errors are silently swallowed since
# coverage is best-effort.
# --------------------------------------------------
sub _snapshot_cover {
my ($self) = @_;
my %snap;
eval {
my $cover = Devel::Cover::get_coverage();
return unless $cover;
for my $file (keys %{$cover}) {
my $branch = $cover->{$file}{branch} or next;
for my $line (keys %{$branch}) {
for my $b (0 .. $#{ $branch->{$line} }) {
( run in 1.783 second using v1.01-cache-2.11-cpan-5623c5533a1 )