Skip to content

Fix flaky preflight tests - #5310

Open
alexleventer wants to merge 1 commit into
masterfrom
fix-flaky-preflight-tests
Open

alexleventer wants to merge 1 commit into
masterfrom
fix-flaky-preflight-tests

Conversation

@alexleventer

Copy link
Copy Markdown
Contributor

Change Summary

What and Why:

About 40% of scheduled Build runs on master fail in the preflight jobs, often alternating pass/fail on the same commit. I classified all 258 test failures across the 145 failed scheduled runs since 2026-09-23. This PR fixes the ones caused by the tests themselves (~50 failures):

Test Failures Cause
TestFlyLaunchWithBuildSecrets 17 The retry loop called f.Fly, so the first failed ssh console attempt failed the test. The 10s window was also shorter than one attempt.
TestDeployDetach (Simple, Batching) ~14 Launch's default auto-stop let the proxy stop the idle machines. Deploy leaves stopped machines stopped (shouldSkipLaunch), so they never reached started.
TestFlyDeploy_DeployMachinesCheck, NoServiceDeployMachinesCheck 2 They asserted on Test Machine <id>: <state>, which is skipped once the test machine has been destroyed.
TestFlyWireguardCreate ~3 Same retry bug as build secrets, with a 10s window.
App cleanup in every test 7 A hung apps destroy failed tests that had passed and could block for 5 minutes against the 15m go test timeout.

How:

  • New FlyBestEffort(timeout, cmd) testlib helper. It reports failures through ExitCode() and timeouts through TimedOut() instead of failing the test, so it's safe in EventuallyWithT loops and cleanup. Commands that assert success still fail on a timeout.
  • Build-secrets and wireguard retries use it with a per-attempt timeout over a longer window. Build secrets also launches with --auto-stop off.
  • TestDeployDetach launches with --auto-stop off and waits up to 60s.
  • The machine-check tests assert on Test machine <id> completed successfully, which is always printed on success.
  • App cleanup uses FlyBestEffort with a 1-minute timeout, logs failures, and leaves leftover apps to preflight_cleanup.yml.
  • cmdHistory is guarded by a mutex, since a retry attempt still running when EventuallyWithT gives up can append after the test returns.

Not fixed here: about 200 failures come from platform or infra errors. The largest group (~75) is app not found / failed to get app: sql: no rows, volume not found and machine not found returned right after the resource was created. The rest are 502s from docker-hub-mirror.fly.io, 408/5xx errors, IP allocation failures and commands that hang past 5 minutes. These happen inside launch/deploy/scale, so users hit them too, and retrying in the tests would hide them. The bluegreen canPerformBluegreenDeployment 504s (19) were already fixed by #5292.

Verified with go vet and with throwaway testlib tests against a fake flyctl binary under -race: the timeout, exit-code, assert-on-timeout and concurrent-history cases all pass. The preflight run on this PR is the real check.

Related to:


Documentation

  • Fresh Produce
  • In superfly/docs, or asked for help from docs team
  • n/a

🤖 Generated with Claude Code

Fix the preflight failures caused by the tests themselves, found by
classifying 258 failures across 145 failed scheduled Build runs since
2026-09-23:

- TestFlyLaunchWithBuildSecrets, TestFlyWireguardCreate: the retry loop
  called f.Fly, so the first failed attempt failed the test, and the 10s
  window was shorter than a single ssh/exec attempt. Retry with
  FlyBestEffort and a per-attempt timeout over a longer window.
- TestDeployDetach: launch's default auto-stop let the proxy stop the idle
  machines, and deploy leaves stopped machines stopped, so they never
  reached "started". Launch with --auto-stop off and wait up to 60s.
- TestFlyDeploy_DeployMachinesCheck, NoServiceDeployMachinesCheck: the
  "Test Machine <id>: <state>" line is skipped once the test machine is
  destroyed. Assert on the "completed successfully" line instead.
- App cleanup: a hung `apps destroy` failed tests that had passed and could
  block for 5 minutes. Give it a minute and leave stragglers to the
  preflight cleanup workflow.

Adds FlyBestEffort, which reports failures and timeouts via
ExitCode/TimedOut instead of failing the test, and guards cmdHistory with a
mutex since retry goroutines can outlive EventuallyWithT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f.FlyAllowExitFailure("apps destroy --yes %s", appName)
// A slow destroy shouldn't fail a test that already passed, or hold up
// the suite. Apps left behind are removed by the preflight cleanup workflow.
res := f.FlyBestEffort(time.Minute, "apps destroy --yes %s", appName)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure about this one. A destroy that takes a long time is a bug and we should fix it instead of ignoring it.

// The backend is eventually consistent. The peer may not be immediately visible.
assert.EventuallyWithT(t, func(t *assert.CollectT) {
result := f.Fly("machine exec -a %s %s 'dig +short aaaa @fdaa::3 %s._peer.internal'", appName, machineID, peerName)
// A failed or hung exec must be retried rather than failing the test.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would help to log the number and duration of each retry; here and in the other EventuallyWithT.

ssh := f.Fly("ssh console -C 'cat /tmp/secrets.txt'")
assert.Equal(c, "SECRET1", ssh.StdOut().String())
}, 10*time.Second, 1*time.Second)
// The first attempt may need to start the agent and open a WireGuard

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it looks like the original EventuallyWithT was already very generous, time-wise. I'm a bit worried that there are bugs hiding here and by making the tests even more generous, we're simply ignoring them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants