Commands

Ten scripts, all installed by the distribution. Four carry the daily workflow — generate, extract, mutate, report — and the rest are helpers around them.

CommandPurpose
fuzz-harness-generatorSchema → runnable test file.
extract-schemasPerl module → schemas, optionally fuzzed and minimised.
test-generator-mutateMutation testing against your suite.
test-generator-indexDashboard HTML from coverage, LCSAJ and mutation data.
generate-test-dashboardThe whole CI pipeline as one shell script.
benchmark-generatorSchema → Benchmark::cmpthese script.
pod-example-testerPOD examples → round-trip test file.
deploy-workflowsTwo GitHub Actions workflows → another repository.
instrument-modulesWrites the .lcsaj.json path definitions.
demo_extractorSelf-contained demonstration of schema extraction.

fuzz-harness-generator

generate
fuzz-harness-generator [-r] [-o FILE] schema.yml
fuzz-harness-generator --dry-run schema.yml
fuzz-harness-generator --replay-corpus DIR -o t/fuzz_replay.t

Renders the harness. Without -o it prints the test file to standard output, which is how many projects keep generated tests out of version control. With -r it writes the file to a temporary path, runs it through prove and prints the result, which is how it is used from t/fuzz.t.

OptionEffect
-r, --runRun the generated harness with prove and report the outcome.
-o FILEWrite the harness to FILE instead of standard output.
--dry-runParse the schema and report what would be generated, without writing a test file.
--replay-corpus DIR|FILEEmit a harness that replays a saved corpus — every recorded input, in a deterministic order — rather than fuzzing afresh.
-v, --verboseMore diagnostics on standard error.

extract-schemas

extract
extract-schemas [options] lib/My/Module.pm
OptionEffect
-o DIR, --output-dir DIRWhere the YAML files go. Default schemas/; created if absent.
-F NAME, --function NAMEExtract everything, then keep only NAME.yml and test only that routine. Prints the available names and exits 1 if NAME does not exist.
-s MODE, --strict-pod MODEoff, warn or fatal: what to do when POD and code disagree about a parameter's type.
-f, --fuzzFuzz the extracted schemas as they are produced.
--fuzz-iters NIterations per method while fuzzing. Default 100.
--fuzz-allFuzz routines that have no input schema too.
-c DIR, --corpus-dir DIRWhere corpora persist between runs. Default schemas/corpus/.
--minimize-corpusTrim each corpus to the smallest subset that still covers every discovered branch.
-v, --verboseShow the analysis as it runs.

How extraction works, and what the confidence levels mean.

test-generator-mutate

mutation
test-generator-mutate --lib lib --tests t
test-generator-mutate --file lib/My/Module.pm
test-generator-mutate --min-score 85 --json mutation.json
OptionEffect
--lib DIRThe library directory to mutate. Default lib.
--tests DIRThe test directory to run per mutant. Default t.
--file PATHMutate one file only.
--json FILEWrite the machine-readable report. mutation.json is what the dashboard reads.
--min-score NExit non-zero if the kill percentage falls below N.
--changed_only, --base_sha SHAMutate only files changed against a git base — the fast mode for a pull request.
--exclude REGEXSkip files matching the pattern. Repeatable.
--cover_json FILEUse a Devel::Cover report to prioritise mutants in covered code.
--fail-fastStop at the first surviving mutant.
-v, --verboseShow each mutant as it is applied and tested.

Operators, skip markers and the survivor pipeline.

test-generator-index

report
test-generator-index
test-generator-index --generate_mutant_tests=t --generate_test=mutant --generate_fuzz

Reads the artefacts other steps produced and writes the dashboard. It takes no general options beyond the three that drive the mutant-test pipeline; where its inputs live is fixed, so that the CI workflow and a local run cannot disagree.

OptionEffect
--generate_mutant_tests=DIRWrite a TODO-stub test file for every survivor into DIR.
--generate_test=mutantFor NUM_BOUNDARY survivors, extract the enclosing routine's schema and write a runnable boundary schema into t/conf/.
--generate_fuzzMerge survivor boundary values into copies of the existing schemas, as t/conf/mutant_fuzz_*.yml.

The feedback loop explains what each of those flags produces.

generate-test-dashboard

pipeline
bin/generate-test-dashboard

A bash script, not a Perl program. It clears last run's artefacts, builds blib, runs the suite under Devel::Cover, exports JSON coverage, derives LCSAJ targets, runs the suite again under the LCSAJ runtime debugger, merges the per-process hit files, runs mutation testing, writes the mutant schemas and finally calls test-generator-index.

It runs the suite twice on purpose: once for statement and branch coverage, once for path coverage. That is why the workflow sets NO_NETWORK_TESTING and why t/fuzz.t keeps its extended-test guard — without them, a CI run takes hours rather than minutes.

benchmark-generator

benchmark
benchmark-generator -i schemas/abs.yml
benchmark-generator -i schemas/abs.yml -o benchmarks/abs.pl
benchmark-generator -i schemas/abs.yml -r

Each transform in the schema becomes one named variant in a Benchmark::cmpthese call, with representative values derived from that transform's types and bounds. A schema with no transforms produces a single 'default' variant. -r writes the script to a temporary file and runs it immediately.

pod-example-tester

generate
pod-example-tester lib/My/Module.pm
pod-example-tester --output t/synopsis.t lib/My/Module.pm
pod-example-tester --no-annotated lib/My/Module.pm

Collects runnable examples from a module's POD — SYNOPSIS verbatim blocks, =for example begin/end blocks, and indented lines annotated # returns value or # => value — and emits one subtest per example. Annotated examples get an is() assertion on the documented result; unannotated blocks are wrapped in eval and checked for "did not die".

Two safety filters matter: blocks that contain only shell commands are dropped, and any line that would run system, exec, a backtick or qx is replaced with a note, so a SYNOPSIS that demonstrates invoking a command does not actually run it inside your test suite.

deploy-workflows

ci
deploy-workflows
deploy-workflows --target /path/to/my-module
deploy-workflows --dry-run --target /path/to/my-module

Copies dashboard.yml and mutate.yml into TARGET/.github/workflows/. Both files are embedded in the script, so it works after a CPAN install with no source tree present. It refuses to overwrite an existing file unless --force is given, and --dry-run prints what it would do without touching the filesystem.

The two workflows are portable by design: no shared script under .github/scripts/, because a dependent repository only ever receives the two YAML files.

instrument-modules

metrics
instrument-modules
instrument-modules lib/My/Module.pm

Writes a .lcsaj.json control-flow path definition for each module into cover_html/mutation_html/lib/. With no arguments it walks lib/ recursively. The dashboard reads these files to compute the TER3 column, and the LCSAJ runtime debugger uses them to record which paths were hit.

demo_extractor

demo
demo_extractor

Creates a temporary sample module, extracts its schemas, prints them, and compares the result with the expected output. Useful as a tour of what extraction can infer, and as a quick check that the extractor still behaves after an upgrade.

Exit codes

The generating and reporting commands follow the usual Perl convention: 0 on success, 2 for a usage error, non-zero for a failure. test-generator-mutate additionally exits non-zero when --min-score is not met, which is what makes it usable as a CI gate.

← Schema reference Extraction →