A harness gate test whose head sha matches no CI run spins forever instead of failing #508

Closed
opened 2026-09-03 05:44:19 +00:00 by mph · 0 comments
Owner

A PhasePrHarnessTest case that reaches CiGate.poll with a forge head sha matching no run in the scripted CiSnapshot does not fail. It spins in CiGate.loop, heartbeating the card through ReviewStateWrite.tryUpdate on every turn, and never stops.

Observed 2026-09-03 while writing the tests for #467: twenty minutes of a test JVM at ~190% CPU, killed by hand. jstack put main in ReviewStateValidator.validate under CiGate.heartbeat -> CiGate.loop -> PhasePr.gate.

Root cause

The real gate is bounded by a wall-clock budget. FakeClock does not advance on its own, so in a harness test that budget never expires. The gate is right; the fixture gives a mistyped test no way to end, and munit's per-test timeout does not fire because the test is making progress, not blocking.

Why it matters

The failure mode is indistinguishable from a hung build: no assertion, no output, no exit. It costs whoever hits it the time to find a JVM and read its stack before they learn they scripted a sha wrong.

Options

  • Advance FakeClock on each fetchCiSnapshot call, so a wait in a harness test ends the way a real one does.
  • Cap the poll count in the fake and fail the test naming the sha that matched nothing.

The first keeps the fake honest about the passage of time and needs no new concept. Either way the fix belongs in core/test/fixtures/FakeCommandEnv.scala, not in CiGate.

A `PhasePrHarnessTest` case that reaches `CiGate.poll` with a forge head sha matching no run in the scripted `CiSnapshot` does not fail. It spins in `CiGate.loop`, heartbeating the card through `ReviewStateWrite.tryUpdate` on every turn, and never stops. Observed 2026-09-03 while writing the tests for #467: twenty minutes of a test JVM at ~190% CPU, killed by hand. `jstack` put `main` in `ReviewStateValidator.validate` under `CiGate.heartbeat` -> `CiGate.loop` -> `PhasePr.gate`. ## Root cause The real gate is bounded by a wall-clock budget. `FakeClock` does not advance on its own, so in a harness test that budget never expires. The gate is right; the fixture gives a mistyped test no way to end, and munit's per-test timeout does not fire because the test is making progress, not blocking. ## Why it matters The failure mode is indistinguishable from a hung build: no assertion, no output, no exit. It costs whoever hits it the time to find a JVM and read its stack before they learn they scripted a sha wrong. ## Options - Advance `FakeClock` on each `fetchCiSnapshot` call, so a wait in a harness test ends the way a real one does. - Cap the poll count in the fake and fail the test naming the sha that matched nothing. The first keeps the fake honest about the passage of time and needs no new concept. Either way the fix belongs in `core/test/fixtures/FakeCommandEnv.scala`, not in `CiGate`.
mph closed this issue 2026-09-03 09:21:31 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
iterative-works/iw-cli#508
No description provided.