mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 15:25:20 +00:00
Stabilize VoiceRecorder async gate tests
This commit is contained in:
@@ -13,17 +13,16 @@ import Testing
|
||||
private final class VoiceRecorderTestSession: SessionApplying, @unchecked Sendable {
|
||||
private let lock = NSLock()
|
||||
private let activationGate = DispatchSemaphore(value: 0)
|
||||
private let activationBeganGate = DispatchSemaphore(value: 0)
|
||||
private let shouldGateFirstActivation: Bool
|
||||
private var gatedFirstActivation = false
|
||||
private var _activationCalls: [Bool] = []
|
||||
private var _activationBegan = false
|
||||
|
||||
init(gateFirstActivation: Bool = false) {
|
||||
self.shouldGateFirstActivation = gateFirstActivation
|
||||
}
|
||||
|
||||
var activationCalls: [Bool] { lock.withLock { _activationCalls } }
|
||||
var activationBegan: Bool { lock.withLock { _activationBegan } }
|
||||
|
||||
func setCategory(_ category: AudioSessionCoordinator.Category) throws {}
|
||||
|
||||
@@ -32,14 +31,28 @@ private final class VoiceRecorderTestSession: SessionApplying, @unchecked Sendab
|
||||
_activationCalls.append(active)
|
||||
guard active, shouldGateFirstActivation, !gatedFirstActivation else { return false }
|
||||
gatedFirstActivation = true
|
||||
_activationBegan = true
|
||||
return true
|
||||
}
|
||||
if shouldWait {
|
||||
activationBeganGate.signal()
|
||||
activationGate.wait()
|
||||
}
|
||||
}
|
||||
|
||||
func waitUntilActivationBegan(
|
||||
timeout: DispatchTimeInterval = .seconds(5)
|
||||
) async -> Bool {
|
||||
await withCheckedContinuation { continuation in
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
continuation.resume(
|
||||
returning: self.activationBeganGate.wait(
|
||||
timeout: DispatchTime.now() + timeout
|
||||
) == .success
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resumeActivation() {
|
||||
activationGate.signal()
|
||||
}
|
||||
@@ -142,26 +155,38 @@ private final class TestVoiceAudioRecorderFactory: VoiceAudioRecorderCreating {
|
||||
/// this remains deterministic when the full test suite saturates the executor.
|
||||
private final class VoiceRecorderPaddingGate: @unchecked Sendable {
|
||||
private let lock = NSLock()
|
||||
private var _entered = false
|
||||
private let enteredGate = DispatchSemaphore(value: 0)
|
||||
private var isOpen = false
|
||||
private var openWaiters: [CheckedContinuation<Void, Never>] = []
|
||||
|
||||
var entered: Bool { lock.withLock { _entered } }
|
||||
|
||||
func wait() async {
|
||||
await withCheckedContinuation { continuation in
|
||||
let resumeImmediately = lock.withLock { () -> Bool in
|
||||
_entered = true
|
||||
guard !isOpen else { return true }
|
||||
openWaiters.append(continuation)
|
||||
return false
|
||||
}
|
||||
enteredGate.signal()
|
||||
if resumeImmediately {
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitUntilEntered(
|
||||
timeout: DispatchTimeInterval = .seconds(5)
|
||||
) async -> Bool {
|
||||
await withCheckedContinuation { continuation in
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
continuation.resume(
|
||||
returning: self.enteredGate.wait(
|
||||
timeout: DispatchTime.now() + timeout
|
||||
) == .success
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func open() {
|
||||
let waiters = lock.withLock { () -> [CheckedContinuation<Void, Never>] in
|
||||
isOpen = true
|
||||
@@ -181,18 +206,6 @@ struct VoiceRecorderTests {
|
||||
return url
|
||||
}
|
||||
|
||||
private func waitUntil(
|
||||
_ condition: () -> Bool,
|
||||
sourceLocation: SourceLocation = #_sourceLocation
|
||||
) async {
|
||||
let deadline = ContinuousClock.now.advanced(by: .seconds(5))
|
||||
while !condition(), ContinuousClock.now < deadline {
|
||||
await Task.yield()
|
||||
try? await Task.sleep(nanoseconds: 1_000_000)
|
||||
}
|
||||
#expect(condition(), sourceLocation: sourceLocation)
|
||||
}
|
||||
|
||||
@Test func cancelWhileSessionAcquireIsInFlightNeverCreatesARecorder() async throws {
|
||||
let directory = try makeTemporaryDirectory()
|
||||
defer { try? FileManager.default.removeItem(at: directory) }
|
||||
@@ -210,7 +223,7 @@ struct VoiceRecorderTests {
|
||||
let owner = VoiceRecorder.RecordingOwner()
|
||||
|
||||
let startTask = Task { try await voiceRecorder.startRecording(owner: owner) }
|
||||
await waitUntil { session.activationBegan }
|
||||
#expect(await session.waitUntilActivationBegan())
|
||||
|
||||
await voiceRecorder.cancelRecording(owner: owner)
|
||||
session.resumeActivation()
|
||||
@@ -308,7 +321,7 @@ struct VoiceRecorderTests {
|
||||
try await finishingHold.start()
|
||||
let firstURL = try #require(factory.urls.first)
|
||||
let finishTask = Task { await finishingHold.finish() }
|
||||
await waitUntil { paddingGate.entered }
|
||||
#expect(await paddingGate.waitUntilEntered())
|
||||
|
||||
await #expect(throws: VoiceRecorder.RecorderError.recordingInProgress) {
|
||||
try await rejectedHold.start()
|
||||
|
||||
Reference in New Issue
Block a user