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
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:
MutationCrapListenerFactoryis listed inMETA-INF/services/org.pitest.mutationtest.MutationResultListenerFactoryand registers the output formatWERKCRAPPIT_MUTATION_CRAP. The specification starts withMUTATION_CRAP(section 2.3). The name is changed so that anoutputFormatsline shows which plugin provides the format, and so that no other plugin on PIT's tool classpath can claim it by chance. The metric idmutation-crapand the report filemutation-crap.jsonstay as specified. - Mutation results: the listener collects every
ClassMutationResultsblock and evaluates nothing beforerunEnd(), 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 inMethodId; JaCoCo's slashed name is converted in the JaCoCo adapter. - Original bytecode:
ListenerArguments.data()returns PIT'sReportOptions, 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
Analyzerwith an emptyExecutionDataStoreand aCoverageBuilder; per method the total ofgetComplexityCounter(). - Report: written through
ListenerArguments.getOutputStrategy(), so it lands in PIT's report directory next to the HTML and XML reports. - Dependencies:
pitest-entryiscompileOnly, since PIT provides it at run time.org.jacoco.coreis 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:
- PIT finds the factory and the report exists.
- The results arrive with class, method and descriptor and are joined with complexity 3 for
classify(I)Ljava/lang/String;. - The analysed bytes have the SHA-256 of
build/classes/java/main/demo/Classifier.class. - No result stays unmatched, no class is missing.
mutations.xmlandindex.htmlexist; thetesttask did not run; no.execfile exists.
The tested versions are in docs/compatibility.md.
Risks
- PIT does not promise its extension interfaces across versions;
ReportOptionsis 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
schemaVersion0 and no score; step 2 adds the metric, step 5 the versioned contract.