diff --git a/bitchatTests/Nostr/GeoRelayDirectoryTests.swift b/bitchatTests/Nostr/GeoRelayDirectoryTests.swift index 3a9c92ff..92ef6e4f 100644 --- a/bitchatTests/Nostr/GeoRelayDirectoryTests.swift +++ b/bitchatTests/Nostr/GeoRelayDirectoryTests.swift @@ -580,8 +580,16 @@ final class GeoRelayDirectoryTests: XCTestCase { /// constrained CI runners (2-core, serialized testing) can starve the /// detached utility-priority fetch task for seconds before it runs, and /// a successful wait returns as soon as the condition becomes true. + /// Default deliberately far larger than the work being awaited. + /// + /// The directory performs its fetch in a `Task.detached(priority: .utility)`, + /// and utility priority competes with every other suite on a CI runner. At + /// ten seconds the retry-scheduling test timed out at exactly 10.06s with + /// the retry never scheduled — which reads like a missing retry rather than + /// a starved background task. Returning as soon as the condition holds means + /// a longer deadline only extends the genuine-failure case. private func waitUntil( - timeout: TimeInterval = 10.0, + timeout: TimeInterval = 30.0, condition: @escaping @MainActor () async -> Bool ) async -> Bool { let deadline = Date().addingTimeInterval(timeout) diff --git a/bitchatTests/VoiceNotePlaybackControllerTests.swift b/bitchatTests/VoiceNotePlaybackControllerTests.swift index f7853f1d..d6221b15 100644 --- a/bitchatTests/VoiceNotePlaybackControllerTests.swift +++ b/bitchatTests/VoiceNotePlaybackControllerTests.swift @@ -59,11 +59,24 @@ struct VoiceNotePlaybackControllerTests { return url } + /// Waits for an async settle, then asserts. + /// + /// The deadline is deliberately far larger than the work it waits on. Every + /// condition here depends on a `@MainActor` Task that playback schedules + /// (the session acquire and its failure path), and on a CI runner executing + /// many suites in parallel that Task can simply not be scheduled for + /// seconds. At five seconds this timed out on CI and reported *two* + /// failures — the wait itself, and the `!isPlaying` that the un-run failure + /// path had not yet reset — which reads like a playback bug rather than a + /// starved scheduler. + /// + /// A generous deadline costs nothing when the condition holds, since this + /// returns as soon as it does; it only extends the genuine-failure case. private func waitUntil( _ condition: () -> Bool, sourceLocation: SourceLocation = #_sourceLocation ) async { - let deadline = ContinuousClock.now.advanced(by: .seconds(5)) + let deadline = ContinuousClock.now.advanced(by: .seconds(30)) while !condition(), ContinuousClock.now < deadline { await Task.yield() try? await Task.sleep(nanoseconds: 1_000_000)