build.mill instruments Scoverage unconditionally, and Scala 3.3.8 would make that grow without bound #565
Labels
No labels
agent-ready
bug
contract
documentation
duplicate
enhancement
good first issue
help wanted
invalid
needs-human
needs-input
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/iw-cli#565
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
build.millinstruments for Scoverage unconditionally. Three modules extendScoverageTests, soevery local
./iw ./testwrites coverage data:Those files are small and stable today for one reason only: we are on Scala 3.3.7.
The defect this exposes us to
A Scala 3 coverage defect doubles the backslashes in
scoverage.coverageon every incrementalcompile:
InstrumentCoverage.runOnre-reads the previousscoverage.coverageand adds back thestatements of every source file not in the current run (scala/scala3#24773).
Serializer.serializewrites those fields throughescaped, which turns\into\\.Serializer.deserializereads them as they are and never reversesescaped.Each compile therefore adds one escaping level to every carried-forward statement. The growth is
exponential in the number of incremental compiles that do not recompile a file.
3.3.7 does not reproduce it. The carry-forward arrived in 3.8.2, and on the LTS line in
3.3.8. So a routine patch bump arms this.
The full analysis, with a compiler-only reproducer, is in the
proceduresrepository atdocs/scala3-coverage-backslash-growth.md.What it cost in
proceduresscoverage.coveragereached 48 GB once, and 1.3–6.2 GB per module across six of eight worktrees,17.7 GiB in total, with single statement descriptions of 55 MB carrying runs of 2^21 backslashes.
The build dies as
OutOfMemoryError: Java heap spaceinSerializer$.deserializeorSerializer$.escaped— naming neither coverage nor the file — and it dies that way with a 12 GBheap. Deleting the file repairs the build until it grows again.
iw-cli has the same eight-worktree multiplier.
The fix
Port the gate
proceduresalready uses (commitdfa514de, "build: collect Scoverage coverage onlyin CI"): a system property that CI passes and local runs leave unset, so
scoverage.compileyieldsthe module's plain compile result and nothing writes coverage data.
It is worth doing before the next Scala bump rather than after, because the failure names nothing
that points at coverage. It also removes instrumentation from every local test run, which buys us
nothing today.
Acceptance
./mill __.testwrites noscoverage.coverage.build.millnames the defect and the version range, so the next person to bumpScala meets it.
Intake: needs-human
Workflow: waterfall
Reason: G4 the analysis proposes a change to CLAUDE.md (the "Coverage (scoverage)" snippet, Documentation Requirements, analysis.md:325)
Analysis: project-management/issues/IW-565/analysis.md on branch IW-565