Skip to content

[stdlib] Add a version number for the stdlib itself; use it in unit tests #24254

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

Closed
wants to merge 8 commits into from
1 change: 1 addition & 0 deletions stdlib/private/StdlibUnittest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ add_swift_target_library(swiftStdlibUnittest ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES}
RaceTest.swift
Statistics.swift
StdlibCoreExtras.swift
StdlibVersion.swift
StringConvertible.swift
TestHelpers.swift
TypeIndexed.swift
Expand Down
8 changes: 8 additions & 0 deletions stdlib/private/StdlibUnittest/StdlibUnittest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1899,6 +1899,8 @@ public enum TestRunPredicate : CustomStringConvertible {
case objCRuntime(/*reason:*/ String)
case nativeRuntime(/*reason:*/ String)

case stdlibOlderThan(StdlibVersion, reason: String)

public var description: String {
switch self {
case .custom(_, let reason):
Expand Down Expand Up @@ -1995,6 +1997,9 @@ public enum TestRunPredicate : CustomStringConvertible {
return "Objective-C runtime, reason: \(reason))"
case .nativeRuntime(let reason):
return "Native runtime (no ObjC), reason: \(reason))"

case .stdlibOlderThan(let version, reason: let reason):
return "stdlibOlderThan(\(version), reason: \(reason))"
}
}

Expand Down Expand Up @@ -2295,6 +2300,9 @@ public enum TestRunPredicate : CustomStringConvertible {
#else
return true
#endif

case .stdlibOlderThan(let version, reason: _):
return StdlibVersion.currentlyRunning < version
}
}
}
Expand Down
59 changes: 59 additions & 0 deletions stdlib/private/StdlibUnittest/StdlibVersion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

public struct StdlibVersion: RawRepresentable {
public let rawValue: (Int, Int)

public init(rawValue: (Int, Int)) {
self.rawValue = rawValue
}
}

extension StdlibVersion {
public static func custom(_ version1: Int, _ version2: Int) -> StdlibVersion {
return StdlibVersion(rawValue: (version1, version2))
}

public static var currentlyRunning: StdlibVersion {
return StdlibVersion(rawValue: _stdlibDynamicVersion)
}

// Shipped in macOS 10.14.2, iOS 12.2, watchOS 5.2, tvOS 13.2
public static var swift5_0: StdlibVersion {
return StdlibVersion(rawValue: (1000, 0))
}
}

extension StdlibVersion: CustomStringConvertible {
public var description: String {
return "\(rawValue)"
}
}

extension StdlibVersion: Equatable {
public static func == (left: StdlibVersion, right: StdlibVersion) -> Bool {
return left.rawValue == right.rawValue
}
}

extension StdlibVersion: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(rawValue.0)
hasher.combine(rawValue.1)
}
}

extension StdlibVersion: Comparable {
public static func < (left: StdlibVersion, right: StdlibVersion) -> Bool {
return left.rawValue < right.rawValue
}
}
49 changes: 49 additions & 0 deletions stdlib/public/core/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,52 @@ public func _stdlib_isOSVersionAtLeast(
return false._value
#endif
}

/// Returns the version number for the Swift Standard Library that the code
/// calling this was originally compiled with.
///
/// The version number is an arbitrary pair of integers, with lexicographical
/// ordering. Version numbers of (logically) successive stdlib releases form a
/// monotonically increasing sequence; i.e., versions should not decrease, but
/// they are allowed to stay the same.
///
/// The two integer components are not intended to correspond to major or minor
/// versions in a semantic versioning scheme. Neither do they correlate with
/// toolchain or OS version numbers.
public var _stdlibStaticVersion: (Int, Int) {
Copy link
Member

Choose a reason for hiding this comment

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

Still a little confused. Why is this public?

Copy link
Member Author

@lorentey lorentey Apr 27, 2019

Choose a reason for hiding this comment

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

It allows tests to easily determine if they are back/forward deployed. Binaries that are intimately tied to a particular revision of the stdlib and should not be separately back/forward deployed (e.g. libswiftFoundation) could use it for sanity checks.

if _stdlibDynamicVersion < _stdlibStaticVersion {
  print("I'm back-deployed")
}

This is not strictly necessary, but it seems like a cheap addition. If it's available, the "current" version need not be duplicated (and updated) in more than one place.

Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW, dyld provides two similar version numbers through NSVersionOfRunTimeLibrary and NSVersionOfLinkTimeLibrary.

(The link-time version queries the linkage of the main executable, not the current object, so it's different(ly useful) than the static version number implemented here.)

@_alwaysEmitIntoClient
get {
// On the master branch, increment the first number.
// On release branches, increment the second number.
return (1001, 0)
}
}

/// Returns the version number for the Swift Standard Library that is currently
/// loaded.
///
/// The version number is an arbitrary pair of integers, with lexicographical
/// ordering. Version numbers of (logically) successive stdlib releases form a
/// monotonically increasing sequence; i.e., versions should not decrease, but
/// they are allowed to stay the same.
///
/// The two integer components are not intended to correspond to major or minor
/// versions in a semantic versioning scheme. Neither do they correlate with
/// toolchain or OS version numbers.
@_alwaysEmitIntoClient // Introduced in 5.1
Copy link
Member

Choose a reason for hiding this comment

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

Why do we want back-deployment?

Copy link
Member Author

@lorentey lorentey Apr 26, 2019

Choose a reason for hiding this comment

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

Is there a reason we shouldn't? I expect to always be able to retrieve a version number; tying it to an explicit availability check would reduce its usefulness.

public var _stdlibDynamicVersion: (Int, Int) {
if #available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) {
return _stdlibOpaqueVersion
}
else {
// When linked with the 5.0 stdlib, we return this default value.
return (1000, 0)
}
}

