rainbow-dragon/Tests/ForgeCameraTests/ArriSimTests.swift

188 lines
7.2 KiB
Swift
Raw Normal View History

import XCTest
import Foundation
import ForgeCameraARRI
import ForgeOffload
@testable import ForgeSim
/// CAP-level sim tests: raw TCP client speaking binary CAP frames.
final class ArriSimTests: XCTestCase {
var sim: ArriSimulator!
override func setUp() async throws {
sim = ArriSimulator(password: "arri")
try await sim.start(port: 0)
}
override func tearDown() async throws {
try? await sim.stop()
sim = nil
}
/// Minimal blocking CAP test client.
final class TestClient {
let fd: Int32
var buffer = Data()
var msgId: UInt16 = 100
init?(port: Int) {
fd = socket(AF_INET, Int32(SOCK_STREAM.rawValue), 0)
guard fd >= 0 else { return nil }
var tv = timeval(tv_sec: 2, tv_usec: 0)
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, socklen_t(MemoryLayout<timeval>.size))
var addr = sockaddr_in()
addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = in_port_t(UInt16(port).bigEndian)
_ = inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr)
let rc = withUnsafePointer(to: &addr) { ptr in
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
connect(fd, sa, socklen_t(MemoryLayout<sockaddr_in>.size))
}
}
guard rc == 0 else {
close(fd)
return nil
}
}
deinit {
close(fd)
}
func send(_ cmd: CAP.Command, payload: Data = Data()) -> UInt16 {
msgId &+= 1
let frame = CAP.Frame(
msgType: CAP.MsgType.command.rawValue,
msgId: msgId,
cmdCode: cmd.rawValue,
payload: payload)
let data = CAP.encode(frame)
_ = data.withUnsafeBytes { write(fd, $0.baseAddress, $0.count) }
return msgId
}
func readFrame() -> CAP.Frame? {
var chunk = [UInt8](repeating: 0, count: 8192)
for _ in 0..<40 {
if let f = try? CAP.decodeFirst(&buffer), f != nil {
return f
}
let n = read(fd, &chunk, chunk.count)
if n > 0 {
buffer.append(contentsOf: chunk[0..<n])
} else if n == 0 {
return nil
}
}
return try? CAP.decodeFirst(&buffer) ?? nil
}
/// Read frames until predicate matches (skips events/other replies).
func readUntil(_ predicate: (CAP.Frame) -> Bool) -> CAP.Frame? {
for _ in 0..<50 {
guard let f = readFrame() else { return nil }
if predicate(f) { return f }
}
return nil
}
func authenticate(password: String) -> Bool {
// Welcome arrives unsolicited.
guard readUntil({ $0.cmdCode == CAP.Command.welcome.rawValue }) != nil else { return false }
let chalId = send(.requestPwdChallenge)
guard let chalReply = readUntil({ $0.msgId == chalId }),
let (challenge, _) = CAP.readString(chalReply.payload, at: 0) else { return false }
var pw = Data()
CAP.putString(MD5.hexString(Data((challenge + password).utf8)), into: &pw)
let pwId = send(.password, payload: pw)
guard let pwReply = readUntil({ $0.msgId == pwId }),
pwReply.cmdCode == CAP.Result.ok.rawValue else { return false }
return true
}
}
func testWelcomeOnConnect() async throws {
let port = await sim.boundPort!
let client = try XCTUnwrap(TestClient(port: port))
let welcome = client.readUntil { $0.cmdCode == CAP.Command.welcome.rawValue }
XCTAssertNotNil(welcome)
let (version, _) = CAP.readString(welcome!.payload, at: 0) ?? ("", 0)
XCTAssertTrue(version.contains("CAP"))
}
func testAuthFlow() async throws {
let port = await sim.boundPort!
let client = try XCTUnwrap(TestClient(port: port))
XCTAssertTrue(client.authenticate(password: "arri"))
}
func testWrongPassword() async throws {
let port = await sim.boundPort!
let client = try XCTUnwrap(TestClient(port: port))
XCTAssertFalse(client.authenticate(password: "nope"))
}
func testUnauthenticatedGetRejected() async throws {
let port = await sim.boundPort!
let client = try XCTUnwrap(TestClient(port: port))
_ = client.readUntil { $0.cmdCode == CAP.Command.welcome.rawValue }
var payload = Data()
CAP.putU16(CAP.Variable.exposureIndex.rawValue, into: &payload)
let id = client.send(.getVariable, payload: payload)
let reply = client.readUntil { $0.msgId == id }
XCTAssertEqual(reply?.cmdCode, CAP.Result.notAuthorized.rawValue)
}
func testGetVariable() async throws {
await sim.setMetadata(ei: 1280, colorTemp: 3200, tint: -2, fps: 25, timecode: "01:02:03:04")
let port = await sim.boundPort!
let client = try XCTUnwrap(TestClient(port: port))
XCTAssertTrue(client.authenticate(password: "arri"))
var payload = Data()
CAP.putU16(CAP.Variable.exposureIndex.rawValue, into: &payload)
let id = client.send(.getVariable, payload: payload)
let reply = client.readUntil { $0.msgId == id }
XCTAssertEqual(reply?.cmdCode, CAP.Result.ok.rawValue)
XCTAssertEqual(CAP.readU16(reply!.payload, at: 0), CAP.Variable.exposureIndex.rawValue)
XCTAssertEqual(CAP.readU32(reply!.payload, at: 2), 1280)
}
func testSetCDLStored() async throws {
let port = await sim.boundPort!
let client = try XCTUnwrap(TestClient(port: port))
XCTAssertTrue(client.authenticate(password: "arri"))
var payload = Data()
CAP.putU16(CAP.Variable.cdlValues.rawValue, into: &payload)
payload.append(CAP.encodeCDLBlob(
slope: (1.5, 1, 1), offset: (0, 0, 0), power: (1, 1, 1), saturation: 0.8))
let id = client.send(.setVariable, payload: payload)
let reply = client.readUntil { $0.msgId == id }
XCTAssertEqual(reply?.cmdCode, CAP.Result.ok.rawValue)
let stored = await sim.lastCDL
XCTAssertEqual(stored?[0] ?? 0, 1.5, accuracy: 1e-5)
XCTAssertEqual(stored?[9] ?? 0, 0.8, accuracy: 1e-5)
}
func testSubscriptionPushesEvents() async throws {
let port = await sim.boundPort!
let client = try XCTUnwrap(TestClient(port: port))
XCTAssertTrue(client.authenticate(password: "arri"))
var payload = Data()
CAP.putU16(CAP.Variable.cameraState.rawValue, into: &payload)
let id = client.send(.requestVariables, payload: payload)
_ = client.readUntil { $0.msgId == id }
await sim.setRecording(true)
let event = client.readUntil { frame in
frame.msgType == CAP.MsgType.event.rawValue
&& CAP.readU16(frame.payload, at: 0) == CAP.Variable.cameraState.rawValue
&& CAP.CameraStateBits(rawValue: CAP.readU16(frame.payload, at: 2) ?? 0).contains(.recording)
}
XCTAssertNotNil(event)
}
}