Skip to content

Commit 0b81a36

Browse files
committed
Merge branch 'main' into jgrynspan/fallback-event-handler
2 parents 71c5fa5 + a8a3fcb commit 0b81a36

File tree

10 files changed

+241
-88
lines changed

10 files changed

+241
-88
lines changed

Package.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,15 @@ let package = Package(
117117
}(),
118118

119119
dependencies: [
120-
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "602.0.0-latest"),
120+
// swift-syntax periodically publishes a new tag with a suffix of the format
121+
// "-prerelease-YYYY-MM-DD". We always want to use the most recent tag
122+
// associated with a particular Swift version, without needing to hardcode
123+
// an exact tag and manually keep it up-to-date. Specifying the suffix
124+
// "-latest" on this dependency is a workaround which causes Swift package
125+
// manager to use the lexicographically highest-sorted tag with the
126+
// specified semantic version, meaning the most recent "prerelease" tag will
127+
// always be used.
128+
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "603.0.0-latest"),
121129
],
122130

123131
targets: [
@@ -148,7 +156,10 @@ let package = Package(
148156
"_Testing_WinSDK",
149157
"MemorySafeTestingTests",
150158
],
151-
swiftSettings: .packageSettings
159+
swiftSettings: .packageSettings,
160+
linkerSettings: [
161+
.linkedLibrary("util", .when(platforms: [.openbsd]))
162+
]
152163
),
153164

154165
// Use a plain `.target` instead of a `.testTarget` to avoid the unnecessary

Sources/Testing/ABI/Encoded/ABI.EncodedAttachment.swift

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,10 @@
88
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
99
//
1010

11-
private import _TestingInternals
12-
1311
#if canImport(Foundation)
1412
private import Foundation
1513
#endif
1614

