TeraRuntimeClientTests.swift (13535B)
1 @testable import TeraApp 2 import XCTest 3 4 final class TeraRuntimeClientTests: XCTestCase { 5 func testConcurrentStartSharesOneBackend() async throws { 6 let harness = RuntimeHarness() 7 let client = TeraRuntimeClient(factory: harness.start) 8 let configuration = makeConfiguration(generation: "01") 9 10 let snapshots = try await withThrowingTaskGroup(of: TeraRuntimeSnapshot.self) { group in 11 for _ in 0 ..< 64 { 12 group.addTask { 13 try await client.start(configuration: configuration) 14 } 15 } 16 return try await group.reduce(into: []) { $0.append($1) } 17 } 18 19 XCTAssertEqual(snapshots.count, 64) 20 let startCount = await harness.startCount() 21 let lifecycle = await client.lifecycle() 22 XCTAssertEqual(startCount, 1) 23 XCTAssertEqual(lifecycle, .running(generation: TeraSessionGeneration(rawValue: 1))) 24 _ = try await client.stop() 25 } 26 27 func testOverlappingStopsAwaitOneTypedShutdownFailure() async throws { 28 let failure = TeraRuntimeFailure.local( 29 operation: "test.shutdown", 30 code: "test.shutdown_failed", 31 safeMessage: "Shutdown did not complete." 32 ) 33 let harness = RuntimeHarness(shutdownFailure: failure) 34 let client = TeraRuntimeClient(factory: harness.start) 35 _ = try await client.start(configuration: makeConfiguration(generation: "03")) 36 37 let results = await withTaskGroup(of: Result<TeraRuntimeShutdownReceipt, Error>.self) { 38 group in 39 for _ in 0 ..< 32 { 40 group.addTask { 41 do { 42 return try await .success(client.stop()) 43 } catch { 44 return .failure(error) 45 } 46 } 47 } 48 return await group.reduce(into: []) { $0.append($1) } 49 } 50 51 let shutdownCount = await harness.shutdownCount() 52 XCTAssertEqual(shutdownCount, 1) 53 XCTAssertEqual(results.count, 32) 54 for result in results { 55 guard case let .failure(error) = result else { 56 return XCTFail("Expected every waiter to receive the shutdown failure") 57 } 58 XCTAssertEqual(error as? TeraRuntimeClientError, .shutdown(failure)) 59 } 60 } 61 62 func testNewConfigurationSupersedesAndClosesSlowStartup() async throws { 63 let harness = RuntimeHarness(startDelayNanoseconds: 30_000_000) 64 let client = TeraRuntimeClient(factory: harness.start) 65 let firstConfiguration = makeConfiguration(generation: "04") 66 let secondConfiguration = makeConfiguration(generation: "05") 67 68 let first = Task { 69 try await client.start(configuration: firstConfiguration) 70 } 71 try await Task.sleep(nanoseconds: 2_000_000) 72 let second = Task { 73 try await client.start(configuration: secondConfiguration) 74 } 75 76 do { 77 _ = try await first.value 78 XCTFail("The older startup should be superseded") 79 } catch { 80 XCTAssertEqual(error as? TeraRuntimeClientError, .superseded) 81 } 82 let snapshot = try await second.value 83 XCTAssertEqual(snapshot.identity.publicKeyHex, secondConfiguration.publicKeyHex) 84 let startCount = await harness.startCount() 85 let supersededShutdownCount = await harness.shutdownCount() 86 XCTAssertEqual(startCount, 2) 87 XCTAssertEqual(supersededShutdownCount, 1) 88 _ = try await client.stop() 89 let finalShutdownCount = await harness.shutdownCount() 90 XCTAssertEqual(finalShutdownCount, 2) 91 } 92 93 func testBufferCapacityAndStoppedSubscriptionFailClosed() async throws { 94 let harness = RuntimeHarness() 95 let client = TeraRuntimeClient(factory: harness.start) 96 do { 97 _ = try await client.changes() 98 XCTFail("A stopped runtime cannot subscribe") 99 } catch { 100 XCTAssertEqual(error as? TeraRuntimeClientError, .notRunning) 101 } 102 103 _ = try await client.start(configuration: makeConfiguration(generation: "06")) 104 for invalidCapacity in [0, 65] { 105 do { 106 _ = try await client.changes(bufferCapacity: invalidCapacity) 107 XCTFail("Invalid capacities must fail closed") 108 } catch { 109 XCTAssertEqual(error as? TeraRuntimeClientError, .invalidBufferCapacity) 110 } 111 } 112 _ = try await client.stop() 113 } 114 115 func testStartupDeadlineReturnsPromptlyAndClosesLateBackend() async throws { 116 let harness = RuntimeHarness(startDelayNanoseconds: 60_000_000) 117 let client = TeraRuntimeClient( 118 factory: harness.start, 119 deadlines: makeDeadlines(startup: 5_000_000) 120 ) 121 122 do { 123 _ = try await client.start(configuration: makeConfiguration(generation: "07")) 124 XCTFail("A startup that exceeds its deadline must fail") 125 } catch let TeraRuntimeClientError.startup(failure) { 126 XCTAssertEqual(failure.code, "ios.runtime.deadline_exceeded") 127 XCTAssertTrue(failure.operationID?.hasSuffix("-startup") == true) 128 } 129 130 try await Task.sleep(nanoseconds: 80_000_000) 131 let shutdownCount = await harness.shutdownCount() 132 XCTAssertEqual(shutdownCount, 1) 133 } 134 135 func testStopDrainsCancellationIgnoringStartup() async throws { 136 let harness = RuntimeHarness(startDelayNanoseconds: 100_000_000) 137 let client = TeraRuntimeClient(factory: harness.start) 138 let configuration = makeConfiguration(generation: "08") 139 let startup = Task { 140 try await client.start(configuration: configuration) 141 } 142 try await Task.sleep(nanoseconds: 2_000_000) 143 144 let receipt = try await client.stop() 145 146 XCTAssertEqual(receipt, .alreadyStopped) 147 let completedCloses = await harness.shutdownCount() 148 XCTAssertEqual(completedCloses, 1) 149 do { 150 _ = try await startup.value 151 XCTFail("The cancelled startup must not claim backend ownership") 152 } catch { 153 XCTAssertEqual(error as? TeraRuntimeClientError, .superseded) 154 } 155 let shutdownCount = await harness.shutdownCount() 156 XCTAssertEqual(shutdownCount, 1) 157 } 158 159 func testCancellingOneStartupWaiterDoesNotCancelSharedStartup() async throws { 160 let harness = RuntimeHarness(startDelayNanoseconds: 40_000_000) 161 let client = TeraRuntimeClient(factory: harness.start) 162 let configuration = makeConfiguration(generation: "0c") 163 let cancelledWaiter = Task { 164 try await client.start(configuration: configuration) 165 } 166 let survivingWaiter = Task { 167 try await client.start(configuration: configuration) 168 } 169 try await Task.sleep(nanoseconds: 2_000_000) 170 cancelledWaiter.cancel() 171 172 do { 173 _ = try await cancelledWaiter.value 174 XCTFail("The cancelled waiter must return promptly") 175 } catch let TeraRuntimeClientError.startup(failure) { 176 XCTAssertEqual(failure.code, "ios.runtime.cancelled") 177 } 178 let snapshot = try await survivingWaiter.value 179 XCTAssertEqual(snapshot.identity.publicKeyHex, configuration.publicKeyHex) 180 let startCount = await harness.startCount() 181 XCTAssertEqual(startCount, 1) 182 _ = try await client.stop() 183 } 184 185 func testOperationDeadlineAndLateValueCannotEscape() async throws { 186 let harness = RuntimeHarness(snapshotDelayNanoseconds: 60_000_000) 187 let client = TeraRuntimeClient( 188 factory: harness.start, 189 deadlines: makeDeadlines(operation: 5_000_000) 190 ) 191 _ = try await client.start(configuration: makeConfiguration(generation: "09")) 192 193 do { 194 _ = try await client.snapshot() 195 XCTFail("A runtime operation that exceeds its deadline must fail") 196 } catch let TeraRuntimeClientError.status(failure) { 197 XCTAssertEqual(failure.code, "ios.runtime.deadline_exceeded") 198 XCTAssertTrue(failure.operationID?.hasSuffix("-operation") == true) 199 } 200 201 _ = try await client.stop() 202 } 203 204 func testSuspendCancelsTrackedOperationWithoutClosingRuntime() async throws { 205 let harness = RuntimeHarness(snapshotDelayNanoseconds: 60_000_000) 206 let client = TeraRuntimeClient(factory: harness.start) 207 let configuration = makeConfiguration(generation: "0d") 208 _ = try await client.start(configuration: configuration) 209 let pendingSnapshot = Task { 210 try await client.snapshot() 211 } 212 try await Task.sleep(nanoseconds: 2_000_000) 213 214 await client.suspend() 215 216 do { 217 _ = try await pendingSnapshot.value 218 XCTFail("Suspension must cancel tracked presentation work") 219 } catch let TeraRuntimeClientError.status(failure) { 220 XCTAssertEqual(failure.code, "ios.runtime.cancelled") 221 } 222 let lifecycle = await client.lifecycle() 223 XCTAssertEqual(lifecycle, .running(generation: TeraSessionGeneration(rawValue: 1))) 224 try await Task.sleep(nanoseconds: 80_000_000) 225 let recoveredSnapshot = try await client.snapshot() 226 XCTAssertEqual(recoveredSnapshot.identity.publicKeyHex, configuration.publicKeyHex) 227 _ = try await client.stop() 228 } 229 230 func testShutdownDeadlineQuarantinesBackendUntilLateSuccess() async throws { 231 let harness = RuntimeHarness(shutdownDelayNanoseconds: 60_000_000) 232 let client = TeraRuntimeClient( 233 factory: harness.start, 234 deadlines: makeDeadlines(shutdown: 5_000_000) 235 ) 236 _ = try await client.start(configuration: makeConfiguration(generation: "0a")) 237 238 do { 239 _ = try await client.stop() 240 XCTFail("A shutdown that exceeds its deadline must fail") 241 } catch let TeraRuntimeClientError.shutdown(failure) { 242 XCTAssertEqual(failure.code, "ios.runtime.deadline_exceeded") 243 } 244 if case .failed = await client.lifecycle() { 245 // Expected while the detached backend remains quarantined. 246 } else { 247 XCTFail("The timed-out backend must remain quarantined") 248 } 249 250 try await Task.sleep(nanoseconds: 80_000_000) 251 let finalLifecycle = await client.lifecycle() 252 let finalReceipt = try await client.stop() 253 XCTAssertEqual(finalLifecycle, .stopped) 254 XCTAssertEqual(finalReceipt, .alreadyStopped) 255 } 256 257 @MainActor 258 func testObservationFailureIsVisibleAndRecovers() async throws { 259 let harness = RuntimeHarness(subscriptionFailures: 1) 260 let client = TeraRuntimeClient(factory: harness.start) 261 let snapshot = try await client.start(configuration: makeConfiguration(generation: "0b")) 262 let store = TeraTodayStore( 263 runtimeClient: client, 264 contexts: [.defaultContext(snapshot: snapshot)], 265 observationDelay: { _ in try await Task.sleep(nanoseconds: 20_000_000) } 266 ) 267 268 await store.start() 269 try await waitUntil { 270 if case .retrying = store.observationState { 271 return true 272 } 273 return false 274 } 275 try await waitUntil { store.observationState == .active } 276 277 let subscriptionAttempts = await harness.subscriptionAttemptCount() 278 XCTAssertGreaterThanOrEqual(subscriptionAttempts, 2) 279 store.stop() 280 XCTAssertEqual(store.observationState, .stopped) 281 _ = try await client.stop() 282 } 283 284 private func makeDeadlines( 285 startup: UInt64 = 1_000_000_000, 286 operation: UInt64 = 1_000_000_000, 287 subscription: UInt64 = 1_000_000_000, 288 shutdown: UInt64 = 1_000_000_000 289 ) -> TeraRuntimeDeadlinePolicy { 290 TeraRuntimeDeadlinePolicy( 291 startupNanoseconds: startup, 292 operationNanoseconds: operation, 293 subscriptionNanoseconds: subscription, 294 shutdownNanoseconds: shutdown 295 ) 296 } 297 298 @MainActor 299 private func waitUntil( 300 _ predicate: @escaping @MainActor () -> Bool 301 ) async throws { 302 for _ in 0 ..< 200 { 303 if predicate() { 304 return 305 } 306 try await Task.sleep(nanoseconds: 1_000_000) 307 } 308 XCTAssertTrue(predicate()) 309 } 310 311 func makeConfiguration(generation: String) -> TeraRuntimeLaunchConfiguration { 312 TeraRuntimeLaunchConfiguration( 313 applicationSupportDirectory: "/tmp/radroots-runtime-tests-\(generation)", 314 publicKeyHex: String(repeating: generation, count: 32), 315 sourceGenerationHex: String(repeating: generation, count: 32), 316 sourceGenerationCreatedAtUnixMilliseconds: 1, 317 protectedData: .available, 318 networkProfile: .simulator, 319 writableRelays: ["ws://127.0.0.1:7447"], 320 blossom: TeraBlossomEndpointConfiguration( 321 hostKind: .simulator, 322 endpointAuthority: .loopbackDevelopment, 323 primaryOrigin: "http://127.0.0.1:3000", 324 fallbackOrigins: [] 325 ), 326 app: TeraRuntimeAppMetadata( 327 bundleIdentifier: "org.radroots.tests", 328 version: "0.1.0-alpha", 329 buildNumber: "1", 330 buildSHA: nil 331 ), 332 signerGeneration: generation, 333 signer: TestRuntimeSigner(), 334 adoptBootstrapSettings: false 335 ) 336 } 337 }