Changes for version 0.46 - 2026-08-18

  • Enhancements
    • Add CPD drill-down pages (cover_html/cpd_html/): per-file HTML showing source with duplicate lines highlighted amber, with clickable ◆ diamonds linking to the matching file's own CPD page. Generated by _cpd_file_report. Dup% badges in the Mutation Files table now link to the drill-down page. Mutation-page ◆ markers and the CPD legend heading also link to CPD pages.
    • Consistent visual style across all per-file pages: CPD drill-down pages now use the same _mutant_file_header()/_mutant_file_footer() frame as mutation pages — same sans-serif font, CSS variables, dark-mode toggle, and theme- persistence JS. CPD-specific styles (.dup-line, .intra-dot, .cpd-legend) are embedded in the shared stylesheet.
    • Sortable Mutation Files table and Duplication Report table: clicking any column header sorts by that column; ▲/▼ arrows indicate the current sort direction and active column, matching the behaviour of the Coverage table. The sortTable() JS function now auto-detects numeric vs. text values via parseFloat() (replacing a hardcoded colIndex===0 date check), supports a data-sort-type="date" attribute for date columns, and respects a data-value="N" attribute on <td> cells for custom sort keys (used so the Duplication table's Lines column sorts by start-line number, not the "lines N–M" text). Fixed a pre-existing column-alignment bug: the TER1/TER2[/TER3] column was always emitted in data rows but only appeared in the Mutation Files table header when lcsaj_root was set, making the Dup% column index inconsistent; TER is now always in the header.
    • Drag-to-reorder columns: all three sortable tables (Coverage, Mutation Files, Duplication Report) allow dragging a column header to a new position. Drag-and-drop uses the HTML5 API with pointer-events:none on child <span> elements to prevent flicker. The active sort column tracks its new position after reordering.
    • Persistent sort and column order: sort direction, active sort column, and column order for all four tables (coverage, cpan, mut, dup) are saved to localStorage under keys atg_sort_<id> and atg_col_order_<id> and restored on the next page load. Columns are identified by their original index via data-col-id so the saved order survives new columns being added. A _initializingTables flag prevents the restore phase from overwriting saved state with the defaults.
    • Add t/domain.t: 24 domain tests using Equivalence Partitioning and Boundary Value Analysis across six modules. Covers: Model::Method::new (name/source required-vs-undef domain), add_evidence (category enum EP, signal enum EP, weight BVA including zero vs absent vs negative), resolve_confidence (BVA at thresholds 19/20/39/40 for low/medium/high), resolve_return_type (all signal→ bucket mappings including legacy_type values and alphabetical tie-breaks); BenchmarkGenerator::new (schema type domain: valid hashref, empty hashref, absent, undef, arrayref, scalarref), generate (module/function absent croaks, builtin module, transforms present vs absent), _representative_value (all type partitions, numeric min/max BVA including strict-greater-than boundary at min=default and strict-less-than boundary at max=default, min+max midpoint), _quote_value (undef, numeric 0, plain string, embedded single-quote, empty); CoverageGuidedFuzzer::new (schema/target_sub absent and undef, iterations and timeout BVA at 0 and 1, seed default vs explicit); Mutant::new (exists vs defined distinction for required attrs, transform type domain, optional attrs default to undef, line=0 edge); Analyzer::Complexity::analyze (cyclomatic threshold BVA at scores 3/4/7/8, early_returns BVA at 1/2/3 returns, nesting_depth BVA including unmatched-brace guard); Analyzer::SideEffect:: analyze (purity_level EP for pure/self_mutating/impure combinations, mutation_fields deduplication domain, string-stripping false-positive guards).
    • Add t/data-flow.t: 30 data-flow tests tracing Define-Use chains across CoverageGuidedFuzzer (seed storage/srand, stats vs corpus coherence, corpus-ref staleness after minimize, load_corpus isolation for bugs and seed, accumulated stats across consecutive run() calls), Model::Method (evidence insertion order, evidence_ref aliasing, weight=0 stored as 0 not 1, resolve_return_type idempotent DD write, evidence isolation between objects), Exporter::YAML (plan round-trip, required-arg validation, O-U-K file-handle lifecycle), Analyzer::SideEffect (purity_level derived after all flag assignments, mutation_fields deduplication, code_only stripping of string literals), Planner::Mock (pure-method omission, scalar vs arrayref strategy values), and BenchmarkGenerator (absent input/transforms keys coerced to empty hashes, schema not mutated by generate()).
    • Add t/transaction.t: comprehensive transaction-flow tests covering ten multi-step lifecycle sequences across CoverageGuidedFuzzer, Mutator, and SchemaExtractor. Tests verify: corpus lifecycle (run→minimize→save→load round-trip), minimize idempotency, bug-input preservation across minimize, mid-flight failure recovery (bad save/load paths leave in-memory state intact), multi-session corpus accumulation, Mutator state-machine ordering (apply_mutant without prepare_workspace croaks), MUTANT_SKIP annotation integrity (unclosed BEGIN, orphan END, nested BEGIN all croak; valid block excludes annotated lines), fast-vs-full mutation level count invariant, extract_all() YAML idempotency (data-structure comparison ignoring comment metadata), and extract_all() auto-creating a missing output dir.
  • Bug fixes
    • Fix test-generator-mutate ignoring subdirectory tests (GitHub issue #10): both the baseline prove run and each per-mutant prove run now pass -r so that .t files in subdirectories of --tests are included. Without -r, prove silently skipped subdirs, every mutant survived, and the mutation score was always 0% when tests lived below the top level. Regression tests added to t/cli-test-generator-mutate.t.
    • Fix dashboard delta column showing 0% for same-day runs with different coverage. Coverage snapshots are named YYYY-MM-DD-SHA.json; the delta computation sorts archive files alphabetically and takes the last as "most recent prior run". For same-day runs the SHA hex value determines sort order, which is unrelated to commit time, so an earlier run can sort after a later one and become the spurious "previous", producing a zero or wrong delta. Fix: change SNAPSHOT_TIMESTAMP from `date +%Y-%m-%d` to `date -u +%Y-%m-%dT%H%M%SZ` (full UTC datetime without colons) so alphabetical filename order matches chronological run order for all cases. Updated in dashboard.yml and the embedded __DATA__ copy in bin/deploy-workflows.

Documentation

Generate Benchmark harnesses from ATG schema files
Demonstrate the schema extractor
Deploy ATG GitHub Actions workflows to a CPAN module repository
Extract test schemas from Perl modules
Generate fuzzing + corpus-based test harnesses from test schemas
Generate round-trip tests from POD code examples
Test coverage dashboard generator
Run mutation testing against a Perl test suite

Modules

Fuzz Testing, Mutation Testing, LCSAJ Metrics and Test Dashboard for Perl modules
Generate Benchmark harnesses from ATG schemas
AFL-style coverage-guided fuzzing for App::Test::Generator
Serialise a test plan to YAML
Static LCSAJ extraction for Perl
Merge LCSAJ path data with runtime hits
Evidence-based model of a single method under test
Negate boolean return expressions to expose missing assertion coverage
Replace return expressions with undef to expose missing undef-return checks in the test suite
Generate and apply mutation tests
Extract runnable code examples from a Perl module's POD
Example module for schema extraction testing
Extract test schemas from Perl modules
Template for the test files generated by App::Test::Generator
Debugger backend for LCSAJ coverage

Provides

in lib/App/Test/Generator/Analyzer/Complexity.pm
in lib/App/Test/Generator/Analyzer/Return.pm
in lib/App/Test/Generator/Analyzer/ReturnMeta.pm
in lib/App/Test/Generator/Analyzer/SideEffect.pm
in lib/App/Test/Generator/Emitter/Perl.pm
in lib/App/Test/Generator/Mutant.pm
in lib/App/Test/Generator/Mutation/Base.pm
in lib/App/Test/Generator/Mutation/ConditionalInversion.pm
in lib/App/Test/Generator/Mutation/NumericBoundary.pm
in lib/App/Test/Generator/Planner.pm
in lib/App/Test/Generator/Planner/Fixture.pm
in lib/App/Test/Generator/Planner/Grouping.pm
in lib/App/Test/Generator/Planner/Isolation.pm
in lib/App/Test/Generator/Planner/Mock.pm
in lib/App/Test/Generator/TestStrategy.pm