import Testing @testable import FlixyDownloads @Suite("Downloadrate und Restzeit") struct DownloadRateTests { /// Ein fester Anfangszeitpunkt, von dem aus gerechnet wird. /// /// Die Uhr kommt von außen herein — deshalb braucht hier nichts zu warten, /// und eine Übertragung über eine Viertelstunde ist in Mikrosekunden /// nachgestellt. private let start = ContinuousClock.now @Test("Vor dem ersten Abstand gibt es keine Zahl") func noRateFromASinglePoint() { var rate = DownloadRate() rate.record(bytes: 0, at: start) // Null wäre eine Auskunft („es geht nichts durch"), und die stimmt // nicht — es ist nur noch nichts gemessen. #expect(rate.bytesPerSecond == nil) } @Test("Gleichmäßiger Zufluss ergibt eine ruhige Zahl") func steadyFlowIsSteady() { var rate = DownloadRate() for sekunde in 0...5 { rate.record(bytes: Int64(sekunde) * 1_000_000, at: start.advanced(by: .seconds(sekunde))) } // Auf das Byte genau: die Glättung zieht einen Messwert nur dann in // eine Richtung, wenn er vom bisherigen abweicht. #expect(rate.bytesPerSecond == 1_000_000) } @Test("Ein Ausreißer verschiebt die Zahl, aber reißt sie nicht mit") func outlierIsDamped() { var rate = DownloadRate() for sekunde in 0...5 { rate.record(bytes: Int64(sekunde) * 1_000_000, at: start.advanced(by: .seconds(sekunde))) } // Eine einzelne Sekunde mit dem Zehnfachen. rate.record(bytes: 15_000_000, at: start.advanced(by: .seconds(6))) let gemessen = try! #require(rate.bytesPerSecond) // Roh wären es zehn Millionen. Geglättet liegt die Zahl dazwischen — // sie folgt, statt zu springen. #expect(gemessen > 1_000_000) #expect(gemessen < 10_000_000) } @Test("Messpunkte dichter als eine halbe Sekunde zählen nicht") func samplesTooCloseAreIgnored() { var rate = DownloadRate() rate.record(bytes: 0, at: start) rate.record(bytes: 50_000, at: start.advanced(by: .milliseconds(100))) rate.record(bytes: 100_000, at: start.advanced(by: .milliseconds(200))) #expect(rate.bytesPerSecond == nil) // Und der Bezugspunkt ist dabei nicht weitergerückt: gemessen wird // über die volle Sekunde seit dem ersten Punkt, nicht über die letzten // achthundert Millisekunden. rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) #expect(rate.bytesPerSecond == 1_000_000) } @Test("Eine lange Stille verfälscht die Rate nicht") func longGapResetsTheMeasurement() { var rate = DownloadRate() rate.record(bytes: 0, at: start) rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) #expect(rate.bytesPerSecond == 1_000_000) // Eine halbe Minute ohne Rückmeldung heißt nicht „langsam", sondern // „stand still": App angehalten, Gerät gesperrt, Netz weg. Daraus eine // Rate zu rechnen ergäbe dreißig Kilobyte je Sekunde und eine Restzeit // von Stunden. rate.record(bytes: 1_030_000, at: start.advanced(by: .seconds(31))) #expect(rate.bytesPerSecond == nil) } @Test("Fängt die Übertragung von vorn an, gilt keine alte Zahl") func restartFromZeroDiscardsTheRate() { var rate = DownloadRate() rate.record(bytes: 0, at: start) rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) #expect(rate.bytesPerSecond == 1_000_000) // Genau das passiert, wenn ein gescheiterter Download ohne // Wiederaufnahme-Datensatz neu beginnt. rate.record(bytes: 0, at: start.advanced(by: .seconds(2))) #expect(rate.bytesPerSecond == nil) } @Test("Die Restzeit folgt aus Rate und Rest") func remainingFollowsFromRate() { var rate = DownloadRate() rate.record(bytes: 0, at: start) rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) let rest = try! #require(rate.remaining(received: 1_000_000, total: 11_000_000)) #expect(rest == .seconds(10)) } @Test("Ohne Rate keine Restzeit") func noRemainingWithoutRate() { var rate = DownloadRate() rate.record(bytes: 0, at: start) // Eine Restzeit ohne Messung wäre geraten — und geraten sieht man ihr // nicht an. #expect(rate.remaining(received: 0, total: 1_000_000) == nil) } @Test("Ist alles da, gibt es nichts mehr zu schätzen") func noRemainingWhenComplete() { var rate = DownloadRate() rate.record(bytes: 0, at: start) rate.record(bytes: 1_000_000, at: start.advanced(by: .seconds(1))) #expect(rate.remaining(received: 1_000_000, total: 1_000_000) == nil) } }