field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

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 }