Verlauf über 24 Stunden, sieben Tage und dreißig Tage, aufgezeichnet alle fünf Minuten. Als Säulen und nicht als Linie: eine Linie verbindet auch dort, wo nichts gemessen wurde, und ein zugeklappter Rechner hatte keinen Ladestand — eine Linie darüber behauptet einen. Dazu Segelmodus und Hitzeschutz als reine Regeln im Helfer. Beide haben zwei Schwellen, eine zum Anhalten und eine zum Weitermachen. Ohne diesen Abstand hinge der Akku am Limit und würde bei jedem verlorenen Prozent nachgeladen — hunderte winzige Ladevorgänge am Tag, und genau die altern ihn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
3.8 KiB
Swift
93 lines
3.8 KiB
Swift
import Testing
|
|
import Foundation
|
|
@testable import MetricsProvider
|
|
|
|
@Suite("Akkuverlauf")
|
|
struct BatteryHistoryTests {
|
|
|
|
private func sample(_ seconds: TimeInterval, charge: Double) -> BatterySample {
|
|
BatterySample(at: Date(timeIntervalSince1970: seconds), charge: charge,
|
|
celsius: nil, watts: nil)
|
|
}
|
|
|
|
private let now = Date(timeIntervalSince1970: 100 * 86_400)
|
|
|
|
@Test("Ohne vorherige Messung wird sofort aufgezeichnet")
|
|
func firstSampleIsRecorded() {
|
|
#expect(BatteryHistoryRules.shouldRecord(last: nil, now: now))
|
|
}
|
|
|
|
@Test("Vor Ablauf des Abstands nicht noch einmal")
|
|
func waitsForTheInterval() {
|
|
let recent = now.addingTimeInterval(-60)
|
|
#expect(!BatteryHistoryRules.shouldRecord(last: recent, now: now))
|
|
}
|
|
|
|
@Test("Nach dem Abstand wieder")
|
|
func recordsAfterTheInterval() {
|
|
let old = now.addingTimeInterval(-BatteryHistoryRules.interval)
|
|
#expect(BatteryHistoryRules.shouldRecord(last: old, now: now))
|
|
}
|
|
|
|
@Test("Zu alte Messpunkte fallen heraus")
|
|
func oldSamplesArePruned() {
|
|
let samples = [
|
|
sample(now.timeIntervalSince1970 - 40 * 86_400, charge: 0.5),
|
|
sample(now.timeIntervalSince1970 - 1 * 86_400, charge: 0.6),
|
|
]
|
|
let kept = BatteryHistoryRules.pruned(samples, now: now)
|
|
#expect(kept.count == 1)
|
|
#expect(kept.first?.charge == 0.6)
|
|
}
|
|
|
|
@Test("Die Säulen bekommen den Mittelwert ihres Abschnitts")
|
|
func bucketsAverage() {
|
|
// Zwei Messungen in derselben halben Stunde ergeben eine Säule mit
|
|
// ihrem Mittel — nicht die erste und nicht die letzte.
|
|
let base = now.timeIntervalSince1970 - 600
|
|
let samples = [sample(base, charge: 0.4), sample(base + 60, charge: 0.6)]
|
|
let buckets = BatteryHistoryRules.buckets(samples, window: .day, now: now)
|
|
let filled = buckets.compactMap { $0 }
|
|
#expect(filled.count == 1)
|
|
#expect(abs(filled[0] - 0.5) < 0.0001)
|
|
}
|
|
|
|
@Test("Abschnitte ohne Messung bleiben leer")
|
|
func gapsStayEmpty() {
|
|
// Ein zugeklappter Rechner hat dort keinen Ladestand gehabt. Eine
|
|
// durchgezogene Linie darüber wäre erfunden.
|
|
let samples = [sample(now.timeIntervalSince1970 - 600, charge: 0.5)]
|
|
let buckets = BatteryHistoryRules.buckets(samples, window: .day, now: now)
|
|
#expect(buckets.count == HistoryWindow.day.buckets)
|
|
#expect(buckets.filter { $0 == nil }.count == HistoryWindow.day.buckets - 1)
|
|
}
|
|
|
|
@Test("Messpunkte außerhalb des Fensters zählen nicht mit")
|
|
func outOfWindowIsIgnored() {
|
|
let samples = [sample(now.timeIntervalSince1970 - 10 * 86_400, charge: 0.9)]
|
|
#expect(BatteryHistoryRules.buckets(samples, window: .day, now: now).allSatisfy { $0 == nil })
|
|
#expect(BatteryHistoryRules.summary(samples, window: .day, now: now) == nil)
|
|
}
|
|
|
|
@Test("Die Zusammenfassung nennt Tief, Hoch und Mittel")
|
|
func summaryReportsRange() {
|
|
let base = now.timeIntervalSince1970 - 3600
|
|
let samples = [sample(base, charge: 0.2), sample(base + 60, charge: 0.8)]
|
|
let summary = BatteryHistoryRules.summary(samples, window: .day, now: now)
|
|
#expect(summary?.low == 0.2)
|
|
#expect(summary?.high == 0.8)
|
|
#expect(abs((summary?.average ?? 0) - 0.5) < 0.0001)
|
|
}
|
|
|
|
@Test("Jedes Fenster hat weniger Säulen als Messpunkte")
|
|
func bucketsAreFewerThanSamples() {
|
|
// Über einen Monat wären es bei fünf Minuten Abstand über achttausend
|
|
// Punkte — mehr Punkte als Bildpunkte zu zeichnen kostet Zeit, ohne
|
|
// etwas zu zeigen.
|
|
for window in HistoryWindow.allCases {
|
|
let possible = Int(window.duration / BatteryHistoryRules.interval)
|
|
#expect(window.buckets < possible)
|
|
}
|
|
}
|
|
}
|