Skip to content

Commit 19257a7

Browse files
committed
Merge pull request #453 from apple/jgrynspan/fulfillment-of
XCTestCase.fulfillment(…) missing on Linux #436
1 parent faf3ba3 commit 19257a7

File tree

3 files changed

+105
-3
lines changed

3 files changed

+105
-3
lines changed

Sources/XCTest/Public/Asynchronous/XCTWaiter.swift

Lines changed: 60 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,43 @@ 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, *)
280+
@discardableResult
281+
open func fulfillment(of expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) async -> Result {
282+
return await withCheckedContinuation { continuation in
283+
// This function operates by blocking a background thread instead of one owned by libdispatch or by the
284+
// Swift runtime (as used by Swift concurrency.) To ensure we use a thread owned by neither subsystem, use
285+
// Foundation's Thread.detachNewThread(_:).
286+
Thread.detachNewThread { [self] in
287+
let result = wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line)
288+
continuation.resume(returning: result)
289+
}
290+
}
291+
}
292+
255293
/// 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
256294
/// must be fulfilled in the given order. May return early based on fulfillment of the waited on expectations. The waiter
257295
/// is discarded when the wait completes.
@@ -268,10 +306,32 @@ open class XCTWaiter {
268306
/// expectations are not fulfilled before the given timeout. Default is the line
269307
/// number of the call to this method in the calling file. It is rare to
270308
/// provide this parameter when calling this method.
309+
@available(*, noasync, message: "Use await fulfillment(of:timeout:enforceOrder:) instead.")
271310
open class func wait(for expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) -> Result {
272311
return XCTWaiter().wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line)
273312
}
274313

314+
/// 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
315+
/// must be fulfilled in the given order. May return early based on fulfillment of the waited on expectations. The waiter
316+
/// is discarded when the wait completes.
317+
///
318+
/// - Parameter expectations: The expectations to wait on.
319+
/// - Parameter timeout: The maximum total time duration to wait on all expectations.
320+
/// - Parameter enforceOrder: Specifies whether the expectations must be fulfilled in the order
321+
/// they are specified in the `expectations` Array. Default is false.
322+
/// - Parameter file: The file name to use in the error message if
323+
/// expectations are not fulfilled before the given timeout. Default is the file
324+
/// containing the call to this method. It is rare to provide this
325+
/// parameter when calling this method.
326+
/// - Parameter line: The line number to use in the error message if the
327+
/// expectations are not fulfilled before the given timeout. Default is the line
328+
/// number of the call to this method in the calling file. It is rare to
329+
/// provide this parameter when calling this method.
330+
@available(macOS 12.0, *)
331+
open class func fulfillment(of expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) async -> Result {
332+
return await XCTWaiter().fulfillment(of: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line)
333+
}
334+
275335
deinit {
276336
for expectation in state.allExpectations {
277337
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: 19 additions & 3 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() {
@@ -548,6 +563,7 @@ class ExpectationsTestCase: XCTestCase {
548563

549564
// Multiple Expectations
550565
("test_multipleExpectations", test_multipleExpectations),
566+
("test_multipleExpectations_async", asyncTest(test_multipleExpectations_async)),
551567
("test_multipleExpectationsEnforceOrderingCorrect", test_multipleExpectationsEnforceOrderingCorrect),
552568
("test_multipleExpectationsEnforceOrderingCorrectBeforeWait", test_multipleExpectationsEnforceOrderingCorrectBeforeWait),
553569
("test_multipleExpectationsEnforceOrderingIncorrect", test_multipleExpectationsEnforceOrderingIncorrect),
@@ -591,11 +607,11 @@ class ExpectationsTestCase: XCTestCase {
591607
}()
592608
}
593609
// CHECK: Test Suite 'ExpectationsTestCase' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
594-
// CHECK: \t Executed 34 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
610+
// CHECK: \t Executed 35 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
595611

596612
XCTMain([testCase(ExpectationsTestCase.allTests)])
597613

598614
// CHECK: Test Suite '.*\.xctest' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
599-
// CHECK: \t Executed 34 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
615+
// CHECK: \t Executed 35 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
600616
// CHECK: Test Suite 'All tests' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
601-
// CHECK: \t Executed 34 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
617+
// CHECK: \t Executed 35 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds

0 commit comments

Comments
 (0)