17-
/// The maximum size, in bytes, of an attachment that will be stored inline in
18-
/// an encoded attachment.
19-
private let _maximumInlineAttachmentByteCount: Int = {
20-
let pageSize: Int
21-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)
22-
pageSize = Int(clamping: sysconf(_SC_PAGESIZE))
23-
#elseif os(WASI)
24-
// sysconf(_SC_PAGESIZE) is a complex macro in wasi-libc.
25-
pageSize = Int(clamping: getpagesize())
26-
#elseif os(Windows)
27-
var systemInfo = SYSTEM_INFO()
28-
GetSystemInfo(&systemInfo)
29-
pageSize = Int(clamping: systemInfo.dwPageSize)
30-
#else
31-
#warning("Platform-specific implementation missing: page size unknown (assuming 4KB)")
32-
pageSize = 4 * 1024
33-
#endif
34-
35-
return pageSize // i.e. we'll store up to a page-sized attachment
36-
}()
37-
3815
extension ABI {
3916
/// A type implementing the JSON encoding of ``Attachment`` for the ABI entry
4017
/// point and event stream output.
@@ -54,8 +31,9 @@ extension ABI {
5431

5532
/// The raw content of the attachment, if available.
5633
///
57-
/// If the value of this property is `nil`, the attachment can instead be
58-
/// read from ``path``.
34+
/// The value of this property is set if the attachment was not first saved
35+
/// to a file. It may also be `nil` if an error occurred while trying to get
36+
/// the original attachment's serialized representation.
5937
///
6038
/// - Warning: Inline attachment content is not yet part of the JSON schema.
6139
var _bytes: Bytes?
@@ -68,19 +46,18 @@ extension ABI {
6846

6947
init(encoding attachment: borrowing Attachment<AnyAttachable>, in eventContext: borrowing Event.Context) {
7048
path = attachment.fileSystemPath
71-
_preferredName = attachment.preferredName
7249

73-
if let estimatedByteCount = attachment.attachableValue.estimatedAttachmentByteCount,
74-
estimatedByteCount <= _maximumInlineAttachmentByteCount {
75-
_bytes = try? attachment.withUnsafeBytes { bytes in
76-
if bytes.count > 0 && bytes.count < _maximumInlineAttachmentByteCount {
50+
if V.versionNumber >= ABI.v6_3.versionNumber {
51+
_preferredName = attachment.preferredName
52+
53+
if path == nil {
54+
_bytes = try? attachment.withUnsafeBytes { bytes in
7755
return Bytes(rawValue: [UInt8](bytes))
7856
}
79-
return nil
8057
}
81-
}
8258

83-
_sourceLocation = attachment.sourceLocation
59+
_sourceLocation = attachment.sourceLocation
60+
}
8461
}
8562

8663
/// A structure representing the bytes of an attachment.
@@ -134,20 +111,32 @@ extension ABI.EncodedAttachment: Attachable {
134111
_bytes?.rawValue.count
135112
}
136113

114+
/// An error type that is thrown when ``ABI/EncodedAttachment`` cannot satisfy
115+
/// a request for the underlying attachment's bytes.
137116
fileprivate struct BytesUnavailableError: Error {}
138117

139118
borrowing func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
140119
if let bytes = _bytes?.rawValue {
141120
return try bytes.withUnsafeBytes(body)
142121
}
143122

123+
#if !SWT_NO_FILE_IO
144124
guard let path else {
145125
throw BytesUnavailableError()
146126
}
127+
#if canImport(Foundation)
128+
// Leverage Foundation's file-mapping logic since we're using Data anyway.
129+
let url = URL(fileURLWithPath: path, isDirectory: false)
130+
let bytes = try Data(contentsOf: url, options: [.mappedIfSafe])
131+
#else
147132
let fileHandle = try FileHandle(forReadingAtPath: path)
148-
// TODO: map the attachment back into memory
149133
let bytes = try fileHandle.readToEnd()
134+
#endif
150135
return try bytes.withUnsafeBytes(body)
136+
#else
137+
// Cannot read the attachment from disk on this platform.
138+
throw BytesUnavailableError()
139+
#endif
151140
}
152141

153142
borrowing func preferredName(for attachment: borrowing Attachment<Self>, basedOn suggestedName: String) -> String {

Sources/Testing/ABI/Encoded/ABI.EncodedIssue.swift

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,21 @@ extension ABI {
2626

2727
/// The severity of this issue.
2828
///
29-
/// - Warning: Severity is not yet part of the JSON schema.
30-
var _severity: Severity?
29+
/// Prior to 6.3, this is nil.
30+
///
31+
/// @Metadata {
32+
/// @Available(Swift, introduced: 6.3)
33+
/// }
34+
var severity: Severity?
3135

3236
/// If the issue is a failing issue.
3337
///
34-
/// - Warning: Non-failing issues are not yet part of the JSON schema.
35-
var _isFailure: Bool?
38+
/// Prior to 6.3, this is nil.
39+
///
40+
/// @Metadata {
41+
/// @Available(Swift, introduced: 6.3)
42+
/// }
43+
var isFailure: Bool?
3644

3745
/// Whether or not this issue is known to occur.
3846
var isKnown: Bool
@@ -51,13 +59,20 @@ extension ABI {
5159
var _error: EncodedError<V>?
5260

5361
init(encoding issue: borrowing Issue, in eventContext: borrowing Event.Context) {
54-
_severity = switch issue.severity {
55-
case .warning: .warning
56-
case .error: .error
57-
}
58-
_isFailure = issue.isFailure
62+
// >= v0
5963
isKnown = issue.isKnown
6064
sourceLocation = issue.sourceLocation
65+
66+
// >= v6.3
67+
if V.versionNumber >= ABI.v6_3.versionNumber {
68+
severity = switch issue.severity {
69+
case .warning: .warning
70+
case .error: .error
71+
}
72+
isFailure = issue.isFailure
73+
}
74+
75+
// Experimental
6176
if let backtrace = issue.sourceContext.backtrace {
6277
_backtrace = EncodedBacktrace(encoding: backtrace, in: eventContext)
6378
}

Sources/Testing/ABI/EntryPoints/EntryPoint.swift

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ func entryPoint(passing args: __CommandLineArguments_v0?, eventHandler: Event.Ha
5757
// Check for experimental console output flag
5858
if Environment.flag(named: "SWT_ENABLE_EXPERIMENTAL_CONSOLE_OUTPUT") == true {
5959
// Use experimental AdvancedConsoleOutputRecorder
60-
var advancedOptions = Event.AdvancedConsoleOutputRecorder.Options()
60+
var advancedOptions = Event.AdvancedConsoleOutputRecorder<ABI.HighestVersion>.Options()
6161
advancedOptions.base = .for(.stderr)
62-
63-
let eventRecorder = Event.AdvancedConsoleOutputRecorder(options: advancedOptions) { string in
62+
63+
let eventRecorder = Event.AdvancedConsoleOutputRecorder<ABI.HighestVersion>(options: advancedOptions) { string in
6464
try? FileHandle.stderr.write(string)
6565
}
66-
66+
6767
configuration.eventHandler = { [oldEventHandler = configuration.eventHandler] event, context in
6868
eventRecorder.record(event, in: context)
6969
oldEventHandler(event, context)
@@ -328,13 +328,6 @@ public struct __CommandLineArguments_v0: Sendable {
328328

329329
/// The value of the `--attachments-path` argument.
330330
public var attachmentsPath: String?
331-
332-
/// Whether or not the experimental warning issue severity feature should be
333-
/// enabled.
334-
///
335-
/// This property is intended for use in testing the testing library itself.
336-
/// It is not parsed as a command-line argument.
337-
var isWarningIssueRecordedEventEnabled: Bool?
338331
}
339332

340333
extension __CommandLineArguments_v0: Codable {
@@ -635,19 +628,15 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr
635628
#endif
636629

637630
// Warning issues (experimental).
638-
if args.isWarningIssueRecordedEventEnabled == true {
639-
configuration.eventHandlingOptions.isWarningIssueRecordedEventEnabled = true
640-
} else {
641-
switch args.eventStreamVersionNumber {
642-
case .some(..<ABI.v6_3.versionNumber):
643-
// If the event stream version was explicitly specified to a value < 6.3,
644-
// disable the warning issue event to maintain legacy behavior.
645-
configuration.eventHandlingOptions.isWarningIssueRecordedEventEnabled = false
646-
default:
647-
// Otherwise the requested event stream version is ≥ 6.3, so don't change
648-
// the warning issue event setting.
649-
break
650-
}
631+
switch args.eventStreamVersionNumber {
632+
case .some(..<ABI.v6_3.versionNumber):
633+
// If the event stream version was explicitly specified to a value < 6.3,
634+
// disable the warning issue event to maintain legacy behavior.
635+
configuration.eventHandlingOptions.isWarningIssueRecordedEventEnabled = false
636+
default:
637+
// Otherwise the requested event stream version is ≥ 6.3, so don't change
638+
// the warning issue event setting.
639+
break
651640
}
652641

653642
return configuration

Sources/Testing/Events/Recorder/Event.AdvancedConsoleOutputRecorder.swift

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extension Event {
1414
///
1515
/// This recorder is currently experimental and must be enabled via the
1616
/// `SWT_ENABLE_EXPERIMENTAL_CONSOLE_OUTPUT` environment variable.
17-
struct AdvancedConsoleOutputRecorder: Sendable {
17+
struct AdvancedConsoleOutputRecorder<V: ABI.Version>: Sendable {
1818
/// Configuration options for the advanced console output recorder.
1919
struct Options: Sendable {
2020
/// Base console output recorder options to inherit from.
@@ -25,6 +25,15 @@ extension Event {
2525
}
2626
}
2727

28+
/// Context for storing data across events during test execution.
29+
private struct _Context: Sendable {
30+
/// Storage for test information, keyed by test ID string value.
31+
/// This is needed because ABI.EncodedEvent doesn't contain full test context.
32+
var testStorage: [String: ABI.EncodedTest<V>] = [:]
33+
34+
// Future storage for result data and other event information can be added here
35+
}
36+
2837
/// The options for this recorder.
2938
let options: Options
3039

@@ -34,6 +43,12 @@ extension Event {
3443
/// The fallback console recorder for standard output.
3544
private let _fallbackRecorder: Event.ConsoleOutputRecorder
3645

46+
/// Context storage for test information and results.
47+
private let _context: Locked<_Context>
48+
49+
/// Human-readable output recorder for generating messages.
50+
private let _humanReadableRecorder: Event.HumanReadableOutputRecorder
51+
3752
/// Initialize the advanced console output recorder.
3853
///
3954
/// - Parameters:
@@ -43,21 +58,77 @@ extension Event {
4358
self.options = options
4459
self.write = write
4560
self._fallbackRecorder = Event.ConsoleOutputRecorder(options: options.base, writingUsing: write)
61+
self._context = Locked(rawValue: _Context())
62+
self._humanReadableRecorder = Event.HumanReadableOutputRecorder()
4663
}
4764
}
4865
}
4966

5067
extension Event.AdvancedConsoleOutputRecorder {
5168
/// Record an event by processing it and generating appropriate output.
5269
///
53-
/// Currently this is a skeleton implementation that delegates to
54-
/// ``Event/ConsoleOutputRecorder``.
70+
/// This implementation converts the Event to ABI.EncodedEvent for internal processing,
71+
/// following the ABI-based architecture for future separation into a harness process.
5572
///
5673
/// - Parameters:
5774
/// - event: The event to record.
5875
/// - eventContext: The context associated with the event.
5976
func record(_ event: borrowing Event, in eventContext: borrowing Event.Context) {
60-
// Skeleton implementation: delegate to ConsoleOutputRecorder
77+
// Handle test discovery to populate our test storage
78+
if case .testDiscovered = event.kind, let test = eventContext.test {
79+
let encodedTest = ABI.EncodedTest<V>(encoding: test)
80+
_context.withLock { context in
81+
context.testStorage[encodedTest.id.stringValue] = encodedTest
82+
}
83+
}
84+
85+
// Generate human-readable messages for the event
86+
let messages = _humanReadableRecorder.record(event, in: eventContext)
87+
88+
// Convert Event to ABI.EncodedEvent
89+
if let encodedEvent = ABI.EncodedEvent<V>(encoding: event, in: eventContext, messages: messages) {
90+
// Process the ABI event
91+
_processABIEvent(encodedEvent)
92+
}
93+
94+
// For now, still delegate to the fallback recorder to maintain existing functionality
6195
_fallbackRecorder.record(event, in: eventContext)
6296
}
97+
98+
/// Process an ABI.EncodedEvent for advanced console output.
99+
///
100+
/// This is where the enhanced console logic will be implemented in future PRs.
101+
/// Currently this is a placeholder that demonstrates the ABI conversion.
102+
///
103+
/// - Parameters:
104+
/// - encodedEvent: The ABI-encoded event to process.
105+
private func _processABIEvent(_ encodedEvent: ABI.EncodedEvent<V>) {
106+
// TODO: Implement enhanced console output logic here
107+
// This will be expanded in subsequent PRs for:
108+
// - Failure summary display
109+
// - Progress bar functionality
110+
// - Hierarchical test result display
111+
112+
// For now, we just demonstrate that we can access the ABI event data
113+
switch encodedEvent.kind {
114+
case .runStarted:
115+
// Could implement run start logic here
116+
break
117+
case .testStarted:
118+
// Could implement test start logic here
119+
break
120+
case .issueRecorded:
121+
// Could implement issue recording logic here
122+
break
123+
case .testEnded:
124+
// Could implement test end logic here
125+
break
126+
case .runEnded:
127+
// Could implement run end logic here
128+
break
129+
default:
130+
// Handle other event types
131+
break
132+
}
133+
}
63134
}

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -767,8 +767,12 @@ extension ExitTest {
767767
}
768768
}
769769
configuration.eventHandler = { event, eventContext in
770-
if case .issueRecorded = event.kind {
770+
switch event.kind {
771+
case .issueRecorded, .valueAttached:
771772
eventHandler(event, eventContext)
773+
default:
774+
// Don't forward other kinds of event.
775+
break
772776
}
773777
}
774778

@@ -1034,8 +1038,14 @@ extension ExitTest {
10341038
/// - Throws: Any error encountered attempting to decode or process the JSON.
10351039
private static func _processRecord(_ recordJSON: UnsafeRawBufferPointer, fromBackChannel backChannel: borrowing FileHandle) throws {
10361040
let record = try JSON.decode(ABI.Record<ABI.BackChannelVersion>.self, from: recordJSON)
1037-
if case let .event(event) = record.kind, let issue = Issue(event) {
1041+
guard case let .event(event) = record.kind else {
1042+
return
1043+
}
1044+
1045+
if let issue = Issue(event) {
10381046
issue.record()
1047+
} else if let attachment = event.attachment {
1048+
Attachment.record(attachment, sourceLocation: attachment._sourceLocation!)
10391049
}
10401050
}
10411051

0 commit comments

Comments
 (0)