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
8 changes: 3 additions & 5 deletions Sources/Testing/ABI/ABI.Record+Streaming.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ private import Foundation
extension ABI.Version {
static func eventHandler(
encodeAsJSONLines: Bool,
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: borrowing RawSpan) -> Void
) -> Event.Handler {
// Encode as JSON Lines if requested.
var eventHandlerCopy = eventHandler
Expand Down Expand Up @@ -44,7 +44,7 @@ extension ABI.Version {
extension ABI.Xcode16 {
static func eventHandler(
encodeAsJSONLines: Bool,
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: borrowing RawSpan) -> Void
) -> Event.Handler {
return { event, context in
if case .testDiscovered = event.kind {
Expand All @@ -63,9 +63,7 @@ extension ABI.Xcode16 {
eventContext: Event.Context.Snapshot(snapshotting: context)
)
try? JSON.withEncoding(of: snapshot) { eventAndContextJSON in
eventAndContextJSON.withUnsafeBytes { eventAndContextJSON in
eventHandler(eventAndContextJSON)
}
eventHandler(eventAndContextJSON)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/ABI/ABI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ extension ABI {
/// associated context is created and is passed to `eventHandler`.
static func eventHandler(
encodeAsJSONLines: Bool,
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
forwardingTo eventHandler: @escaping @Sendable (_ recordJSON: borrowing RawSpan) -> Void
) -> Event.Handler
#endif
}
Expand Down
6 changes: 4 additions & 2 deletions Sources/Testing/ABI/EntryPoints/ABIEntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ extension ABI.v0 {
public static var entryPoint: EntryPoint {
return { configurationJSON, recordHandler in
let args = try configurationJSON.map { configurationJSON in
try JSON.decode(__CommandLineArguments_v0.self, from: configurationJSON)
try JSON.decode(__CommandLineArguments_v0.self, from: configurationJSON.bytes)
}
let eventHandler = try eventHandlerForStreamingEvents(withVersionNumber: args?.eventStreamVersionNumber, encodeAsJSONLines: false) { recordJSON in
recordJSON.withUnsafeBytes(recordHandler)
}
let eventHandler = try eventHandlerForStreamingEvents(withVersionNumber: args?.eventStreamVersionNumber, encodeAsJSONLines: false, forwardingTo: recordHandler)

switch await Testing.entryPoint(passing: args, eventHandler: eventHandler) {
case EXIT_SUCCESS, EXIT_NO_TESTS_FOUND:
Expand Down
6 changes: 2 additions & 4 deletions Sources/Testing/ABI/EntryPoints/EntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,7 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
if let path = args.argumentValue(forLabel: "--configuration-path") ?? args.argumentValue(forLabel: "--experimental-configuration-path") {
let file = try FileHandle(forReadingAtPath: path)
let configurationJSON = try file.readToEnd()
result = try configurationJSON.withUnsafeBufferPointer { configurationJSON in
try JSON.decode(__CommandLineArguments_v0.self, from: .init(configurationJSON))
}
result = try JSON.decode(__CommandLineArguments_v0.self, from: configurationJSON.span.bytes)

// NOTE: We don't return early or block other arguments here: a caller is
// allowed to pass a configuration AND e.g. "--verbose" and they'll both be
Expand Down Expand Up @@ -706,7 +704,7 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr
func eventHandlerForStreamingEvents(
withVersionNumber versionNumber: VersionNumber?,
encodeAsJSONLines: Bool,
forwardingTo targetEventHandler: @escaping @Sendable (UnsafeRawBufferPointer) -> Void
forwardingTo targetEventHandler: @escaping @Sendable (borrowing RawSpan) -> Void
) throws -> Event.Handler {
let versionNumber = versionNumber ?? ABI.CurrentVersion.versionNumber
guard let abi = ABI.version(forVersionNumber: versionNumber) else {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/Attachments/Attachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ extension Attachment where AttachableValue: ~Copyable {
// There should be no code path that leads to this call where the attachable
// value is nil.
try withUnsafeBytes { buffer in
try file!.write(buffer)
try file!.write(buffer.bytes)
}

return result
Expand Down
37 changes: 22 additions & 15 deletions Sources/Testing/Events/Event+FallbackHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,27 @@ private import _TestingInternals

extension Event {
#if compiler(>=6.3) && !SWT_NO_INTEROP
/// The installed fallback event handler.
private static let _fallbackEventHandler: SWTFallbackEventHandler? = {
_swift_testing_getFallbackEventHandler()
}()

/// Encode an event and pass it to the installed fallback event handler.
private static let _encodeAndInvoke: Event.Handler? = { [fallbackEventHandler = _fallbackEventHandler] in
guard let fallbackEventHandler else {
return nil
}
return ABI.CurrentVersion.eventHandler(encodeAsJSONLines: false) { recordJSON in
recordJSON.withUnsafeBytes { recordJSON in
fallbackEventHandler(
String(describing: ABI.CurrentVersion.versionNumber),
recordJSON.baseAddress!,
recordJSON.count,
nil
)
}
}
}()
#endif

/// Post this event to the currently-installed fallback event handler.
Expand All @@ -27,23 +45,12 @@ extension Event {
/// `false`.
borrowing func postToFallbackHandler(in context: borrowing Context) -> Bool {
#if compiler(>=6.3) && !SWT_NO_INTEROP
guard let fallbackEventHandler = Self._fallbackEventHandler else {
return false
}

// Encode the event as JSON and pass it to the handler.
let encodeAndInvoke = ABI.CurrentVersion.eventHandler(encodeAsJSONLines: false) { recordJSON in
fallbackEventHandler(
String(describing: ABI.CurrentVersion.versionNumber),
recordJSON.baseAddress!,
recordJSON.count,
nil
)
if let encodeAndInvoke = Self._encodeAndInvoke {
encodeAndInvoke(self, context)
return true
}
encodeAndInvoke(self, context)
return true
#else
return false
#endif
return false
}
}
24 changes: 9 additions & 15 deletions Sources/Testing/ExitTests/ExitTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -605,8 +605,8 @@ extension ExitTest {
/// and standard error streams of the current process.
private static func _writeBarrierValues() {
let barrierValue = Self.barrierValue
try? FileHandle.stdout.write(barrierValue)
try? FileHandle.stderr.write(barrierValue)
try? FileHandle.stdout.write(barrierValue.span.bytes)
try? FileHandle.stderr.write(barrierValue.span.bytes)
}

/// A handler that is invoked when an exit test starts.
Expand Down Expand Up @@ -712,13 +712,11 @@ extension ExitTest {

/// The ID of the exit test to run, if any, specified in the environment.
static var environmentIDForEntryPoint: ID? {
guard var idString = Environment.variable(named: Self._idEnvironmentVariableName) else {
guard let idString = Environment.variable(named: Self._idEnvironmentVariableName) else {
return nil
}

return try? idString.withUTF8 { idBuffer in
try JSON.decode(ExitTest.ID.self, from: UnsafeRawBufferPointer(idBuffer))
}
return try? JSON.decode(ExitTest.ID.self, from: idString.utf8.span.bytes)
}

/// Find the exit test function specified in the environment of the current
Expand Down Expand Up @@ -870,7 +868,7 @@ extension ExitTest {
// Insert a specific variable that tells the child process which exit test
// to run.
try JSON.withEncoding(of: exitTest.id) { json in
childEnvironment[Self._idEnvironmentVariableName] = String(decoding: json, as: UTF8.self)
childEnvironment[Self._idEnvironmentVariableName] = String(decoding: Array(json), as: UTF8.self)
}

typealias ResultUpdater = @Sendable (inout ExitTest.Result) -> Void
Expand Down Expand Up @@ -1007,9 +1005,7 @@ extension ExitTest {

for recordJSON in bytes.split(whereSeparator: \.isASCIINewline) where !recordJSON.isEmpty {
do {
try recordJSON.withUnsafeBufferPointer { recordJSON in
try Self._processRecord(.init(recordJSON), fromBackChannel: backChannel)
}
try Self._processRecord(recordJSON.span.bytes, fromBackChannel: backChannel)
} catch {
// NOTE: an error caught here indicates a decoding problem.
// TODO: should we record these issues as systemic instead?
Expand All @@ -1026,7 +1022,7 @@ extension ExitTest {
/// - backChannel: The file handle that `recordJSON` was read from.
///
/// - Throws: Any error encountered attempting to decode or process the JSON.
private static func _processRecord(_ recordJSON: UnsafeRawBufferPointer, fromBackChannel backChannel: borrowing FileHandle) throws {
private static func _processRecord(_ recordJSON: borrowing RawSpan, fromBackChannel backChannel: borrowing FileHandle) throws {
let record = try JSON.decode(ABI.Record<ABI.BackChannelVersion>.self, from: recordJSON)
guard case let .event(event) = record.kind else {
return
Expand Down Expand Up @@ -1093,9 +1089,7 @@ extension ExitTest {
var capturedValue = capturedValue

func open<T>(_ type: T.Type) throws -> T where T: Codable & Sendable {
return try capturedValueJSON.withUnsafeBytes { capturedValueJSON in
try JSON.decode(type, from: capturedValueJSON)
}
return try JSON.decode(type, from: capturedValueJSON.span.bytes)
}
capturedValue.wrappedValue = try open(capturedValue.typeOfWrappedValue)

Expand All @@ -1118,7 +1112,7 @@ extension ExitTest {
/// This function should only be used when the process was started via the
/// `__swiftPMEntryPoint()` function. The effect of using it under other
/// configurations is undefined.
private borrowing func _withEncodedCapturedValuesForEntryPoint(_ body: (UnsafeRawBufferPointer) throws -> Void) throws -> Void {
private borrowing func _withEncodedCapturedValuesForEntryPoint(_ body: (borrowing RawSpan) throws -> Void) throws -> Void {
for capturedValue in capturedValues {
try JSON.withEncoding(of: capturedValue.wrappedValue!) { capturedValueJSON in
try JSON.asJSONLine(capturedValueJSON, body)
Expand Down
46 changes: 46 additions & 0 deletions Sources/Testing/Support/Additions/ArrayAdditions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,52 @@ extension Array {
}
}

// MARK: - Span/RawSpan support

extension Array where Element == UInt8 {
init(_ bytes: borrowing RawSpan) {
self = bytes.withUnsafeBytes { Array($0) }
}
}

#if SWT_TARGET_OS_APPLE
extension Array {
/// The elements of this array as a span.
///
/// This property is equivalent to the `span` property in the Swift standard
/// library, but is available on earlier Apple platforms.
///
/// For arrays with contiguous storage, getting the value of this property is
/// an _O_(1) operation. For arrays with non-contiguous storage (i.e. bridged
/// from Objective-C), the operation may be up to _O_(_n_).
var span: Span<Element> {
@_lifetime(borrow self)
_read {
let slice = self[...]
yield slice.span
}
}
}

extension String.UTF8View {
/// A raw span representing this string as UTF-8, not including a trailing
/// null character.
///
/// This property is equivalent to the `span` property in the Swift standard
/// library, but is available on earlier Apple platforms.
var span: Span<Element> {
@_lifetime(borrow self)
_read {
// This implementation incurs a copy even for native Swift strings. This
// isn't currently a hot path in the testing library though.
yield ContiguousArray(self).span
}
}
}
#endif

// MARK: - Parameter pack additions

/// Get the number of elements in a parameter pack.
///
/// - Parameters:
Expand Down
63 changes: 10 additions & 53 deletions Sources/Testing/Support/FileHandle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ extension FileHandle {
// MARK: - Writing

extension FileHandle {
/// Write a sequence of bytes to this file handle.
/// Write a span of bytes to this file handle.
///
/// - Parameters:
/// - bytes: The bytes to write. This untyped buffer is interpreted as a
Expand All @@ -376,57 +376,26 @@ extension FileHandle {
///
/// - Throws: Any error that occurred while writing `bytes`. If an error
/// occurs while flushing the file, it is not thrown.
func write(_ bytes: UnsafeBufferPointer<UInt8>, flushAfterward: Bool = true) throws {
func write(_ bytes: borrowing RawSpan, flushAfterward: Bool = true) throws {
try withUnsafeCFILEHandle { file in
defer {
if flushAfterward {
_ = fflush(file)
}
}

let countWritten = fwrite(bytes.baseAddress!, MemoryLayout<UInt8>.stride, bytes.count, file)
if countWritten < bytes.count {
if bytes.isEmpty {
return
}
let countWritten = bytes.withUnsafeBytes { bytes in
fwrite(bytes.baseAddress!, MemoryLayout<UInt8>.stride, bytes.count, file)
}
if countWritten < bytes.byteCount {
throw CError(rawValue: swt_errno())
}
}
}

/// Write a sequence of bytes to this file handle.
///
/// - Parameters:
/// - bytes: The bytes to write.
/// - flushAfterward: Whether or not to flush the file (with `fflush()`)
/// after writing. If `true`, `fflush()` is called even if an error
/// occurred while writing.
///
/// - Throws: Any error that occurred while writing `bytes`. If an error
/// occurs while flushing the file, it is not thrown.
///
/// - Precondition: `bytes` must provide contiguous storage.
func write(_ bytes: some Sequence<UInt8>, flushAfterward: Bool = true) throws {
let hasContiguousStorage: Void? = try bytes.withContiguousStorageIfAvailable { bytes in
try write(bytes, flushAfterward: flushAfterward)
}
precondition(hasContiguousStorage != nil, "byte sequence must provide contiguous storage: \(bytes)")
}

/// Write a sequence of bytes to this file handle.
///
/// - Parameters:
/// - bytes: The bytes to write. This untyped buffer is interpreted as a
/// sequence of `UInt8` values.
/// - flushAfterward: Whether or not to flush the file (with `fflush()`)
/// after writing. If `true`, `fflush()` is called even if an error
/// occurred while writing.
///
/// - Throws: Any error that occurred while writing `bytes`. If an error
/// occurs while flushing the file, it is not thrown.
func write(_ bytes: UnsafeRawBufferPointer, flushAfterward: Bool = true) throws {
try bytes.withMemoryRebound(to: UInt8.self) { bytes in
try write(bytes, flushAfterward: flushAfterward)
}
}

/// Write a string to this file handle.
///
/// - Parameters:
Expand All @@ -441,19 +410,7 @@ extension FileHandle {
/// `string` is converted to a UTF-8 C string (UTF-16 on Windows) and written
/// to this file handle.
func write(_ string: String, flushAfterward: Bool = true) throws {
try withUnsafeCFILEHandle { file in
defer {
if flushAfterward {
_ = fflush(file)
}
}

try string.withCString { string in
if EOF == fputs(string, file) {
throw CError(rawValue: swt_errno())
}
}
}
try write(string.utf8.span.bytes, flushAfterward: flushAfterward)
}
}

Expand Down
Loading
Loading