Skip to content

Various VersionNumber changes of merit. #1246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion Sources/Testing/ABI/ABI.Record.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ extension ABI.Record: Codable {
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

let versionNumber = try container.decode(ABI.VersionNumber.self, forKey: .version)
let versionNumber = try container.decode(VersionNumber.self, forKey: .version)
if versionNumber != V.versionNumber {
throw DecodingError.dataCorrupted(
DecodingError.Context(
Expand Down
22 changes: 21 additions & 1 deletion Sources/Testing/ABI/ABI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ extension ABI {
/// The current supported ABI version (ignoring any experimental versions.)
typealias CurrentVersion = v0

/// The highest supported ABI version (including any experimental versions.)
typealias HighestVersion = v6_3

#if !hasFeature(Embedded)
/// Get the type representing a given ABI version.
///
Expand All @@ -55,7 +58,24 @@ extension ABI {
/// - Returns: A type conforming to ``ABI/Version`` that represents the given
/// ABI version, or `nil` if no such type exists.
static func version(forVersionNumber versionNumber: VersionNumber = ABI.CurrentVersion.versionNumber) -> (any Version.Type)? {
switch versionNumber {
if versionNumber > ABI.HighestVersion.versionNumber {
// If the caller requested an ABI version higher than the current Swift
// compiler version and it's not an ABI version we've explicitly defined,
// then we assume we don't know what they're talking about and return nil.
//
// Note that it is possible for the Swift compiler version to be lower
// than the highest defined ABI version (e.g. if you use a 6.2 toolchain
// to build this package's release/6.3 branch with a 6.3 ABI defined.)
//
// Note also that building an old version of Swift Testing with a newer
// compiler may produce incorrect results here. We don't generally support
// that configuration though.
if versionNumber > swiftCompilerVersion {
return nil
}
}

return switch versionNumber {
case ABI.v6_3.versionNumber...:
ABI.v6_3.self
case ABI.v0.versionNumber...:
Expand Down
12 changes: 6 additions & 6 deletions Sources/Testing/ABI/EntryPoints/EntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public struct __CommandLineArguments_v0: Sendable {
/// This property is internal because its type is internal. External users of
/// this structure can use the ``eventStreamSchemaVersion`` property to get or
/// set the value of this property.
var eventStreamVersionNumber: ABI.VersionNumber?
var eventStreamVersionNumber: VersionNumber?

/// The value of the `--event-stream-version` or `--experimental-event-stream-version`
/// argument, representing the version of the event stream schema to use when
Expand All @@ -282,7 +282,7 @@ public struct __CommandLineArguments_v0: Sendable {
}
set {
eventStreamVersionNumber = newValue.flatMap { newValue in
guard let newValue = ABI.VersionNumber(newValue) else {
guard let newValue = VersionNumber(newValue) else {
preconditionFailure("Invalid event stream version number '\(newValue)'. Specify a version number of the form 'major.minor.patch'.")
}
return newValue
Expand Down Expand Up @@ -404,7 +404,7 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum

// If the caller specified a version that could not be parsed, treat it as
// an invalid argument.
guard let eventStreamVersion = ABI.VersionNumber(versionString) else {
guard let eventStreamVersion = VersionNumber(versionString) else {
let argument = allowExperimental ? "--experimental-event-stream-version" : "--event-stream-version"
throw _EntryPointError.invalidArgument(argument, value: versionString)
}
Expand Down Expand Up @@ -652,7 +652,7 @@ public func configurationForEntryPoint(from args: __CommandLineArguments_v0) thr
///
/// - Throws: If `version` is not a supported ABI version.
func eventHandlerForStreamingEvents(
withVersionNumber versionNumber: ABI.VersionNumber?,
withVersionNumber versionNumber: VersionNumber?,
encodeAsJSONLines: Bool,
forwardingTo targetEventHandler: @escaping @Sendable (UnsafeRawBufferPointer) -> Void
) throws -> Event.Handler {
Expand Down Expand Up @@ -822,7 +822,7 @@ private enum _EntryPointError: Error {
///
/// - Parameters:
/// - versionNumber: The experimental ABI version number.
case experimentalABIVersion(_ versionNumber: ABI.VersionNumber)
case experimentalABIVersion(_ versionNumber: VersionNumber)
}

extension _EntryPointError: CustomStringConvertible {
Expand All @@ -847,7 +847,7 @@ extension __CommandLineArguments_v0 {
eventStreamVersionNumber.map(\.majorComponent).map(Int.init)
}
set {
eventStreamVersionNumber = newValue.map { ABI.VersionNumber(majorComponent: Int8(clamping: $0), minorComponent: 0) }
eventStreamVersionNumber = newValue.map { VersionNumber(majorComponent: .init(clamping: $0), minorComponent: 0) }
}
}
}
2 changes: 1 addition & 1 deletion Sources/Testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ add_library(Testing
ABI/ABI.Record.swift
ABI/ABI.Record+Streaming.swift
ABI/ABI.swift
ABI/ABI.VersionNumber.swift
ABI/Encoded/ABI.EncodedAttachment.swift
ABI/Encoded/ABI.EncodedBacktrace.swift
ABI/Encoded/ABI.EncodedError.swift
Expand Down Expand Up @@ -84,6 +83,7 @@ add_library(Testing
Support/JSON.swift
Support/Locked.swift
Support/Locked+Platform.swift
Support/VersionNumber.swift
Support/Versions.swift
Discovery+Macro.swift
Test.ID.Selection.swift
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,13 @@ extension Event.HumanReadableOutputRecorder {
case .runStarted:
var comments = [Comment]()
if verbosity > 0 {
comments.append("Swift Version: \(swiftStandardLibraryVersion)")
if let swiftStandardLibraryVersion {
comments.append("Swift Standard Library Version: \(swiftStandardLibraryVersion)")
}
comments.append("Swift Compiler Version: \(swiftCompilerVersion)")
#if canImport(Glibc) && !os(FreeBSD) && !os(OpenBSD)
comments.append("GNU C Library Version: \(glibcVersion)")
#endif
}
comments.append("Testing Library Version: \(testingLibraryVersion)")
if let targetTriple {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/ExitTests/SpawnProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func spawnExecutable(
// and https://www.austingroupbugs.net/view.php?id=411).
_ = posix_spawn_file_actions_adddup2(fileActions, fd, fd)
#if canImport(Glibc) && !os(FreeBSD) && !os(OpenBSD)
if _slowPath(glibcVersion.major < 2 || (glibcVersion.major == 2 && glibcVersion.minor < 29)) {
if _slowPath(glibcVersion < VersionNumber(2, 29)) {
// This system is using an older version of glibc that does not
// implement FD_CLOEXEC clearing in posix_spawn_file_actions_adddup2(),
// so we must clear it here in the parent process.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,42 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

extension ABI {
/// A type describing an ABI version number.
private import _TestingInternals

/// A type describing an ABI version number.
///
/// This type implements a subset of the [semantic versioning](https://semver.org)
/// specification (specifically parsing, displaying, and comparing
/// `<version core>` values we expect that the testing library will need for the
/// foreseeable future.)
struct VersionNumber: Sendable {
/// The integer type used to store a component.
///
/// This type implements a subset of the [semantic versioning](https://semver.org)
/// specification (specifically parsing, displaying, and comparing
/// `<version core>` values we expect that Swift will need for the foreseeable
/// future.)
struct VersionNumber: Sendable {
/// The major version.
var majorComponent: Int8
/// The testing library does not generally need to deal with version numbers
/// whose components exceed the width of this type. If we need to deal with
/// larger version number components in the future, we can increase the width
/// of this type accordingly.
typealias Component = Int8

/// The minor version.
var minorComponent: Int8
/// The major version.
var majorComponent: Component

/// The patch, revision, or bug fix version.
var patchComponent: Int8 = 0
}
/// The minor version.
var minorComponent: Component

/// The patch, revision, or bug fix version.
var patchComponent: Component = 0
}

extension ABI.VersionNumber {
init(_ majorComponent: _const Int8, _ minorComponent: _const Int8, _ patchComponent: _const Int8 = 0) {
extension VersionNumber {
init(_ majorComponent: _const Component, _ minorComponent: _const Component, _ patchComponent: _const Component = 0) {
self.init(majorComponent: majorComponent, minorComponent: minorComponent, patchComponent: patchComponent)
}
}

// MARK: - CustomStringConvertible

extension ABI.VersionNumber: CustomStringConvertible {
extension VersionNumber: CustomStringConvertible {
/// Initialize an instance of this type by parsing the given string.
///
/// - Parameters:
Expand All @@ -55,8 +63,8 @@ extension ABI.VersionNumber: CustomStringConvertible {
// Split the string on "." (assuming it is of the form "1", "1.2", or
// "1.2.3") and parse the individual components as integers.
let components = string.split(separator: ".", omittingEmptySubsequences: false)
func componentValue(_ index: Int) -> Int8? {
components.count > index ? Int8(components[index]) : 0
func componentValue(_ index: Int) -> Component? {
components.count > index ? Component(components[index]) : 0
}

guard let majorComponent = componentValue(0),
Expand All @@ -81,7 +89,7 @@ extension ABI.VersionNumber: CustomStringConvertible {

// MARK: - Equatable, Comparable

extension ABI.VersionNumber: Equatable, Comparable {
extension VersionNumber: Equatable, Comparable {
static func <(lhs: Self, rhs: Self) -> Bool {
if lhs.majorComponent != rhs.majorComponent {
return lhs.majorComponent < rhs.majorComponent
Expand All @@ -96,10 +104,10 @@ extension ABI.VersionNumber: Equatable, Comparable {

// MARK: - Codable

extension ABI.VersionNumber: Codable {
extension VersionNumber: Codable {
init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let number = try? container.decode(Int8.self) {
if let number = try? container.decode(Component.self) {
// Allow for version numbers encoded as integers for compatibility with
// Swift 6.2 and earlier.
self.init(majorComponent: number, minorComponent: 0)
Expand Down
46 changes: 37 additions & 9 deletions Sources/Testing/Support/Versions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//

private import _TestingInternals
private import SwiftShims

/// A human-readable string describing the current operating system's version.
///
Expand Down Expand Up @@ -141,23 +142,50 @@ var targetTriple: String? {

/// A human-readable string describing the Swift Standard Library's version.
///
/// This value's format is platform-specific and is not meant to be
/// machine-readable. It is added to the output of a test run when using
/// an event writer.
/// This value is unavailable on some earlier Apple runtime targets. On those
/// targets, this property has a value of `5.0.0`.
///
/// This value is not part of the public interface of the testing library.
let swiftStandardLibraryVersion: String = {
if #available(_swiftVersionAPI, *) {
return String(describing: _SwiftStdlibVersion.current)
let swiftStandardLibraryVersion: VersionNumber? = {
guard #available(_swiftVersionAPI, *) else {
return VersionNumber(5, 0)
}
return "unknown"
let packedValue = _SwiftStdlibVersion.current._value
return VersionNumber(
majorComponent: .init((packedValue & 0xFFFF0000) >> 16),
minorComponent: .init((packedValue & 0x0000FF00) >> 8),
patchComponent: .init((packedValue & 0x000000FF) >> 0)
)
}()

/// The version of the Swift compiler used to build the testing library.
///
/// This value is determined at compile time by the Swift compiler. For more
/// information, see [Version.h](https://github.com/swiftlang/swift/blob/main/include/swift/Basic/Version.h)
/// and [ClangImporter.cpp](https://github.com/swiftlang/swift/blob/main/lib/ClangImporter/ClangImporter.cpp)
/// in the Swift repository.
///
/// This value is not part of the public interface of the testing library.
var swiftCompilerVersion: VersionNumber {
let packedValue = swt_getSwiftCompilerVersion()
if packedValue == 0, let swiftStandardLibraryVersion {
// The compiler did not supply its version. This is currently expected on
// non-Darwin targets in particular. Substitute the stdlib version (which
// should generally be aligned on non-Darwin targets.)
return swiftStandardLibraryVersion
}
return VersionNumber(
majorComponent: .init((packedValue % 1_000_000_000_000_000) / 1_000_000_000_000),
minorComponent: .init((packedValue % 1_000_000_000_000) / 1_000_000_000),
patchComponent: .init((packedValue % 1_000_000_000) / 1_000_000)
)
}

#if canImport(Glibc) && !os(FreeBSD) && !os(OpenBSD)
/// The (runtime, not compile-time) version of glibc in use on this system.
///
/// This value is not part of the public interface of the testing library.
let glibcVersion: (major: Int, minor: Int) = {
let glibcVersion: VersionNumber = {
// Default to the statically available version number if the function call
// fails for some reason.
var major = Int(clamping: __GLIBC__)
Expand All @@ -173,7 +201,7 @@ let glibcVersion: (major: Int, minor: Int) = {
}
}

return (major, minor)
return VersionNumber(majorComponent: .init(clamping: major), minorComponent: .init(clamping: minor))
}()
#endif

Expand Down
8 changes: 8 additions & 0 deletions Sources/_TestingInternals/include/Versions.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@

SWT_ASSUME_NONNULL_BEGIN

static inline uint64_t swt_getSwiftCompilerVersion(void) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be documented, for example to show what format the number is expected to be in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, yes.

#if defined(__SWIFT_COMPILER_VERSION)
return __SWIFT_COMPILER_VERSION;
#else
return 0;
#endif
}

/// Get the human-readable version of the testing library.
///
/// - Returns: A human-readable string describing the version of the testing
Expand Down
32 changes: 16 additions & 16 deletions Tests/TestingTests/ABIEntryPointTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,52 +119,52 @@ struct ABIEntryPointTests {

@Test func decodeVersionNumber() throws {
let version0 = try JSON.withEncoding(of: 0) { versionJSON in
try JSON.decode(ABI.VersionNumber.self, from: versionJSON)
try JSON.decode(VersionNumber.self, from: versionJSON)
}
#expect(version0 == ABI.VersionNumber(0, 0))
#expect(version0 == VersionNumber(0, 0))

let version1_2_3 = try JSON.withEncoding(of: "1.2.3") { versionJSON in
try JSON.decode(ABI.VersionNumber.self, from: versionJSON)
try JSON.decode(VersionNumber.self, from: versionJSON)
}
#expect(version1_2_3.majorComponent == 1)
#expect(version1_2_3.minorComponent == 2)
#expect(version1_2_3.patchComponent == 3)

#expect(throws: DecodingError.self) {
_ = try JSON.withEncoding(of: "not.valid") { versionJSON in
try JSON.decode(ABI.VersionNumber.self, from: versionJSON)
try JSON.decode(VersionNumber.self, from: versionJSON)
}
}
}
#endif

@Test(arguments: [
(ABI.VersionNumber(-1, 0), "-1"),
(ABI.VersionNumber(0, 0), "0"),
(ABI.VersionNumber(1, 0), "1.0"),
(ABI.VersionNumber(2, 0), "2.0"),
(ABI.VersionNumber("0.0.1"), "0.0.1"),
(ABI.VersionNumber("0.1.0"), "0.1"),
]) func abiVersionStringConversion(version: ABI.VersionNumber?, expectedString: String) throws {
(VersionNumber(-1, 0), "-1"),
(VersionNumber(0, 0), "0"),
(VersionNumber(1, 0), "1.0"),
(VersionNumber(2, 0), "2.0"),
(VersionNumber("0.0.1"), "0.0.1"),
(VersionNumber("0.1.0"), "0.1"),
]) func abiVersionStringConversion(version: VersionNumber?, expectedString: String) throws {
let version = try #require(version)
#expect(String(describing: version) == expectedString)
}

@Test func badABIVersionString() {
let version = ABI.VersionNumber("not.valid")
let version = VersionNumber("not.valid")
#expect(version == nil)
}

@Test func abiVersionComparisons() throws {
var versions = [ABI.VersionNumber]()
var versions = [VersionNumber]()
for major in 0 ..< 10 {
let version = try #require(ABI.VersionNumber("\(major)"))
let version = try #require(VersionNumber("\(major)"))
versions.append(version)
for minor in 0 ..< 10 {
let version = try #require(ABI.VersionNumber("\(major).\(minor)"))
let version = try #require(VersionNumber("\(major).\(minor)"))
versions.append(version)
for patch in 0 ..< 10 {
let version = try #require(ABI.VersionNumber("\(major).\(minor).\(patch)"))
let version = try #require(VersionNumber("\(major).\(minor).\(patch)"))
versions.append(version)
}
}
Expand Down
Loading