Skip to content

Commit f762c99

Browse files
committed
XCTestCase.fulfillment(…) missing on Linux #436
1 parent 275e4ff commit f762c99

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

Sources/XCTest/Public/Asynchronous/XCTWaiter.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ open class XCTWaiter {
187187
/// these environments. To ensure compatibility of tests between
188188
/// swift-corelibs-xctest and Apple XCTest, it is not recommended to pass
189189
/// explicit values for `file` and `line`.
190+
@available(*, noasync, message: "Use await fulfillment(of:timeout:enforceOrder:) instead.")
190191
@discardableResult
191192
open func wait(for expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) -> Result {
192193
precondition(Set(expectations).count == expectations.count, "API violation - each expectation can appear only once in the 'expectations' parameter.")
@@ -252,6 +253,42 @@ open class XCTWaiter {
252253
return result
253254
}
254255

256+
/// Wait on an array of expectations for up to the specified timeout, and optionally specify whether they
257+
/// must be fulfilled in the given order. May return early based on fulfillment of the waited on expectations.
258+
///
259+
/// - Parameter expectations: The expectations to wait on.
260+
/// - Parameter timeout: The maximum total time duration to wait on all expectations.
261+
/// - Parameter enforceOrder: Specifies whether the expectations must be fulfilled in the order
262+
/// they are specified in the `expectations` Array. Default is false.
263+
/// - Parameter file: The file name to use in the error message if
264+
/// expectations are not fulfilled before the given timeout. Default is the file
265+
/// containing the call to this method. It is rare to provide this
266+
/// parameter when calling this method.
267+
/// - Parameter line: The line number to use in the error message if the
268+
/// expectations are not fulfilled before the given timeout. Default is the line
269+
/// number of the call to this method in the calling file. It is rare to
270+
/// provide this parameter when calling this method.
271+
///
272+
/// - Note: Whereas Objective-C XCTest determines the file and line
273+
/// number of the "wait" call using symbolication, this implementation
274+
/// opts to take `file` and `line` as parameters instead. As a result,
275+
/// the interface to these methods are not exactly identical between
276+
/// these environments. To ensure compatibility of tests between
277+
/// swift-corelibs-xctest and Apple XCTest, it is not recommended to pass
278+
/// explicit values for `file` and `line`.
279+
@available(macOS 12.0, *) @discardableResult
280+
open func fulfillment(of expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) async -> Result {
281+
return await withCheckedContinuation { continuation in
282+
// This function operates by blocking a background thread instead of one owned by libdispatch or by the
283+
// Swift runtime (as used by Swift concurrency.) To ensure we use a thread owned by neither subsystem, use
284+
// Foundation's Thread.detachNewThread(_:).
285+
Thread.detachNewThread { [self] in
286+
let result = wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line)
287+
continuation.resume(returning: result)
288+
}
289+
}
290+
}
291+
255292
/// Convenience API to create an XCTWaiter which then waits on an array of expectations for up to the specified timeout, and optionally specify whether they
256293
/// must be fulfilled in the given order. May return early based on fulfillment of the waited on expectations. The waiter
257294
/// is discarded when the wait completes.
@@ -268,10 +305,32 @@ open class XCTWaiter {
268305
/// expectations are not fulfilled before the given timeout. Default is the line
269306
/// number of the call to this method in the calling file. It is rare to
270307
/// provide this parameter when calling this method.
308+
@available(*, noasync, message: "Use await fulfillment(of:timeout:enforceOrder:) instead.")
271309
open class func wait(for expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) -> Result {
272310
return XCTWaiter().wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line)
273311
}
274312

313+
/// Convenience API to create an XCTWaiter which then waits on an array of expectations for up to the specified timeout, and optionally specify whether they
314+
/// must be fulfilled in the given order. May return early based on fulfillment of the waited on expectations. The waiter
315+
/// is discarded when the wait completes.
316+
///
317+
/// - Parameter expectations: The expectations to wait on.
318+
/// - Parameter timeout: The maximum total time duration to wait on all expectations.
319+
/// - Parameter enforceOrder: Specifies whether the expectations must be fulfilled in the order
320+
/// they are specified in the `expectations` Array. Default is false.
321+
/// - Parameter file: The file name to use in the error message if
322+
/// expectations are not fulfilled before the given timeout. Default is the file
323+
/// containing the call to this method. It is rare to provide this
324+
/// parameter when calling this method.
325+
/// - Parameter line: The line number to use in the error message if the
326+
/// expectations are not fulfilled before the given timeout. Default is the line
327+
/// number of the call to this method in the calling file. It is rare to
328+
/// provide this parameter when calling this method.
329+
@available(macOS 12.0, *)
330+
open class func fulfillment(of expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) async -> Result {
331+
return await XCTWaiter().fulfillment(of: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line)
332+
}
333+
275334
deinit {
276335
for expectation in state.allExpectations {
277336
expectation.cleanUp()

Sources/XCTest/Public/Asynchronous/XCTestCase+Asynchronous.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,39 @@ public extension XCTestCase {
8585
/// provide this parameter when calling this method.
8686
///
8787
/// - SeeAlso: XCTWaiter
88+
@available(*, noasync, message: "Use await fulfillment(of:timeout:enforceOrder:) instead.")
8889
func wait(for expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) {
8990
let waiter = XCTWaiter(delegate: self)
9091
waiter.wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line)
9192

9293
cleanUpExpectations(expectations)
9394
}
9495

96+
/// Wait on an array of expectations for up to the specified timeout, and optionally specify whether they
97+
/// must be fulfilled in the given order. May return early based on fulfillment of the waited on expectations.
98+
///
99+
/// - Parameter expectations: The expectations to wait on.
100+
/// - Parameter timeout: The maximum total time duration to wait on all expectations.
101+
/// - Parameter enforceOrder: Specifies whether the expectations must be fulfilled in the order
102+
/// they are specified in the `expectations` Array. Default is false.
103+
/// - Parameter file: The file name to use in the error message if
104+
/// expectations are not fulfilled before the given timeout. Default is the file
105+
/// containing the call to this method. It is rare to provide this
106+
/// parameter when calling this method.
107+
/// - Parameter line: The line number to use in the error message if the
108+
/// expectations are not fulfilled before the given timeout. Default is the line
109+
/// number of the call to this method in the calling file. It is rare to
110+
/// provide this parameter when calling this method.
111+
///
112+
/// - SeeAlso: XCTWaiter
113+
@available(macOS 12.0, *)
114+
func fulfillment(of expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) async {
115+
let waiter = XCTWaiter(delegate: self)
116+
await waiter.fulfillment(of: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line)
117+
118+
cleanUpExpectations(expectations)
119+
}
120+
95121
/// Creates and returns an expectation associated with the test case.
96122
///
97123
/// - Parameter description: This string will be displayed in the test log

Tests/Functional/Asynchronous/Expectations/main.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ class ExpectationsTestCase: XCTestCase {
9696
XCTWaiter(delegate: self).wait(for: [foo, bar], timeout: 1)
9797
}
9898

99+
// CHECK: Test Case 'ExpectationsTestCase.test_multipleExpectations_async' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
100+
// CHECK: Test Case 'ExpectationsTestCase.test_multipleExpectations_async' passed \(\d+\.\d+ seconds\)
101+
func test_multipleExpectations_async() async {
102+
let foo = expectation(description: "foo")
103+
let bar = XCTestExpectation(description: "bar")
104+
DispatchQueue.global(qos: .default).asyncAfter(wallDeadline: .now() + 0.01) {
105+
bar.fulfill()
106+
}
107+
DispatchQueue.global(qos: .default).asyncAfter(wallDeadline: .now() + 0.01) {
108+
foo.fulfill()
109+
}
110+
111+
await XCTWaiter(delegate: self).fulfillment(of: [foo, bar], timeout: 1)
112+
}
113+
99114
// CHECK: Test Case 'ExpectationsTestCase.test_multipleExpectationsEnforceOrderingCorrect' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
100115
// CHECK: Test Case 'ExpectationsTestCase.test_multipleExpectationsEnforceOrderingCorrect' passed \(\d+\.\d+ seconds\)
101116
func test_multipleExpectationsEnforceOrderingCorrect() {

0 commit comments

Comments
 (0)