Skip to content

Commit 81a5ccb

Browse files
authored
Hoist JSON Lines logic from EntryPoint.swift. (#701)
This PR moves the logic that implements JSON Lines support (i.e. the code that strips newlines from JSON generated by Foundation) out of EntryPoint.swift so that it can be used by other callers. This change was originally part of #697. I'm splitting it out into its own PR to make that one (both really) easier to read and understand. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 260faee commit 81a5ccb

File tree

5 files changed

+69
-53
lines changed

5 files changed

+69
-53
lines changed

Sources/Testing/ABI/EntryPoints/ABIEntryPoint.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private func entryPoint(
120120
args?.eventStreamVersion = eventStreamVersionIfNil
121121
}
122122

123-
let eventHandler = try eventHandlerForStreamingEvents(version: args?.eventStreamVersion, forwardingTo: recordHandler)
123+
let eventHandler = try eventHandlerForStreamingEvents(version: args?.eventStreamVersion, encodeAsJSONLines: false, forwardingTo: recordHandler)
124124
let exitCode = await entryPoint(passing: args, eventHandler: eventHandler)
125125

126126
// To maintain compatibility with Xcode 16 Beta 1, suppress custom exit codes.

Sources/Testing/ABI/EntryPoints/EntryPoint.swift

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,11 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr
468468
// Event stream output (experimental)
469469
if let eventStreamOutputPath = args.eventStreamOutputPath {
470470
let file = try FileHandle(forWritingAtPath: eventStreamOutputPath)
471-
let eventHandler = try eventHandlerForStreamingEvents(version: args.eventStreamVersion) { json in
472-
try? _writeJSONLine(json, to: file)
471+
let eventHandler = try eventHandlerForStreamingEvents(version: args.eventStreamVersion, encodeAsJSONLines: true) { json in
472+
_ = try? file.withLock {
473+
try file.write(json)
474+
try file.write("\n")
475+
}
473476
}
474477
configuration.eventHandler = { [oldEventHandler = configuration.eventHandler] event, context in
475478
eventHandler(event, context)
@@ -536,13 +539,20 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr
536539
///
537540
/// - Parameters:
538541
/// - version: The ABI version to use.
542+
/// - encodeAsJSONLines: Whether or not to ensure JSON passed to
543+
/// `eventHandler` is encoded as JSON Lines (i.e. that it does not contain
544+
/// extra newlines.)
539545
/// - eventHandler: The event handler to forward encoded events to. The
540546
/// encoding of events depends on `version`.
541547
///
542548
/// - Returns: An event handler.
543549
///
544550
/// - Throws: If `version` is not a supported ABI version.
545-
func eventHandlerForStreamingEvents(version: Int?, forwardingTo eventHandler: @escaping @Sendable (UnsafeRawBufferPointer) -> Void) throws -> Event.Handler {
551+
func eventHandlerForStreamingEvents(
552+
version: Int?,
553+
encodeAsJSONLines: Bool,
554+
forwardingTo eventHandler: @escaping @Sendable (UnsafeRawBufferPointer) -> Void
555+
) throws -> Event.Handler {
546556
switch version {
547557
#if !SWT_NO_SNAPSHOT_TYPES
548558
case -1:
@@ -551,57 +561,11 @@ func eventHandlerForStreamingEvents(version: Int?, forwardingTo eventHandler: @e
551561
eventHandlerForStreamingEventSnapshots(to: eventHandler)
552562
#endif
553563
case nil, 0:
554-
ABIv0.Record.eventHandler(forwardingTo: eventHandler)
564+
ABIv0.Record.eventHandler(encodeAsJSONLines: encodeAsJSONLines, forwardingTo: eventHandler)
555565
case let .some(unsupportedVersion):
556566
throw _EntryPointError.invalidArgument("--event-stream-version", value: "\(unsupportedVersion)")
557567
}
558568
}
559-
560-
/// Post-process encoded JSON and write it to a file.
561-
///
562-
/// - Parameters:
563-
/// - json: The JSON to write.
564-
/// - file: The file to write to.
565-
///
566-
/// - Throws: Whatever is thrown when writing to `file`.
567-
private func _writeJSONLine(_ json: UnsafeRawBufferPointer, to file: borrowing FileHandle) throws {
568-
func isASCIINewline(_ byte: UInt8) -> Bool {
569-
byte == UInt8(ascii: "\r") || byte == UInt8(ascii: "\n")
570-
}
571-
572-
func write(_ json: UnsafeRawBufferPointer) throws {
573-
try file.withLock {
574-
try file.write(json)
575-
try file.write("\n")
576-
}
577-
}
578-
579-
// We don't actually expect the JSON encoder to produce output containing
580-
// newline characters, so in debug builds we'll log a diagnostic message.
581-
if _slowPath(json.contains(where: isASCIINewline)) {
582-
#if DEBUG
583-
let message = Event.ConsoleOutputRecorder.warning(
584-
"JSON encoder produced one or more newline characters while encoding an event to JSON. Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new",
585-
options: .for(.stderr)
586-
)
587-
#if SWT_TARGET_OS_APPLE
588-
try? FileHandle.stderr.write(message)
589-
#else
590-
print(message)
591-
#endif
592-
#endif
593-
594-
// Remove the newline characters to conform to JSON lines specification.
595-
var json = Array(json)
596-
json.removeAll(where: isASCIINewline)
597-
try json.withUnsafeBytes { json in
598-
try write(json)
599-
}
600-
} else {
601-
// No newlines found, no need to copy the buffer.
602-
try write(json)
603-
}
604-
}
605569
#endif
606570

607571
// MARK: - Command-line interface options

Sources/Testing/ABI/v0/ABIv0.Record+Streaming.swift

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,46 @@
1010

1111
#if canImport(Foundation) && (!SWT_NO_FILE_IO || !SWT_NO_ABI_ENTRY_POINT)
1212
extension ABIv0.Record {
13+
/// Post-process encoded JSON and write it to a file.
14+
///
15+
/// - Parameters:
16+
/// - json: The JSON to write.
17+
/// - file: The file to write to.
18+
///
19+
/// - Throws: Whatever is thrown when writing to `file`.
20+
private static func _asJSONLine(_ json: UnsafeRawBufferPointer, _ eventHandler: (_ recordJSON: UnsafeRawBufferPointer) throws -> Void) rethrows {
21+
// We don't actually expect the JSON encoder to produce output containing
22+
// newline characters, so in debug builds we'll log a diagnostic message.
23+
if _slowPath(json.contains(where: \.isASCIINewline)) {
24+
#if DEBUG
25+
let message = Event.ConsoleOutputRecorder.warning(
26+
"JSON encoder produced one or more newline characters while encoding an event to JSON. Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new",
27+
options: .for(.stderr)
28+
)
29+
#if SWT_TARGET_OS_APPLE
30+
try? FileHandle.stderr.write(message)
31+
#else
32+
print(message)
33+
#endif
34+
#endif
35+
36+
// Remove the newline characters to conform to JSON lines specification.
37+
var json = Array(json)
38+
json.removeAll(where: \.isASCIINewline)
39+
try json.withUnsafeBytes(eventHandler)
40+
} else {
41+
// No newlines found, no need to copy the buffer.
42+
try eventHandler(json)
43+
}
44+
}
45+
1346
/// Create an event handler that encodes events as JSON and forwards them to
1447
/// an ABI-friendly event handler.
1548
///
1649
/// - Parameters:
50+
/// - encodeAsJSONLines: Whether or not to ensure JSON passed to
51+
/// `eventHandler` is encoded as JSON Lines (i.e. that it does not contain
52+
/// extra newlines.)
1753
/// - eventHandler: The event handler to forward events to. See
1854
/// ``ABIv0/EntryPoint-swift.typealias`` for more information.
1955
///
@@ -27,10 +63,17 @@ extension ABIv0.Record {
2763
/// performs additional postprocessing before writing JSON data to ensure it
2864
/// does not contain any newline characters.
2965
static func eventHandler(
66+
encodeAsJSONLines: Bool,
3067
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
3168
) -> Event.Handler {
69+
// Encode as JSON Lines if requested.
70+
var eventHandlerCopy = eventHandler
71+
if encodeAsJSONLines {
72+
eventHandlerCopy = { @Sendable in _asJSONLine($0, eventHandler) }
73+
}
74+
3275
let humanReadableOutputRecorder = Event.HumanReadableOutputRecorder()
33-
return { event, context in
76+
return { [eventHandler = eventHandlerCopy] event, context in
3477
if case .testDiscovered = event.kind, let test = context.test {
3578
try? JSON.withEncoding(of: Self(encoding: test)) { testJSON in
3679
eventHandler(testJSON)

Sources/Testing/Support/Additions/NumericAdditions.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,12 @@ extension Numeric {
2525
return "\(self) \(noun)s"
2626
}
2727
}
28+
29+
// MARK: -
30+
31+
extension UInt8 {
32+
/// Whether or not this instance is an ASCII newline character (`\n` or `\r`).
33+
var isASCIINewline: Bool {
34+
self == UInt8(ascii: "\r") || self == UInt8(ascii: "\n")
35+
}
36+
}

Tests/TestingTests/SwiftPMTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ struct SwiftPMTests {
228228

229229
func decodeABIv0RecordStream(fromFileAtPath path: String) throws -> [ABIv0.Record] {
230230
try FileHandle(forReadingAtPath: path).readToEnd()
231-
.split(separator: 10) // "\n"
231+
.split(whereSeparator: \.isASCIINewline)
232232
.map { line in
233233
try line.withUnsafeBytes { line in
234234
try JSON.decode(ABIv0.Record.self, from: line)

0 commit comments

Comments
 (0)