TimeTests.swift (8089B)
1 /* 2 * This file is part of GNU Taler, ©2022-23 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 import XCTest 6 @testable import taler_swift 7 8 class TimeTests: XCTestCase { 9 func testParsing() { 10 // The protocol wire format is TalerProtocolTimestamp: {"t_s": <seconds>} with the 11 // string sentinel "never". The legacy {"t_ms": ...} form is still accepted for 12 // numbers only (see testParsingLegacyMilliseconds). 13 let json1 = "{ \"t_s\" : 12309 }".data(using: .utf8)! 14 let json2 = "{ \"t_s\" : \"never\" }".data(using: .utf8)! 15 let json3 = "{ \"t_s\" : \"sometime\" }".data(using: .utf8)! 16 17 var t: Timestamp = try! JSONDecoder().decode(Timestamp.self, from: json1) 18 XCTAssertEqual(t, Timestamp.milliseconds(12309 * 1000)) 19 20 t = try! JSONDecoder().decode(Timestamp.self, from: json2) 21 XCTAssertEqual(t, Timestamp.never) 22 XCTAssertThrowsError(t = try JSONDecoder().decode(Timestamp.self, from: json3)) 23 } 24 25 func testParsingLegacyMilliseconds() { 26 // A numeric legacy t_ms still decodes... 27 let json1 = "{ \"t_ms\" : 12309487 }".data(using: .utf8)! 28 let t: Timestamp = try! JSONDecoder().decode(Timestamp.self, from: json1) 29 XCTAssertEqual(t, Timestamp.milliseconds(12309487)) 30 // ...but "never" under the legacy key does NOT: the fallback in 31 // Timestamp.init(from:) only ever looks for the string sentinel under `t_s`. 32 // Documented here as current behaviour, not endorsed — see bugs.txt. 33 let json2 = "{ \"t_ms\" : \"never\" }".data(using: .utf8)! 34 XCTAssertThrowsError(try JSONDecoder().decode(Timestamp.self, from: json2)) 35 } 36 37 func testParsingOutOfRangeSeconds() { 38 // The protocol caps a timestamp at 2^53-1 seconds, which must still decode... 39 let maxSeconds: UInt64 = (1 << 53) - 1 40 let json1 = "{ \"t_s\" : \(maxSeconds) }".data(using: .utf8)! 41 let t: Timestamp = try! JSONDecoder().decode(Timestamp.self, from: json1) 42 XCTAssertEqual(t, Timestamp.milliseconds(maxSeconds * 1000)) 43 44 // ...while anything above it must be rejected. Multiplying it by 1000 unchecked 45 // overflows UInt64 and traps, which no do/catch around the decode can survive. 46 for tooBig in [maxSeconds + 1, 20_000_000_000_000_000, UInt64.max] { 47 let json = "{ \"t_s\" : \(tooBig) }".data(using: .utf8)! 48 XCTAssertThrowsError(try JSONDecoder().decode(Timestamp.self, from: json)) 49 } 50 } 51 52 func testSerialize() { 53 // Encoding is lossy: milliseconds are truncated to whole seconds (see bugs.txt), 54 // so a round trip only holds for whole-second timestamps. 55 let str1 = "{\"t_s\":12309}" 56 let time1 = Timestamp.milliseconds(12309 * 1000) 57 58 let str2 = "{\"t_s\":\"never\"}" 59 let time2 = Timestamp.never 60 61 XCTAssert(try! String(data: JSONEncoder().encode(time1), encoding: .utf8)! == str1) 62 XCTAssert(try! String(data: JSONEncoder().encode(time2), encoding: .utf8)! == str2) 63 } 64 65 func testTimestampDurationAdd() { 66 XCTAssert(Timestamp.never + Duration.microseconds((0) * 1000) == Timestamp.never) 67 XCTAssert(Timestamp.never + Duration.microseconds((123456) * 1000) == Timestamp.never) 68 XCTAssert(Timestamp.never + Duration.forever == Timestamp.never) 69 70 XCTAssert(Timestamp.milliseconds(0) + Duration.microseconds((0) * 1000) == Timestamp.milliseconds(0)) 71 XCTAssert(Timestamp.milliseconds(0) + Duration.microseconds((12309487) * 1000) == Timestamp.milliseconds(12309487)) 72 XCTAssert(Timestamp.milliseconds(0) + Duration.forever == Timestamp.never) 73 74 XCTAssert(Timestamp.milliseconds(12309487) + Duration.microseconds((0) * 1000) == Timestamp.milliseconds(12309487)) 75 // Duration is microseconds, Timestamp is milliseconds, and `+` divides the addend 76 // by 1000. So the largest addend any Duration can express is UInt64.max/1000 ms — 77 // which means the overflow boundary now sits near the top of the *Timestamp* range, 78 // not near the addend. Test the exact boundary and one millisecond past it. 79 let maxAddendMs = UInt64.max / 1000 80 XCTAssert(Timestamp.milliseconds(UInt64.max - maxAddendMs) + Duration.microseconds(UInt64.max) == Timestamp.milliseconds(UInt64.max)) 81 XCTAssert(Timestamp.milliseconds(UInt64.max - maxAddendMs + 1) + Duration.microseconds(UInt64.max) == Timestamp.never) 82 XCTAssert(Timestamp.milliseconds(12309487) + Duration.forever == Timestamp.never) 83 } 84 85 func testTimestampDurationSubtraction() { 86 XCTAssert(Timestamp.never - Duration.microseconds((0) * 1000) == Timestamp.never) 87 XCTAssert(Timestamp.never - Duration.microseconds((123456) * 1000) == Timestamp.never) 88 XCTAssert(Timestamp.never - Duration.forever == Timestamp.never) 89 90 XCTAssert(Timestamp.milliseconds(0) - Duration.microseconds((0) * 1000) == Timestamp.milliseconds(0)) 91 XCTAssert(Timestamp.milliseconds(0) - Duration.microseconds((123456) * 1000) == Timestamp.milliseconds(0)) 92 XCTAssert(Timestamp.milliseconds(0) - Duration.forever == Timestamp.milliseconds(0)) 93 94 XCTAssert(Timestamp.milliseconds(12309487) - Duration.microseconds((0) * 1000) == Timestamp.milliseconds(12309487)) 95 XCTAssert(Timestamp.milliseconds(12309487) - Duration.microseconds((487) * 1000) == Timestamp.milliseconds(12309000)) 96 XCTAssert(Timestamp.milliseconds(12309487) - Duration.microseconds((12309487) * 1000) == Timestamp.milliseconds(0)) 97 XCTAssert(Timestamp.milliseconds(12309487) - Duration.microseconds((12309488) * 1000) == Timestamp.milliseconds(0)) 98 XCTAssert(Timestamp.milliseconds(12309487) - Duration.forever == Timestamp.milliseconds(0)) 99 } 100 101 func testTimestampDifference() { 102 XCTAssert(try! Timestamp.never - Timestamp.milliseconds(0) == Duration.forever) 103 XCTAssert(try! Timestamp.never - Timestamp.milliseconds(123456) == Duration.forever) 104 XCTAssert(try! Timestamp.never - Timestamp.never == Duration.forever) 105 106 XCTAssert(try! Timestamp.milliseconds(0) - Timestamp.milliseconds(0) == Duration.microseconds((0) * 1000)) 107 XCTAssert(try! Timestamp.milliseconds(0) - Timestamp.milliseconds(123456) == Duration.microseconds((0) * 1000)) 108 XCTAssertThrowsError(try Timestamp.milliseconds(0) - Timestamp.never) 109 110 XCTAssert(try! Timestamp.milliseconds(12309487) - Timestamp.milliseconds(0) == Duration.microseconds((12309487) * 1000)) 111 XCTAssert(try! Timestamp.milliseconds(12309487) - Timestamp.milliseconds(12309000) == Duration.microseconds((487) * 1000)) 112 XCTAssert(try! Timestamp.milliseconds(12309487) - Timestamp.milliseconds(12309487) == Duration.microseconds((0) * 1000)) 113 XCTAssert(try! Timestamp.milliseconds(12309487) - Timestamp.milliseconds(12309488) == Duration.microseconds((0) * 1000)) 114 XCTAssertThrowsError(try Timestamp.milliseconds(12309487) - Timestamp.never) 115 } 116 117 func testTimestampDifferenceOverflow() { 118 // t_s = 2^53-1 is a timestamp the protocol allows, so it can arrive from any 119 // exchange or merchant. Its distance from now doesn't fit into microseconds: 120 // the difference must saturate to forever, not trap. 121 let maxSeconds: UInt64 = (1 << 53) - 1 122 let farFuture = Timestamp.milliseconds(maxSeconds * 1000) 123 XCTAssert(try! farFuture - Timestamp.milliseconds(0) == Duration.forever) 124 XCTAssert(try! farFuture.duration() == Duration.forever) 125 XCTAssert(try! Timestamp.milliseconds(UInt64.max) - Timestamp.milliseconds(0) == Duration.forever) 126 127 // right below the boundary the exact difference is still returned 128 let maxDiffMs = UInt64.max / 1000 129 XCTAssert(try! Timestamp.milliseconds(maxDiffMs) - Timestamp.milliseconds(0) == Duration.microseconds(maxDiffMs * 1000)) 130 XCTAssert(try! Timestamp.milliseconds(maxDiffMs + 1) - Timestamp.milliseconds(0) == Duration.forever) 131 } 132 }