@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
@usableFromInline
Copy link
Member

Choose a reason for hiding this comment

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

Not needed if not back deploying

internal var _stdlibOpaqueVersion: (Int, Int) {
return _stdlibStaticVersion
}

12 changes: 10 additions & 2 deletions validation-test/stdlib/Dictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3003,7 +3003,11 @@ DictionaryTestSuite.test("BridgedFromObjC.Verbatim.RemoveAll") {
}
}

DictionaryTestSuite.test("BridgedFromObjC.Nonverbatim.RemoveAll") {
DictionaryTestSuite.test("BridgedFromObjC.Nonverbatim.RemoveAll")
.skip(
.stdlibOlderThan(.custom(1001, 0),
reason: "https://github.com/apple/swift/pull/22527"))
.code {
do {
var d = getBridgedNonverbatimDictionary([:])
assert(isNativeDictionary(d))
Expand Down Expand Up @@ -3297,7 +3301,11 @@ DictionaryTestSuite.test("BridgedFromObjC.Verbatim.EqualityTest_Empty") {
assert(identity2 == d2._rawIdentifier())
}

DictionaryTestSuite.test("BridgedFromObjC.Nonverbatim.EqualityTest_Empty") {
DictionaryTestSuite.test("BridgedFromObjC.Nonverbatim.EqualityTest_Empty")
.skip(
.stdlibOlderThan(.custom(1001, 0),
reason: "https://github.com/apple/swift/pull/22527"))
.code {
let d1 = getBridgedNonverbatimEquatableDictionary([:])
let identity1 = d1._rawIdentifier()
assert(isNativeDictionary(d1))
Expand Down
30 changes: 27 additions & 3 deletions validation-test/stdlib/Set.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1918,7 +1918,11 @@ SetTestSuite.test("BridgedFromObjC.Nonverbatim.Remove")
}
}

SetTestSuite.test("BridgedFromObjC.Verbatim.RemoveAll") {
SetTestSuite.test("BridgedFromObjC.Verbatim.RemoveAll")
.skip(
.stdlibOlderThan(.custom(1001, 0),
reason: "https://github.com/apple/swift/pull/22527"))
.code {
do {
var s = getBridgedVerbatimSet([])
expectTrue(isCocoaSet(s))
Expand Down Expand Up @@ -2173,7 +2177,11 @@ SetTestSuite.test("BridgedFromObjC.Verbatim.EqualityTest_Empty") {
expectNotEqual(s1, s2)
}

SetTestSuite.test("BridgedFromObjC.Nonverbatim.EqualityTest_Empty") {
SetTestSuite.test("BridgedFromObjC.Nonverbatim.EqualityTest_Empty")
.skip(
.stdlibOlderThan(.custom(1001, 0),
reason: "https://github.com/apple/swift/pull/22527"))
.code {
let s1 = getBridgedNonverbatimSet([])
let identity1 = s1._rawIdentifier()
expectTrue(isNativeSet(s1))
Expand Down Expand Up @@ -4618,6 +4626,9 @@ SetTestSuite.test("IndexValidation.RemoveAt.AfterGrow") {

#if _runtime(_ObjC)
SetTestSuite.test("ForcedNonverbatimBridge.Trap.String")
.skip(
.stdlibOlderThan(.custom(1001, 0),
reason: "https://github.com/apple/swift/pull/23174"))
.skip(.custom(
{ _isFastAssertConfiguration() },
reason: "this trap is not guaranteed to happen in -Ounchecked"))
Expand All @@ -4639,6 +4650,9 @@ SetTestSuite.test("ForcedNonverbatimBridge.Trap.String")

#if _runtime(_ObjC)
SetTestSuite.test("ForcedNonverbatimBridge.Trap.Int")
.skip(
.stdlibOlderThan(.custom(1001, 0),
reason: "https://github.com/apple/swift/pull/23174"))
.skip(.custom(
{ _isFastAssertConfiguration() },
reason: "this trap is not guaranteed to happen in -Ounchecked"))
Expand Down Expand Up @@ -4769,6 +4783,9 @@ SetTestSuite.test("ForcedVerbatimDowncast.Trap.Int")

#if _runtime(_ObjC)
SetTestSuite.test("ForcedBridgingNonverbatimDowncast.Trap.String")
.skip(
.stdlibOlderThan(.custom(1001, 0),
reason: "https://github.com/apple/swift/pull/23174"))
.skip(.custom(
{ _isFastAssertConfiguration() },
reason: "this trap is not guaranteed to happen in -Ounchecked"))
Expand All @@ -4790,6 +4807,9 @@ SetTestSuite.test("ForcedBridgingNonverbatimDowncast.Trap.String")

#if _runtime(_ObjC)
SetTestSuite.test("ForcedBridgingNonverbatimDowncast.Trap.Int")
.skip(
.stdlibOlderThan(.custom(1001, 0),
reason: "https://github.com/apple/swift/pull/23174"))
.skip(.custom(
{ _isFastAssertConfiguration() },
reason: "this trap is not guaranteed to happen in -Ounchecked"))
Expand All @@ -4811,7 +4831,11 @@ SetTestSuite.test("ForcedBridgingNonverbatimDowncast.Trap.Int")
#endif

#if _runtime(_ObjC)
SetTestSuite.test("Upcast.StringEqualityMismatch") {
SetTestSuite.test("Upcast.StringEqualityMismatch")
.skip(
.stdlibOlderThan(.custom(1001, 0),
reason: "https://github.com/apple/swift/pull/23683"))
.code {
// Upcasting from NSString to String keys changes their concept of equality,
// resulting in two equal keys, one of which should be discarded by the
// downcast. (Along with its associated value.)
Expand Down