App-Test-Generator
view release on metacpan or search on metacpan
t/CoverageGuided_Fuzzer_unit.t view on Meta::CPAN
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'string' } },
target_sub => sub { 1 },
);
my $after = time();
ok($f->{seed} >= $before && $f->{seed} <= $after,
'default seed is approximately time()');
};
subtest 'new() stores optional instance' => sub {
my $obj = bless {}, 'FakeClass';
my $f = _fuzzer(instance => $obj);
is($f->{instance}, $obj, 'instance stored correctly');
};
subtest 'new() initialises corpus, covered, and bugs to empty' => sub {
my $f = _fuzzer();
is(ref($f->{corpus}), 'ARRAY', 'corpus is arrayref');
is(ref($f->{covered}), 'HASH', 'covered is hashref');
is(ref($f->{bugs}), 'ARRAY', 'bugs is arrayref');
is(scalar @{$f->{corpus}}, 0, 'corpus initially empty');
is(scalar @{$f->{bugs}}, 0, 'bugs initially empty');
is(scalar keys %{$f->{covered}}, 0, 'covered initially empty');
};
subtest 'new() each call returns a distinct object' => sub {
my $f1 = _fuzzer();
my $f2 = _fuzzer();
isnt($f1, $f2, 'distinct objects returned');
};
subtest 'new() defaults timeout to 5 seconds' => sub {
my $f = _fuzzer();
is($f->{timeout}, 5, 'timeout defaults to 5');
};
subtest 'new() stores supplied timeout' => sub {
my $f = _fuzzer(timeout => 1);
is($f->{timeout}, 1, 'timeout stored correctly');
};
# ==================================================================
# Regression: a hanging target_sub must not hang the whole fuzzing
# run. Both the Devel::Cover and non-cover call sites in _run_one /
# _run_with_cover now wrap the target_sub call in an alarm()-bounded
# eval, recording the timeout as a bug rather than blocking forever.
# ==================================================================
subtest 'run() aborts a hanging target_sub via timeout and records a bug' => sub {
my $f = _fuzzer(
timeout => 1,
iterations => 1,
target_sub => sub { sleep 30; return 1 },
);
my $start = time();
my $r;
lives_ok(sub { $r = $f->run() }, 'run() returns rather than hanging');
my $elapsed = time() - $start;
ok($elapsed < 25, "run() returned promptly (elapsed=${elapsed}s), not after the full sleep")
or diag("run() took ${elapsed}s â timeout did not abort the hanging call");
ok(scalar(@{$f->bugs()}) >= 1, 'hanging call recorded as a bug');
like($f->bugs()->[0]{error}, qr/timed out/, 'bug error mentions the timeout');
};
subtest 'run() with timeout disabled (0) does not wrap target_sub in alarm' => sub {
my $f = _fuzzer(
timeout => 0,
iterations => 3,
target_sub => sub { 1 },
);
lives_ok(sub { $f->run() }, 'run() lives with timeout disabled');
};
# ==================================================================
# run()
#
# POD spec:
# Returns a hashref with keys: total_iterations, interesting_inputs,
# corpus_size, branches_covered, bugs_found, bugs
# ==================================================================
subtest 'run() returns a hashref' => sub {
my $f = _fuzzer();
my $r;
lives_ok(sub { $r = $f->run() }, 'run() lives');
is(ref($r), 'HASH', 'returns hashref');
};
subtest 'run() report contains all required keys' => sub {
my $f = _fuzzer();
my $r = $f->run();
for my $key (qw(total_iterations interesting_inputs
corpus_size branches_covered bugs_found bugs)) {
ok(exists $r->{$key}, "$key key present");
}
};
subtest 'run() total_iterations matches configured iterations' => sub {
my $f = _fuzzer(iterations => 7);
my $r = $f->run();
is($r->{total_iterations}, 7, 'total_iterations equals configured value');
};
subtest 'run() bugs key is an arrayref' => sub {
my $f = _fuzzer();
my $r = $f->run();
is(ref($r->{bugs}), 'ARRAY', 'bugs is arrayref');
};
subtest 'run() corpus_size is non-negative' => sub {
my $f = _fuzzer();
my $r = $f->run();
ok($r->{corpus_size} >= 0, 'corpus_size is non-negative');
};
subtest 'run() does not croak for target that always returns 1' => sub {
my $f = _fuzzer(target_sub => sub { 1 });
lives_ok(sub { $f->run() }, 'run() lives for well-behaved target');
( run in 1.323 second using v1.01-cache-2.11-cpan-0b5f733616e )