rainbow-dragon/Tests/ForgeCameraREDTests/RedDriverTests.swift

152 lines
5 KiB
Swift
Raw Normal View History

import XCTest
import Foundation
import ForgeCamera
import ForgeGrade
import ForgeColor
import ForgeSim
@testable import ForgeCameraRED
final class RedDriverTests: XCTestCase {
var sim: RedSimulator!
override func setUp() async throws {
sim = RedSimulator()
try await sim.start(port: 0)
}
override func tearDown() async throws {
try? await sim.stop()
sim = nil
}
func makeDriver(pollInterval: Duration = .milliseconds(20)) async -> RedDriver {
let port = await sim.boundPort!
return RedDriver(host: "127.0.0.1", port: port, pollInterval: pollInterval)
}
// Capabilities: RED = native CDL (IPP2), 17/33 LUTs.
func testCapabilities() async {
let driver = await makeDriver()
XCTAssertEqual(driver.capabilities.vendor, .red)
XCTAssertTrue(driver.capabilities.supportsNativeCDL)
XCTAssertTrue(driver.capabilities.lut3dSizes.contains(33))
}
// Connect performs RCP-style handshake, emits connected.
func testConnectHandshake() 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)
let handshakes = await sim.handshakeCount
XCTAssertEqual(handshakes, 1)
await driver.disconnect()
}
func testConnectFailsOnDeadPort() async {
let driver = RedDriver(host: "127.0.0.1", port: 1, pollInterval: .milliseconds(20))
do {
try await driver.connect()
XCTFail("expected throw")
} catch {}
}
// Rec state + clip name via param polling.
func testRecStateEvents() async throws {
let driver = await makeDriver()
var iterator = driver.state.makeAsyncIterator()
try await driver.connect()
await sim.setParam("RECORD_STATE", value: "1")
await sim.setParam("CLIP_NAME", value: "B001_C001_0710AB")
var sawRec = false
for _ in 0..<30 {
if let s = await iterator.next(), s.isRecording {
XCTAssertEqual(s.clipName, "B001_C001_0710AB")
sawRec = true
break
}
}
XCTAssertTrue(sawRec)
await sim.setParam("RECORD_STATE", value: "0")
var sawStop = false
for _ in 0..<30 {
if let s = await iterator.next(), !s.isRecording {
sawStop = true
break
}
}
XCTAssertTrue(sawStop)
await driver.disconnect()
}
// Metadata params surface.
func testMetadata() async throws {
await sim.setParam("ISO", value: "1600")
await sim.setParam("COLOR_TEMP", value: "4500")
await sim.setParam("TINT", value: "3")
await sim.setParam("SENSOR_FPS", value: "23.98")
await sim.setParam("TIMECODE", value: "15:30:00:00")
let driver = await makeDriver()
var iterator = driver.state.makeAsyncIterator()
try await driver.connect()
var got = false
for _ in 0..<30 {
if let s = await iterator.next(), let md = s.metadata, md.exposureIndex == 1600 {
XCTAssertEqual(md.whiteBalance, 4500)
XCTAssertEqual(md.tint, 3)
XCTAssertEqual(md.timecode, "15:30:00:00")
got = true
break
}
}
XCTAssertTrue(got)
await driver.disconnect()
}
// Push: CDL to IPP2 slots + LUT upload.
func testPushLookWithNativeCDL() async throws {
let driver = await makeDriver()
try await driver.connect()
let cdl = CDL(slope: SIMD3(1.1, 1.0, 0.9), offset: SIMD3(0.02, 0, 0), power: .one, saturation: 1.3)
let look = FlattenedLook(cdl: cdl, lut: Lut3D.identity(size: 33), latticeSize: 33)
try await driver.push(look: look)
let cdlValue = await sim.params["IPP2_CDL"]
XCTAssertNotNil(cdlValue)
let obj = try JSONSerialization.jsonObject(with: Data(cdlValue!.utf8)) as? [String: Any]
let slope = obj?["slope"] as? [Double]
XCTAssertEqual(slope?[0] ?? 0, 1.1, accuracy: 1e-5)
let luts = await sim.uploadedLuts
XCTAssertEqual(luts.count, 1)
XCTAssertEqual(luts[0].size, 33)
await driver.disconnect()
}
// Unsupported LUT size rejected.
func testUnsupportedLutRejected() async throws {
let driver = await makeDriver()
try await driver.connect()
do {
try await driver.push(look: FlattenedLook(cdl: nil, lut: Lut3D.identity(size: 65), latticeSize: 65))
XCTFail("expected unsupportedLook")
} catch let e as CameraError {
guard case .unsupportedLook = e else { return XCTFail("wrong error") }
}
await driver.disconnect()
}
}