Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ namespace NextIteration.SpectreConsole.Auth.Tests.Infrastructure
/// final attempt still fails</b>. A persistent error still fails the test with its real
/// message; only a transient one is ridden out.
/// </para>
/// <para>
/// The default budget is 40 attempts × 50 ms ≈ 2 s. The original 20 × 25 ms ≈ 500 ms
/// proved too tight on a contended <c>macos-15</c> runner: a selection item written by
/// <c>RestoreCredentialAsync</c> moments earlier was not always visible within it, so
/// the <c>IsSelected</c> retry in <c>RestoreCredentialAsync_PreservesAccountIdAndSelection</c>
/// 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.
/// </para>
/// </summary>
internal static class RetryHelper
{
Expand All @@ -29,8 +38,8 @@ internal static class RetryHelper
/// </summary>
internal static async Task<bool> UntilTrueAsync(
Func<Task<bool>> action,
int maxAttempts = 20,
int delayMs = 25)
int maxAttempts = 40,
int delayMs = 50)
{
for (var attempt = 1; attempt <= maxAttempts; attempt++)
{
Expand Down Expand Up @@ -69,8 +78,8 @@ internal static async Task<bool> UntilTrueAsync(
internal static async Task<T> UntilAsync<T>(
Func<Task<T>> action,
Func<T, bool> predicate,
int maxAttempts = 20,
int delayMs = 25)
int maxAttempts = 40,
int delayMs = 50)
{
T result = default!;
var satisfied = false;
Expand Down
Loading