Skip to content

Commit 6a1c6db

Browse files
committed
Merge main branch with latest upstream changes
- Update to latest main branch from upstream/main - Integrate new ABI version handling and entry point improvements - Preserve AdvancedConsoleOutputRecorder skeleton implementation - Ready for first PR submission
2 parents 699459c + f0047e6 commit 6a1c6db

File tree

16 files changed

+381
-205
lines changed

16 files changed

+381
-205
lines changed

Documentation/StyleGuide.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,26 @@ to the code called by the initialization expression causing the inferred type of
7171
its property to change unknowingly, which could break clients. Properties with
7272
lower access levels may have an inferred type.
7373

74-
Exported C and C++ symbols that are exported should be given the prefix `swt_`
75-
and should otherwise be named using the same lowerCamelCase naming rules as in
76-
Swift. Use the `SWT_EXTERN` macro to ensure that symbols are consistently
77-
visible in C, C++, and Swift. For example:
74+
C and C++ symbols that are used by the testing library should be given the
75+
prefix `swt_` and should otherwise be named using the same lowerCamelCase naming
76+
rules as in Swift. Use the `SWT_EXTERN` macro to ensure that symbols are
77+
consistently visible in C, C++, and Swift. For example:
7878

7979
```c
8080
SWT_EXTERN bool swt_isDebugModeEnabled(void);
8181

8282
SWT_EXTERN void swt_setDebugModeEnabled(bool isEnabled);
8383
```
8484
85+
> [!NOTE]
86+
> If a symbol is meant to be **publicly visible** and can be called by modules
87+
> other than Swift Testing, use the prefix `swift_testing_` instead of `swt_`
88+
> for consistency with the Swift standard library:
89+
>
90+
> ```c
91+
> SWT_EXTERN void swift_testing_debugIfNeeded(void);
92+
> ```
93+
8594
C and C++ types should be given the prefix `SWT` and should otherwise be named
8695
using the same UpperCamelCase naming rules as in Swift. For example:
8796

