97 lines
3.8 KiB
Swift
97 lines
3.8 KiB
Swift
import AdaptiveReplayKit
|
|
import Foundation
|
|
import Testing
|
|
|
|
/// Purely informational trace-level stats surfaced by `AdaptiveReplayCLI` — computed directly from
|
|
/// raw `decision` records, independent of any `ReplayPolicy` or `ReplayEngine` simulation.
|
|
struct ActivityCoverageStatsTests {
|
|
private static let referenceNow = Date(timeIntervalSinceReferenceDate: 800_000_000)
|
|
|
|
private static func decision(codex: TimeInterval?, claude: TimeInterval?) -> AdaptiveRefreshTraceRecord {
|
|
.decision(
|
|
timestamp: self.referenceNow,
|
|
menuAgeSeconds: nil,
|
|
lowPowerModeEnabled: false,
|
|
thermalState: .nominal,
|
|
reason: "warm",
|
|
delaySeconds: 300,
|
|
codexActivitySeconds: codex,
|
|
claudeActivitySeconds: claude)
|
|
}
|
|
|
|
@Test
|
|
func `an empty trace reports zero decisions and zero fractions`() {
|
|
let stats = ActivityCoverageStats.compute(from: [])
|
|
#expect(stats.decisionCount == 0)
|
|
#expect(stats.sampledCount == 0)
|
|
#expect(stats.activeCount == 0)
|
|
#expect(stats.sampledFraction == 0)
|
|
#expect(stats.activeFraction == 0)
|
|
}
|
|
|
|
@Test
|
|
func `non decision records are ignored entirely`() {
|
|
let records: [AdaptiveRefreshTraceRecord] = [
|
|
.menuOpen(timestamp: Self.referenceNow),
|
|
.refreshCompleted(timestamp: Self.referenceNow),
|
|
]
|
|
let stats = ActivityCoverageStats.compute(from: records)
|
|
#expect(stats.decisionCount == 0)
|
|
}
|
|
|
|
@Test
|
|
func `a decision with neither activity field set counts toward decisionCount but not sampledCount`() {
|
|
let stats = ActivityCoverageStats.compute(from: [Self.decision(codex: nil, claude: nil)])
|
|
#expect(stats.decisionCount == 1)
|
|
#expect(stats.sampledCount == 0)
|
|
#expect(stats.activeCount == 0)
|
|
}
|
|
|
|
@Test
|
|
func `a decision with only one activity field set still counts as sampled`() {
|
|
let stats = ActivityCoverageStats.compute(from: [Self.decision(codex: 500, claude: nil)])
|
|
#expect(stats.sampledCount == 1)
|
|
}
|
|
|
|
@Test
|
|
func `a sampled decision under the active threshold on either CLI counts as active`() {
|
|
let codexActive = ActivityCoverageStats.compute(from: [Self.decision(codex: 100, claude: nil)])
|
|
#expect(codexActive.activeCount == 1)
|
|
|
|
let claudeActive = ActivityCoverageStats.compute(from: [Self.decision(codex: nil, claude: 100)])
|
|
#expect(claudeActive.activeCount == 1)
|
|
}
|
|
|
|
@Test
|
|
func `a sampled decision at or above the active threshold on both CLIs does not count as active`() {
|
|
let stats = ActivityCoverageStats.compute(from: [Self.decision(codex: 500, claude: 400)])
|
|
#expect(stats.sampledCount == 1)
|
|
#expect(stats.activeCount == 0)
|
|
}
|
|
|
|
@Test
|
|
func `fractions are computed against decisionCount and sampledCount respectively`() {
|
|
let records: [AdaptiveRefreshTraceRecord] = [
|
|
Self.decision(codex: 100, claude: nil), // sampled, active
|
|
Self.decision(codex: 500, claude: 400), // sampled, not active
|
|
Self.decision(codex: nil, claude: nil), // not sampled
|
|
Self.decision(codex: nil, claude: nil), // not sampled
|
|
]
|
|
let stats = ActivityCoverageStats.compute(from: records)
|
|
#expect(stats.decisionCount == 4)
|
|
#expect(stats.sampledCount == 2)
|
|
#expect(stats.activeCount == 1)
|
|
#expect(stats.sampledFraction == 0.5)
|
|
#expect(stats.activeFraction == 0.5)
|
|
}
|
|
|
|
@Test
|
|
func `a custom active threshold changes the active classification`() {
|
|
let stats = ActivityCoverageStats.compute(
|
|
from: [Self.decision(codex: 250, claude: nil)],
|
|
activeThresholdSeconds: 60)
|
|
#expect(stats.sampledCount == 1)
|
|
#expect(stats.activeCount == 0)
|
|
}
|
|
}
|