You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
5.5 KiB

import Testing
@testable import MusubiCore
@Suite struct DisplayModeSelectionTests {
let current = DisplayModeInfo(width: 1920, height: 1080, pixelWidth: 1920, pixelHeight: 1080, refreshRate: 60)
@Test func picksSameSizeAt50Hz() {
let modes = [
current,
DisplayModeInfo(width: 1920, height: 1080, pixelWidth: 1920, pixelHeight: 1080, refreshRate: 50),
DisplayModeInfo(width: 1280, height: 720, pixelWidth: 1280, pixelHeight: 720, refreshRate: 50),
]
#expect(DisplayModeSelection.mode(matching: 50, current: current, available: modes)?.refreshRate == 50)
#expect(DisplayModeSelection.mode(matching: 50, current: current, available: modes)?.width == 1920)
}
@Test func prefersSameBackingResolution() {
let hidpi = DisplayModeInfo(width: 1920, height: 1080, pixelWidth: 3840, pixelHeight: 2160, refreshRate: 60)
let modes = [
DisplayModeInfo(width: 1920, height: 1080, pixelWidth: 1920, pixelHeight: 1080, refreshRate: 50),
DisplayModeInfo(width: 1920, height: 1080, pixelWidth: 3840, pixelHeight: 2160, refreshRate: 50),
]
#expect(DisplayModeSelection.mode(matching: 50, current: hidpi, available: modes)?.pixelWidth == 3840)
}
@Test func nilWhenAlreadyAt50HzOrUnavailable() {
let at50 = DisplayModeInfo(width: 1920, height: 1080, pixelWidth: 1920, pixelHeight: 1080, refreshRate: 50)
#expect(DisplayModeSelection.mode(matching: 50, current: at50, available: [at50]) == nil)
#expect(DisplayModeSelection.mode(matching: 50, current: current, available: [current]) == nil)
}
}
@Suite struct FrameStatsTests {
// 50 fps → 200_000 ticks per frame.
let frame: Int64 = 200_000
@Test func countsGapsAsMissingFrames() {
var stats = FrameStats()
stats.record(timestamp: 0, frameRateN: 50, frameRateD: 1)
stats.record(timestamp: frame, frameRateN: 50, frameRateD: 1)
stats.record(timestamp: frame * 4, frameRateN: 50, frameRateD: 1) // 2 frames missing
#expect(stats.received == 3)
#expect(stats.missing == 2)
#expect(stats.lossRate == 0.4)
}
@Test func toleratesJitter() {
var stats = FrameStats()
stats.record(timestamp: 0, frameRateN: 50, frameRateD: 1)
stats.record(timestamp: frame + frame / 4, frameRateN: 50, frameRateD: 1)
#expect(stats.missing == 0)
}
@Test func outageIsNotCountedAsLoss() {
var stats = FrameStats()
stats.record(timestamp: 0, frameRateN: 50, frameRateD: 1)
stats.record(timestamp: 50_000_000, frameRateN: 50, frameRateD: 1) // 5 s later
#expect(stats.missing == 0)
}
@Test func includesFramesDroppedByReceiver() {
var stats = FrameStats()
for i in 0..<998 { stats.record(timestamp: Int64(i) * frame, frameRateN: 50, frameRateD: 1) }
stats.updateDroppedByReceiver(2)
#expect(stats.lossRate == 0.002)
}
@Test func handlesNTSCRates() {
var stats = FrameStats()
let ticks = Int64((1001.0 / 30000.0 * FrameStats.ticksPerSecond).rounded())
stats.record(timestamp: 0, frameRateN: 30000, frameRateD: 1001)
stats.record(timestamp: ticks * 3, frameRateN: 30000, frameRateD: 1001)
#expect(stats.missing == 2)
}
}
@Suite struct ConnectionWatchdogTests {
@Test func connectingThenLive() {
var dog = ConnectionWatchdog(now: 0)
#expect(dog.state(at: 1) == .connecting)
dog.frameArrived(at: 1)
#expect(dog.state(at: 1.5) == .live)
}
@Test func stallsAndRetriesEveryInterval() {
var dog = ConnectionWatchdog(now: 0)
dog.frameArrived(at: 10)
#expect(dog.state(at: 11.9) == .live)
#expect(dog.state(at: 12) == .stalled(retryIn: 3))
let retry1 = dog.shouldRetry(at: 13)
#expect(!retry1)
let retry2 = dog.shouldRetry(at: 15)
#expect(retry2)
#expect(dog.state(at: 16) == .stalled(retryIn: 2))
let retry3 = dog.shouldRetry(at: 18)
#expect(retry3)
}
@Test func recoversWhenFramesReturn() {
var dog = ConnectionWatchdog(now: 0)
dog.frameArrived(at: 0)
let retry4 = dog.shouldRetry(at: 5)
#expect(retry4)
dog.frameArrived(at: 6)
#expect(dog.state(at: 6.5) == .live)
}
@Test func stillImageSourceWithConnectionIsNotStalled() {
var dog = ConnectionWatchdog(now: 0)
dog.frameArrived(at: 0)
for second in 1...10 { dog.connectionConfirmed(at: Double(second)) }
#expect(dog.state(at: 10.5) == .live)
let retry = dog.shouldRetry(at: 10.5)
#expect(!retry)
}
@Test func connectionWithoutAnyFrameStaysConnecting() {
var dog = ConnectionWatchdog(now: 0)
dog.connectionConfirmed(at: 5)
#expect(dog.state(at: 5.5) == .connecting)
}
@Test func neverConnectedSourceAlsoRetries() {
var dog = ConnectionWatchdog(now: 0)
let retry5 = dog.shouldRetry(at: 5)
#expect(retry5)
}
}
@Suite struct RouteArgumentTests {
@Test func parsesRoutes() {
let args = ["Musubi", "--route", "ZOWIEBOX (PGM)=1", "-NSFoo", "YES", "--route", "MV=0"]
#expect(RouteArgument.parse(args) == [
.init(ndiName: "ZOWIEBOX (PGM)", screenIndex: 1),
.init(ndiName: "MV", screenIndex: 0),
])
}
@Test func ignoresMalformed() {
#expect(RouteArgument.parse(["--route", "noindex", "--route", "=1", "--route", "A=-1", "--route"]).isEmpty)
}
}

Powered by TurnKey Linux.