Files
werkcrappit/docs/adr/0003-result-aggregation.md
miandmhoennig 6453ecc24b
werkator build succeeded after 00:35
Step 4 of 8: complete aggregation of PIT results (#5)
Step 4 of 8 from section 8.3 of the specification.

- `MutantAggregator` (core): statuses per distinct mutant (MethodId + mutator + instruction indexes); duplicates count once, contradicting final results count under none and make the method INCONCLUSIVE (`contradicting-results:<n>`), preliminary NOT_STARTED/STARTED give way to the final status, block order no longer matters.
- Listener test with real PIT result objects and a real `ReportOptions`: merged blocks, byte-identical report for every order, duplicates, contradictions.
- Functional test: every mutant of PIT's `mutations.xml` is counted exactly once; PIT's analysis ran once.
- ADR 0003, feature 04, MC-014 to MC-018 mapped.

CI on werkator.javagil.de: green (33 s), 79 unit and 10 functional tests.

PR-doc: `docs/prs/2026-10-01-PR#5-result-aggregation.md`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #5
2026-10-01 15:59:41 +02:00

1.8 KiB

ADR 0003: Result Aggregation

  • Status: accepted
  • Date: 2026-10-01
  • PR: #5

Context

PIT delivers results in ClassMutationResults blocks; one class may arrive in several of them. PIT holds some results back and delivers them later, for example to consolidate inlined code. The specification (4.5) forbids double counting, "last write wins" and confusing preliminary states with extra mutants.

Decision

  • Threading. PIT 1.30.0 calls every listener from one thread, in MutationAnalysisExecutor.processResult, then runEnd(). The listener's methods stay synchronized anyway; its state lives in the listener instance, one per run, with no static caches.
  • Identity. A mutant is identified by its MethodId plus PIT's mutator id and instruction indexes from MutationIdentifier. The listener keeps only identities and status names, never PIT's result objects.
  • Aggregation. MutantAggregator collects the set of statuses per mutant and decides at runEnd():
    • one final status, however often delivered → counted once under it;
    • several different final statuses → counted under none and reported as contradicted; the method becomes INCONCLUSIVE with contradicting-results:<n>;
    • NOT_STARTED or STARTED beside a final status → preliminary, gives way to the final one;
    • only preliminary statuses → counted under the latest of them, which makes the method INCONCLUSIVE under strict-v1.
  • Order. Statuses are kept as sets and results sorted by identity, so the report depends only on what arrived.

Consequences

  • The sum of all status counts in the report equals the number of mutants in PIT's mutations.xml, minus contradicted mutants; the functional test checks it.
  • Which statuses count as preliminary is the policy's knowledge (StatusTreatment.INCOMPLETE), not the aggregator's.