Skip to content

Commit dd8988e

Browse files
committed
Propose a stabilized C-compatible ABI interface and JSON schema for event streaming.
1 parent bfd57a6 commit dd8988e

File tree

9 files changed

+605
-93
lines changed

9 files changed

+605
-93
lines changed

Documentation/ABI/JSON.md

+5-9
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,9 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors
1111
-->
1212

1313
This document outlines the JSON schemas used by the testing library for its ABI
14-
entry point and for the `--experimental-event-stream-output` command-line
15-
argument. For more information about the ABI entry point, see the documentation
16-
for [ABI.EntryPoint_v0](https://github.com/search?q=repo%3Aapple%2Fswift-testing%EntryPoint_v0&type=code).
17-
18-
> [!WARNING]
19-
> This JSON schema is still being developed and is subject to any and all
20-
> changes including removal from the package.
14+
entry point and for the `--event-stream-output` command-line argument. For more
15+
information about the ABI entry point, see the documentation for
16+
[ABIv0.EntryPoint](https://github.com/search?q=repo%3Aapple%2Fswift-testing%EntryPoint&type=code).
2117

2218
## Modified Backus-Naur form
2319

@@ -103,8 +99,8 @@ encoded as a single [JSON Lines](https://jsonlines.org) value.
10399

104100
A stream consists of a sequence of values encoded as [JSON Lines](https://jsonlines.org).
105101
A single instance of `<output-stream>` is defined per test process and can be
106-
accessed by passing `--experimental-event-stream-output` to the test executable
107-
created by `swift build --build-tests`.
102+
accessed by passing `--event-stream-output` to the test executable created by
103+
`swift build --build-tests`.
108104

109105
```
110106
<output-stream> ::= <output-record>\n | <output-record>\n <output-stream>

Documentation/Proposals/NNNN-json-abi.md

+413
Large diffs are not rendered by default.

Sources/Testing/EntryPoints/ABIEntryPoint.swift

+13-51
Original file line numberDiff line numberDiff line change
@@ -9,65 +9,27 @@
99
//
1010

1111
#if canImport(Foundation) && !SWT_NO_ABI_ENTRY_POINT
12-
/// The type of the entry point to the testing library used by tools that want
13-
/// to remain version-agnostic regarding the testing library.
14-
///
15-
/// - Parameters:
16-
/// - argumentsJSON: A buffer to memory representing the JSON encoding of an
17-
/// instance of `__CommandLineArguments_v0`. If `nil`, a new instance is
18-
/// created from the command-line arguments to the current process.
19-
/// - recordHandler: A JSON record handler to which is passed a buffer to
20-
/// memory representing each record as described in `ABI/JSON.md`.
21-
///
22-
/// - Returns: The result of invoking the testing library. The type of this
23-
/// value is subject to change.
24-
///
25-
/// - Throws: Any error that occurred prior to running tests. Errors that are
26-
/// thrown while tests are running are handled by the testing library.
27-
///
28-
/// This function examines the command-line arguments to the current process
29-
/// and then invokes available tests in the current process.
12+
private import _TestingInternals
13+
14+
/// An older signature for ``ABIv0/EntryPoint-swift.typealias`` used by Xcode 16
15+
/// Beta 1.
3016
///
31-
/// - Warning: This function's signature and the structure of its JSON inputs
32-
/// and outputs have not been finalized yet.
33-
@_spi(Experimental) @_spi(ForToolsIntegrationOnly)
34-
public typealias ABIEntryPoint_v0 = @Sendable (
17+
/// This type will be removed in a future update.
18+
@available(*, deprecated, message: "Use ABIv0.EntryPoint instead.")
19+
typealias ABIEntryPoint_v0 = @Sendable (
3520
_ argumentsJSON: UnsafeRawBufferPointer?,
3621
_ recordHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
3722
) async throws -> CInt
3823

39-
/// Get the entry point to the testing library used by tools that want to remain
40-
/// version-agnostic regarding the testing library.
24+
/// An older signature for ``ABIv0/entryPoint-swift.type.property`` used by
25+
/// Xcode 16 Beta 1.
4126
///
42-
/// - Returns: A pointer to an instance of ``ABIEntryPoint_v0`` representing the
43-
/// ABI-stable entry point to the testing library. The caller owns this memory
44-
/// and is responsible for deinitializing and deallocating it when done.
45-
///
46-
/// This function can be used by tools that do not link directly to the testing
47-
/// library and wish to invoke tests in a binary that has been loaded into the
48-
/// current process. The function is emitted into the binary under the name
49-
/// `"swt_copyABIEntryPoint_v0"` and can be dynamically looked up at runtime
50-
/// using `dlsym()` or a platform equivalent.
51-
///
52-
/// The returned function can be thought of as equivalent to
53-
/// `swift test --experimental-event-stream-output` except that, instead of
54-
/// streaming JSON records to a named pipe or file, it streams them to an
55-
/// in-process callback.
56-
///
57-
/// - Warning: This function's signature and the structure of its JSON inputs
58-
/// and outputs have not been finalized yet.
27+
/// This function will be removed in a future update.
28+
@available(*, deprecated, message: "Use ABIv0.entryPoint (swt_abiv0_getEntryPoint()) instead.")
5929
@_cdecl("swt_copyABIEntryPoint_v0")
60-
@_spi(Experimental) @_spi(ForToolsIntegrationOnly)
61-
public func copyABIEntryPoint_v0() -> UnsafeMutableRawPointer {
30+
@usableFromInline func copyABIEntryPoint_v0() -> UnsafeMutableRawPointer {
6231
let result = UnsafeMutablePointer<ABIEntryPoint_v0>.allocate(capacity: 1)
63-
result.initialize { argumentsJSON, recordHandler in
64-
let args = try argumentsJSON.map { argumentsJSON in
65-
try JSON.decode(__CommandLineArguments_v0.self, from: argumentsJSON)
66-
}
67-
68-
let eventHandler = try eventHandlerForStreamingEvents(version: args?.experimentalEventStreamVersion, forwardingTo: recordHandler)
69-
return await entryPoint(passing: args, eventHandler: eventHandler)
70-
}
32+
result.initialize { try await ABIv0.entryPoint($0, $1) ? EXIT_SUCCESS : EXIT_FAILURE }
7133
return .init(result)
7234
}
7335
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
#if canImport(Foundation) && !SWT_NO_ABI_ENTRY_POINT
12+
private import _TestingInternals
13+
14+
@_spi(ForToolsIntegrationOnly)
15+
extension ABIv0 {
16+
/// The type of the entry point to the testing library used by tools that want
17+
/// to remain version-agnostic regarding the testing library.
18+
///
19+
/// - Parameters:
20+
/// - configurationJSON: A buffer to memory representing the test
21+
/// configuration and options. If `nil`, a new instance is synthesized
22+
/// from the command-line arguments to the current process.
23+
/// - recordHandler: A JSON record handler to which is passed a buffer to
24+
/// memory representing each record as described in `ABI/JSON.md`.
25+
///
26+
/// - Returns: Whether or not the test run finished successfully.
27+
///
28+
/// - Throws: Any error that occurred prior to running tests. Errors that are
29+
/// thrown while tests are running are handled by the testing library.
30+
public typealias EntryPoint = @convention(thin) @Sendable (
31+
_ configurationJSON: UnsafeRawBufferPointer?,
32+
_ recordHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
33+
) async throws -> Bool
34+
35+
/// The entry point to the testing library used by tools that want to remain
36+
/// version-agnostic regarding the testing library.
37+
///
38+
/// The value of this property is a Swift function that can be used by tools
39+
/// that do not link directly to the testing library and wish to invoke tests
40+
/// in a binary that has been loaded into the current process. The value of
41+
/// this property is accessible from C and C++ as a function with name
42+
/// `"swt_abiv0_getEntryPoint"` and can be dynamically looked up at runtime
43+
/// using `dlsym()` or a platform equivalent.
44+
///
45+
/// The value of this property can be thought of as equivalent to
46+
/// `swift test --event-stream-output` except that, instead of streaming JSON
47+
/// records to a named pipe or file, it streams them to an in-process
48+
/// callback.
49+
public static var entryPoint: EntryPoint {
50+
return { configurationJSON, recordHandler in
51+
let args = try configurationJSON.map { configurationJSON in
52+
try JSON.decode(__CommandLineArguments_v0.self, from: configurationJSON)
53+
}
54+
55+
let eventHandler = try eventHandlerForStreamingEvents(version: args?.eventStreamVersion, forwardingTo: recordHandler)
56+
let exitCode = await Testing.entryPoint(passing: args, eventHandler: eventHandler)
57+
return exitCode == EXIT_SUCCESS
58+
}
59+
}
60+
}
61+
62+
/// An exported C function that is the equivalent of
63+
/// ``ABIv0/entryPoint-swift.type.property``.
64+
///
65+
/// - Returns: The value of ``ABIv0/entryPoint-swift.type.property`` cast to an
66+
/// untyped pointer.
67+
@_cdecl("swt_abiv0_getEntryPoint")
68+
@usableFromInline func abiv0_getEntryPoint() -> UnsafeRawPointer {
69+
unsafeBitCast(ABIv0.entryPoint, to: UnsafeRawPointer.self)
70+
}
71+
#endif

Sources/Testing/EntryPoints/ABIv0/ABIv0.Record+Streaming.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ extension ABIv0.Record {
1515
///
1616
/// - Parameters:
1717
/// - eventHandler: The event handler to forward events to. See
18-
/// ``ABIv0/EntryPoint`` for more information.
18+
/// ``ABIv0/EntryPoint-swift.typealias`` for more information.
1919
///
2020
/// - Returns: An event handler.
2121
///
@@ -74,7 +74,7 @@ extension EventAndContextSnapshot: Codable {}
7474
///
7575
/// - Parameters:
7676
/// - eventHandler: The event handler to forward events to. See
77-
/// ``ABIEntryPoint_v0`` for more information.
77+
/// ``ABIv0/EntryPoint-swift.typealias`` for more information.
7878
///
7979
/// - Returns: An event handler.
8080
///

Sources/Testing/EntryPoints/ABIv0/ABIv0.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
//
1010

1111
/// A namespace for ABI version 0 symbols.
12-
enum ABIv0: Sendable {}
12+
@_spi(ForToolsIntegrationOnly)
13+
public enum ABIv0: Sendable {}

Sources/Testing/EntryPoints/EntryPoint.swift

+19-16
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ private import _TestingInternals
2525
/// to this function.
2626
///
2727
/// External callers cannot call this function directly. The can use
28-
/// ``copyABIEntryPoint_v0()`` to get a reference to an ABI-stable version of
29-
/// this function.
28+
/// ``ABIv0/entryPoint-swift.type.property`` to get a reference to an ABI-stable
29+
/// version of this function.
3030
func entryPoint(passing args: __CommandLineArguments_v0?, eventHandler: Event.Handler?) async -> CInt {
3131
let exitCode = Locked(rawValue: EXIT_SUCCESS)
3232

@@ -200,7 +200,7 @@ public struct __CommandLineArguments_v0: Sendable {
200200
/// The value of the `--xunit-output` argument.
201201
public var xunitOutput: String?
202202

203-
/// The value of the `--experimental-event-stream-output` argument.
203+
/// The value of the `--event-stream-output` argument.
204204
///
205205
/// Data is written to this file in the [JSON Lines](https://jsonlines.org)
206206
/// text format. For each event handled by the resulting event handler, a JSON
@@ -215,18 +215,18 @@ public struct __CommandLineArguments_v0: Sendable {
215215
///
216216
/// The file is closed when this process terminates or the test run completes,
217217
/// whichever occurs first.
218-
public var experimentalEventStreamOutput: String?
218+
public var eventStreamOutput: String?
219219

220220
/// The version of the event stream schema to use when writing events to
221-
/// ``experimentalEventStreamOutput``.
221+
/// ``eventStreamOutput``.
222222
///
223223
/// If the value of this property is `nil`, events are encoded verbatim (using
224224
/// ``Event/Snapshot``.) Otherwise, the corresponding stable schema is used
225225
/// (e.g. ``ABIv0/Record`` for `0`.)
226226
///
227227
/// - Warning: The behavior of this property will change when the ABI version
228228
/// 0 JSON schema is finalized.
229-
public var experimentalEventStreamVersion: Int?
229+
public var eventStreamVersion: Int?
230230

231231
/// The value(s) of the `--filter` argument.
232232
public var filter: [String]?
@@ -252,8 +252,8 @@ extension __CommandLineArguments_v0: Codable {
252252
case quiet
253253
case _verbosity = "verbosity"
254254
case xunitOutput
255-
case experimentalEventStreamOutput
256-
case experimentalEventStreamVersion
255+
case eventStreamOutput
256+
case eventStreamVersion
257257
case filter
258258
case skip
259259
case repetitions
@@ -288,7 +288,8 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
288288
// NOTE: While the output event stream is opened later, it is necessary to
289289
// open the configuration file early (here) in order to correctly construct
290290
// the resulting __CommandLineArguments_v0 instance.
291-
if let configurationIndex = args.firstIndex(of: "--experimental-configuration-path"), !isLastArgument(at: configurationIndex) {
291+
if let configurationIndex = args.firstIndex(of: "--configuration-path") ?? args.firstIndex(of: "--experimental-configuration-path"),
292+
!isLastArgument(at: configurationIndex) {
292293
let path = args[args.index(after: configurationIndex)]
293294
let file = try FileHandle(forReadingAtPath: path)
294295
let configurationJSON = try file.readToEnd()
@@ -302,12 +303,14 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
302303
}
303304

304305
// Event stream output (experimental)
305-
if let eventOutputIndex = args.firstIndex(of: "--experimental-event-stream-output"), !isLastArgument(at: eventOutputIndex) {
306-
result.experimentalEventStreamOutput = args[args.index(after: eventOutputIndex)]
306+
if let eventOutputIndex = args.firstIndex(of: "--event-stream-output") ?? args.firstIndex(of: "--experimental-event-stream-output"),
307+
!isLastArgument(at: eventOutputIndex) {
308+
result.eventStreamOutput = args[args.index(after: eventOutputIndex)]
307309
}
308310
// Event stream output (experimental)
309-
if let eventOutputVersionIndex = args.firstIndex(of: "--experimental-event-stream-version"), !isLastArgument(at: eventOutputVersionIndex) {
310-
result.experimentalEventStreamVersion = Int(args[args.index(after: eventOutputVersionIndex)])
311+
if let eventOutputVersionIndex = args.firstIndex(of: "--event-stream-version") ?? args.firstIndex(of: "--experimental-event-stream-version"),
312+
!isLastArgument(at: eventOutputVersionIndex) {
313+
result.eventStreamVersion = Int(args[args.index(after: eventOutputVersionIndex)])
311314
}
312315
#endif
313316

@@ -404,9 +407,9 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr
404407

405408
#if canImport(Foundation)
406409
// Event stream output (experimental)
407-
if let eventStreamOutputPath = args.experimentalEventStreamOutput {
410+
if let eventStreamOutputPath = args.eventStreamOutput {
408411
let file = try FileHandle(forWritingAtPath: eventStreamOutputPath)
409-
let eventHandler = try eventHandlerForStreamingEvents(version: args.experimentalEventStreamVersion) { json in
412+
let eventHandler = try eventHandlerForStreamingEvents(version: args.eventStreamVersion) { json in
410413
try? _writeJSONLine(json, to: file)
411414
}
412415
configuration.eventHandler = { [oldEventHandler = configuration.eventHandler] event, context in
@@ -487,7 +490,7 @@ func eventHandlerForStreamingEvents(version: Int?, forwardingTo eventHandler: @e
487490
case 0:
488491
ABIv0.Record.eventHandler(forwardingTo: eventHandler)
489492
case let .some(unsupportedVersion):
490-
throw _EntryPointError.invalidArgument("--experimental-event-stream-version", value: "\(unsupportedVersion)")
493+
throw _EntryPointError.invalidArgument("--event-stream-version", value: "\(unsupportedVersion)")
491494
}
492495
}
493496

0 commit comments

Comments
 (0)