rainbow-dragon/Tests/ForgeCameraARRITests/ArriDriverTests.swift

168 lines
5.7 KiB
Swift

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()
}
}