Skip to content

Commit 7a1d389

Browse files
authored
[TestJSONEncoder] Re-enable tests and add a special case to handle a rare condition caused by snprintf's %g which JSONSerialization uses internally for double values. This bug effects Darwin, FreeBSD, and Linux currently. (#23721)
1 parent a8c3b84 commit 7a1d389

File tree

1 file changed

+62
-3
lines changed

1 file changed

+62
-3
lines changed

Darwin/Foundation-swiftoverlay-Tests/TestJSONEncoder.swift

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
// RUN: %target-run-simple-swift
1010
// REQUIRES: executable_test
1111
// REQUIRES: objc_interop
12-
// REQUIRES: rdar49161053
1312

1413
import Swift
1514
import Foundation
@@ -231,11 +230,71 @@ class TestJSONEncoder : TestJSONEncoderSuper {
231230

232231
// MARK: - Date Strategy Tests
233232
func testEncodingDate() {
233+
234+
func formattedLength(of value: Double) -> Int {
235+
let empty = UnsafeMutablePointer<Int8>.allocate(capacity: 0)
236+
defer { empty.deallocate() }
237+
let length = snprintf(ptr: empty, 0, "%0.*g", DBL_DECIMAL_DIG, value)
238+
return Int(length)
239+
}
240+
241+
// Duplicated to handle a special case
242+
func localTestRoundTrip<T: Codable & Equatable>(of value: T) {
243+
var payload: Data! = nil
244+
do {
245+
let encoder = JSONEncoder()
246+
payload = try encoder.encode(value)
247+
} catch {
248+
expectUnreachable("Failed to encode \(T.self) to JSON: \(error)")
249+
}
250+
251+
do {
252+
let decoder = JSONDecoder()
253+
let decoded = try decoder.decode(T.self, from: payload)
254+
255+
/// `snprintf`'s `%g`, which `JSONSerialization` uses internally for double values, does not respect
256+
/// our precision requests in every case. This bug effects Darwin, FreeBSD, and Linux currently
257+
/// causing this test (which uses the current time) to fail occasionally.
258+
let evalEdgeCase: (Date, Date) -> () = { decodedDate, expectedDate in
259+
if formattedLength(of: decodedDate.timeIntervalSinceReferenceDate) > DBL_DECIMAL_DIG + 2 {
260+
let adjustedTimeIntervalSinceReferenceDate: (Date) -> Double = {
261+
let adjustment = pow(10, Double(DBL_DECIMAL_DIG))
262+
return Double(floor(adjustment * $0.timeIntervalSinceReferenceDate) / adjustment)
263+
}
264+
265+
let decodedAprox = adjustedTimeIntervalSinceReferenceDate(decodedDate)
266+
let valueAprox = adjustedTimeIntervalSinceReferenceDate(expectedDate)
267+
expectEqual(decodedAprox, valueAprox, "\(T.self) did not round-trip to an equal value after DBL_DECIMAL_DIG adjustment \(decodedAprox) != \(valueAprox).")
268+
}
269+
}
270+
271+
if let decodedDate = (decoded as? TopLevelWrapper<Date>)?.value,
272+
let expectedDate = (value as? TopLevelWrapper<Date>)?.value {
273+
evalEdgeCase(decodedDate, expectedDate)
274+
return
275+
}
276+
277+
if let decodedDate = (decoded as? OptionalTopLevelWrapper<Date>)?.value,
278+
let expectedDate = (value as? OptionalTopLevelWrapper<Date>)?.value {
279+
evalEdgeCase(decodedDate, expectedDate)
280+
return
281+
}
282+
283+
expectEqual(decoded, value, "\(T.self) did not round-trip to an equal value.")
284+
} catch {
285+
expectUnreachable("Failed to decode \(T.self) from JSON: \(error)")
286+
}
287+
}
288+
289+
// Test the above `snprintf` edge case evaluation with a known triggering case
290+
let knownBadDate = Date(timeIntervalSinceReferenceDate: 0.0021413276231263384)
291+
localTestRoundTrip(of: TopLevelWrapper(knownBadDate))
292+
234293
// We can't encode a top-level Date, so it'll be wrapped in a dictionary.
235-
_testRoundTrip(of: TopLevelWrapper(Date()))
294+
localTestRoundTrip(of: TopLevelWrapper(Date()))
236295

237296
// Optional dates should encode the same way.
238-
_testRoundTrip(of: OptionalTopLevelWrapper(Date()))
297+
localTestRoundTrip(of: OptionalTopLevelWrapper(Date()))
239298
}
240299

241300
func testEncodingDateSecondsSince1970() {

0 commit comments

Comments
 (0)