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) } }