App-Test-Generator
view release on metacpan or search on metacpan
# ââ Step 1: extract schemas ââââââââââââââââââââââââââââââââââââââ
diag("step 1: extracting schemas from $pm_file") if $VERBOSE;
my $t0 = time;
my $extractor = App::Test::Generator::SchemaExtractor->new(
input_file => $pm_file,
output_dir => $tmpdir,
strict_pod => 'fatal',
verbose => 0,
);
my $schemas;
eval { $schemas = $extractor->extract_all() };
if ($@) {
fail("SchemaExtractor died for $pm_file");
diag($@);
diag("Diagnostics kept in: $tmpdir");
done_testing();
return;
}
if (!$schemas || !keys %$schemas) {
pass("no methods extracted from $pm_file");
rmtree($tmpdir);
done_testing();
return;
}
my $n_schemas = scalar keys %$schemas;
diag(sprintf('step 1 done in %ds: %d schemas extracted (%s)',
time - $t0, $n_schemas, join(', ', sort keys %$schemas))) if $VERBOSE;
pass('SchemaExtractor completed without error');
# ââ Step 2: generate a fuzz harness for each schema ââââââââââââââ
diag("step 2: generating fuzz harnesses") if $VERBOSE;
$t0 = time;
# Functions that require real file-system inputs can't be meaningfully
# fuzz-tested: the harness would supply random strings as file paths and
# every call would die with "file not found". Syntax-check these files
# (step 3a) but skip them in the prove run (step 3b).
# DB::DB is a Perl debugger hook in the DB package; ATG extracts it
# from Devel::... files but it is not callable as a module method.
# get_data_section returns a reference whose type PVS cannot validate.
# new constructors often require mandatory parameters the fuzzer can't supply.
# export conflicts with Perl's import mechanism; generated test uses 'use export'.
# merge/if/applies_to require real files or PPI objects or are Perl keywords.
# mutate/applies_to require a PPI::Document object the fuzzer can't construct.
# render_args_hash/render_arrayref_map/render_hash return '' for
# wrong-type inputs rather than dying, so fuzz "dies" tests fail.
my %no_prove = map { $_ => 1 } qw(generate DB::DB get_data_section new export merge mutate applies_to if render_args_hash render_arrayref_map render_hash);
my @test_files;
my @prove_files;
for my $func (sort keys %$schemas) {
my $schema_file = File::Spec->catfile($tmpdir, "${func}.yml");
next unless -f $schema_file;
# Patch schema: short per-case timeout (3s vs default 10s), no
# test_empty, low iterations, and cap string max lengths.
# The 64K-string cases come from a separate path and are only
# suppressed by setting max; test_empty only removes '' cases.
my $skip_prove = 0;
eval {
my ($schema) = LoadFile($schema_file);
if (ref($schema) eq 'HASH') {
$schema->{iterations} = 3;
$schema->{config}{timeout} = 3;
$schema->{config}{test_empty} = 0;
$schema->{config}{close_stdin} = 1;
# Cap unconstrained file-path string fields to prevent 64K-char test cases.
# Only applied to fields whose name suggests a file path â not general
# string arguments like 's', 'v', etc., which don't enforce length.
if (ref($schema->{input}) eq 'HASH') {
for my $field (keys %{$schema->{input}}) {
my $spec = $schema->{input}{$field};
next unless ref($spec) eq 'HASH';
if (($spec->{type}//'') eq 'string'
&& !defined($spec->{max})
&& $field =~ /(?:file|path|dir|filename|dirname)/i) {
$spec->{max} = 1025;
}
# A mandatory 'object'-type param without 'can' cannot be
# mocked by the fuzz harness â skip prove for such functions.
if (($spec->{type}//'') eq 'object'
&& !$spec->{optional}
&& !defined($spec->{can})) {
$skip_prove = 1;
}
}
}
# OOP instance methods have 'new:' in their schema â the fuzz
# harness would need to instantiate the class, which fails for
# classes whose constructors require mandatory parameters.
$skip_prove = 1 if exists $schema->{new};
DumpFile($schema_file, $schema);
}
};
# Private functions (leading underscore) are internal helpers that
# typically lack input validation; fuzz tests expect die-on-bad-input.
$skip_prove = 1 if $func =~ /^_/;
my $test_file = File::Spec->catfile($tmpdir, "${func}.t");
diag(" generating $func.t") if $VERBOSE;
my (undef, undef) = capture {
eval {
App::Test::Generator->generate(
schema_file => $schema_file,
output_file => $test_file,
);
};
};
if ($@) {
fail("Generator died for $func in $pm_file");
diag($@);
diag("Diagnostics kept in: $tmpdir");
$failed++;
next;
}
if (-f $test_file) {
push @test_files, $test_file;
push @prove_files, $test_file
unless $no_prove{$func} || $skip_prove;
( run in 1.021 second using v1.01-cache-2.11-cpan-71847e10f99 )