TeraRuntimeBoundedTaskTests.swift (5294B)
1 import Foundation 2 @testable import TeraApp 3 import XCTest 4 5 final class TeraRuntimeBoundedTaskTests: XCTestCase { 6 private typealias Bounded = TeraRuntimeBoundedTask<Int> 7 8 func testCompletionTimeoutAndCancellationBeforeInstallation() async { 9 for terminal in Terminal.allCases { 10 await assertInstallation(terminal: terminal, terminalFirst: true) 11 } 12 } 13 14 func testCompletionTimeoutAndCancellationAfterInstallation() async { 15 for terminal in Terminal.allCases { 16 await assertInstallation(terminal: terminal, terminalFirst: false) 17 } 18 } 19 20 func testCompletedFailureDoesNotCancelLateInstalledOperation() async { 21 let state = Bounded.State() 22 let gate = BoundedTestGate() 23 let operation = Task { await gate.wait() } 24 let timer = Task { await gate.wait() } 25 let failure = TeraRuntimeFailure.local(operation: "test", code: "test.failure", safeMessage: "Test failure") 26 XCTAssertTrue(state.resolve(.completed(.failure(failure)))) 27 state.install(operationTask: operation, timeoutTask: timer) 28 XCTAssertFalse(operation.isCancelled) 29 XCTAssertTrue(timer.isCancelled) 30 if case let .completed(.failure(observed)) = await state.value(cancelsOperationWhenWaiterCancelled: true) { 31 XCTAssertEqual(observed, failure) 32 } else { 33 XCTFail("The first completed result must remain owned by its caller") 34 } 35 await gate.open() 36 await operation.value 37 await timer.value 38 } 39 40 func testAbandonedSuccessfulOperationStillRunsItsCleanupExactlyOnce() async { 41 let started = BoundedTestGate() 42 let release = BoundedTestGate() 43 let abandoned = expectation(description: "Abandoned result cleanup") 44 abandoned.assertForOverFulfill = true 45 let task = Bounded(deadlineNanoseconds: .max) { 46 await started.open() 47 await release.wait() 48 return .success(42) 49 } onAbandonedResult: { result in 50 XCTAssertEqual(try? result.get(), 42) 51 abandoned.fulfill() 52 } 53 await started.wait() 54 task.cancel() 55 task.cancel() 56 if case .cancelled = await task.value() {} else { 57 XCTFail("Cancellation must resolve the caller before the operation returns") 58 } 59 await release.open() 60 await fulfillment(of: [abandoned], timeout: 2) 61 } 62 63 func testSuccessfulWrapperKeepsItsResultAfterLaterCancellation() async { 64 let task = Bounded(deadlineNanoseconds: .max, operation: { .success(42) }) { _ in 65 XCTFail("A result owned by its caller must not be abandoned") 66 } 67 await assertOutcome(task.value(), terminal: .completion) 68 task.cancel() 69 await assertOutcome(task.value(), terminal: .completion) 70 } 71 72 func testCallerCancellationDoesNotClaimActualOperationSettlement() async { 73 let pause = ResourceTestPause() 74 let task = Bounded(deadlineNanoseconds: .max) { 75 await pause.wait() 76 return .success(42) 77 } 78 await pause.entered.wait() 79 task.cancel() 80 await assertOutcome(task.value(), terminal: .cancellation) 81 XCTAssertNil(task.settlement()) 82 await pause.resume.open() 83 let actual = await task.settle() 84 guard case .success(42) = actual else { return XCTFail("Preserve the actual late result") } 85 await assertOutcome(task.value(), terminal: .cancellation) 86 } 87 88 private func assertInstallation(terminal: Terminal, terminalFirst: Bool) async { 89 let state = Bounded.State() 90 let gate = BoundedTestGate() 91 let operation = Task { await gate.wait() } 92 let timer = Task { await gate.wait() } 93 if terminalFirst { 94 terminal.resolve(state) 95 } 96 state.install(operationTask: operation, timeoutTask: timer) 97 if !terminalFirst { 98 terminal.resolve(state) 99 } 100 XCTAssertEqual(operation.isCancelled, terminal != .completion, "\(terminal), before=\(terminalFirst)") 101 XCTAssertTrue(timer.isCancelled) 102 await assertOutcome(state.value(cancelsOperationWhenWaiterCancelled: true), terminal: terminal) 103 XCTAssertFalse(state.resolve(.completed(.success(99)))) 104 state.cancel() 105 state.expire() 106 await assertOutcome(state.value(cancelsOperationWhenWaiterCancelled: true), terminal: terminal) 107 await gate.open() 108 await operation.value 109 await timer.value 110 state.finishOperation(.success(42)) 111 } 112 113 private func assertOutcome(_ outcome: Bounded.Outcome, terminal: Terminal) { 114 switch (terminal, outcome) { 115 case let (.completion, .completed(.success(value))): XCTAssertEqual(value, 42) 116 case (.timeout, .timedOut), (.cancellation, .cancelled): break 117 default: XCTFail("The first terminal outcome must resolve each wait exactly once") 118 } 119 } 120 121 private enum Terminal: CaseIterable { 122 case completion, timeout, cancellation 123 124 func resolve(_ state: Bounded.State) { 125 switch self { 126 case .completion: XCTAssertTrue(state.resolve(.completed(.success(42)))) 127 case .timeout: state.expire() 128 case .cancellation: state.cancel() 129 } 130 } 131 } 132 } 133 134 private actor BoundedTestGate { 135 private var opened = false 136 private var waiters: [CheckedContinuation<Void, Never>] = [] 137 138 func wait() async { 139 guard !opened else { return } 140 await withCheckedContinuation { waiters.append($0) } 141 } 142 143 func open() { 144 guard !opened else { return } 145 opened = true 146 let pending = waiters 147 waiters.removeAll() 148 for waiter in pending { 149 waiter.resume() 150 } 151 } 152 }