diff --git a/Sources/XCTest/Public/Asynchronous/XCTestCase+Asynchronous.swift b/Sources/XCTest/Public/Asynchronous/XCTestCase+Asynchronous.swift index 7b122532..b9935ff7 100644 --- a/Sources/XCTest/Public/Asynchronous/XCTestCase+Asynchronous.swift +++ b/Sources/XCTest/Public/Asynchronous/XCTestCase+Asynchronous.swift @@ -41,7 +41,7 @@ public extension XCTestCase { /// these environments. To ensure compatibility of tests between /// swift-corelibs-xctest and Apple XCTest, it is not recommended to pass /// explicit values for `file` and `line`. - // FIXME: This should have `@MainActor` to match Xcode XCTest, but adding it causes errors in tests authored pre-Swift Concurrency which don't typically have `@MainActor`. + @preconcurrency @MainActor func waitForExpectations(timeout: TimeInterval, file: StaticString = #file, line: Int = #line, handler: XCWaitCompletionHandler? = nil) { precondition(Thread.isMainThread, "\(#function) must be called on the main thread") if currentWaiter != nil { diff --git a/Sources/XCTest/Public/XCTestCase.swift b/Sources/XCTest/Public/XCTestCase.swift index aadfecc7..4d734cd8 100644 --- a/Sources/XCTest/Public/XCTestCase.swift +++ b/Sources/XCTest/Public/XCTestCase.swift @@ -48,7 +48,7 @@ open class XCTestCase: XCTest { return 1 } - // FIXME: Once `waitForExpectations(timeout:...handler:)` gains `@MainActor`, this may be able to add it as well. + @MainActor internal var currentWaiter: XCTWaiter? /// The set of expectations made upon this test case. diff --git a/Tests/Functional/Asynchronous/Expectations/main.swift b/Tests/Functional/Asynchronous/Expectations/main.swift index 6ebb3352..8d0b1599 100644 --- a/Tests/Functional/Asynchronous/Expectations/main.swift +++ b/Tests/Functional/Asynchronous/Expectations/main.swift @@ -551,6 +551,28 @@ class ExpectationsTestCase: XCTestCase { RunLoop.main.run(until: Date() + 1) } +// CHECK: Test Case 'ExpectationsTestCase.test_waitForExpectationsAsync' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+ +// CHECK: Test Case 'ExpectationsTestCase.test_waitForExpectationsAsync' passed \(\d+\.\d+ seconds\) + func test_waitForExpectationsAsync() async { + // Basic check that waitForExpectations() is functional when used with the + // await keyword in an async function. + let expectation = self.expectation(description: "foo") + expectation.fulfill() + await self.waitForExpectations(timeout: 0.0) + } + +// CHECK: Test Case 'ExpectationsTestCase.test_waitForExpectationsFromMainActor' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+ +// CHECK: Test Case 'ExpectationsTestCase.test_waitForExpectationsFromMainActor' passed \(\d+\.\d+ seconds\) + func test_waitForExpectationsFromMainActor() async { + await MainActor.run { + // Basic check that waitForExpectations() is functional and does not need + // the await keyword when used from a main-actor-isolated test function. + let expectation = self.expectation(description: "foo") + expectation.fulfill() + self.waitForExpectations(timeout: 0.0) + } + } + static var allTests = { return [ ("test_waitingForAnUnfulfilledExpectation_fails", test_waitingForAnUnfulfilledExpectation_fails), @@ -603,15 +625,19 @@ class ExpectationsTestCase: XCTestCase { ("test_expectationCreationOnSecondaryThread", test_expectationCreationOnSecondaryThread), ("test_expectationCreationWhileWaiting", test_expectationCreationWhileWaiting), ("test_runLoopInsideDispatch", test_runLoopInsideDispatch), + + // waitForExpectations() + @MainActor + ("test_waitForExpectationsAsync", asyncTest(test_waitForExpectationsAsync)), + ("test_waitForExpectationsFromMainActor", asyncTest(test_waitForExpectationsFromMainActor)), ] }() } // CHECK: Test Suite 'ExpectationsTestCase' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+ -// CHECK: \t Executed 35 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds +// CHECK: \t Executed 37 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds XCTMain([testCase(ExpectationsTestCase.allTests)]) // CHECK: Test Suite '.*\.xctest' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+ -// CHECK: \t Executed 35 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds +// CHECK: \t Executed 37 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds // CHECK: Test Suite 'All tests' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+ -// CHECK: \t Executed 35 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds +// CHECK: \t Executed 37 tests, with 16 failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds