rainbow-dragon/Tests/ForgeVideoTests/VideoTests.swift

78 lines
3.6 KiB
Swift

import XCTest
import Foundation
import ForgeGrade
import ForgeColor
@testable import ForgeVideo
final class VideoTests: XCTestCase {
// Solid-color test source produces frames of declared size.
func testTestPatternSource() async throws {
let source = TestPatternSource(width: 8, height: 4, color: SIMD3(0.5, 0.25, 0.75))
let frame = try await source.nextFrame()
XCTAssertEqual(frame.width, 8)
XCTAssertEqual(frame.height, 4)
XCTAssertEqual(frame.pixels.count, 8 * 4)
XCTAssertEqual(frame.pixels[0].x, 0.5, accuracy: 1e-6)
}
// Grading a frame applies the stack per pixel.
func testGradeFrame() async throws {
let source = TestPatternSource(width: 4, height: 2, color: SIMD3(0.25, 0.25, 0.25))
let stack = GradeStack(nodes: [GradeNode(kind: .cdl(CDL(
slope: SIMD3(2, 2, 2), offset: .zero, power: .one, saturation: 1)))])
let frame = try await source.nextFrame()
let graded = FrameProcessor.apply(stack: stack, to: frame)
XCTAssertEqual(graded.pixels[0].x, 0.5, accuracy: 1e-6)
XCTAssertEqual(graded.width, 4)
// Timecode preserved.
XCTAssertEqual(graded.timecode, frame.timecode)
}
// Still grab writes PPM (portable, no image lib dep) and returns path.
func testStillGrabWritesPPM() async throws {
let source = TestPatternSource(width: 4, height: 4, color: SIMD3(1, 0, 0))
let frame = try await source.nextFrame()
let dir = NSTemporaryDirectory() + "forge-stills-\(UUID().uuidString)"
let grabber = StillGrabber(directory: dir)
let path = try grabber.grab(frame: frame, clipName: "A001C001")
defer { try? FileManager.default.removeItem(atPath: dir) }
XCTAssertTrue(FileManager.default.fileExists(atPath: path))
XCTAssertTrue(path.hasSuffix(".ppm"))
XCTAssertTrue(path.contains("A001C001"))
let data = try Data(contentsOf: URL(fileURLWithPath: path))
// Compare header bytes directly string decoding of Data slices is
// unreliable across Foundation implementations.
let expectedHeader = [UInt8]("P6\n4 4\n255\n".utf8)
XCTAssertEqual([UInt8](data.prefix(expectedHeader.count)), expectedHeader)
// First pixel red: 255 0 0.
let headerLen = "P6\n4 4\n255\n".utf8.count
XCTAssertEqual(data[headerLen], 255)
XCTAssertEqual(data[headerLen + 1], 0)
XCTAssertEqual(data[headerLen + 2], 0)
}
// Values clamp to 0-255 on write.
func testStillClampsRange() async throws {
let source = TestPatternSource(width: 2, height: 2, color: SIMD3(2.0, -0.5, 0.5))
let frame = try await source.nextFrame()
let dir = NSTemporaryDirectory() + "forge-stills-\(UUID().uuidString)"
defer { try? FileManager.default.removeItem(atPath: dir) }
let path = try StillGrabber(directory: dir).grab(frame: frame, clipName: "clamp")
let data = try Data(contentsOf: URL(fileURLWithPath: path))
let headerLen = "P6\n2 2\n255\n".utf8.count
XCTAssertEqual(data[headerLen], 255) // 2.0 -> 255
XCTAssertEqual(data[headerLen + 1], 0) // -0.5 -> 0
XCTAssertEqual(data[headerLen + 2], 128) // 0.5 -> 128 (rounded)
}
// Null sink accepts frames without error (SDI-out stand-in).
func testNullSink() async throws {
let sink = NullFrameSink()
let source = TestPatternSource(width: 2, height: 2, color: .zero)
try await sink.consume(frame: try await source.nextFrame())
let count = await sink.consumedCount
XCTAssertEqual(count, 1)
}
}