chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:22:33 +08:00
commit fc4fcbab58
1848 changed files with 472303 additions and 0 deletions
@@ -0,0 +1,96 @@
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)
}
}
@@ -0,0 +1,100 @@
import AdaptiveRefreshCore
import Foundation
import Testing
struct AdaptiveRefreshPolicyCoreTests {
private static let referenceNow = Date(timeIntervalSinceReferenceDate: 800_000_000)
private func input(
ageSeconds: TimeInterval?,
lowPowerModeEnabled: Bool = false,
thermalPressure: AdaptiveRefreshPolicyCore.ThermalPressure = .nominal)
-> AdaptiveRefreshPolicyCore.Input
{
AdaptiveRefreshPolicyCore.Input(
now: Self.referenceNow,
lastMenuOpenAt: ageSeconds.map { Self.referenceNow.addingTimeInterval(-$0) },
lowPowerModeEnabled: lowPowerModeEnabled,
thermalPressure: thermalPressure)
}
@Test(arguments: [
(-600.0, AdaptiveRefreshPolicyCore.Reason.recentInteraction, 120),
(0.0, .recentInteraction, 120),
(299.0, .recentInteraction, 120),
(300.0, .recentInteraction, 120),
(301.0, .warm, 300),
(3599.0, .warm, 300),
(3600.0, .warm, 300),
(3601.0, .idle, 900),
(14399.0, .idle, 900),
(14400.0, .longIdle, 1800),
(100_000.0, .longIdle, 1800),
])
func `age determines the canonical table boundary`(
ageSeconds: TimeInterval,
expectedReason: AdaptiveRefreshPolicyCore.Reason,
expectedDelaySeconds: Int)
{
let decision = AdaptiveRefreshPolicyCore().nextDelay(for: self.input(ageSeconds: ageSeconds))
#expect(decision.reason == expectedReason)
#expect(decision.delay == .seconds(expectedDelaySeconds))
}
@Test
func `nil last menu open is long idle`() {
let decision = AdaptiveRefreshPolicyCore().nextDelay(for: self.input(ageSeconds: nil))
#expect(decision.reason == .longIdle)
#expect(decision.delay == .seconds(30 * 60))
}
@Test
func `low power mode wins over recent interaction`() {
let decision = AdaptiveRefreshPolicyCore().nextDelay(for: self.input(
ageSeconds: 0,
lowPowerModeEnabled: true))
#expect(decision.reason == .constrained)
#expect(decision.delay == .seconds(30 * 60))
}
@Test
func `thermal pressure wins when no menu open is recorded`() {
let decision = AdaptiveRefreshPolicyCore().nextDelay(for: self.input(
ageSeconds: nil,
thermalPressure: .constrained))
#expect(decision.reason == .constrained)
#expect(decision.delay == .seconds(30 * 60))
}
@Test
func `future timestamps read as recent`() {
let decision = AdaptiveRefreshPolicyCore().nextDelay(for: self.input(ageSeconds: -1_000_000))
#expect(decision.reason == .recentInteraction)
#expect(decision.delay == .seconds(2 * 60))
}
@Test
func `every decision stays within the two to thirty minute bounds`() {
let ages: [TimeInterval?] = [nil, -1_000_000, 0, 300, 301, 3600, 3601, 14399, 14400, 1_000_000]
for age in ages {
for lowPowerModeEnabled in [false, true] {
for thermalPressure in [
AdaptiveRefreshPolicyCore.ThermalPressure.nominal,
.constrained,
] {
let decision = AdaptiveRefreshPolicyCore().nextDelay(for: self.input(
ageSeconds: age,
lowPowerModeEnabled: lowPowerModeEnabled,
thermalPressure: thermalPressure))
#expect(decision.delay >= .seconds(2 * 60))
#expect(decision.delay <= .seconds(30 * 60))
}
}
}
}
@Test
func `nominal heuristic interval remains five minutes`() {
#expect(AdaptiveRefreshPolicyCore.nominalIntervalForHeuristics == 5 * 60)
}
}
@@ -0,0 +1,282 @@
import AdaptiveReplayKit
import Foundation
import Testing
/// Hand-computed metric checks against small synthetic traces, plus determinism and baseline
/// (manual/fixed) sanity checks for `ReplayEngine`. Trace construction stays in-code (no fixture
/// files): each trace is small enough that its expected metrics can be derived by hand in the
/// comments beside it, which is the actual verification for requirement 4 ("metric math verified
/// against hand-computed values").
struct AdaptiveReplayEngineTests {
private static let epoch = Date(timeIntervalSinceReferenceDate: 0)
private func at(_ seconds: TimeInterval) -> Date {
Self.epoch.addingTimeInterval(seconds)
}
/// A one-hour span (t=0...3600) pinned by two `decision` boundary records, `FixedIntervalPolicy`
/// refreshing every 10 minutes, and four `menuOpen` events chosen so each falls a different,
/// hand-computable number of seconds after the preceding simulated refresh.
///
/// Refreshes land at t=600,1200,...,3600 (6 total: cursor starts at 0, and 3600 <= end is still
/// included). Staleness samples: menuOpen@50 -> 50-0=50 (no refresh yet, falls back to
/// time-since-trace-start); @900 -> 900-600=300; @2200 -> 2200-1800=400; @3500 -> 3500-3000=500.
/// mean=(50+300+400+500)/4=312.5, median (nearest-rank, sorted=[50,300,400,500])=sorted[1]=300,
/// p95=sorted[3]=500.
private func fixedCadenceTrace() -> [AdaptiveRefreshTraceRecord] {
[
.decision(
timestamp: self.at(0),
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800),
.decision(
timestamp: self.at(3600),
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800),
.menuOpen(timestamp: self.at(50)),
.menuOpen(timestamp: self.at(900)),
.menuOpen(timestamp: self.at(2200)),
.menuOpen(timestamp: self.at(3500)),
]
}
@Test
func `fixed cadence refresh count and staleness match hand computation`() throws {
let metrics = ReplayEngine.run(trace: self.fixedCadenceTrace(), policy: FixedIntervalPolicy(minutes: 10))
#expect(metrics.totalRefreshCount == 6)
#expect(metrics.simulatedSpanSeconds == 3600.0)
#expect(metrics.refreshCountPer24h == 144.0) // 6 refreshes/hour * 24h
#expect(metrics.interactionAdvanceCount == 0) // fixed cadence never advances on interaction
let staleness = try #require(metrics.stalenessAtMenuOpen)
#expect(staleness.sampleCount == 4)
#expect(staleness.mean == 312.5)
#expect(staleness.median == 300.0)
#expect(staleness.p95 == 500.0)
}
@Test
func `replaying the same trace and policy twice is deterministic`() {
let trace = self.fixedCadenceTrace()
let first = ReplayEngine.run(trace: trace, policy: FixedIntervalPolicy(minutes: 10))
let second = ReplayEngine.run(trace: trace, policy: FixedIntervalPolicy(minutes: 10))
#expect(first == second)
}
@Test
func `manual policy never schedules a refresh`() {
let metrics = ReplayEngine.run(trace: self.fixedCadenceTrace(), policy: ManualPolicy())
#expect(metrics.totalRefreshCount == 0)
#expect(metrics.refreshCountPer24h == 0.0)
}
@Test
func `a trace with no menu-open events reports no staleness stats`() {
let trace: [AdaptiveRefreshTraceRecord] = [
.decision(
timestamp: self.at(0),
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800),
.refreshCompleted(timestamp: self.at(1800)),
]
let metrics = ReplayEngine.run(trace: trace, policy: AdaptiveReplayPolicy())
#expect(metrics.stalenessAtMenuOpen == nil)
}
/// A single constrained (`lowPowerModeEnabled: true`) sample at t=0, held for the whole
/// 0...1000 span (no later sample overrides it), replayed against `FixedIntervalPolicy(2m)`
/// (120s, well under the 30-minute constrained floor).
///
/// decide() is called at cursor = 0,120,240,...,960 (9 calls: the call at 960 computes
/// next=1080 > end=1000 and breaks before appending). All 9 calls see the constrained sample,
/// and every one returns a 120s delay, so all 9 are violations. 8 of those calls' `next` landed
/// at or before 1000 (120,240,...,960), so 8 refreshes were recorded.
private func constrainedTrace() -> [AdaptiveRefreshTraceRecord] {
[
.decision(
timestamp: self.at(0),
menuAgeSeconds: nil,
lowPowerModeEnabled: true,
thermalState: .nominal,
reason: "constrained",
delaySeconds: 1800),
.menuOpen(timestamp: self.at(1000)),
]
}
@Test
func `a policy that ignores the constrained floor is flagged non-compliant`() {
let metrics = ReplayEngine.run(trace: self.constrainedTrace(), policy: FixedIntervalPolicy(minutes: 2))
#expect(metrics.totalRefreshCount == 8)
#expect(metrics.constrainedCompliance.constrainedDecisionCount == 9)
#expect(metrics.constrainedCompliance.violationCount == 9)
#expect(!metrics.constrainedCompliance.isCompliant)
}
@Test
func `the shared adaptive policy honors the constrained floor`() {
let metrics = ReplayEngine.run(trace: self.constrainedTrace(), policy: AdaptiveReplayPolicy())
#expect(metrics.constrainedCompliance.constrainedDecisionCount == 1)
#expect(metrics.constrainedCompliance.violationCount == 0)
#expect(metrics.constrainedCompliance.isCompliant)
// The menu open at t=1000 is still under low-power, so the advance-check itself also
// returns the constrained floor (candidate = 1000+1800 = 2800), which is later than the
// already-scheduled t=1800 tick no advance is taken. Mirrors the real
// `noteMenuOpened(at:)` guard: opening the menu while constrained never shortens the timer.
#expect(metrics.interactionAdvanceCount == 0)
}
@Test
func `an empty trace reports zero metrics without crashing`() {
let metrics = ReplayEngine.run(trace: [], policy: AdaptiveReplayPolicy())
#expect(metrics.totalRefreshCount == 0)
#expect(metrics.simulatedSpanSeconds == 0.0)
#expect(metrics.stalenessAtMenuOpen == nil)
#expect(metrics.constrainedCompliance.constrainedDecisionCount == 0)
#expect(metrics.interactionAdvanceCount == 0)
}
// MARK: - Interaction-advance path (mirrors UsageStore.noteMenuOpened(at:))
/// A 300-second span with a single tick boundary at t=0 (which alone would schedule a longIdle
/// refresh at t=1800, far past the trace's end) and one `menuOpen` at t=50 landing inside that
/// tick's window.
///
/// Hand computation for `AdaptiveReplayPolicy` (`advancesOnInteraction == true`):
/// - cursor=0: decide(now:0, lastMenuOpenAt: nil) -> longIdle, delay=1800 -> next=1800.
/// menuOpen@50 falls in (0, 1800]: decide(now:50, lastMenuOpenAt:50) (age 0) ->
/// recentInteraction, delay=120 -> candidate=170. 170 < 1800, so the schedule advances:
/// next=170 (1 advance so far). next(170) <= end(300), so a refresh lands at t=170.
/// - cursor=170: decide(now:170, lastMenuOpenAt:50) (age 120 <= 300 recentInteractionThreshold)
/// -> recentInteraction, delay=120 -> next=290. No more menu opens to scan. 290 <= 300, so a
/// refresh lands at t=290.
/// - cursor=290: decide(now:290, lastMenuOpenAt:50) (age 240 <= 300) -> recentInteraction,
/// delay=120 -> next=410. 410 > end(300), loop breaks without appending.
///
/// Total: 2 refreshes (170, 290), 1 interaction advance. Without the advance, the *only*
/// schedulable event would be the t=1800 tick, which falls entirely outside this 300s span
/// i.e. `totalRefreshCount` would be 0. The non-zero count here is only possible because the
/// engine reproduces the interaction-advance path.
private func menuOpenAdvanceTrace() -> [AdaptiveRefreshTraceRecord] {
[
.decision(
timestamp: self.at(0),
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800),
.decision(
timestamp: self.at(300),
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800),
.menuOpen(timestamp: self.at(50)),
]
}
@Test
func `a menu open pulls the adaptive schedule forward, matching hand computation`() {
let metrics = ReplayEngine.run(trace: self.menuOpenAdvanceTrace(), policy: AdaptiveReplayPolicy())
#expect(metrics.totalRefreshCount == 2)
#expect(metrics.interactionAdvanceCount == 1)
}
@Test
func `a policy that does not advance on interaction ignores the same menu open`() {
// Same trace, but FixedIntervalPolicy(30m) never overrides `advancesOnInteraction` (stays
// false), matching fixed-cadence refresh frequencies in the real app, which never wire
// `noteMenuOpened(at:)`'s advance check at all. The t=1800 tick falls outside the 300s
// span, so nothing is scheduled the menu open at t=50 has zero scheduling effect.
let metrics = ReplayEngine.run(trace: self.menuOpenAdvanceTrace(), policy: FixedIntervalPolicy(minutes: 30))
#expect(metrics.totalRefreshCount == 0)
#expect(metrics.interactionAdvanceCount == 0)
}
@Test
func `a recorded timerAdvanced ground-truth event agrees with the engine's own recomputation`() throws {
// The menuOpen ground truth plus a timerAdvanced record for the accepted schedule change.
// The offline audit checks that record against the policy's recomputed candidate.
let menuOpenAt = self.at(50)
let recordedCandidate = self.at(170) // menuOpenAt + recentInteractionDelay (120s)
var trace = self.menuOpenAdvanceTrace()
trace.append(.timerAdvanced(
timestamp: menuOpenAt,
previousScheduledAt: self.at(1800),
candidateScheduledAt: recordedCandidate,
reason: "recentInteraction",
delaySeconds: 120))
let policy = AdaptiveReplayPolicy()
let recomputed = policy.decide(ReplayPolicyInput(
now: menuOpenAt,
lastMenuOpenAt: menuOpenAt,
lowPowerModeEnabled: false,
thermalState: .nominal))
let recomputedCandidate = try menuOpenAt.addingTimeInterval(#require(recomputed.delaySeconds))
#expect(recomputedCandidate == recordedCandidate)
// The recorded event doesn't change the metrics (the engine recomputes advances itself,
// independent of any timerAdvanced lines in the trace); replaying still reproduces the
// same two refreshes as the trace without the extra record.
let metrics = ReplayEngine.run(trace: trace, policy: policy)
#expect(metrics.totalRefreshCount == 2)
#expect(metrics.interactionAdvanceCount == 1)
}
/// Two menu opens in the same tick window: the second one's candidate is compared against the
/// *already-advanced* schedule from the first, not the original tick schedule mirroring a
/// real second `noteMenuOpened(at:)` call tightening an already-shortened sleep.
///
/// - cursor=0: decide -> longIdle, next=1800. menuOpen@50: candidate=170 < 1800 -> next=170
/// (advance 1). menuOpen@100 also falls in (0, 170]? No 100 <= 170 is true, so it's still
/// scanned: decide(now:100, lastMenuOpenAt:100) -> recentInteraction, candidate=220. Is 220 <
/// next(170)? No, so this second menu open does *not* further advance the schedule (it would
/// move the refresh *later*, which `shouldAdvanceAdaptiveTimer` never does). next stays 170.
/// - Total: 1 refresh (170), 1 advance (only the first menu open's candidate beat the schedule).
private func twoMenuOpensSameWindowTrace() -> [AdaptiveRefreshTraceRecord] {
[
.decision(
timestamp: self.at(0),
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800),
.decision(
timestamp: self.at(170),
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800),
.menuOpen(timestamp: self.at(50)),
.menuOpen(timestamp: self.at(100)),
]
}
@Test
func `a later menu open in the same window cannot postpone an earlier advance`() {
let metrics = ReplayEngine.run(trace: self.twoMenuOpensSameWindowTrace(), policy: AdaptiveReplayPolicy())
#expect(metrics.totalRefreshCount == 1)
#expect(metrics.interactionAdvanceCount == 1)
}
}
@@ -0,0 +1,77 @@
import AdaptiveReplayKit
import Foundation
import Testing
struct AdaptiveReplayPolicyTests {
private static let referenceNow = Date(timeIntervalSinceReferenceDate: 800_000_000)
private func input(
ageSeconds: TimeInterval?,
lowPowerModeEnabled: Bool = false,
thermalState: ReplayThermalState = .nominal) -> ReplayPolicyInput
{
ReplayPolicyInput(
now: Self.referenceNow,
lastMenuOpenAt: ageSeconds.map { Self.referenceNow.addingTimeInterval(-$0) },
lowPowerModeEnabled: lowPowerModeEnabled,
thermalState: thermalState)
}
@Test(arguments: [
(0.0, "recentInteraction", 120.0),
(301.0, "warm", 300.0),
(3601.0, "idle", 900.0),
(14400.0, "longIdle", 1800.0),
])
func `replay adapter preserves canonical decisions`(
ageSeconds: TimeInterval,
expectedReason: String,
expectedDelaySeconds: TimeInterval)
{
let decision = AdaptiveReplayPolicy().decide(self.input(ageSeconds: ageSeconds))
#expect(decision.reason == expectedReason)
#expect(decision.delaySeconds == expectedDelaySeconds)
}
@Test(arguments: [ReplayThermalState.serious, .critical])
func `replay adapter maps serious and critical thermal states to constrained`(
thermalState: ReplayThermalState)
{
let decision = AdaptiveReplayPolicy().decide(self.input(ageSeconds: 0, thermalState: thermalState))
#expect(decision.reason == "constrained")
#expect(decision.delaySeconds == TimeInterval(30 * 60))
}
@Test
func `replay adapter preserves low power precedence`() {
let decision = AdaptiveReplayPolicy().decide(self.input(
ageSeconds: 0,
lowPowerModeEnabled: true,
thermalState: .nominal))
#expect(decision.reason == "constrained")
#expect(decision.delaySeconds == TimeInterval(30 * 60))
}
@Test(arguments: [ReplayThermalState.nominal, .fair])
func `replay adapter maps nominal and fair thermal states to unconstrained`(
thermalState: ReplayThermalState)
{
let decision = AdaptiveReplayPolicy().decide(self.input(ageSeconds: 0, thermalState: thermalState))
#expect(decision.reason == "recentInteraction")
#expect(decision.delaySeconds == TimeInterval(2 * 60))
}
@Test
func `only adaptive replay advances on interaction`() {
#expect(AdaptiveReplayPolicy().advancesOnInteraction)
#expect(!FixedIntervalPolicy(minutes: 5).advancesOnInteraction)
#expect(!ManualPolicy().advancesOnInteraction)
}
@Test
func `fixed interval conversion cannot overflow integer multiplication`() {
let decision = FixedIntervalPolicy(minutes: Int.max).decide(self.input(ageSeconds: 0))
#expect(decision.delaySeconds == TimeInterval(Int.max) * 60)
#expect(decision.delaySeconds?.isFinite == true)
}
}
@@ -0,0 +1,322 @@
import AdaptiveReplayKit
import Foundation
import Testing
struct AdaptiveReplayTraceParserTests {
private static let referenceNow = Date(timeIntervalSinceReferenceDate: 800_000_000)
private func encode(_ record: AdaptiveRefreshTraceRecord) throws -> String {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let data = try encoder.encode(record)
return try #require(String(data: data, encoding: .utf8))
}
@Test
func `parses a well-formed trace and preserves record order`() throws {
let records: [AdaptiveRefreshTraceRecord] = [
.menuOpen(timestamp: Self.referenceNow),
.decision(
timestamp: Self.referenceNow.addingTimeInterval(1),
menuAgeSeconds: 1,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "recentInteraction",
delaySeconds: 120),
.refreshCompleted(timestamp: Self.referenceNow.addingTimeInterval(121)),
]
let text = try records.map(self.encode).joined(separator: "\n")
let parsed = try AdaptiveRefreshTraceParser.parse(text)
#expect(parsed.count == 3)
#expect(parsed[0].kind == .menuOpen)
#expect(parsed[1].kind == .decision)
#expect(parsed[1].reason == "recentInteraction")
#expect(parsed[1].delaySeconds == 120.0)
#expect(parsed[2].kind == .refreshCompleted)
}
@Test
func `ignores blank lines between records`() throws {
let record = AdaptiveRefreshTraceRecord.menuOpen(timestamp: Self.referenceNow)
let text = try "\n\(self.encode(record))\n\n"
let parsed = try AdaptiveRefreshTraceParser.parse(text)
#expect(parsed.count == 1)
}
@Test
func `strict parsing accepts multiple CRLF records`() throws {
let records: [AdaptiveRefreshTraceRecord] = [
.menuOpen(timestamp: Self.referenceNow),
.refreshCompleted(timestamp: Self.referenceNow.addingTimeInterval(1)),
]
let text = try records.map(self.encode).joined(separator: "\r\n")
let parsed = try AdaptiveRefreshTraceParser.parse(text)
#expect(parsed.map(\.kind) == [.menuOpen, .refreshCompleted])
}
@Test
func `empty trace parses to zero records`() throws {
let parsed = try AdaptiveRefreshTraceParser.parse("")
#expect(parsed.isEmpty)
}
@Test
func `a malformed line fails the whole parse with a line number`() throws {
let good = try self.encode(.menuOpen(timestamp: Self.referenceNow))
let text = "\(good)\nnot json\n\(good)"
#expect(throws: AdaptiveRefreshTraceParseError.self) {
try AdaptiveRefreshTraceParser.parse(text)
}
do {
_ = try AdaptiveRefreshTraceParser.parse(text)
Issue.record("expected parse to throw")
} catch let error as AdaptiveRefreshTraceParseError {
#expect(error.lineNumber == 2)
#expect(error.content == "not json")
} catch {
Issue.record("unexpected error type: \(error)")
}
}
@Test
func `tolerant parsing skips malformed lines instead of failing`() throws {
let good = try self.encode(.menuOpen(timestamp: Self.referenceNow))
let text = "\(good)\nnot json\n\(good)"
let parsed = AdaptiveRefreshTraceParser.parseTolerantly(text)
#expect(parsed.count == 2)
}
@Test
func `tolerant parsing accepts CRLF and skips only malformed records`() throws {
let menuOpen = try self.encode(.menuOpen(timestamp: Self.referenceNow))
let refresh = try self.encode(.refreshCompleted(timestamp: Self.referenceNow.addingTimeInterval(1)))
let text = [menuOpen, "not json", refresh].joined(separator: "\r\n")
let parsed = AdaptiveRefreshTraceParser.parseTolerantly(text)
#expect(parsed.map(\.kind) == [.menuOpen, .refreshCompleted])
}
/// `timerAdvanced` round-trips its two extra fields (`previousScheduledAt`,
/// `candidateScheduledAt`) and leaves the signal fields (`menuAgeSeconds`,
/// `lowPowerModeEnabled`, `thermalState`) nil, matching the type's field-presence contract.
@Test
func `parses a timerAdvanced record and preserves its schedule fields`() throws {
let record = AdaptiveRefreshTraceRecord.timerAdvanced(
timestamp: Self.referenceNow,
previousScheduledAt: Self.referenceNow.addingTimeInterval(1800),
candidateScheduledAt: Self.referenceNow.addingTimeInterval(120),
reason: "recentInteraction",
delaySeconds: 120)
let text = try self.encode(record)
let parsed = try AdaptiveRefreshTraceParser.parse(text)
#expect(parsed.count == 1)
#expect(parsed[0].kind == .timerAdvanced)
#expect(parsed[0].reason == "recentInteraction")
#expect(parsed[0].delaySeconds == 120.0)
#expect(parsed[0].previousScheduledAt == Self.referenceNow.addingTimeInterval(1800))
#expect(parsed[0].candidateScheduledAt == Self.referenceNow.addingTimeInterval(120))
#expect(parsed[0].menuAgeSeconds == nil)
#expect(parsed[0].lowPowerModeEnabled == nil)
#expect(parsed[0].thermalState == nil)
}
/// A `timerAdvanced` record whose advance had no prior schedule (`previousScheduledAt == nil`)
/// the "always advance" case `UsageStore.shouldAdvanceAdaptiveTimer` returns for a nil
/// `scheduledAt` round-trips the nil correctly rather than defaulting to some sentinel date.
@Test
func `a timerAdvanced record with no previous schedule round-trips a nil previousScheduledAt`() throws {
let record = AdaptiveRefreshTraceRecord.timerAdvanced(
timestamp: Self.referenceNow,
previousScheduledAt: nil,
candidateScheduledAt: Self.referenceNow.addingTimeInterval(120),
reason: "recentInteraction",
delaySeconds: 120)
let text = try self.encode(record)
let parsed = try AdaptiveRefreshTraceParser.parse(text)
#expect(parsed[0].previousScheduledAt == nil)
}
/// Backward compatibility: the ~500 pre-existing lines in this machine's live trace were
/// written before `codexActivitySeconds`/`claudeActivitySeconds` existed. A hand-written
/// old-format `decision` line (no activity keys at all) must still decode, with both new
/// fields nil rather than failing to parse.
@Test
func `an old-format decision line without activity fields decodes with nil activity signals`() throws {
let oldFormatLine = """
{"kind":"decision","timestamp":"2026-01-01T00:00:00Z","menuAgeSeconds":30,\
"lowPowerModeEnabled":false,"thermalState":"nominal","reason":"longIdle","delaySeconds":1800}
"""
let parsed = try AdaptiveRefreshTraceParser.parse(oldFormatLine)
#expect(parsed.count == 1)
#expect(parsed[0].reason == "longIdle")
#expect(parsed[0].codexActivitySeconds == nil)
#expect(parsed[0].claudeActivitySeconds == nil)
}
/// A `decision` record carrying both activity signals round-trips them exactly.
@Test
func `a decision record with activity signals round-trips both values`() throws {
let record = AdaptiveRefreshTraceRecord.decision(
timestamp: Self.referenceNow,
menuAgeSeconds: 5,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "recentInteraction",
delaySeconds: 120,
codexActivitySeconds: 42,
claudeActivitySeconds: 99)
let text = try self.encode(record)
let parsed = try AdaptiveRefreshTraceParser.parse(text)
#expect(parsed[0].codexActivitySeconds == 42)
#expect(parsed[0].claudeActivitySeconds == 99)
}
/// Encoding must omit nil activity fields rather than emitting explicit `null`s, so old
/// tooling and hand-inspection of a trace stay unsurprised by fields it doesn't expect.
@Test
func `encoding a decision with nil activity signals omits both keys entirely`() throws {
let record = AdaptiveRefreshTraceRecord.decision(
timestamp: Self.referenceNow,
menuAgeSeconds: 5,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "recentInteraction",
delaySeconds: 120)
let text = try self.encode(record)
#expect(!text.contains("codexActivitySeconds"))
#expect(!text.contains("claudeActivitySeconds"))
}
/// Backward compatibility for the "B layer" (session duration / transcript bytes /
/// active-transcript count): an old-format line written before those three fields per CLI
/// existed including one already carrying the earlier "A layer" activity-seconds fields
/// must still decode, with all six new fields nil.
@Test
func `an old-format decision line without B-layer fields decodes with nil B-layer signals`() throws {
let oldFormatLine = """
{"kind":"decision","timestamp":"2026-01-01T00:00:00Z","menuAgeSeconds":30,\
"lowPowerModeEnabled":false,"thermalState":"nominal","reason":"longIdle","delaySeconds":1800,\
"codexActivitySeconds":42,"claudeActivitySeconds":99}
"""
let parsed = try AdaptiveRefreshTraceParser.parse(oldFormatLine)
#expect(parsed.count == 1)
#expect(parsed[0].codexActivitySeconds == 42)
#expect(parsed[0].claudeActivitySeconds == 99)
#expect(parsed[0].codexSessionDurationSeconds == nil)
#expect(parsed[0].claudeSessionDurationSeconds == nil)
#expect(parsed[0].codexTranscriptBytes == nil)
#expect(parsed[0].claudeTranscriptBytes == nil)
#expect(parsed[0].codexActiveTranscriptCount == nil)
#expect(parsed[0].claudeActiveTranscriptCount == nil)
}
/// A trace mixing a pre-B-layer line, a pre-A-layer (original phase 1) line, and a full
/// current-format line all parse together the parser never requires every line in a trace
/// to share the same schema vintage.
@Test
func `a trace mixing old and new format decision lines parses every line`() throws {
let phase1Line = """
{"kind":"decision","timestamp":"2026-01-01T00:00:00Z","reason":"longIdle","delaySeconds":1800}
"""
let aLayerOnlyLine = """
{"kind":"decision","timestamp":"2026-01-02T00:00:00Z","reason":"warm","delaySeconds":300,\
"codexActivitySeconds":10}
"""
let currentLine = try self.encode(.decision(
timestamp: Self.referenceNow,
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "recentInteraction",
delaySeconds: 120,
codexActivitySeconds: 1,
claudeActivitySeconds: 2,
codexSessionDurationSeconds: 3,
claudeSessionDurationSeconds: 4,
codexTranscriptBytes: 5,
claudeTranscriptBytes: 6,
codexActiveTranscriptCount: 7,
claudeActiveTranscriptCount: 8))
let text = [phase1Line, aLayerOnlyLine, currentLine].joined(separator: "\n")
let parsed = try AdaptiveRefreshTraceParser.parse(text)
#expect(parsed.count == 3)
#expect(parsed[0].codexActivitySeconds == nil)
#expect(parsed[1].codexActivitySeconds == 10)
#expect(parsed[1].codexSessionDurationSeconds == nil)
#expect(parsed[2].codexSessionDurationSeconds == 3)
#expect(parsed[2].claudeActiveTranscriptCount == 8)
}
/// A `decision` record carrying all six B-layer fields round-trips them exactly.
@Test
func `a decision record with B-layer fields round-trips all six values`() throws {
let record = AdaptiveRefreshTraceRecord.decision(
timestamp: Self.referenceNow,
menuAgeSeconds: 5,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "recentInteraction",
delaySeconds: 120,
codexSessionDurationSeconds: 600,
claudeSessionDurationSeconds: 900,
codexTranscriptBytes: 12345,
claudeTranscriptBytes: 67890,
codexActiveTranscriptCount: 2,
claudeActiveTranscriptCount: 4)
let text = try self.encode(record)
let parsed = try AdaptiveRefreshTraceParser.parse(text)
#expect(parsed[0].codexSessionDurationSeconds == 600)
#expect(parsed[0].claudeSessionDurationSeconds == 900)
#expect(parsed[0].codexTranscriptBytes == 12345)
#expect(parsed[0].claudeTranscriptBytes == 67890)
#expect(parsed[0].codexActiveTranscriptCount == 2)
#expect(parsed[0].claudeActiveTranscriptCount == 4)
}
/// Encoding must omit nil B-layer fields rather than emitting explicit `null`s, matching the
/// A-layer's contract.
@Test
func `encoding a decision with nil B-layer fields omits all six keys entirely`() throws {
let record = AdaptiveRefreshTraceRecord.decision(
timestamp: Self.referenceNow,
menuAgeSeconds: 5,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "recentInteraction",
delaySeconds: 120)
let text = try self.encode(record)
#expect(!text.contains("codexSessionDurationSeconds"))
#expect(!text.contains("claudeSessionDurationSeconds"))
#expect(!text.contains("codexTranscriptBytes"))
#expect(!text.contains("claudeTranscriptBytes"))
#expect(!text.contains("codexActiveTranscriptCount"))
#expect(!text.contains("claudeActiveTranscriptCount"))
}
}
@@ -0,0 +1,141 @@
import AdaptiveReplayKit
import Foundation
import Testing
struct CodingActivityReplayPolicyTests {
private static let now = Date(timeIntervalSinceReferenceDate: 10000)
private func input(
menuAge: TimeInterval?,
activityAge: TimeInterval?,
constrained: Bool = false) -> ReplayPolicyInput
{
ReplayPolicyInput(
now: Self.now,
lastMenuOpenAt: menuAge.map { Self.now.addingTimeInterval(-$0) },
lastCodingActivityAt: activityAge.map { Self.now.addingTimeInterval(-$0) },
lowPowerModeEnabled: constrained,
thermalState: .nominal)
}
@Test
func `active coding caps idle and long-idle decisions at five minutes`() {
let policy = CodingActivityAdaptivePolicy()
#expect(policy.name == "adaptive-activity")
#expect(policy.decide(self.input(menuAge: 2 * 3600, activityAge: 10)).delaySeconds == 300)
#expect(policy.decide(self.input(menuAge: nil, activityAge: 10)).delaySeconds == 300)
#expect(policy.decide(self.input(menuAge: nil, activityAge: 10)).reason == "codingActivity")
}
@Test
func `existing recent and warm decisions remain unchanged`() {
let policy = CodingActivityAdaptivePolicy()
#expect(policy.decide(self.input(menuAge: 60, activityAge: 10)).delaySeconds == 120)
#expect(policy.decide(self.input(menuAge: 600, activityAge: 10)).delaySeconds == 300)
}
@Test
func `constrained state wins and exact threshold is inactive`() {
let policy = CodingActivityAdaptivePolicy()
#expect(policy.decide(self.input(menuAge: nil, activityAge: 10, constrained: true)).delaySeconds == 1800)
#expect(policy.decide(self.input(menuAge: nil, activityAge: 300)).delaySeconds == 1800)
#expect(policy.decide(self.input(menuAge: nil, activityAge: nil)).delaySeconds == 1800)
}
@Test
func `future activity samples never backfill an earlier replay decision`() {
let trace: [AdaptiveRefreshTraceRecord] = [
.decision(
timestamp: Self.now,
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800),
.decision(
timestamp: Self.now.addingTimeInterval(600),
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800,
codexActivitySeconds: 0),
]
let metrics = ReplayEngine.run(trace: trace, policy: CodingActivityAdaptivePolicy())
#expect(metrics.codingActiveDecisionCount == 0)
#expect(metrics.totalRefreshCount == 0)
}
@Test
func `a newer unavailable observation invalidates older activity`() {
let trace: [AdaptiveRefreshTraceRecord] = [
.menuOpen(timestamp: Self.now),
.decision(
timestamp: Self.now,
menuAgeSeconds: 0,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "recentInteraction",
delaySeconds: 120,
codexActivitySeconds: 0),
.decision(
timestamp: Self.now.addingTimeInterval(100),
menuAgeSeconds: 100,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "recentInteraction",
delaySeconds: 120),
.refreshCompleted(timestamp: Self.now.addingTimeInterval(240)),
]
let metrics = ReplayEngine.run(trace: trace, policy: CodingActivityAdaptivePolicy())
#expect(metrics.totalRefreshCount == 2)
#expect(metrics.codingActiveDecisionCount == 1)
}
@Test
func `active compliance denominator excludes constrained decisions`() {
let trace: [AdaptiveRefreshTraceRecord] = [
.decision(
timestamp: Self.now,
menuAgeSeconds: nil,
lowPowerModeEnabled: true,
thermalState: .nominal,
reason: "constrained",
delaySeconds: 1800,
codexActivitySeconds: 0),
.refreshCompleted(timestamp: Self.now.addingTimeInterval(1800)),
]
let metrics = ReplayEngine.run(trace: trace, policy: CodingActivityAdaptivePolicy())
#expect(metrics.codingActiveDecisionCount == 0)
#expect(metrics.codingActiveDelayViolationCount == 0)
}
@Test
func `manual policy counts as slower than the active freshness cap`() {
let trace: [AdaptiveRefreshTraceRecord] = [
.decision(
timestamp: Self.now,
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: 1800,
codexActivitySeconds: 0),
.refreshCompleted(timestamp: Self.now.addingTimeInterval(600)),
]
let metrics = ReplayEngine.run(trace: trace, policy: ManualPolicy())
#expect(metrics.codingActiveDecisionCount == 1)
#expect(metrics.codingActiveDelayViolationCount == 1)
}
}
@@ -0,0 +1,246 @@
import AdaptiveReplayKit
import Foundation
import Testing
struct RecordedScheduleAuditTests {
private static let epoch = Date(timeIntervalSinceReferenceDate: 0)
private func at(_ seconds: TimeInterval) -> Date {
Self.epoch.addingTimeInterval(seconds)
}
@Test
func `legacy recorded advance validates without evaluation records`() {
let menu = self.at(50)
let trace: [AdaptiveRefreshTraceRecord] = [
.menuOpen(timestamp: menu),
.timerAdvanced(
timestamp: menu,
previousScheduledAt: self.at(1800),
candidateScheduledAt: self.at(170),
reason: "recentInteraction",
delaySeconds: 120),
]
let audit = RecordedScheduleAuditor.audit(trace)
#expect(audit.isValid)
#expect(audit.recordedAdvanceCount == 1)
#expect(audit.evaluatedCount == 0)
}
@Test
func `accepted and rejected live evaluations audit independently of replay`() {
let accepted = AdaptiveRefreshTraceRecord.timerAdvanceEvaluated(
timestamp: self.at(50),
previousScheduledAt: self.at(1800),
candidateScheduledAt: self.at(170),
reason: "recentInteraction",
delaySeconds: 120,
accepted: true,
refreshInFlight: false)
let rejected = AdaptiveRefreshTraceRecord.timerAdvanceEvaluated(
timestamp: self.at(100),
previousScheduledAt: self.at(170),
candidateScheduledAt: self.at(220),
reason: "recentInteraction",
delaySeconds: 120,
accepted: false,
refreshInFlight: true)
let trace: [AdaptiveRefreshTraceRecord] = [
.menuOpen(timestamp: self.at(50)),
accepted,
.timerAdvanced(
timestamp: self.at(50),
previousScheduledAt: self.at(1800),
candidateScheduledAt: self.at(170),
reason: "recentInteraction",
delaySeconds: 120),
.menuOpen(timestamp: self.at(100)),
rejected,
]
let audit = RecordedScheduleAuditor.audit(trace)
#expect(audit.isValid)
#expect(audit.evaluatedCount == 2)
#expect(audit.acceptedEvaluationCount == 1)
#expect(audit.rejectedEvaluationCount == 1)
#expect(audit.ambiguousComparisonCount == 0)
}
@Test
func `evaluation whose accepted flag disagrees with schedule comparison fails`() {
let trace: [AdaptiveRefreshTraceRecord] = [
.timerAdvanceEvaluated(
timestamp: self.at(100),
previousScheduledAt: self.at(170),
candidateScheduledAt: self.at(220),
reason: "recentInteraction",
delaySeconds: 120,
accepted: true,
refreshInFlight: false),
]
let audit = RecordedScheduleAuditor.audit(trace)
#expect(!audit.isValid)
#expect(audit.decisionMismatchCount == 1)
#expect(audit.payloadMismatchCount == 1)
}
@Test
func `unequal schedule dates override a contradictory exact lead`() {
let event = AdaptiveRefreshTraceRecord(
kind: .timerAdvanceEvaluated,
timestamp: self.at(50),
reason: "recentInteraction",
delaySeconds: 120,
previousScheduledAt: self.at(180),
candidateScheduledAt: self.at(170),
timerAdvanceAccepted: false,
scheduleLeadSeconds: -10,
refreshInFlight: false)
let audit = RecordedScheduleAuditor.audit([.menuOpen(timestamp: self.at(50)), event])
#expect(audit.decisionMismatchCount == 1)
#expect(!audit.isValid)
}
@Test
func `accepted evaluation without a previous schedule remains valid`() {
let event = AdaptiveRefreshTraceRecord.timerAdvanceEvaluated(
timestamp: self.at(50),
previousScheduledAt: nil,
candidateScheduledAt: self.at(170),
reason: "recentInteraction",
delaySeconds: 120,
accepted: true,
refreshInFlight: false)
let advanced = AdaptiveRefreshTraceRecord.timerAdvanced(
timestamp: self.at(50),
previousScheduledAt: nil,
candidateScheduledAt: self.at(170),
reason: "recentInteraction",
delaySeconds: 120)
let audit = RecordedScheduleAuditor.audit([.menuOpen(timestamp: self.at(50)), event, advanced])
#expect(audit.isValid)
}
@Test
func `fractional live lead survives whole-second date serialization`() throws {
let timestamp = self.at(50.2)
let candidate = self.at(170.2)
let previous = self.at(170.8)
let record = AdaptiveRefreshTraceRecord.timerAdvanceEvaluated(
timestamp: timestamp,
previousScheduledAt: previous,
candidateScheduledAt: candidate,
reason: "recentInteraction",
delaySeconds: 120,
accepted: true,
refreshInFlight: false)
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let line = try #require(String(data: encoder.encode(record), encoding: .utf8))
let parsed = try #require(AdaptiveRefreshTraceParser.parse(line).first)
#expect(parsed.previousScheduledAt == parsed.candidateScheduledAt)
#expect(try abs(#require(parsed.scheduleLeadSeconds) - 0.6) < 0.001)
let advanced = try AdaptiveRefreshTraceRecord.timerAdvanced(
timestamp: parsed.timestamp,
previousScheduledAt: parsed.previousScheduledAt,
candidateScheduledAt: #require(parsed.candidateScheduledAt),
reason: #require(parsed.reason),
delaySeconds: #require(parsed.delaySeconds))
#expect(RecordedScheduleAuditor.audit([.menuOpen(timestamp: parsed.timestamp), parsed, advanced]).isValid)
}
@Test
func `legacy equal timestamps are reported as ambiguous instead of mismatched`() {
let event = AdaptiveRefreshTraceRecord(
kind: .timerAdvanceEvaluated,
timestamp: self.at(50),
reason: "recentInteraction",
delaySeconds: 120,
previousScheduledAt: self.at(170),
candidateScheduledAt: self.at(170),
timerAdvanceAccepted: true,
refreshInFlight: false)
let audit = RecordedScheduleAuditor.audit([.menuOpen(timestamp: self.at(50)), event])
#expect(audit.decisionMismatchCount == 0)
#expect(audit.ambiguousComparisonCount == 1)
#expect(!audit.isValid)
}
@Test
func `evaluation without a menu-open source fails linkage audit`() {
let event = AdaptiveRefreshTraceRecord.timerAdvanceEvaluated(
timestamp: self.at(50),
previousScheduledAt: self.at(1800),
candidateScheduledAt: self.at(170),
reason: "recentInteraction",
delaySeconds: 120,
accepted: true,
refreshInFlight: false)
let audit = RecordedScheduleAuditor.audit([event])
#expect(audit.menuLinkMismatchCount == 1)
#expect(!audit.isValid)
}
@Test
func `duplicate accepted evaluations require matching advance multiplicity`() {
let evaluation = AdaptiveRefreshTraceRecord.timerAdvanceEvaluated(
timestamp: self.at(50),
previousScheduledAt: self.at(1800),
candidateScheduledAt: self.at(170),
reason: "recentInteraction",
delaySeconds: 120,
accepted: true,
refreshInFlight: false)
let advance = AdaptiveRefreshTraceRecord.timerAdvanced(
timestamp: self.at(50),
previousScheduledAt: self.at(1800),
candidateScheduledAt: self.at(170),
reason: "recentInteraction",
delaySeconds: 120)
let audit = RecordedScheduleAuditor.audit([
.menuOpen(timestamp: self.at(50)),
evaluation,
evaluation,
advance,
])
#expect(audit.payloadMismatchCount == 1)
#expect(!audit.isValid)
}
@Test
func `duplicate rejected evaluations require distinct menu opens`() {
let evaluation = AdaptiveRefreshTraceRecord.timerAdvanceEvaluated(
timestamp: self.at(50),
previousScheduledAt: self.at(170),
candidateScheduledAt: self.at(220),
reason: "recentInteraction",
delaySeconds: 170,
accepted: false,
refreshInFlight: true)
let audit = RecordedScheduleAuditor.audit([
.menuOpen(timestamp: self.at(50)),
evaluation,
evaluation,
])
#expect(audit.menuLinkMismatchCount == 1)
#expect(!audit.isValid)
}
}
@@ -0,0 +1,117 @@
import AdaptiveReplayKit
import Foundation
import Testing
struct ReplayTraceSegmentationTests {
private static let epoch = Date(timeIntervalSinceReferenceDate: 0)
private func at(_ seconds: TimeInterval) -> Date {
Self.epoch.addingTimeInterval(seconds)
}
private func decision(_ seconds: TimeInterval, delay: TimeInterval = 600) -> AdaptiveRefreshTraceRecord {
.decision(
timestamp: self.at(seconds),
menuAgeSeconds: nil,
lowPowerModeEnabled: false,
thermalState: .nominal,
reason: "longIdle",
delaySeconds: delay)
}
@Test
func `segmentation excludes only time beyond the expected deadline`() {
let firstRun = stride(from: 0.0, through: 3000.0, by: 600.0).map { self.decision($0) }
let secondRun = stride(from: 68400.0, through: 71400.0, by: 600.0).map { self.decision($0) }
let trace = firstRun + secondRun + [.refreshCompleted(timestamp: self.at(72000))]
let report = ReplayTraceSegmenter.automatic(trace)
#expect(report.segments.count == 2)
#expect(report.segments[0].start == self.at(0))
#expect(report.segments[0].end == self.at(3600))
#expect(report.segments[1].start == self.at(68400))
#expect(report.segments[1].end == self.at(72000))
#expect(report.excludedGapSeconds == 18 * 60 * 60)
#expect(report.includedSpanSeconds == 2 * 60 * 60)
}
@Test
func `segmented rate uses summed span instead of averaging segment rates`() {
let firstRun = stride(from: 0.0, through: 3000.0, by: 600.0).map { self.decision($0) }
let secondRun = stride(from: 68400.0, through: 71400.0, by: 600.0).map { self.decision($0) }
let trace = firstRun + secondRun + [.refreshCompleted(timestamp: self.at(72000))]
let metrics = ReplayEngine.runSegmented(trace: trace, policy: FixedIntervalPolicy(minutes: 10))
#expect(metrics.totalRefreshCount == 12)
#expect(metrics.simulatedSpanSeconds == 7200)
#expect(metrics.refreshCountPer24h == 144)
#expect(metrics.segmentCount == 2)
#expect(metrics.excludedGapSeconds == 18 * 60 * 60)
}
@Test
func `a normal scheduled wait remains in the preceding segment`() {
let trace = [
self.decision(0, delay: 1800),
.menuOpen(timestamp: self.at(1700)),
self.decision(4000, delay: 1800),
]
let report = ReplayTraceSegmenter.automatic(trace)
#expect(report.segments.count == 2)
#expect(report.segments[0].end == self.at(1800))
#expect(report.excludedGapSeconds == 2200)
}
@Test
func `menu opens before the first recorded refresh are censored equally`() throws {
let trace = [
self.decision(0, delay: 600),
.menuOpen(timestamp: self.at(100)),
.refreshCompleted(timestamp: self.at(600)),
self.decision(600, delay: 600),
.menuOpen(timestamp: self.at(700)),
.refreshCompleted(timestamp: self.at(1200)),
]
let metrics = ReplayEngine.runSegmented(trace: trace, policy: FixedIntervalPolicy(minutes: 10))
#expect(metrics.boundaryCensoredMenuOpenCount == 1)
#expect(try #require(metrics.stalenessAtMenuOpen).sampleCount == 1)
}
@Test
func `recorded refresh anchors staleness before a policy refresh`() throws {
let trace = [
self.decision(0, delay: 600),
.refreshCompleted(timestamp: self.at(600)),
.menuOpen(timestamp: self.at(700)),
self.decision(1200, delay: 600),
]
let metrics = ReplayEngine.runSegmented(trace: trace, policy: ManualPolicy())
let staleness = try #require(metrics.stalenessAtMenuOpen)
#expect(staleness.sampleCount == 1)
#expect(staleness.mean == 100)
}
@Test
func `recorded refresh supersedes an earlier simulated refresh`() throws {
let trace = [
self.decision(0, delay: 650),
.refreshCompleted(timestamp: self.at(650)),
.menuOpen(timestamp: self.at(700)),
self.decision(1200, delay: 600),
]
let metrics = ReplayEngine.runSegmented(trace: trace, policy: FixedIntervalPolicy(minutes: 10))
let staleness = try #require(metrics.stalenessAtMenuOpen)
#expect(staleness.sampleCount == 1)
#expect(staleness.mean == 50)
}
}