NAME

App::Test::Generator - Generate fuzz and corpus-driven test harnesses

SYNOPSIS

From the command line:

fuzz-harness-generator t/conf/add.conf > t/add_fuzz.t

From Perl:

use App::Test::Generator qw(generate);

# Generate to STDOUT
App::Test::Generator::generate("t/conf/add.conf");

# Generate directly to a file
App::Test::Generator::generate('t/conf/add.conf', 't/add_fuzz.t');

OVERVIEW

This module takes a formal input/output specification for a routine or method and automatically generates test cases. In effect, it allows you to easily add comprehensive black-box tests in addition to the more common white-box tests that are typically written for CPAN modules and other subroutines.

The generated tests combine:

This approach strengthens your test suite by probing both expected and unexpected inputs, helping you to catch boundary errors, invalid data handling, and regressions without manually writing every case.

DESCRIPTION

This module implements the logic behind fuzz-harness-generator. It parses configuration files (fuzz and/or corpus YAML), and produces a ready-to-run .t test script using Test::Most.

It reads configuration files (Perl .conf with our variables, and optional YAML corpus files), and generates a Test::Most-based fuzzing harness combining:

EDGE CASE GENERATION

In addition to purely random fuzz cases, the harness generates deterministic edge cases for parameters that declare min, max or len in their schema definitions.

For each constraint, three edge cases are added:

Supported constraint types:

These edge cases are inserted automatically, in addition to the random fuzzing inputs, so each run will reliably probe boundary conditions without relying solely on randomness.

CONFIGURATION

The configuration file is either a file that can be read by Config::Abstraction or a trusted input Perl file that should set variables with our.

The documentation here covers the old trusted input style input, but that will go away so you are recommended to use Config::Abstraction files. Example: the generator expects your config to use our %input, our $function, etc.

Recognized items:

EXAMPLES

Math::Simple::add()

Functional fuzz + Perl corpus + seed:

our $module = 'Math::Simple';
our $function = 'add';
our %input = ( a => { type => 'integer' }, b => { type => 'integer' } );
our %output = ( type => 'integer' );
our %cases = (
  '3'     => [1, 2],
  '0'     => [0, 0],
  '-1'    => [-2, 1],
  '_STATUS:DIES'  => [ 'a', 'b' ],     # non-numeric args should die
  '_STATUS:WARNS' => [ undef, undef ], # undef args should warn
);
our $seed = 12345;
our $iterations = 100;

Adding YAML file to generate tests

OO fuzz + YAML corpus + edge cases:

    our %input = ( query => { type => 'string' } );
    our %output = ( type => 'string' );
    our $function = 'search';
    our $new = { api_key => 'ABC123' };
    our $yaml_cases = 't/corpus.yml';
    our %edge_cases = ( query => [ '', '    ', '<script>' ] );
    our %type_edge_cases = ( string => [ \"\\0", "\x{FFFD}" ] );
    our $seed = 999;

YAML Corpus Example (t/corpus.yml)

A YAML mapping of expected -> args array:

    "success":
      - "Alice"
      - 30
    "failure":
      - "Bob"

Example with arrayref + hashref

our %input = (
  tags   => { type => 'arrayref', optional => 1 },
  config => { type => 'hashref' },
);
our %output = ( type => 'hashref' );

Example with memberof

our %input = (
    status => { type => 'string', memberof => [ 'ok', 'error', 'pending' ] },
);
our %output = ( type => 'string' );
our %config = ( test_nuls => 0, test_undef => 1 );

This will generate fuzz cases for 'ok', 'error', 'pending', and one invalid string that should die.

New format input

Testing HTML::Genealogy::Map:

---

module: HTML::Genealogy::Map
function: onload_render

input:
  gedcom:
    type: object
    can: individuals
  geocoder:
    type: object
    can: geocode
  debug:
    type: boolean
    optional: true
  google_key:
    type: string
    optional: true
    min: 39
    max: 39
    matches: "^AIza[0-9A-Za-z_-]{35}$"

config:
  test_undef: 0

OUTPUT

By default, writes t/fuzz.t. The generated test:

NOTES

SEE ALSO

AUTHOR

Nigel Horne, <njh at nigelhorne.com>

Portions of this module's design and documentation were created with the assistance of ChatGPT (GPT-5), with final curation and authorship by Nigel Horne.