# Newer Swift Testing APIs

Companion to the `swift-testing` skill. Each section names the Swift Testing
proposal (ST-nnnn) and the toolchain that ships it.

## Warnings: `Issue.record(_:severity:)`

ST-0013, Swift 6.3 and Xcode 26.4 or later. An issue recorded with
`.warning` shows up in the results without failing the test.

```swift
@Test func thumbnailBatch() async throws {
    let clock = ContinuousClock()
    let start = clock.now
    let thumbnails = try await Thumbnailer().render(Fixtures.photos)
    let elapsed = start.duration(to: clock.now)

    #expect(thumbnails.count == Fixtures.photos.count)
    if elapsed > .seconds(2) {
        Issue.record("Batch took \(elapsed)", severity: .warning)
    }
}
```

Good uses:
- performance drift that should be visible but not block a merge,
- a deprecated path that still works,
- data checks that are nice to have,
- a flaky condition you want to track without failing CI.

## Cancelling a Test: `Test.cancel(_:)`

ST-0016, Swift 6.3 and Xcode 26.4 or later. `try Test.cancel()` stops the test
and reports it as cancelled: neither passed nor failed.

```swift
@Test func downloadsLiveCatalog() async throws {
    guard await Reachability.isOnline() else {
        try Test.cancel("No network on this runner")
    }
    let catalog = try await CatalogClient.live.fetch()
    #expect(!catalog.isEmpty)
}
```

| Outcome | How you get it |
|---------|----------------|
| Failed | The test throws an error |
| Expected failure | `withKnownIssue`; the test keeps running |
| Cancelled | `Test.cancel`; the test stops, neutral result |

## Capturing Values in Exit Tests

ST-0012 lets an exit test's closure capture values from the surrounding scope.
It compiles with the Swift 6.3.1 compiler in Xcode 26.4; earlier compilers were
not checked. Exit tests themselves arrived in Swift 6.2
(Xcode 26.0) through the `processExitsWith:` macros. Captured values must be
`Sendable` and `Codable`, because they are sent to the child process.

```swift
@Test func abortsOnUnknownMode() async {
    let mode = "turbo"
    await #expect(processExitsWith: .failure) { [mode = mode as String] in
        Launcher.start(mode: mode)
    }
}
```

Spell out each captured value's type as above; the macro rejects a bare
`[mode]` as ambiguous.

## Image Attachments

ST-0014, Swift 6.3 and Xcode 26.4 or later. Import `Testing` together with the
UI framework that defines the image type; the support comes from cross-import
overlays. Pass `as:` to choose the file format.

```swift
import Testing
import UIKit

@MainActor
@Test func sparklineRendering() {
    let size = CGSize(width: 240, height: 80)
    let image = UIGraphicsImageRenderer(size: size).image { context in
        SparklineRenderer(values: [3, 5, 2, 8]).draw(in: context.cgContext, size: size)
    }
    Attachment.record(image, named: "sparkline", as: .png)
}
```

| Type | Where |
|------|-------|
| `UIImage` | iOS, tvOS, visionOS, and watchOS with UIKit |
| `CGImage` | Core Graphics |
| `CIImage` | Core Image |
| `NSImage` | macOS with AppKit |

## Version Gates

| Feature | Minimum |
|---------|---------|
| Attachments of standard values | Swift 6.2, Xcode 26.0 |
| Exit tests with `processExitsWith:` | Swift 6.2, Xcode 26.0; macOS, Linux, FreeBSD, OpenBSD, Windows |
| Capture lists in exit tests | Verified with the Swift 6.3.1 compiler (Xcode 26.4) |
| `Issue.record(_:severity:)`, `Test.cancel(_:)`, image attachments | Swift 6.3, Xcode 26.4 |

iOS, tvOS and watchOS have no exit tests. Put the exiting behaviour behind a
small pure function, test the branches that do not exit, and keep real exit
tests for host or tool targets where they are supported.

## Proposals

| Proposal | Adds |
|----------|------|
| ST-0009 | Attachments: the `Attachment` type and recording it |
| ST-0012 | Capturing values in exit tests |
| ST-0013 | Issue severity, including warnings |
| ST-0014 | Image attachments on Apple platforms through cross-import overlays |
| ST-0016 | `Test.cancel()` |