Package.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,7 @@ extension Array where Element == PackageDescription.SwiftSetting {
341341
// This setting is enabled in the package, but not in the toolchain build
342342
// (via CMake). Enabling it is dependent on acceptance of the @section
343343
// proposal via Swift Evolution.
344-
//
345-
// FIXME: Re-enable this once a CI blocker is resolved:
346-
// https://github.com/swiftlang/swift-testing/issues/1138.
347-
// .enableExperimentalFeature("SymbolLinkageMarkers"),
344+
.enableExperimentalFeature("SymbolLinkageMarkers"),
348345

349346
// This setting is no longer needed when building with a 6.2 or later
350347
// toolchain now that SE-0458 has been accepted and implemented, but it is

Sources/Testing/ABI/ABI.Record.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ extension ABI.Record: Codable {
6666
init(from decoder: any Decoder) throws {
6767
let container = try decoder.container(keyedBy: CodingKeys.self)
6868

69-
let versionNumber = try container.decode(Int.self, forKey: .version)
69+
let versionNumber = try container.decode(ABI.VersionNumber.self, forKey: .version)
7070
if versionNumber != V.versionNumber {
7171
throw DecodingError.dataCorrupted(
7272
DecodingError.Context(
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2024–2025 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+
extension ABI {
12+
/// A type describing an ABI version number.
13+
///
14+
/// This type implements a subset of the [semantic versioning](https://semver.org)
15+
/// specification (specifically parsing, displaying, and comparing
16+
/// `<version core>` values we expect that Swift will need for the foreseeable
17+
/// future.)
18+
struct VersionNumber: Sendable {
19+
/// The major version.
20+
var majorComponent: Int8
21+
22+
/// The minor version.
23+
var minorComponent: Int8
24+
25+
/// The patch, revision, or bug fix version.
26+
var patchComponent: Int8 = 0
27+
}
28+
}
29+
30+
extension ABI.VersionNumber {
31+
init(_ majorComponent: _const Int8, _ minorComponent: _const Int8, _ patchComponent: _const Int8 = 0) {
32+
self.init(majorComponent: majorComponent, minorComponent: minorComponent, patchComponent: patchComponent)
33+
}
34+
}
35+
36+
// MARK: - CustomStringConvertible
37+
38+
extension ABI.VersionNumber: CustomStringConvertible {
39+
/// Initialize an instance of this type by parsing the given string.
40+
///
41+
/// - Parameters:
42+
/// - string: The string to parse, such as `"0"` or `"6.3.0"`.
43+
///
44+
/// @Comment {
45+
/// - Bug: We are not able to reuse the logic from swift-syntax's
46+
/// `VersionTupleSyntax` type here because we cannot link to swift-syntax
47+
/// in this target.
48+
/// }
49+
///
50+
/// If `string` contains fewer than 3 numeric components, the missing
51+
/// components are inferred to be `0` (for example, `"1.2"` is equivalent to
52+
/// `"1.2.0"`.) If `string` contains more than 3 numeric components, the
53+
/// additional components are ignored.
54+
init?(_ string: String) {
55+
// Split the string on "." (assuming it is of the form "1", "1.2", or
56+
// "1.2.3") and parse the individual components as integers.
57+
let components = string.split(separator: ".", omittingEmptySubsequences: false)
58+
func componentValue(_ index: Int) -> Int8? {
59+
components.count > index ? Int8(components[index]) : 0
60+
}
61+
62+
guard let majorComponent = componentValue(0),
63+
let minorComponent = componentValue(1),
64+
let patchComponent = componentValue(2) else {
65+
return nil
66+
}
67+
self.init(majorComponent: majorComponent, minorComponent: minorComponent, patchComponent: patchComponent)
68+
}
69+
70+
var description: String {
71+
if majorComponent <= 0 && minorComponent == 0 && patchComponent == 0 {
72+
// Version 0 and earlier are described as integers for compatibility with
73+
// Swift 6.2 and earlier.
74+
return String(describing: majorComponent)
75+
} else if patchComponent == 0 {
76+
return "\(majorComponent).\(minorComponent)"
77+
}
78+
return "\(majorComponent).\(minorComponent).\(patchComponent)"
79+
}
80+
}
81+
82+
// MARK: - Equatable, Comparable
83+
84+
extension ABI.VersionNumber: Equatable, Comparable {
85+
static func <(lhs: Self, rhs: Self) -> Bool {
86+
if lhs.majorComponent != rhs.majorComponent {
87+
return lhs.majorComponent < rhs.majorComponent
88+
} else if lhs.minorComponent != rhs.minorComponent {
89+
return lhs.minorComponent < rhs.minorComponent
90+
} else if lhs.patchComponent != rhs.patchComponent {
91+
return lhs.patchComponent < rhs.patchComponent
92+
}
93+
return false
94+
}
95+
}
96+
97+
// MARK: - Codable
98+
99+
extension ABI.VersionNumber: Codable {
100+
init(from decoder: any Decoder) throws {
101+
let container = try decoder.singleValueContainer()
102+
if let number = try? container.decode(Int8.self) {
103+
// Allow for version numbers encoded as integers for compatibility with
104+
// Swift 6.2 and earlier.
105+
self.init(majorComponent: number, minorComponent: 0)
106+
} else {
107+
let string = try container.decode(String.self)
108+
guard let result = Self(string) else {
109+
throw DecodingError.dataCorrupted(
110+
.init(
111+
codingPath: decoder.codingPath,
112+
debugDescription: "Unexpected string '\(string)' (expected an integer or a string of the form '1.2.3')"
113+
)
114+
)
115+
}
116+
self = result
117+
}
118+
}
119+
120+
func encode(to encoder: any Encoder) throws {
121+
var container = encoder.singleValueContainer()
122+
if majorComponent <= 0 && minorComponent == 0 && patchComponent == 0 {
123+
// Version 0 and earlier are encoded as integers for compatibility with
124+
// Swift 6.2 and earlier.
125+
try container.encode(majorComponent)
126+
} else {
127+
try container.encode("\(majorComponent).\(minorComponent).\(patchComponent)")
128+
}
129+
}
130+
}

Sources/Testing/ABI/ABI.swift

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ extension ABI {
1818
/// A protocol describing the types that represent different ABI versions.
1919
protocol Version: Sendable {
2020
/// The numeric representation of this ABI version.
21-
static var versionNumber: Int { get }
21+
static var versionNumber: VersionNumber { get }
2222

2323
#if canImport(Foundation) && (!SWT_NO_FILE_IO || !SWT_NO_ABI_ENTRY_POINT)
2424
/// Create an event handler that encodes events as JSON and forwards them to
@@ -44,6 +44,33 @@ extension ABI {
4444

4545
/// The current supported ABI version (ignoring any experimental versions.)
4646
typealias CurrentVersion = v0
47+
48+
#if !hasFeature(Embedded)
49+
/// Get the type representing a given ABI version.
50+
///
51+
/// - Parameters:
52+
/// - versionNumber: The ABI version number for which a concrete type is
53+
/// needed.
54+
///
55+
/// - Returns: A type conforming to ``ABI/Version`` that represents the given
56+
/// ABI version, or `nil` if no such type exists.
57+
static func version(forVersionNumber versionNumber: VersionNumber = ABI.CurrentVersion.versionNumber) -> (any Version.Type)? {
58+
switch versionNumber {
59+
case ABI.v6_3.versionNumber...:
60+
ABI.v6_3.self
61+
case ABI.v0.versionNumber...:
62+
ABI.v0.self
63+
#if !SWT_NO_SNAPSHOT_TYPES
64+
case ABI.Xcode16.versionNumber:
65+
// Legacy support for Xcode 16. Support for this undocumented version will
66+
// be removed in a future update. Do not use it.
67+
ABI.Xcode16.self
68+
#endif
69+
default:
70+
nil
71+
}
72+
}
73+
#endif
4774
}
4875

4976
// MARK: - Concrete ABI versions
@@ -54,28 +81,28 @@ extension ABI {
5481
///
5582
/// - Warning: This type will be removed in a future update.
5683
enum Xcode16: Sendable, Version {
57-
static var versionNumber: Int {
58-
-1
84+
static var versionNumber: VersionNumber {
85+
VersionNumber(-1, 0)
5986
}
6087
}
6188
#endif
6289

6390
/// A namespace and type for ABI version 0 symbols.
6491
public enum v0: Sendable, Version {
65-
static var versionNumber: Int {
66-
0
92+
static var versionNumber: VersionNumber {
93+
VersionNumber(0, 0)
6794
}
6895
}
6996

70-
/// A namespace and type for ABI version 1 symbols.
97+
/// A namespace and type for ABI version 6.3 symbols.
7198
///
7299
/// @Metadata {
73-
/// @Available("Swift Testing ABI", introduced: 1)
100+
/// @Available(Swift, introduced: 6.3)
74101
/// }
75102
@_spi(Experimental)
76-
public enum v1: Sendable, Version {
77-
static var versionNumber: Int {
78-
1
103+
public enum v6_3: Sendable, Version {
104+
static var versionNumber: VersionNumber {
105+
VersionNumber(6, 3)
79106
}
80107
}
81108
}

Sources/Testing/ABI/Encoded/ABI.EncodedTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ extension ABI {
9898
sourceLocation = test.sourceLocation
9999
id = ID(encoding: test.id)
100100

101-
if V.versionNumber >= ABI.v1.versionNumber {
101+
if V.versionNumber >= ABI.v6_3.versionNumber {
102102
let tags = test.tags
103103
if !tags.isEmpty {
104104
_tags = tags.map(String.init(describing:))

Sources/Testing/ABI/EntryPoints/ABIEntryPoint.swift

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ extension ABI.v0 {
5050
let args = try configurationJSON.map { configurationJSON in
5151
try JSON.decode(__CommandLineArguments_v0.self, from: configurationJSON)
5252
}
53-
let eventHandler = try eventHandlerForStreamingEvents(version: args?.eventStreamVersion, encodeAsJSONLines: false, forwardingTo: recordHandler)
53+
let eventHandler = try eventHandlerForStreamingEvents(withVersionNumber: args?.eventStreamVersionNumber, encodeAsJSONLines: false, forwardingTo: recordHandler)
5454

5555
switch await Testing.entryPoint(passing: args, eventHandler: eventHandler) {
5656
case EXIT_SUCCESS, EXIT_NO_TESTS_FOUND:
@@ -67,50 +67,12 @@ extension ABI.v0 {
6767
///
6868
/// - Returns: The value of ``ABI/v0/entryPoint-swift.type.property`` cast to an
6969
/// untyped pointer.
70+
///
71+
/// - Note: This function's name is prefixed with `swt_` instead of
72+
/// `swift_testing_` for binary compatibility reasons. Future ABI entry point
73+
/// functions should use the `swift_testing_` prefix instead.
7074
@_cdecl("swt_abiv0_getEntryPoint")
7175
@usableFromInline func abiv0_getEntryPoint() -> UnsafeRawPointer {
7276
unsafeBitCast(ABI.v0.entryPoint, to: UnsafeRawPointer.self)
7377
}
74-
75-
#if !SWT_NO_SNAPSHOT_TYPES
76-
// MARK: - Xcode 16 compatibility
77-
78-
extension ABI.Xcode16 {
79-
/// An older signature for ``ABI/v0/EntryPoint-swift.typealias`` used by
80-
/// Xcode&nbsp;16.
81-
///
82-
/// - Warning: This type will be removed in a future update.
83-
@available(*, deprecated, message: "Use ABI.v0.EntryPoint instead.")
84-
typealias EntryPoint = @Sendable (
85-
_ argumentsJSON: UnsafeRawBufferPointer?,
86-
_ recordHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void
87-
) async throws -> CInt
88-
}
89-
90-
/// An older signature for ``ABI/v0/entryPoint-swift.type.property`` used by
91-
/// Xcode&nbsp;16.
92-
///
93-
/// - Warning: This function will be removed in a future update.
94-
@available(*, deprecated, message: "Use ABI.v0.entryPoint (swt_abiv0_getEntryPoint()) instead.")
95-
@_cdecl("swt_copyABIEntryPoint_v0")
96-
@usableFromInline func copyABIEntryPoint_v0() -> UnsafeMutableRawPointer {
97-
let result = UnsafeMutablePointer<ABI.Xcode16.EntryPoint>.allocate(capacity: 1)
98-
result.initialize { configurationJSON, recordHandler in
99-
var args = try configurationJSON.map { configurationJSON in
100-
try JSON.decode(__CommandLineArguments_v0.self, from: configurationJSON)
101-
}
102-
if args?.eventStreamVersion == nil {
103-
args?.eventStreamVersion = ABI.Xcode16.versionNumber
104-
}
105-
let eventHandler = try eventHandlerForStreamingEvents(version: args?.eventStreamVersion, encodeAsJSONLines: false, forwardingTo: recordHandler)
106-
107-
var exitCode = await Testing.entryPoint(passing: args, eventHandler: eventHandler)
108-
if exitCode == EXIT_NO_TESTS_FOUND {
109-
exitCode = EXIT_SUCCESS
110-
}
111-
return exitCode
112-
}
113-
return .init(result)
114-
}
115-
#endif
11678
#endif

0 commit comments

Comments
 (0)