Skip to content

Exit test value capture fails for non-finite floating-point values #1836

Description

@Kyle-Ye

Description

Starting in Swift 6.3, values explicitly captured by exit test closures are encoded as JSON in the parent process and decoded in the child process.

Float and Double satisfy the Codable and Sendable requirements for captured values, but the default JSONEncoder rejects .infinity, -.infinity, and .nan. As a result, the exit test reports an encoding error before launching its body.

This became observable when exit-test value capturing was introduced in Swift 6.3.

Reproduction

import Testing

@Test
func captureNonFiniteFloatingPointValue() async {
  let value = Double.infinity

  await #expect(processExitsWith: .success) { [value = value as Double] in
    precondition(value == .infinity)
  }
}

Run swift test.

The test reports an error containing:

A system failure occurred: EncodingError.invalidValue:: inf(Double). Debug description: Unable to encode Double.inf directly in JSON

Expected behavior

Non-finite floating-point values should round-trip through an exit test capture list so the subprocess body can run. Positive and negative infinity should retain their sign, and NaN should remain NaN.

Environment

Swift Testing Library Version: 1902
Apple Swift version 6.3.3 effective-5.10 (swiftlang-6.3.3.1.3 clang-2100.1.1.101)
Xcode 26.6 (17F113)
macOS 26.5.2 (25F84)

The same failure also occurs in CI on macOS 15.5 with Xcode 26.6 / Swift 6.3.3.

Additional information

Exit-test value capture was introduced by ST-0012: Exit Test Value Capturing.

A workaround is to capture each floating-point value's bitPattern as an integer and reconstruct the value in the subprocess closure, but this exposes an implementation detail to every affected test.

-                await #expect(processExitsWith: .failure) { [min, ideal, max] in
+                await #expect(processExitsWith: .failure) {
+                    [
+                        minBitPattern = Double(min).bitPattern as UInt64,
+                        idealBitPattern = Double(ideal).bitPattern as UInt64,
+                        maxBitPattern = Double(max).bitPattern as UInt64,
+                    ] in
+                    let min = CGFloat(Double(bitPattern: minBitPattern))
+                    let ideal = CGFloat(Double(bitPattern: idealBitPattern))
+                    let max = CGFloat(Double(bitPattern: maxBitPattern))

Swift Open Source Slack channel discussion https://swift-open-source.slack.com/archives/C06QXFKB4ER/p1786820881281939

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestexit-test-capture-lists🥍 Work related to exit test capture listsexit-tests☠️ Work related to exit tests

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions