Skip to content

Commit c09df49

Browse files
committed
Tests: Add integration tests for @_backDeploy that verify that use of declarations annotated with @_backDeploy behave as expected when running a client binary on an older OS that does not have the back deployed APIs.
APIs with a variety of function signatures (inout parameters, throwing, generic, existential parameters and return values, etc.) are exercised to verify SILGen for these cases.
1 parent fbd6946 commit c09df49

File tree

2 files changed

+385
-0
lines changed

2 files changed

+385
-0
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
2+
/// Returns the dsohandle for the dynamic library.
3+
public func libraryHandle() -> UnsafeRawPointer {
4+
return #dsohandle
5+
}
6+
7+
/// Prepends "client:" or "library:" to the given string (depending on whether
8+
/// the dsohandle belongs to the library or not) and then prints it.
9+
public func testPrint(handle: UnsafeRawPointer, _ string: String) {
10+
let libraryHandle = #dsohandle
11+
let prefix = (handle == libraryHandle) ? "library" : "client"
12+
print("\(prefix): \(string)")
13+
}
14+
15+
/// Returns true if the host OS version is BackDeploy 2.0 or later, indicating
16+
/// that APIs back deployed before 2.0 should run in the library.
17+
public func isV2OrLater() -> Bool {
18+
if #available(BackDeploy 2.0, *) {
19+
return true
20+
} else {
21+
return false
22+
}
23+
}
24+
25+
/// Returns true if BackDeploy 2.0 APIs have been stripped from the binary.
26+
public func v2APIsAreStripped() -> Bool {
27+
#if STRIP_V2_APIS
28+
return true
29+
#else
30+
return false
31+
#endif // STRIP_V2_APIS
32+
}
33+
34+
public enum BadError: Error, Equatable {
35+
/// Indicates badness
36+
case bad
37+
}
38+
39+
/// A totally unnecessary wrapper for `Int` that adds mutability.
40+
public struct MutableInt {
41+
@usableFromInline
42+
internal var _value: Int
43+
44+
public init(_ value: Int) { _value = value }
45+
}
46+
47+
/// A totally unnecessary wrapper for `Int` that provides reference semantics.
48+
public class ReferenceInt {
49+
@usableFromInline
50+
internal var _value: Int
51+
52+
public init(_ value: Int) { _value = value }
53+
}
54+
55+
/// Describes types that can be incremented.
56+
public protocol Incrementable {
57+
associatedtype Operand
58+
59+
mutating func incrementByOne() -> String
60+
mutating func increment(by amount: Operand) -> Operand
61+
}
62+
63+
extension MutableInt: Incrementable {
64+
public mutating func incrementByOne() -> String {
65+
_value += 1
66+
return String(_value)
67+
}
68+
69+
public mutating func increment(by amount: Int) -> Int {
70+
_value += amount
71+
return _value
72+
}
73+
}
74+
75+
extension ReferenceInt: Incrementable {
76+
public func incrementByOne() -> String {
77+
_value += 1
78+
return String(_value)
79+
}
80+
81+
public func increment(by amount: Int) -> Int {
82+
_value += amount
83+
return _value
84+
}
85+
}
86+
87+
extension Int {
88+
@usableFromInline internal func byte(at index: Int) -> UInt8 {
89+
UInt8(truncatingIfNeeded: self >> (index * 8))
90+
}
91+
}
92+
93+
// MARK: - Back deployed APIs
94+
95+
#if !STRIP_V2_APIS
96+
97+
@available(BackDeploy 1.0, *)
98+
@_backDeploy(BackDeploy 2.0)
99+
public func trivial() {
100+
testPrint(handle: #dsohandle, "trivial")
101+
}
102+
103+
@available(BackDeploy 1.0, *)
104+
@_backDeploy(BackDeploy 2.0)
105+
public func pleaseThrow(_ shouldThrow: Bool) throws -> Bool {
106+
if shouldThrow { throw BadError.bad }
107+
return !shouldThrow
108+
}
109+
110+
@available(BackDeploy 1.0, *)
111+
@_backDeploy(BackDeploy 2.0)
112+
public func genericIncrement<T: Incrementable>(
113+
_ x: inout T,
114+
by amount: T.Operand
115+
) -> T.Operand {
116+
return x.increment(by: amount)
117+
}
118+
119+
@available(BackDeploy 1.0, *)
120+
@_backDeploy(BackDeploy 2.0)
121+
public func existentialIncrementByOne(_ x: inout any Incrementable) {
122+
testPrint(handle: #dsohandle, x.incrementByOne())
123+
}
124+
125+
extension MutableInt {
126+
@available(BackDeploy 1.0, *)
127+
@_backDeploy(BackDeploy 2.0)
128+
public var value: Int { _value }
129+
130+
@available(BackDeploy 1.0, *)
131+
@_backDeploy(BackDeploy 2.0)
132+
public func print() {
133+
// Tests recursive @_backDeploy since `value` is also @_backDeploy
134+
testPrint(handle: #dsohandle, String(value))
135+
}
136+
137+
@available(BackDeploy 1.0, *)
138+
@_backDeploy(BackDeploy 2.0)
139+
public static var zero: Self { MutableInt(0) }
140+
141+
@available(BackDeploy 1.0, *)
142+
@_backDeploy(BackDeploy 2.0)
143+
public mutating func decrement(by amount: Int) -> Int {
144+
_value -= amount
145+
return _value
146+
}
147+
148+
@available(BackDeploy 1.0, *)
149+
@_backDeploy(BackDeploy 2.0)
150+
public func toIncrementable() -> any Incrementable { self }
151+
152+
@available(BackDeploy 1.0, *)
153+
@_backDeploy(BackDeploy 2.0)
154+
public subscript(byteAt index: Int) -> UInt8 { _value.byte(at: index) }
155+
}
156+
157+
extension ReferenceInt {
158+
@available(BackDeploy 1.0, *)
159+
@_backDeploy(BackDeploy 2.0)
160+
public final var value: Int { _value }
161+
162+
@available(BackDeploy 1.0, *)
163+
@_backDeploy(BackDeploy 2.0)
164+
public final func print() {
165+
// Tests recursive use of back deployed APIs, since `value` is also
166+
testPrint(handle: #dsohandle, String(value))
167+
}
168+
169+
@available(BackDeploy 1.0, *)
170+
@_backDeploy(BackDeploy 2.0)
171+
public final func copy() -> ReferenceInt {
172+
return ReferenceInt(value)
173+
}
174+
175+
@available(BackDeploy 1.0, *)
176+
@_backDeploy(BackDeploy 2.0)
177+
public final class var zero: ReferenceInt { ReferenceInt(0) }
178+
179+
@available(BackDeploy 1.0, *)
180+
@_backDeploy(BackDeploy 2.0)
181+
public final func decrement(by amount: Int) -> Int {
182+
_value -= amount
183+
return _value
184+
}
185+
186+
@available(BackDeploy 1.0, *)
187+
@_backDeploy(BackDeploy 2.0)
188+
public final func toIncrementable() -> any Incrementable { self }
189+
190+
@available(BackDeploy 1.0, *)
191+
@_backDeploy(BackDeploy 2.0)
192+
public final subscript(byteAt index: Int) -> UInt8 { _value.byte(at: index) }
193+
}
194+
195+
#endif // !STRIP_V2_APIS
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
//
2+
// At a high level, this test is designed to verify that use of declarations
3+
// annotated with @_backDeploy behave as expected when running a client binary
4+
// on an older OS that does not have the back deployed APIs. The
5+
// BackDeployHelper framework has a number of APIs that are available in the
6+
// OSes identified by the "BackDeploy 1.0" availability macro and are back
7+
// deployed before OSes identified "BackDeploy 2.0". Verification is performed
8+
// the following way:
9+
//
10+
// 1. Build the helper framework with both BackDeploy 1.0 defined to an
11+
// OS version before Swift ABI stability and 2.0 defined to an OS version
12+
// after Swift ABI stability. Note that stradling ABI stability is not
13+
// a necessary requirement of this test; it's just convenient to use OS
14+
// versions that correspond to existing lit substitutions.
15+
// 2. Build the client executable with a deployment target set to the same
16+
// OS version as BackDeploy 2.0.
17+
// 3. Run the client executable, verifying that the copies of the functions in
18+
// the framework are used (verified by runtime logic using #dsohandle).
19+
// 4. Build a new copy of the helper framework, this time with BackDeploy 2.0
20+
// set to a distant future OS version.
21+
// 5. Build a new copy of the client executable using the new framework and
22+
// the deployment target set to the same OS version as BackDeploy 1.0.
23+
// 6. Run the new executable, verifying with #dsohandle that client copies of
24+
// the APIs are used.
25+
// 7. Re-build the framework in place, this time stripping the definitions of
26+
// the back deployed APIs entirely.
27+
// 8. Re-run the unmodified executable, with the same expectations as in (6).
28+
// However, this time we're also verifying that the executable can run even
29+
// without the original API symbols present in the linked dylib.
30+
//
31+
32+
// REQUIRES: executable_test
33+
// REQUIRES: VENDOR=apple
34+
35+
// ---- (0) Prepare SDK
36+
// RUN: %empty-directory(%t)
37+
// RUN: %empty-directory(%t/SDK_ABI)
38+
// RUN: %empty-directory(%t/SDK_BD)
39+
40+
// ---- (1) Build famework with BackDeploy 2.0 in the past
41+
// RUN: mkdir -p %t/SDK_ABI/Frameworks/BackDeployHelper.framework/Modules/BackDeployHelper.swiftmodule
42+
// RUN: %target-build-swift-dylib(%t/SDK_ABI/Frameworks/BackDeployHelper.framework/BackDeployHelper) \
43+
// RUN: -emit-module-path %t/SDK_ABI/Frameworks/BackDeployHelper.framework/Modules/BackDeployHelper.swiftmodule/%module-target-triple.swiftmodule \
44+
// RUN: -module-name BackDeployHelper -emit-module %S/Inputs/BackDeployHelper.swift \
45+
// RUN: -Xfrontend -target -Xfrontend %target-next-stable-abi-triple \
46+
// RUN: -Xfrontend -define-availability \
47+
// RUN: -Xfrontend 'BackDeploy 1.0:macOS 10.14.3, iOS 12.1, tvOS 12.1, watchOS 5.1' \
48+
// RUN: -Xfrontend -define-availability \
49+
// RUN: -Xfrontend 'BackDeploy 2.0:macOS 10.15, iOS 13, tvOS 13, watchOS 6' \
50+
// RUN: -Xlinker -install_name -Xlinker @rpath/BackDeployHelper.framework/BackDeployHelper \
51+
// RUN: -enable-library-evolution
52+
53+
// ---- (2) Build executable
54+
// RUN: %target-build-swift -emit-executable %s -g -o %t/test_ABI \
55+
// RUN: -Xfrontend -target -Xfrontend %target-pre-stable-abi-triple \
56+
// RUN: -Xfrontend -define-availability \
57+
// RUN: -Xfrontend 'BackDeploy 1.0:macOS 10.14.3, iOS 12.1, tvOS 12.1, watchOS 5.1' \
58+
// RUN: -Xfrontend -define-availability \
59+
// RUN: -Xfrontend 'BackDeploy 2.0:macOS 10.15, iOS 13, tvOS 13, watchOS 6' \
60+
// RUN: -F %t/SDK_ABI/Frameworks/ -framework BackDeployHelper \
61+
// RUN: %target-rpath(@executable_path/SDK_ABI/Frameworks)
62+
63+
// ---- (3) Run executable
64+
// RUN: %target-codesign %t/test_ABI
65+
// RUN: %target-run %t/test_ABI %t/SDK_ABI/Frameworks/BackDeployHelper.framework/BackDeployHelper | %FileCheck --check-prefix=CHECK --check-prefix=CHECK-ABI %s
66+
67+
// ---- (4) Build famework with BackDeploy 2.0 in the future
68+
// RUN: mkdir -p %t/SDK_BD/Frameworks/BackDeployHelper.framework/Modules/BackDeployHelper.swiftmodule
69+
// RUN: %target-build-swift-dylib(%t/SDK_BD/Frameworks/BackDeployHelper.framework/BackDeployHelper) \
70+
// RUN: -emit-module-path %t/SDK_BD/Frameworks/BackDeployHelper.framework/Modules/BackDeployHelper.swiftmodule/%module-target-triple.swiftmodule \
71+
// RUN: -module-name BackDeployHelper -emit-module %S/Inputs/BackDeployHelper.swift \
72+
// RUN: -Xfrontend -target -Xfrontend %target-next-stable-abi-triple \
73+
// RUN: -Xfrontend -define-availability \
74+
// RUN: -Xfrontend 'BackDeploy 1.0:macOS 10.14.3, iOS 12.1, tvOS 12.1, watchOS 5.1' \
75+
// RUN: -Xfrontend -define-availability \
76+
// RUN: -Xfrontend 'BackDeploy 2.0:macOS 999.0, iOS 999.0, watchOS 999.0, tvOS 999.0' \
77+
// RUN: -Xlinker -install_name -Xlinker @rpath/BackDeployHelper.framework/BackDeployHelper \
78+
// RUN: -enable-library-evolution
79+
80+
// ---- (5) Build executable
81+
// RUN: %target-build-swift -emit-executable %s -g -o %t/test_BD \
82+
// RUN: -Xfrontend -target -Xfrontend %target-next-stable-abi-triple \
83+
// RUN: -Xfrontend -define-availability \
84+
// RUN: -Xfrontend 'BackDeploy 1.0:macOS 10.14.3, iOS 12.1, tvOS 12.1, watchOS 5.1' \
85+
// RUN: -Xfrontend -define-availability \
86+
// RUN: -Xfrontend 'BackDeploy 2.0:macOS 999.0, iOS 999.0, watchOS 999.0, tvOS 999.0' \
87+
// RUN: -F %t/SDK_BD/Frameworks/ -framework BackDeployHelper \
88+
// RUN: %target-rpath(@executable_path/SDK_BD/Frameworks)
89+
90+
// ---- (6) Run executable
91+
// RUN: %target-codesign %t/test_BD
92+
// RUN: %target-run %t/test_BD %t/SDK_BD/Frameworks/BackDeployHelper.framework/BackDeployHelper | %FileCheck --check-prefix=CHECK --check-prefix=CHECK-BD %s
93+
94+
// ---- (7) Re-build famework with the back deployed APIs stripped
95+
// RUN: mkdir -p %t/SDK_BD/Frameworks/BackDeployHelper.framework/Modules/BackDeployHelper.swiftmodule
96+
// RUN: %target-build-swift-dylib(%t/SDK_BD/Frameworks/BackDeployHelper.framework/BackDeployHelper) \
97+
// RUN: -emit-module-path %t/SDK_BD/Frameworks/BackDeployHelper.framework/Modules/BackDeployHelper.swiftmodule/%module-target-triple.swiftmodule \
98+
// RUN: -module-name BackDeployHelper -emit-module %S/Inputs/BackDeployHelper.swift \
99+
// RUN: -Xfrontend -target -Xfrontend %target-next-stable-abi-triple \
100+
// RUN: -Xfrontend -define-availability \
101+
// RUN: -Xfrontend 'BackDeploy 1.0:macOS 10.14.3, iOS 12.1, tvOS 12.1, watchOS 5.1' \
102+
// RUN: -Xfrontend -define-availability \
103+
// RUN: -Xfrontend 'BackDeploy 2.0:macOS 999.0, iOS 999.0, watchOS 999.0, tvOS 999.0' \
104+
// RUN: -Xlinker -install_name -Xlinker @rpath/BackDeployHelper.framework/BackDeployHelper \
105+
// RUN: -enable-library-evolution -DSTRIP_V2_APIS
106+
107+
// ---- (8) Re-run executable
108+
// RUN: %target-codesign %t/test_BD
109+
// RUN: %target-run %t/test_BD %t/SDK_BD/Frameworks/BackDeployHelper.framework/BackDeployHelper | %FileCheck --check-prefix=CHECK --check-prefix=CHECK-BD %s
110+
111+
import BackDeployHelper
112+
113+
// CHECK: client: check
114+
testPrint(handle: #dsohandle, "check")
115+
// CHECK: library: check
116+
testPrint(handle: libraryHandle(), "check")
117+
118+
if isV2OrLater() {
119+
assert(!v2APIsAreStripped())
120+
}
121+
122+
// CHECK-ABI: library: trivial
123+
// CHECK-BD: client: trivial
124+
trivial()
125+
126+
assert(try! pleaseThrow(false))
127+
do {
128+
_ = try pleaseThrow(true)
129+
fatalError("Should have thrown")
130+
} catch {
131+
assert(error as? BadError == BadError.bad)
132+
}
133+
134+
do {
135+
let zero = MutableInt.zero
136+
assert(zero.value == 0)
137+
138+
var int = MutableInt(5)
139+
140+
// CHECK-ABI: library: 5
141+
// CHECK-BD: client: 5
142+
int.print()
143+
144+
assert(int.increment(by: 2) == 7)
145+
assert(genericIncrement(&int, by: 3) == 10)
146+
assert(int.decrement(by: 1) == 9)
147+
148+
var incrementable: any Incrementable = int.toIncrementable()
149+
150+
// CHECK-ABI: library: 10
151+
// CHECK-BD: client: 10
152+
existentialIncrementByOne(&incrementable)
153+
154+
let int2 = MutableInt(0x7BB7914B)
155+
for (i, expectedByte) in [0x4B, 0x91, 0xB7, 0x7B].enumerated() {
156+
assert(int2[byteAt: i] == expectedByte)
157+
}
158+
}
159+
160+
do {
161+
let zero = ReferenceInt.zero
162+
assert(zero.value == 0)
163+
164+
var int = ReferenceInt(42)
165+
166+
// CHECK-ABI: library: 42
167+
// CHECK-BD: client: 42
168+
int.print()
169+
170+
do {
171+
let copy = int.copy()
172+
assert(int !== copy)
173+
assert(copy.value == 42)
174+
}
175+
176+
assert(int.increment(by: 2) == 44)
177+
assert(genericIncrement(&int, by: 3) == 47)
178+
assert(int.decrement(by: 46) == 1)
179+
180+
var incrementable: any Incrementable = int.toIncrementable()
181+
182+
// CHECK-ABI: library: 2
183+
// CHECK-BD: client: 2
184+
existentialIncrementByOne(&incrementable)
185+
186+
let int2 = MutableInt(0x08AFAB76)
187+
for (i, expectedByte) in [0x76, 0xAB, 0xAF, 0x08].enumerated() {
188+
assert(int2[byteAt: i] == expectedByte)
189+
}
190+
}

0 commit comments

Comments
 (0)