App-Test-Generator
view release on metacpan or search on metacpan
bin/fuzz-harness-generator view on Meta::CPAN
acting as permanent regression tests.
Only corpus entries with recorded bugs are included. Clean corpus entries
(inputs that did not cause a bug) are ignored.
=item B<--version>
Prints the version of L<App::Test::Generator>
=back
=cut
my $infile;
my $outfile;
my $help;
my $run;
my $verbose;
my $version;
my $dry_run;
my $replay_corpus;
Getopt::Long::Configure('bundling');
GetOptions(
'help|h' => \$help,
'input|i=s' => \$infile,
'dry-run|n' => \$dry_run,
'output|o=s' => \$outfile,
'run|r' => \$run,
'verbose|v' => \$verbose,
'version|V' => \$version,
'replay-corpus|R=s' => \$replay_corpus,
) or pod2usage(2);
pod2usage(-exitval => 0, -verbose => 1) if($help);
if($version) {
print $App::Test::Generator::VERSION, "\n";
exit 0;
}
# ---------------------------------------------------------------------------
# --replay-corpus mode: generate a regression .t from corpus bug entries
# ---------------------------------------------------------------------------
if($replay_corpus) {
pod2usage('--replay-corpus cannot be combined with --dry-run') if $dry_run;
pod2usage('--replay-corpus cannot be combined with --input') if $infile;
my @corpus_files = _collect_corpus_files($replay_corpus);
die "No corpus JSON files found at: $replay_corpus\n" unless @corpus_files;
my $tap = _generate_replay_tap(@corpus_files);
if($outfile) {
open(my $fh, '>', $outfile)
or die "Cannot write to $outfile: $!";
print $fh $tap;
close $fh;
chmod 0755, $outfile;
print "Replay test written to: $outfile\n";
if($run) {
exit system('prove', '-l', $outfile) >> 8;
}
} else {
print $tap;
}
exit 0;
}
if($infile && @ARGV) {
pod2usage('Specify input file either as argument or via --input, not both');
}
if($infile) {
my $schema = eval { LoadFile($infile) };
if($@) {
die "Cannot parse '$infile' as YAML: $@";
}
unless(ref($schema) eq 'HASH') {
die "Input file '$infile' does not contain a YAML hash";
}
unless($schema->{function}) {
die "Input file '$infile' is missing required 'function' key";
}
}
$infile ||= shift @ARGV or pod2usage('No config file given');
if($dry_run && $run) {
pod2usage('--dry-run cannot be used with --run');
}
if($dry_run && $outfile) {
warn '--dry-run specified; --output will be ignored';
}
if($verbose) {
$ENV{'TEST_VERBOSE'} = 1;
}
if($run && !$outfile) {
my ($fh, $tmp) = File::Temp::tempfile();
close $fh;
App::Test::Generator->generate($infile, $tmp);
exit system('prove', '-l', $tmp) >> 8;
}
if($dry_run) {
my ($fh, $tmp) = File::Temp::tempfile();
close $fh;
eval {
App::Test::Generator->generate($infile, $tmp);
1;
} or do {
die "Dry-run failed for $infile: $@";
};
unlink $tmp;
print "Dry-run OK: $infile parsed and validated successfully\n";
exit 0;
} elsif($outfile && -e $outfile && !$run) {
warn "Overwriting existing file: $outfile";
}
App::Test::Generator->generate($infile, $outfile);
if($outfile) {
chmod 0755, $outfile if($outfile =~ /\.(pl|cgi)$/);
if($run) {
# Use list form to avoid shell interpolation of $outfile
system('prove', '-l', $outfile);
}
}
exit 0;
# ---------------------------------------------------------------------------
# Helpers for --replay-corpus
# ---------------------------------------------------------------------------
# --------------------------------------------------
# _collect_corpus_files
#
# Collect the list of corpus JSON files
# to process for --replay-corpus mode.
# Accepts either a single file path or
# a directory, returning all *.json files
# found in the directory case.
#
# Entry: $path - filesystem path to either a
# single .json file or a directory
# containing .json files.
#
# Exit: Returns a sorted list of file paths.
# Returns an empty list if the path does
# not exist or contains no .json files.
#
# Side effects: None.
#
# Notes: Directory globbing matches only *.json
# files at the top level of the directory;
# subdirectories are not recursed into.
# --------------------------------------------------
sub _collect_corpus_files {
my ($path) = @_;
if(-f $path) {
return ($path);
} elsif(-d $path) {
my @files = glob(File::Spec->catfile($path, '*.json'));
return sort @files;
}
return ();
}
# --------------------------------------------------
# _generate_replay_tap
#
# Purpose: Read one or more corpus JSON files and
# produce a complete .t file as a string.
# Each bug entry in the corpus becomes
# one lives_ok test that calls the target
# method with the exact input that
# previously caused a crash, asserting
# that it no longer dies.
#
# Entry: @corpus_files - list of paths to corpus
( run in 1.005 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )