test: wait for the retired tunnel start's cleanup before checking it

Merged
AU
17d ago
+13-8

CloudTunnelLaunchGateTests.optOutDuringInstallRemovesLateSavedConfiguration no longer checks the tunnel's cleanup before that cleanup has run. It failed once in the #15488 validation run with controller.calls == ["install", "stop"] where ["install", "stop", "remove"] was expected. The test now waits for the cleanup's last step before it asserts anything.

Cause

The test starts a tunnel whose install waits for approval, turns Cloud Machines off, and then approves late:

  1. requestDown() retires the start that is waiting on approval and publishes .off.
  2. start waits on the state stream, so it throws as soon as .off is published. The retired start hasn't been resumed yet at that point.
  3. controller.approve() resumes the retired start. On the coordinator's actor it calls discard(install:), which always runs controller.remove() and then enroller.discardEnrollment().

The test asserted right after start threw, which races step 3. In shard 4 of run 36713362165, controller.calls had no remove at line 371, yet discardCount == 1 held two lines later. Since remove always comes first, the cleanup ran between those two expectations. No tunnel code, test or fake changed since main's last green full suite, where the test passed.

Change

  • FakeTunnelEnroller gets a discarded signal that resolves at the first discard that actually removes an enrollment.
  • The test waits for that signal before asserting, and .timeLimit(.minutes(1)) bounds the wait.
  • Every assertion is unchanged.

The second half of the test, cmux vpn down with the opt-in still on, has the mirror-image race. There it can only produce a false pass, so it is left alone.

Verification

Both proof runs use app-host-test-rerun.yml with 3 iterations. For the proof only, each one adds a 300 ms delay to the fake controller's remove(), which widens the race without changing the order.

  • Red, test unchanged: run 36725360565 failed every iteration at lines 371–373: controller.calls had no remove, the configuration was still installed and discardCount was 0. The cleanup simply hadn't run yet.
  • Green, with this change: run 36725366119 passed every iteration with the same delay.

Changelog

  • none

🤖 Generated with Claude Code