import XCTest import ForgeCamera import ForgeGrade import ForgeColor import ForgeSim @testable import ForgeCameraARRI final class ArriDriverTests: XCTestCase { var sim: ArriSimulator! override func setUp() async throws { sim = ArriSimulator() try await sim.start(port: 0) } override func tearDown() async throws { try? await sim.stop() sim = nil } func makeDriver(pollInterval: Duration = .milliseconds(20)) async -> ArriDriver { let port = await sim.boundPort! return ArriDriver(host: "127.0.0.1", port: port, pollInterval: pollInterval) } // Capabilities: ARRI = native CDL + 33/65 LUTs. func testCapabilities() async { let driver = await makeDriver() XCTAssertEqual(driver.capabilities.vendor, .arri) XCTAssertTrue(driver.capabilities.supportsNativeCDL) XCTAssertTrue(driver.capabilities.lut3dSizes.contains(33)) XCTAssertTrue(driver.capabilities.metadataFields.contains(.clipName)) } // Connect verifies system info and emits connected state. func testConnectEmitsConnected() async throws { let driver = await makeDriver() var iterator = driver.state.makeAsyncIterator() try await driver.connect() var connected = false for _ in 0..<5 { if let s = await iterator.next(), s.connection == .connected { connected = true break } } XCTAssertTrue(connected) await driver.disconnect() } // Connect to dead port throws. func testConnectFailsOnDeadPort() async { let driver = ArriDriver(host: "127.0.0.1", port: 1, pollInterval: .milliseconds(20)) do { try await driver.connect() XCTFail("expected throw") } catch {} } // Poll loop surfaces metadata. func testMetadataPolling() async throws { await sim.setMetadata(ei: 1280, wb: 3200, tint: -2, fps: 25, timecode: "10:20:30:12") let driver = await makeDriver() var iterator = driver.state.makeAsyncIterator() try await driver.connect() var got = false for _ in 0..<20 { if let s = await iterator.next(), let md = s.metadata, md.exposureIndex == 1280, md.whiteBalance == 3200 { XCTAssertEqual(md.timecode, "10:20:30:12") got = true break } } XCTAssertTrue(got) await driver.disconnect() } // Rec start/stop events with clip name. func testRecStateEvents() async throws { let driver = await makeDriver() var iterator = driver.state.makeAsyncIterator() try await driver.connect() await sim.setRecording(true, clipName: "A001C007_250710_R1CD") var sawRec = false for _ in 0..<30 { if let s = await iterator.next(), s.isRecording { XCTAssertEqual(s.clipName, "A001C007_250710_R1CD") sawRec = true break } } XCTAssertTrue(sawRec) await sim.setRecording(false) var sawStop = false for _ in 0..<30 { if let s = await iterator.next(), !s.isRecording { sawStop = true break } } XCTAssertTrue(sawStop) await driver.disconnect() } // push(look:) uploads CDL + LUT payload sim can parse. func testPushLook() async throws { let driver = await makeDriver() try await driver.connect() let cdl = CDL(slope: SIMD3(1.1, 1.0, 0.9), offset: .zero, power: .one, saturation: 1.2) let lut = Lut3D.identity(size: 33) let look = FlattenedLook(cdl: cdl, lut: lut, latticeSize: 33) try await driver.push(look: look) let uploads = await sim.uploadedLooks XCTAssertEqual(uploads.count, 1) let obj = try JSONSerialization.jsonObject(with: uploads[0]) as? [String: Any] let cdlObj = obj?["cdl"] as? [String: Any] let slope = cdlObj?["slope"] as? [Double] XCTAssertEqual(slope?[0] ?? 0, 1.1, accuracy: 1e-5) XCTAssertEqual(cdlObj?["saturation"] as? Double ?? 0, 1.2, accuracy: 1e-5) XCTAssertEqual(obj?["lut3dSize"] as? Int, 33) let table = obj?["lut3dTable"] as? [Double] XCTAssertEqual(table?.count, 33 * 33 * 33 * 3) await driver.disconnect() } // Unsupported LUT size rejected before hitting the wire. func testUnsupportedLutSizeRejected() async throws { let driver = await makeDriver() try await driver.connect() let look = FlattenedLook(cdl: nil, lut: Lut3D.identity(size: 5), latticeSize: 5) do { try await driver.push(look: look) XCTFail("expected unsupportedLook") } catch let error as CameraError { guard case .unsupportedLook = error else { return XCTFail("wrong error") } } let uploads = await sim.uploadedLooks XCTAssertTrue(uploads.isEmpty) await driver.disconnect() } // Reconnect after sim restart. func testReconnectAfterSimRestart() async throws { let port = await sim.boundPort! let driver = ArriDriver(host: "127.0.0.1", port: port, pollInterval: .milliseconds(20)) try await driver.connect() await driver.disconnect() try await sim.stop() sim = ArriSimulator() try await sim.start(port: port) try await driver.connect() let look = FlattenedLook(cdl: nil, lut: Lut3D.identity(size: 33), latticeSize: 33) try await driver.push(look: look) let uploads = await sim.uploadedLooks XCTAssertEqual(uploads.count, 1) await driver.disconnect() } }