Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Sources/Testing/ExitTests/ExitTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,11 @@ extension ExitTest {

func open<T>(_ type: T.Type) throws -> T where T: Codable & Sendable {
return try capturedValueJSON.withUnsafeBytes { capturedValueJSON in
try JSON.decode(type, from: capturedValueJSON)
try JSON.decode(
type,
from: capturedValueJSON,
userInfo: [.allowNonFiniteFloatingPointValuesUserInfoKey: true]
)
}
}
capturedValue.wrappedValue = try open(capturedValue.typeOfWrappedValue)
Expand All @@ -1141,7 +1145,10 @@ extension ExitTest {
/// configurations is undefined.
private borrowing func _withEncodedCapturedValuesForEntryPoint(_ body: (UnsafeRawBufferPointer) throws -> Void) throws -> Void {
for capturedValue in capturedValues {
try JSON.withEncoding(of: capturedValue.wrappedValue!) { capturedValueJSON in
try JSON.withEncoding(
of: capturedValue.wrappedValue!,
userInfo: [.allowNonFiniteFloatingPointValuesUserInfoKey: true]
) { capturedValueJSON in
try JSON.asJSONLine(capturedValueJSON, body)
}
}
Expand Down
56 changes: 50 additions & 6 deletions Sources/Testing/Support/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ enum JSON {
/// testing library to improve the readability of JSON output.
private static let _prettyPrintingEnabled = Environment.flag(named: "SWT_PRETTY_PRINT_JSON") == true

/// String representations of non-finite floating-point values.
private static let _positiveInfinityString = "Infinity"
private static let _negativeInfinityString = "-Infinity"
private static let _nanString = "NaN"

/// Encode a value as JSON.
///
/// - Parameters:
Expand All @@ -34,19 +39,31 @@ enum JSON {
/// - Returns: Whatever is returned by `body`.
///
/// - Throws: Whatever is thrown by `body` or by the encoding process.
static func withEncoding<R>(of value: some Encodable, userInfo: [CodingUserInfoKey: any Sendable] = [:], _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
static func withEncoding<R>(
of value: some Encodable,
userInfo: [CodingUserInfoKey: any Sendable] = [:],
_ body: (UnsafeRawBufferPointer) throws -> R
) throws -> R {
let encoder = JSONEncoder()

// Set user info keys that clients want to use during encoding.
encoder.userInfo.merge(userInfo, uniquingKeysWith: { _, rhs in rhs })

if encoder.userInfo[.allowNonFiniteFloatingPointValuesUserInfoKey] as? Bool == true {
encoder.nonConformingFloatEncodingStrategy = .convertToString(
positiveInfinity: _positiveInfinityString,
negativeInfinity: _negativeInfinityString,
nan: _nanString
)
}

// Keys must be sorted to ensure deterministic matching of encoded data.
encoder.outputFormatting.insert(.sortedKeys)
if _prettyPrintingEnabled {
encoder.outputFormatting.insert(.prettyPrinted)
encoder.outputFormatting.insert(.withoutEscapingSlashes)
}

// Set user info keys that clients want to use during encoding.
encoder.userInfo.merge(userInfo, uniquingKeysWith: { _, rhs in rhs})

let data = try encoder.encode(value)
return try data.withUnsafeBytes(body)
}
Expand Down Expand Up @@ -82,11 +99,16 @@ enum JSON {
/// - Parameters:
/// - type: The type of value to decode.
/// - jsonRepresentation: The JSON encoding of the value to decode.
/// - userInfo: Any user info to pass into the decoder during decoding.
///
/// - Returns: An instance of `T` decoded from `jsonRepresentation`.
///
/// - Throws: Whatever is thrown by the decoding process.
static func decode<T>(_ type: T.Type, from jsonRepresentation: UnsafeRawBufferPointer) throws -> T where T: Decodable {
static func decode<T>(
_ type: T.Type,
from jsonRepresentation: UnsafeRawBufferPointer,
userInfo: [CodingUserInfoKey: any Sendable] = [:]
) throws -> T where T: Decodable {
try withExtendedLifetime(jsonRepresentation) {
let byteCount = jsonRepresentation.count
let data = if byteCount > 0 {
Expand All @@ -98,8 +120,30 @@ enum JSON {
} else {
Data()
}
return try JSONDecoder().decode(type, from: data)
let decoder = JSONDecoder()

// Set user info keys that clients want to use during decoding.
decoder.userInfo.merge(userInfo, uniquingKeysWith: { _, rhs in rhs })

if decoder.userInfo[.allowNonFiniteFloatingPointValuesUserInfoKey] as? Bool == true {
decoder.nonConformingFloatDecodingStrategy = .convertFromString(
positiveInfinity: _positiveInfinityString,
negativeInfinity: _negativeInfinityString,
nan: _nanString
)
}
return try decoder.decode(type, from: data)
}
}
#endif
}

#if !SWT_NO_CODABLE
extension CodingUserInfoKey {
/// A coding user info key whose value is a `Bool` indicating whether or not
/// non-finite floating-point values are allowed.
static var allowNonFiniteFloatingPointValuesUserInfoKey: Self {
Self(rawValue: "org.swift.testing.coding-user-info-key.allow-non-finite-floating-point-values")!
}
}
#endif
21 changes: 21 additions & 0 deletions Tests/TestingTests/ExitTestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,27 @@ private import _TestingInternals
}
}

@Test(
"Capture list (non-finite floating-point values)",
arguments: [Double.infinity, -Double.infinity, Double.nan]
)
func captureListWithNonFiniteFloatingPointValues(_ value: Double) async {
let expectedIsNaN = value.isNaN
let expectedIsNegative = value.sign == .minus
await #expect(processExitsWith: .success) {
[
Comment thread
Kyle-Ye marked this conversation as resolved.
value,
expectedIsNaN = expectedIsNaN as Bool,
expectedIsNegative = expectedIsNegative as Bool,
] in
#expect(value.isNaN == expectedIsNaN)
if !expectedIsNaN {
#expect(value.isInfinite)
#expect((value.sign == .minus) == expectedIsNegative)
}
}
}

@Test("Capture list (very long encoded form)")
func longCaptureList() async {
let count = 1 * 1024 * 1024
Expand Down