From c8795c49493a988d49645852e3cae395cb83d16a Mon Sep 17 00:00:00 2001 From: Stuart Meeks Date: Mon, 21 Sep 2026 06:40:49 +0000 Subject: [PATCH] test: raise Keychain retry budget to stop the macos-15 selection flake RetryHelper defaulted to 20 attempts x 25 ms ~= 500 ms. On a contended macos-15 runner that was too tight for cross-process Keychain visibility: the selection item written by RestoreCredentialAsync was not always visible within it, so RestoreCredentialAsync_PreservesAccountIdAndSelection timed out its IsSelected retry and the following Assert.True(restored.IsSelected) failed intermittently (seen red on #89 and #92, green on re-run and elsewhere). Raise the default budget to 40 x 50 ms ~= 2 s. Retries return as soon as the store is consistent, so a passing run does not pay the ceiling; only a genuine failure now waits the full ~2 s before surfacing. RetryHelperTests pass explicit values and are unaffected. Test-only change. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 9 +++++++++ .../Infrastructure/RetryHelper.cs | 17 +++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1cf597..61f17e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 still bind the keystore to this machine/user, and the security boundary is unchanged (filesystem permissions on the credentials directory). +- **The Keychain test-retry budget is raised so `macos-15` stops flaking.** The + `RetryHelper` default was 20 attempts × 25 ms ≈ 500 ms, which was too tight on + a contended `macos-15` runner: a selection item written by + `RestoreCredentialAsync` was not always visible within it, so + `RestoreCredentialAsync_PreservesAccountIdAndSelection` timed out its + `IsSelected` retry and the assertion failed intermittently. The default is now + 40 × 50 ms ≈ 2 s; retries still return as soon as the store is consistent, so + passing runs are unaffected. Test-only change. + ### Changed - **Keystore format bumped to version 2** (`.keystore` header). A version-1 diff --git a/tests/NextIteration.SpectreConsole.Auth.Tests/Infrastructure/RetryHelper.cs b/tests/NextIteration.SpectreConsole.Auth.Tests/Infrastructure/RetryHelper.cs index ce1aa73..5385bb4 100644 --- a/tests/NextIteration.SpectreConsole.Auth.Tests/Infrastructure/RetryHelper.cs +++ b/tests/NextIteration.SpectreConsole.Auth.Tests/Infrastructure/RetryHelper.cs @@ -18,6 +18,15 @@ namespace NextIteration.SpectreConsole.Auth.Tests.Infrastructure /// final attempt still fails. A persistent error still fails the test with its real /// message; only a transient one is ridden out. /// + /// + /// The default budget is 40 attempts × 50 ms ≈ 2 s. The original 20 × 25 ms ≈ 500 ms + /// proved too tight on a contended macos-15 runner: a selection item written by + /// RestoreCredentialAsync moments earlier was not always visible within it, so + /// the IsSelected retry in RestoreCredentialAsync_PreservesAccountIdAndSelection + /// timed out and the following assertion failed. Retries return + /// as soon as the store is consistent, so a passing run does not pay the ceiling; only a + /// genuine failure waits the full 2 s before surfacing. + /// /// internal static class RetryHelper { @@ -29,8 +38,8 @@ internal static class RetryHelper /// internal static async Task UntilTrueAsync( Func> action, - int maxAttempts = 20, - int delayMs = 25) + int maxAttempts = 40, + int delayMs = 50) { for (var attempt = 1; attempt <= maxAttempts; attempt++) { @@ -69,8 +78,8 @@ internal static async Task UntilTrueAsync( internal static async Task UntilAsync( Func> action, Func predicate, - int maxAttempts = 20, - int delayMs = 25) + int maxAttempts = 40, + int delayMs = 50) { T result = default!; var satisfied = false;