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.
128 lines
4.9 KiB
128 lines
4.9 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 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)
|
|
}
|
|
}
|