Files
werkcrappit/docs/adr/0001-pit-integration.md
miandmhoennig aea74c2c6f
werkator build succeeded after 00:34
Step 3 of 8: method inventory and reliable bytecode access (#4)
Step 3 of 8 from section 8.3 of the specification.

- Classes are read only from PIT's code roots (PIT's own code-path rule, never dependency JARs); differing copies are ambiguous, a missing class makes the scope incomplete.
- JaCoCo's analysis is matched against the methods the class file declares (ASM): methods JaCoCo filtered are EXCLUDED with proof (`jacoco-filtered:bridge|synthetic|generated-annotation|other`), abstract/native methods get no complexity.
- PIT's `excludedMethods` → EXCLUDED with `pit-excluded-method:<glob>`.
- Every method of the selected classes appears in the provisional report (schemaVersion 0) with a score or a reason.
- ADR 0002, feature 03, MC-008 to MC-012 mapped; MC-013 open.

CI on werkator.javagil.de: green (52 s), 66 unit and 8 functional tests.

PR-doc: `docs/prs/2026-10-01-PR#4-method-inventory.md`

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

4.3 KiB

ADR 0001: PIT Integration

  • Status: accepted; the class lookup is refined by ADR 0002
  • Date: 2026-10-01
  • PR: #2

Context

werkcrappit joins PIT's mutation results with the JaCoCo complexity of the same JVM method. The specification demands the results from PIT's listener interface, the complexity from the original class files, and no extra test or PIT run. Section 3.4 of the specification warns that ListenerArguments.getLocator() yields source readers, not bytecode. So the first step had to prove where a result listener gets the original class bytes from.

Decision

werkcrappit is a PIT result listener plugin: one JAR on PIT's tool classpath, loaded through the Gradle PIT plugin's pitest configuration.

  • Registration: MutationCrapListenerFactory is listed in META-INF/services/org.pitest.mutationtest.MutationResultListenerFactory and registers the output format WERKCRAPPIT_MUTATION_CRAP. The specification starts with MUTATION_CRAP (section 2.3). The name is changed so that an outputFormats line shows which plugin provides the format, and so that no other plugin on PIT's tool classpath can claim it by chance. The metric id mutation-crap and the report file mutation-crap.json stay as specified.
  • Mutation results: the listener collects every ClassMutationResults block and evaluates nothing before runEnd(), since a class may arrive in several blocks.
  • Method identity: MutationDetails.getId().getLocation() carries class, JVM method name and full descriptor. The class name is normalized to the dotted binary name in MethodId; JaCoCo's slashed name is converted in the JaCoCo adapter.
  • Original bytecode: ListenerArguments.data() returns PIT's ReportOptions, a public type. getMutationClassPaths().code() names the classes PIT selected for mutation; getClassPathElements() gives the classpath in PIT's order. werkcrappit reads each class file from the first element that holds it, records that element as origin, and hashes the bytes with SHA-256. This is option 2 of section 3.4: data PIT was started with, no private fields, no reflection, no class loading. A dedicated Gradle hand-over (option 3) is not needed.
  • Why the bytes are original: PIT mutates in memory inside its minion processes; the class files on disk stay as the build compiled them.
  • Complexity: JaCoCo Core's Analyzer with an empty ExecutionDataStore and a CoverageBuilder; per method the total of getComplexityCounter().
  • Report: written through ListenerArguments.getOutputStrategy(), so it lands in PIT's report directory next to the HTML and XML reports.
  • Dependencies: pitest-entry is compileOnly, since PIT provides it at run time. org.jacoco.core is a regular dependency and reaches PIT's tool classpath through the published metadata. PIT 1.30.0 and JaCoCo 0.8.15 both use ASM 9.10.1, so no shading is needed.

Verification

PitWalkingSkeletonTest (source set functionalTest) publishes the JAR into a repository under build/, then runs pitest with Gradle TestKit on the fixture fixtures/java-basic. The fixture consumes the published artifact, not the classes of this build. The test proves the five points of section 3.3:

  1. PIT finds the factory and the report exists.
  2. The results arrive with class, method and descriptor and are joined with complexity 3 for classify(I)Ljava/lang/String;.
  3. The analysed bytes have the SHA-256 of build/classes/java/main/demo/Classifier.class.
  4. No result stays unmatched, no class is missing.
  5. mutations.xml and index.html exist; the test task did not run; no .exec file exists.

The tested versions are in docs/compatibility.md.

Risks

  • PIT does not promise its extension interfaces across versions; ReportOptions is public but not documented as plugin API. An upgrade of PIT needs the functional test to pass before it is pinned.
  • getClassPathElements() includes dependency JARs. A selected class that also exists in an earlier JAR would be read from that JAR; the recorded origin makes that visible. Step 3 decides whether to restrict the lookup to PIT's code paths.
  • Duplicate and contradicting results are not handled yet (step 4).
  • The report has schemaVersion 0 and no score; step 2 adds the metric, step 5 the versioned contract.