Files
2026-07-13 12:22:33 +08:00

827 lines
28 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCLI
@testable import CodexBarCore
struct CLIServeTimeoutTests {
@Test
func `serve cost keeps pricing refresh outside the request deadline`() {
#expect(CodexBarCLI.serveCostRefreshesPricingInBackground)
}
@Test
func `serve deadlines clamp once from request entry`() throws {
#expect(CodexBarCLI.clampedServeRequestTimeout(.greatestFiniteMagnitude) == 86400)
#expect(CodexBarCLI.clampedServeRequestTimeout(1e308) == 86400)
#expect(CodexBarCLI.clampedServeRequestTimeout(-5) == 0)
let startedAt = ContinuousClock().now
let deadline = try #require(CodexBarCLI.serveRequestDeadline(
startedAt: startedAt,
requestTimeout: .greatestFiniteMagnitude))
#expect(startedAt.duration(to: deadline) == .seconds(86400))
#expect(CodexBarCLI.serveRequestDeadline(startedAt: startedAt, requestTimeout: 0) == nil)
let requestDeadline = startedAt.advanced(by: .seconds(40))
#expect(CodexBarCLI.serveCostProviderDeadline(
startedAt: startedAt,
providerTimeout: 30,
requestDeadline: requestDeadline) == startedAt.advanced(by: .seconds(30)))
#expect(CodexBarCLI.serveCostProviderDeadline(
startedAt: startedAt.advanced(by: .seconds(20)),
providerTimeout: 30,
requestDeadline: requestDeadline) == requestDeadline)
#expect(CodexBarCLI.serveCostProviderDeadline(
startedAt: startedAt,
providerTimeout: nil,
requestDeadline: nil) == nil)
}
@Test
func `timed out source stays owned and later requests never overlap`() async {
let clock = ServeManualDeadlineClock()
let gate = ServeFetchGate<Int>()
let coordinator: CLIServeOperationCoordinator<Int> = self.makeCoordinator(clock: clock)
let deadline = clock.now().advanced(by: .seconds(30))
let first = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: deadline,
timeoutValue: -1)
{
await gate.run(1)
}
}
await gate.waitForStarts(1)
await clock.waitForPendingSleeps(1)
await clock.fireAll()
#expect(await first.value == -1)
#expect(await coordinator.snapshot().operationCount == 1)
#expect(await coordinator.snapshot().timerCount == 0)
let later = (0..<4).map { _ in
Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: deadline.advanced(by: .seconds(30)),
timeoutValue: -1)
{
await gate.run(2)
}
}
}
await self.waitForOperationCount(2, coordinator: coordinator)
await clock.waitForPendingSleeps(1)
await clock.fireAll()
for task in later {
#expect(await task.value == -1)
}
#expect(await gate.startCount() == 1)
#expect(await gate.peakCount() == 1)
await gate.releaseAll()
await gate.waitForActive(0)
await self.waitForOperationCount(0, coordinator: coordinator)
}
@Test
func `earlier follower tightens the shared absolute budget`() async {
let clock = ServeManualDeadlineClock()
let gate = ServeFetchGate<Int>()
let coordinator: CLIServeOperationCoordinator<Int> = self.makeCoordinator(clock: clock)
let firstDeadline = clock.now().advanced(by: .seconds(30))
let first = Task {
await coordinator.value(
for: "cost:",
fingerprint: "config-a",
deadline: firstDeadline,
timeoutValue: -1)
{
await gate.run(1)
}
}
await gate.waitForStarts(1)
await clock.waitForPendingSleeps(1)
let shorterFollower = Task {
await coordinator.value(
for: "cost:",
fingerprint: "config-a",
deadline: firstDeadline.advanced(by: .seconds(-1)),
timeoutValue: -2)
{
await gate.run(2)
}
}
await self.waitForWaiterCount(2, coordinator: coordinator)
await clock.waitForCancellations(1)
await clock.waitForPendingSleeps(1)
clock.advance(by: .seconds(29))
await clock.fireAll()
#expect(await first.value == -1)
#expect(await shorterFollower.value == -2)
#expect(await gate.startCount() == 1)
await gate.releaseAll()
await gate.waitForActive(0)
await self.waitForOperationCount(0, coordinator: coordinator)
#expect(await coordinator.snapshot().operationCount == 0)
}
@Test
func `source completing at an overdue deadline cannot beat a delayed timer`() async {
let clock = ServeManualDeadlineClock()
let gate = ServeFetchGate<Int>()
let acceptance = ServeAcceptanceProbe<Int>()
let coordinator: CLIServeOperationCoordinator<Int> = self.makeCoordinator(clock: clock)
let result = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: clock.now().advanced(by: .seconds(30)),
timeoutValue: -1,
accept: { await acceptance.accept($0) },
operation: { await gate.run(7) })
}
await gate.waitForStarts(1)
await clock.waitForPendingSleeps(1)
clock.advance(by: .seconds(30))
await gate.releaseAll()
#expect(await result.value == -1)
#expect(await acceptance.callCount() == 0)
await clock.waitForCancellations(1)
await self.waitForOperationCount(0, coordinator: coordinator)
}
@Test
func `shared deadline returns each waiters own timeout value`() async {
let clock = ServeManualDeadlineClock()
let gate = ServeFetchGate<Int>()
let coordinator: CLIServeOperationCoordinator<Int> = self.makeCoordinator(clock: clock)
let deadline = clock.now().advanced(by: .seconds(30))
let leader = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: deadline,
timeoutValue: -1)
{
await gate.run(1)
}
}
await gate.waitForStarts(1)
await clock.waitForPendingSleeps(1)
let follower = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: deadline.advanced(by: .seconds(1)),
timeoutValue: -2)
{
await gate.run(2)
}
}
await self.waitForWaiterCount(2, coordinator: coordinator)
await clock.fireAll()
#expect(await leader.value == -1)
#expect(await follower.value == -2)
await gate.releaseAll()
await gate.waitForActive(0)
await self.waitForOperationCount(0, coordinator: coordinator)
}
@Test
func `finite follower fails closed behind deadline free source`() async {
let gate = ServeFetchGate<Int>()
let coordinator = CLIServeOperationCoordinator<Int>()
let first = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: nil,
timeoutValue: -1)
{
await gate.run(1)
}
}
await gate.waitForStarts(1)
let follower = await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: ContinuousClock().now.advanced(by: .seconds(30)),
timeoutValue: -2)
{
await gate.run(2)
}
#expect(follower == -2)
#expect(await gate.startCount() == 1)
await gate.releaseAll()
#expect(await first.value == 1)
}
@Test
func `waiter cancellation unregisters and last waiter cancels source`() async {
let clock = ServeManualDeadlineClock()
let gate = ServeFetchGate<Int>()
let coordinator: CLIServeOperationCoordinator<Int> = self.makeCoordinator(clock: clock)
let deadline = clock.now().advanced(by: .seconds(30))
let leader = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: deadline,
timeoutValue: -1)
{
await gate.run(1)
}
}
await gate.waitForStarts(1)
await clock.waitForPendingSleeps(1)
let follower = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: deadline.advanced(by: .seconds(1)),
timeoutValue: -2)
{
await gate.run(2)
}
}
await self.waitForWaiterCount(2, coordinator: coordinator)
follower.cancel()
#expect(await follower.value == -2)
await self.waitForWaiterCount(1, coordinator: coordinator)
#expect(await coordinator.snapshot().operationCount == 1)
leader.cancel()
#expect(await leader.value == -1)
await clock.waitForCancellations(1)
let retained = await coordinator.snapshot()
#expect(retained.operationCount == 1)
#expect(retained.waiterCount == 0)
#expect(retained.timerCount == 0)
await gate.releaseAll()
await gate.waitForActive(0)
await self.waitForOperationCount(0, coordinator: coordinator)
}
@Test
func `source completion cancels the operation timer`() async {
let clock = ServeManualDeadlineClock()
let gate = ServeFetchGate<Int>()
let coordinator: CLIServeOperationCoordinator<Int> = self.makeCoordinator(clock: clock)
let result = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: clock.now().advanced(by: .seconds(30)),
timeoutValue: -1)
{
await gate.run(7)
}
}
await gate.waitForStarts(1)
await clock.waitForPendingSleeps(1)
await gate.releaseAll()
#expect(await result.value == 7)
await clock.waitForCancellations(1)
#expect(await clock.pendingSleepCount() == 0)
#expect(await coordinator.snapshot() == .init(
operationCount: 0,
waiterCount: 0,
timerCount: 0,
isShutDown: false))
}
@Test
func `accepted value stays owned through asynchronous commit`() async {
let source = ServeFetchGate<Int>()
let commit = ServeFetchGate<Int>()
let coordinator = CLIServeOperationCoordinator<Int>()
await source.releaseAll()
let first = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: nil,
timeoutValue: -1,
accept: { await commit.run($0) },
operation: { await source.run(1) })
}
await commit.waitForStarts(1)
let follower = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: nil,
timeoutValue: -2,
accept: { await commit.run($0) },
operation: { await source.run(2) })
}
await self.waitForWaiterCount(2, coordinator: coordinator)
#expect(await source.startCount() == 1)
#expect(await coordinator.snapshot().operationCount == 1)
await commit.releaseAll()
#expect(await first.value == 1)
#expect(await follower.value == 1)
#expect(await source.startCount() == 1)
await self.waitForOperationCount(0, coordinator: coordinator)
}
@Test
func `earlier finite follower fails closed during accepted commit`() async {
let source = ServeFetchGate<Int>()
let commit = ServeFetchGate<Int>()
let coordinator = CLIServeOperationCoordinator<Int>()
let leaderDeadline = ContinuousClock().now.advanced(by: .seconds(30))
await source.releaseAll()
let leader = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: leaderDeadline,
timeoutValue: -1,
accept: { await commit.run($0) },
operation: { await source.run(1) })
}
await commit.waitForStarts(1)
let follower = await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: leaderDeadline.advanced(by: .seconds(-1)),
timeoutValue: -2)
{
await source.run(2)
}
#expect(follower == -2)
#expect(await source.startCount() == 1)
let accepting = await coordinator.snapshot()
#expect(accepting.waiterCount == 1)
#expect(accepting.timerCount == 0)
await commit.releaseAll()
#expect(await leader.value == 1)
await self.waitForOperationCount(0, coordinator: coordinator)
}
@Test
func `config change queues a nonoverlapping successor without a deadline`() async {
let gate = ServeFetchGate<Int>()
let coordinator = CLIServeOperationCoordinator<Int>()
let old = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: nil,
timeoutValue: -1)
{
await gate.run(1)
}
}
await gate.waitForStarts(1)
let successor = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-b",
deadline: nil,
timeoutValue: -2)
{
await gate.run(2)
}
}
await self.waitForOperationCount(2, coordinator: coordinator)
#expect(await gate.startCount() == 1)
#expect(await gate.peakCount() == 1)
await gate.releaseAll()
#expect(await old.value == 1)
#expect(await successor.value == 2)
await gate.waitForActive(0)
await self.waitForOperationCount(0, coordinator: coordinator)
#expect(await gate.startCount() == 2)
#expect(await gate.peakCount() == 1)
}
@Test
func `shutdown cancels owned work and rejects new operations`() async {
let clock = ServeManualDeadlineClock()
let gate = ServeFetchGate<Int>()
let coordinator: CLIServeOperationCoordinator<Int> = self.makeCoordinator(clock: clock)
let active = Task {
await coordinator.value(
for: "usage:",
fingerprint: "config-a",
deadline: clock.now().advanced(by: .seconds(30)),
timeoutValue: -1)
{
await gate.run(1)
}
}
await gate.waitForStarts(1)
await clock.waitForPendingSleeps(1)
await coordinator.shutdown()
#expect(await active.value == -1)
await clock.waitForCancellations(1)
let rejected = await coordinator.value(
for: "cost:",
fingerprint: "config-a",
deadline: nil,
timeoutValue: -2)
{
2
}
#expect(rejected == -2)
let retained = await coordinator.snapshot()
#expect(retained.operationCount == 1)
#expect(retained.isShutDown)
await gate.releaseAll()
await gate.waitForActive(0)
await self.waitForOperationCount(0, coordinator: coordinator)
}
@Test
func `provider timeout preserves healthy rows and cannot stack provider work`() async {
let clock = ServeManualDeadlineClock()
let blocked = ServeFetchGate<UsageCommandOutput>()
let healthy = ServeFetchGate<UsageCommandOutput>()
let operations: CLIServeOperationCoordinator<UsageCommandOutput> = self.makeCoordinator(clock: clock)
let deadline = clock.now().advanced(by: .seconds(30))
await healthy.releaseAll()
let first = Task {
await CodexBarCLI.serveCollectUsageOutputs(
providers: [.claude, .gemini],
configFingerprint: "config-a",
deadline: deadline,
operations: operations)
{ provider in
if provider == .claude {
return await blocked.run(UsageCommandOutput(sections: ["late:claude"]))
}
return await healthy.run(UsageCommandOutput(sections: ["ok:gemini"]))
}
}
await blocked.waitForStarts(1)
await healthy.waitForStarts(1)
await self.waitForOperationCount(1, coordinator: operations)
await clock.waitForPendingSleeps(1)
clock.advance(by: .seconds(30))
await clock.fireAll()
let firstOutput = await first.value
#expect(firstOutput.sections == ["ok:gemini"])
#expect(firstOutput.payload.count == 1)
#expect(firstOutput.payload.first?.provider == UsageProvider.claude.rawValue)
#expect(firstOutput.payload.first?.error?.kind == .provider)
let second = Task {
await CodexBarCLI.serveCollectUsageOutputs(
providers: [.claude],
configFingerprint: "config-a",
deadline: deadline.advanced(by: .seconds(30)),
operations: operations)
{ _ in
await blocked.run(UsageCommandOutput(sections: ["overlap"]))
}
}
await self.waitForOperationCount(2, coordinator: operations)
await clock.waitForPendingSleeps(1)
clock.advance(by: .seconds(30))
await clock.fireAll()
let secondOutput = await second.value
#expect(secondOutput.payload.first?.error?.kind == .provider)
#expect(await blocked.startCount() == 1)
#expect(await blocked.peakCount() == 1)
await blocked.releaseAll()
await blocked.waitForActive(0)
await self.waitForOperationCount(0, coordinator: operations)
}
@Test
func `cost route variants cannot stack the same provider scan`() async {
let clock = ServeManualDeadlineClock()
let late = CodexBarCLI.makeCostPayload(provider: .claude, snapshot: nil, error: nil)
let blocked = ServeFetchGate<CostPayload>()
let operations: CLIServeOperationCoordinator<CostPayload> = self.makeCoordinator(clock: clock)
let requestDeadline = clock.now().advanced(by: .seconds(40))
let firstContext = ServeCostCollectionContext(
configFingerprint: "config-a",
providerTimeout: 30,
requestDeadline: requestDeadline,
now: { clock.now() },
providerOperations: operations)
let first = Task {
await CodexBarCLI.serveCollectCostPayloads(
providers: [.claude, .codex],
context: firstContext)
{ provider in
if provider == .claude {
return await blocked.run(late)
}
return CodexBarCLI.makeCostPayload(provider: provider, snapshot: nil, error: nil)
}
}
await blocked.waitForStarts(1)
await clock.waitForPendingSleeps(1)
clock.advance(by: .seconds(30))
await clock.fireAll()
let firstPayload = await first.value
#expect(firstPayload.map(\.provider) == ["claude", "codex"])
#expect(firstPayload[0].error?.message == "claude cost refresh timed out")
#expect(firstPayload[1].error == nil)
let overlappingContext = ServeCostCollectionContext(
configFingerprint: "config-a",
providerTimeout: 30,
requestDeadline: requestDeadline.advanced(by: .seconds(20)),
now: { clock.now() },
providerOperations: operations)
let overlappingVariant = Task {
await CodexBarCLI.serveCollectCostPayloads(
providers: [.claude],
context: overlappingContext)
{ _ in
await blocked.run(late)
}
}
await self.waitForOperationCount(2, coordinator: operations)
await clock.waitForPendingSleeps(1)
clock.advance(by: .seconds(30))
await clock.fireAll()
let secondPayload = await overlappingVariant.value
#expect(secondPayload.first?.error?.message == "claude cost refresh timed out")
#expect(await blocked.startCount() == 1)
#expect(await blocked.peakCount() == 1)
await blocked.releaseAll()
await blocked.waitForActive(0)
await self.waitForOperationCount(0, coordinator: operations)
}
private func makeCoordinator<Value: Sendable>(
clock: ServeManualDeadlineClock) -> CLIServeOperationCoordinator<Value>
{
CLIServeOperationCoordinator(
now: { clock.now() },
sleepUntil: { deadline in try await clock.sleep(until: deadline) })
}
private func waitForOperationCount(
_ expected: Int,
coordinator: CLIServeOperationCoordinator<some Sendable>) async
{
for _ in 0..<1000 {
if await coordinator.snapshot().operationCount == expected {
return
}
await Task.yield()
}
Issue.record("operation count did not reach \(expected)")
}
private func waitForWaiterCount(
_ expected: Int,
coordinator: CLIServeOperationCoordinator<some Sendable>) async
{
for _ in 0..<1000 {
if await coordinator.snapshot().waiterCount == expected {
return
}
await Task.yield()
}
Issue.record("waiter count did not reach \(expected)")
}
}
private actor ServeAcceptanceProbe<Value: Sendable> {
private var calls = 0
func accept(_ value: Value) -> Value {
self.calls += 1
return value
}
func callCount() -> Int {
self.calls
}
}
private actor ServeFetchGate<Value: Sendable> {
private var starts = 0
private var active = 0
private var peak = 0
private var released = false
private var releaseContinuations: [CheckedContinuation<Void, Never>] = []
private var startWaiters: [(Int, CheckedContinuation<Void, Never>)] = []
private var activeWaiters: [(Int, CheckedContinuation<Void, Never>)] = []
func run(_ value: Value) async -> Value {
self.starts += 1
self.active += 1
self.peak = max(self.peak, self.active)
self.resumeStartWaiters()
if !self.released {
await withCheckedContinuation { continuation in
self.releaseContinuations.append(continuation)
}
}
self.active -= 1
self.resumeActiveWaiters()
return value
}
func waitForStarts(_ expected: Int) async {
guard self.starts < expected else { return }
await withCheckedContinuation { continuation in
self.startWaiters.append((expected, continuation))
}
}
func waitForActive(_ expected: Int) async {
guard self.active != expected else { return }
await withCheckedContinuation { continuation in
self.activeWaiters.append((expected, continuation))
}
}
func releaseAll() {
self.released = true
let continuations = self.releaseContinuations
self.releaseContinuations.removeAll()
for continuation in continuations {
continuation.resume()
}
}
func startCount() -> Int {
self.starts
}
func peakCount() -> Int {
self.peak
}
private func resumeStartWaiters() {
let ready = self.startWaiters.filter { self.starts >= $0.0 }
self.startWaiters.removeAll { self.starts >= $0.0 }
for (_, continuation) in ready {
continuation.resume()
}
}
private func resumeActiveWaiters() {
let ready = self.activeWaiters.filter { self.active == $0.0 }
self.activeWaiters.removeAll { self.active == $0.0 }
for (_, continuation) in ready {
continuation.resume()
}
}
}
private final class ServeManualDeadlineClock: @unchecked Sendable {
private let lock = NSLock()
private var instant = ContinuousClock().now
private let sleeper = ServeManualSleeper()
func now() -> ContinuousClock.Instant {
self.lock.lock()
defer { self.lock.unlock() }
return self.instant
}
func advance(by duration: Duration) {
self.lock.lock()
self.instant = self.instant.advanced(by: duration)
self.lock.unlock()
}
func sleep(until deadline: ContinuousClock.Instant) async throws {
try await self.sleeper.sleep(until: deadline)
}
func waitForPendingSleeps(_ expected: Int) async {
await self.sleeper.waitForPendingCount(expected)
}
func waitForCancellations(_ expected: Int) async {
await self.sleeper.waitForCancellationCount(expected)
}
func pendingSleepCount() async -> Int {
await self.sleeper.pendingCount()
}
func fireAll() async {
await self.sleeper.fireAll()
}
}
private actor ServeManualSleeper {
private typealias SleepContinuation = CheckedContinuation<Void, any Error>
private struct Pending {
let id: UUID
let continuation: SleepContinuation
}
private var pending: [Pending] = []
private var cancellationCount = 0
private var pendingWaiters: [(Int, CheckedContinuation<Void, Never>)] = []
private var cancellationWaiters: [(Int, CheckedContinuation<Void, Never>)] = []
func sleep(until _: ContinuousClock.Instant) async throws {
let id = UUID()
try await withTaskCancellationHandler(operation: {
try await withCheckedThrowingContinuation { (continuation: SleepContinuation) in
guard !Task.isCancelled else {
continuation.resume(throwing: CancellationError())
return
}
self.pending.append(Pending(id: id, continuation: continuation))
self.resumePendingWaiters()
}
}, onCancel: {
Task { await self.cancel(id: id) }
})
}
func waitForPendingCount(_ expected: Int) async {
guard self.pending.count < expected else { return }
await withCheckedContinuation { continuation in
self.pendingWaiters.append((expected, continuation))
}
}
func waitForCancellationCount(_ expected: Int) async {
guard self.cancellationCount < expected else { return }
await withCheckedContinuation { continuation in
self.cancellationWaiters.append((expected, continuation))
}
}
func pendingCount() -> Int {
self.pending.count
}
func fireAll() {
let pending = self.pending
self.pending.removeAll()
for item in pending {
item.continuation.resume()
}
}
private func cancel(id: UUID) {
guard let index = self.pending.firstIndex(where: { $0.id == id }) else { return }
let item = self.pending.remove(at: index)
self.cancellationCount += 1
item.continuation.resume(throwing: CancellationError())
self.resumeCancellationWaiters()
}
private func resumePendingWaiters() {
let ready = self.pendingWaiters.filter { self.pending.count >= $0.0 }
self.pendingWaiters.removeAll { self.pending.count >= $0.0 }
for (_, continuation) in ready {
continuation.resume()
}
}
private func resumeCancellationWaiters() {
let ready = self.cancellationWaiters.filter { self.cancellationCount >= $0.0 }
self.cancellationWaiters.removeAll { self.cancellationCount >= $0.0 }
for (_, continuation) in ready {
continuation.resume()
}
}
}