Skip to content

Commit 03c6518

Browse files
authored
Merge pull request #636 from swiftlang/main-next
Merge main-next into main.
2 parents bda8474 + 888caad commit 03c6518

32 files changed

+625
-94
lines changed

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ extension Array where Element == PackageDescription.SwiftSetting {
148148
.enableExperimentalFeature("AvailabilityMacro=_clockAPI:macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0"),
149149
.enableExperimentalFeature("AvailabilityMacro=_regexAPI:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0"),
150150
.enableExperimentalFeature("AvailabilityMacro=_swiftVersionAPI:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0"),
151+
.enableExperimentalFeature("AvailabilityMacro=_synchronizationAPI:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0"),
151152

152153
.enableExperimentalFeature("AvailabilityMacro=_distantFuture:macOS 99.0, iOS 99.0, watchOS 99.0, tvOS 99.0, visionOS 99.0"),
153154
]

Sources/Testing/ABI/EntryPoints/ABIEntryPoint.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@
99
//
1010

1111
#if canImport(Foundation) && !SWT_NO_ABI_ENTRY_POINT
12-
#if SWT_BUILDING_WITH_CMAKE
13-
@_implementationOnly import _TestingInternals
14-
#else
1512
private import _TestingInternals
16-
#endif
1713

1814
extension ABIv0 {
1915
/// The type of the entry point to the testing library used by tools that want

Sources/Testing/Events/Clock.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@ extension Test {
4141
/// The wall-clock time corresponding to this instant.
4242
fileprivate(set) var wall: TimeValue = {
4343
var wall = timespec()
44+
#if os(Android)
45+
// Android headers recommend `clock_gettime` over `timespec_get` which
46+
// is available with API Level 29+ for `TIME_UTC`.
47+
clock_gettime(CLOCK_REALTIME, &wall)
48+
#else
4449
timespec_get(&wall, TIME_UTC)
50+
#endif
4551
return TimeValue(wall)
4652
}()
4753
#endif

Sources/Testing/ExitTests/ExitCondition.swift

Lines changed: 146 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ public enum ExitCondition: Sendable {
4444
/// | Linux | [`<stdlib.h>`](https://sourceware.org/glibc/manual/latest/html_node/Exit-Status.html), `<sysexits.h>` |
4545
/// | Windows | [`<stdlib.h>`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/exit-success-exit-failure) |
4646
///
47-
/// On POSIX-like systems including macOS and Linux, only the low unsigned 8
48-
/// bits (0&ndash;255) of the exit code are reliably preserved and reported to
49-
/// a parent process.
47+
/// On macOS and Windows, the full exit code reported by the process is
48+
/// yielded to the parent process. Linux and other POSIX-like systems may only
49+
/// reliably report the low unsigned 8 bits (0&ndash;255) of the exit code.
5050
case exitCode(_ exitCode: CInt)
5151

5252
/// The process terminated with the given signal.
@@ -62,43 +62,171 @@ public enum ExitCondition: Sendable {
6262
/// | macOS | [`<signal.h>`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/signal.3.html) |
6363
/// | Linux | [`<signal.h>`](https://sourceware.org/glibc/manual/latest/html_node/Standard-Signals.html) |
6464
/// | Windows | [`<signal.h>`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-constants) |
65+
///
66+
/// On Windows, by default, the C runtime will terminate a process with exit
67+
/// code `-3` if a raised signal is not handled, exactly as if `exit(-3)` were
68+
/// called. As a result, this case is unavailable on that platform. Developers
69+
/// should use ``failure`` instead when testing signal handling on Windows.
6570
#if os(Windows)
6671
@available(*, unavailable, message: "On Windows, use .failure instead.")
6772
#endif
6873
case signal(_ signal: CInt)
6974
}
7075

71-
// MARK: -
76+
// MARK: - Equatable
7277

7378
#if SWT_NO_EXIT_TESTS
7479
@available(*, unavailable, message: "Exit tests are not available on this platform.")
7580
#endif
7681
extension ExitCondition {
77-
/// Check whether this instance matches another.
82+
/// Check whether or not two values of this type are equal.
7883
///
7984
/// - Parameters:
80-
/// - other: The other instance to compare against.
85+
/// - lhs: One value to compare.
86+
/// - rhs: Another value to compare.
8187
///
82-
/// - Returns: Whether or not this instance is equal to, or at least covers,
83-
/// the other instance.
84-
func matches(_ other: ExitCondition) -> Bool {
85-
return switch (self, other) {
86-
case (.failure, .failure):
87-
true
88+
/// - Returns: Whether or not `lhs` and `rhs` are equal.
89+
///
90+
/// Two instances of this type can be compared; if either instance is equal to
91+
/// ``failure``, it will compare equal to any instance except ``success``. To
92+
/// check if two instances are exactly equal, use the ``===(_:_:)`` operator:
93+
///
94+
/// ```swift
95+
/// let lhs: ExitCondition = .failure
96+
/// let rhs: ExitCondition = .signal(SIGINT)
97+
/// print(lhs == rhs) // prints "true"
98+
/// print(lhs === rhs) // prints "false"
99+
/// ```
100+
///
101+
/// This special behavior means that the ``==(_:_:)`` operator is not
102+
/// transitive, and does not satisfy the requirements of
103+
/// [`Equatable`](https://developer.apple.com/documentation/swift/equatable)
104+
/// or [`Hashable`](https://developer.apple.com/documentation/swift/hashable).
105+
///
106+
/// For any values `a` and `b`, `a == b` implies that `a != b` is `false`.
107+
public static func ==(lhs: Self, rhs: Self) -> Bool {
108+
#if SWT_NO_EXIT_TESTS
109+
fatalError("Unsupported")
110+
#else
111+
return switch (lhs, rhs) {
88112
case let (.failure, .exitCode(exitCode)), let (.exitCode(exitCode), .failure):
89113
exitCode != EXIT_SUCCESS
114+
#if !os(Windows)
115+
case (.failure, .signal), (.signal, .failure):
116+
// All terminating signals are considered failures.
117+
true
118+
#endif
119+
default:
120+
lhs === rhs
121+
}
122+
#endif
123+
}
124+
125+
/// Check whether or not two values of this type are _not_ equal.
126+
///
127+
/// - Parameters:
128+
/// - lhs: One value to compare.
129+
/// - rhs: Another value to compare.
130+
///
131+
/// - Returns: Whether or not `lhs` and `rhs` are _not_ equal.
132+
///
133+
/// Two instances of this type can be compared; if either instance is equal to
134+
/// ``failure``, it will compare equal to any instance except ``success``. To
135+
/// check if two instances are not exactly equal, use the ``!==(_:_:)``
136+
/// operator:
137+
///
138+
/// ```swift
139+
/// let lhs: ExitCondition = .failure
140+
/// let rhs: ExitCondition = .signal(SIGINT)
141+
/// print(lhs != rhs) // prints "false"
142+
/// print(lhs !== rhs) // prints "true"
143+
/// ```
144+
///
145+
/// This special behavior means that the ``!=(_:_:)`` operator is not
146+
/// transitive, and does not satisfy the requirements of
147+
/// [`Equatable`](https://developer.apple.com/documentation/swift/equatable)
148+
/// or [`Hashable`](https://developer.apple.com/documentation/swift/hashable).
149+
///
150+
/// For any values `a` and `b`, `a == b` implies that `a != b` is `false`.
151+
public static func !=(lhs: Self, rhs: Self) -> Bool {
152+
#if SWT_NO_EXIT_TESTS
153+
fatalError("Unsupported")
154+
#else
155+
!(lhs == rhs)
156+
#endif
157+
}
158+
159+
/// Check whether or not two values of this type are identical.
160+
///
161+
/// - Parameters:
162+
/// - lhs: One value to compare.
163+
/// - rhs: Another value to compare.
164+
///
165+
/// - Returns: Whether or not `lhs` and `rhs` are identical.
166+
///
167+
/// Two instances of this type can be compared; if either instance is equal to
168+
/// ``failure``, it will compare equal to any instance except ``success``. To
169+
/// check if two instances are exactly equal, use the ``===(_:_:)`` operator:
170+
///
171+
/// ```swift
172+
/// let lhs: ExitCondition = .failure
173+
/// let rhs: ExitCondition = .signal(SIGINT)
174+
/// print(lhs == rhs) // prints "true"
175+
/// print(lhs === rhs) // prints "false"
176+
/// ```
177+
///
178+
/// This special behavior means that the ``==(_:_:)`` operator is not
179+
/// transitive, and does not satisfy the requirements of
180+
/// [`Equatable`](https://developer.apple.com/documentation/swift/equatable)
181+
/// or [`Hashable`](https://developer.apple.com/documentation/swift/hashable).
182+
///
183+
/// For any values `a` and `b`, `a === b` implies that `a !== b` is `false`.
184+
public static func ===(lhs: Self, rhs: Self) -> Bool {
185+
return switch (lhs, rhs) {
186+
case (.failure, .failure):
187+
true
90188
case let (.exitCode(lhs), .exitCode(rhs)):
91189
lhs == rhs
92190
#if !os(Windows)
93191
case let (.signal(lhs), .signal(rhs)):
94192
lhs == rhs
95-
case (.signal, .failure), (.failure, .signal):
96-
// All terminating signals are considered failures.
97-
true
98-
case (.signal, .exitCode), (.exitCode, .signal):
99-
// Signals do not match exit codes.
100-
false
101193
#endif
194+
default:
195+
false
102196
}
103197
}
198+
199+
/// Check whether or not two values of this type are _not_ identical.
200+
///
201+
/// - Parameters:
202+
/// - lhs: One value to compare.
203+
/// - rhs: Another value to compare.
204+
///
205+
/// - Returns: Whether or not `lhs` and `rhs` are _not_ identical.
206+
///
207+
/// Two instances of this type can be compared; if either instance is equal to
208+
/// ``failure``, it will compare equal to any instance except ``success``. To
209+
/// check if two instances are not exactly equal, use the ``!==(_:_:)``
210+
/// operator:
211+
///
212+
/// ```swift
213+
/// let lhs: ExitCondition = .failure
214+
/// let rhs: ExitCondition = .signal(SIGINT)
215+
/// print(lhs != rhs) // prints "false"
216+
/// print(lhs !== rhs) // prints "true"
217+
/// ```
218+
///
219+
/// This special behavior means that the ``!=(_:_:)`` operator is not
220+
/// transitive, and does not satisfy the requirements of
221+
/// [`Equatable`](https://developer.apple.com/documentation/swift/equatable)
222+
/// or [`Hashable`](https://developer.apple.com/documentation/swift/hashable).
223+
///
224+
/// For any values `a` and `b`, `a === b` implies that `a !== b` is `false`.
225+
public static func !==(lhs: Self, rhs: Self) -> Bool {
226+
#if SWT_NO_EXIT_TESTS
227+
fatalError("Unsupported")
228+
#else
229+
!(lhs === rhs)
230+
#endif
231+
}
104232
}

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public struct ExitTest: Sendable {
2121
public var expectedExitCondition: ExitCondition
2222

2323
/// The body closure of the exit test.
24-
fileprivate var body: @Sendable () async -> Void
24+
fileprivate var body: @Sendable () async throws -> Void
2525

2626
/// The source location of the exit test.
2727
///
@@ -37,12 +37,16 @@ public struct ExitTest: Sendable {
3737
/// terminate the process in a way that causes the corresponding expectation
3838
/// to fail.
3939
public func callAsFunction() async -> Never {
40-
await body()
40+
do {
41+
try await body()
42+
} catch {
43+
_errorInMain(error)
44+
}
4145

4246
// Run some glue code that terminates the process with an exit condition
4347
// that does not match the expected one. If the exit test's body doesn't
4448
// terminate, we'll manually call exit() and cause the test to fail.
45-
let expectingFailure = expectedExitCondition.matches(.failure)
49+
let expectingFailure = expectedExitCondition == .failure
4650
exit(expectingFailure ? EXIT_SUCCESS : EXIT_FAILURE)
4751
}
4852
}
@@ -63,7 +67,7 @@ public protocol __ExitTestContainer {
6367
static var __sourceLocation: SourceLocation { get }
6468

6569
/// The body function of the exit test.
66-
static var __body: @Sendable () async -> Void { get }
70+
static var __body: @Sendable () async throws -> Void { get }
6771
}
6872

6973
extension ExitTest {
@@ -118,7 +122,7 @@ extension ExitTest {
118122
/// convention.
119123
func callExitTest(
120124
exitsWith expectedExitCondition: ExitCondition,
121-
performing body: @escaping @Sendable () async -> Void,
125+
performing body: @escaping @Sendable () async throws -> Void,
122126
expression: __Expression,
123127
comments: @autoclosure () -> [Comment],
124128
isRequired: Bool,
@@ -150,7 +154,7 @@ func callExitTest(
150154
}
151155

152156
return __checkValue(
153-
expectedExitCondition.matches(actualExitCondition),
157+
expectedExitCondition == actualExitCondition,
154158
expression: expression,
155159
expressionWithCapturedRuntimeValues: expression.capturingRuntimeValues(actualExitCondition),
156160
mismatchedExitConditionDescription: String(describingForTest: expectedExitCondition),

Sources/Testing/Expectations/Expectation+Macro.swift

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,34 @@ public macro require(
101101
sourceLocation: SourceLocation = #_sourceLocation
102102
) -> Bool = #externalMacro(module: "TestingMacros", type: "AmbiguousRequireMacro")
103103

104+
/// Unwrap an optional value or, if it is `nil`, fail and throw an error.
105+
///
106+
/// - Parameters:
107+
/// - optionalValue: The optional value to be unwrapped.
108+
/// - comment: A comment describing the expectation.
109+
/// - sourceLocation: The source location to which recorded expectations and
110+
/// issues should be attributed.
111+
///
112+
/// - Returns: The unwrapped value of `optionalValue`.
113+
///
114+
/// - Throws: An instance of ``ExpectationFailedError`` if `optionalValue` is
115+
/// `nil`.
116+
///
117+
/// If `optionalValue` is `nil`, an ``Issue`` is recorded for the test that is
118+
/// running in the current task and an instance of ``ExpectationFailedError`` is
119+
/// thrown.
120+
///
121+
/// This overload of ``require(_:_:sourceLocation:)-6w9oo`` is used when a
122+
/// non-optional, non-`Bool` value is passed to `#require()`. It emits a warning
123+
/// diagnostic indicating that the expectation is redundant.
124+
@freestanding(expression)
125+
@_documentation(visibility: private)
126+
public macro require<T>(
127+
_ optionalValue: T,
128+
_ comment: @autoclosure () -> Comment? = nil,
129+
sourceLocation: SourceLocation = #_sourceLocation
130+
) -> T = #externalMacro(module: "TestingMacros", type: "NonOptionalRequireMacro")
131+
104132
// MARK: - Matching errors by type
105133

106134
/// Check that an expression always throws an error of a given type.
@@ -440,7 +468,9 @@ public macro require(
440468
/// a clean environment for execution, it is not called within the context of
441469
/// the original test. If `expression` does not terminate the child process, the
442470
/// process is terminated automatically as if the main function of the child
443-
/// process were allowed to return naturally.
471+
/// process were allowed to return naturally. If an error is thrown from
472+
/// `expression`, it is handed as if the error were thrown from `main()` and the
473+
/// process is terminated.
444474
///
445475
/// Once the child process terminates, the parent process resumes and compares
446476
/// its exit status against `exitCondition`. If they match, the exit test has
@@ -488,8 +518,8 @@ public macro require(
488518
/// issues should be attributed.
489519
/// - expression: The expression to be evaluated.
490520
///
491-
/// - Throws: An instance of ``ExpectationFailedError`` if `condition` evaluates
492-
/// to `false`.
521+
/// - Throws: An instance of ``ExpectationFailedError`` if the exit condition of
522+
/// the child process does not equal `expectedExitCondition`.
493523
///
494524
/// Use this overload of `#require()` when an expression will cause the current
495525
/// process to terminate and the nature of that termination will determine if
@@ -515,7 +545,9 @@ public macro require(
515545
/// a clean environment for execution, it is not called within the context of
516546
/// the original test. If `expression` does not terminate the child process, the
517547
/// process is terminated automatically as if the main function of the child
518-
/// process were allowed to return naturally.
548+
/// process were allowed to return naturally. If an error is thrown from
549+
/// `expression`, it is handed as if the error were thrown from `main()` and the
550+
/// process is terminated.
519551
///
520552
/// Once the child process terminates, the parent process resumes and compares
521553
/// its exit status against `exitCondition`. If they match, the exit test has
@@ -550,5 +582,5 @@ public macro require(
550582
exitsWith expectedExitCondition: ExitCondition,
551583
_ comment: @autoclosure () -> Comment? = nil,
552584
sourceLocation: SourceLocation = #_sourceLocation,
553-
performing expression: @convention(thin) () async -> Void
585+
performing expression: @convention(thin) () async throws -> Void
554586
) = #externalMacro(module: "TestingMacros", type: "ExitTestRequireMacro")

Sources/Testing/Expectations/ExpectationChecking+Macro.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ public func __checkValue(
9595
// Post an event for the expectation regardless of whether or not it passed.
9696
// If the current event handler is not configured to handle events of this
9797
// kind, this event is discarded.
98-
var expectation = Expectation(evaluatedExpression: expression, isPassing: condition, isRequired: isRequired, sourceLocation: sourceLocation)
99-
Event.post(.expectationChecked(expectation))
98+
lazy var expectation = Expectation(evaluatedExpression: expression, isPassing: condition, isRequired: isRequired, sourceLocation: sourceLocation)
99+
if Configuration.deliverExpectationCheckedEvents {
100+
Event.post(.expectationChecked(expectation))
101+
}
100102

101103
// Early exit if the expectation passed.
102104
if condition {
@@ -1103,15 +1105,15 @@ public func __checkClosureCall<R>(
11031105
@_spi(Experimental)
11041106
public func __checkClosureCall(
11051107
exitsWith expectedExitCondition: ExitCondition,
1106-
performing body: @convention(thin) () async -> Void,
1108+
performing body: @convention(thin) () async throws -> Void,
11071109
expression: __Expression,
11081110
comments: @autoclosure () -> [Comment],
11091111
isRequired: Bool,
11101112
sourceLocation: SourceLocation
11111113
) async -> Result<Void, any Error> {
11121114
await callExitTest(
11131115
exitsWith: expectedExitCondition,
1114-
performing: { await body() },
1116+
performing: { try await body() },
11151117
expression: expression,
11161118
comments: comments(),
11171119
isRequired: isRequired,

0 commit comments

Comments
 (0)