swiftlang / swift-corelibs-xctest

The XCTest Project, A Swift core library for providing unit test support
swift.org
Apache License 2.0
1.15k stars 267 forks source link

XCTestCase.fulfillment(…) missing on Linux #436

Closed tonyarnold closed 1 year ago

tonyarnold commented 1 year ago

Hi folks,

It looks like the fulfillment(…) methods on XCTestCase and XCTWaiter are both missing on Linux.

It would be helpful to have API parity here, as Xcode 14.3 complains if I use the older wait(…) methods:

Instance method 'wait' is unavailable from asynchronous contexts; Use await fulfillment(of:timeout:enforceOrder:) instead; this is an error in Swift 6

Here are the missing methods:

extension XCTestCase {

    /// Waits on a group of expectations for up to the specified timeout,
    /// optionally enforcing their order of fulfillment.
    ///
    /// - Parameters:
    ///     - expectations: An array of expectations that must be fulfilled.
    ///     - seconds: The number of seconds within which all expectations must
    ///         be fulfilled. The default timeout allows the test to run until
    ///         it reaches its execution time allowance.
    ///     - enforceOrderOfFulfillment: If `true`, the expectations specified
    ///         by the `expectations` parameter must be satisfied in the order
    ///         they appear in the array.
    ///
    /// Expectations can only appear in the list once. This function may return
    /// early based on fulfillment of the provided expectations.
    ///
    /// - Note: If you do not specify a timeout when calling this function, it
    ///     is recommended that you enable test timeouts to prevent a runaway
    ///     expectation from hanging the test.
    @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
    @nonobjc public func fulfillment(of expectations: [XCTestExpectation], timeout seconds: TimeInterval = .infinity, enforceOrder enforceOrderOfFulfillment: Bool = false) async
}

extension XCTWaiter {

    /// Waits on a group of expectations for up to the specified timeout,
    /// optionally enforcing their order of fulfillment.
    ///
    /// - Parameters:
    ///     - expectations: An array of expectations that must be fulfilled.
    ///     - seconds: The number of seconds within which all expectations must
    ///         be fulfilled. The default timeout allows the test to run until
    ///         it reaches its execution time allowance.
    ///     - enforceOrderOfFulfillment: If `true`, the expectations specified
    ///         by the `expectations` parameter must be satisfied in the order
    ///         they appear in the array.
    ///
    /// - Returns: A value describing the outcome of waiting for `expectations`.
    ///
    /// Expectations can only appear in the list once. This function may return
    /// early based on fulfillment of the provided expectations.
    ///
    /// - Note: If you do not specify a timeout when calling this function, it
    ///     is recommended that you enable test timeouts to prevent a runaway
    ///     expectation from hanging the test.
    @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
    @discardableResult
    @nonobjc public func fulfillment(of expectations: [XCTestExpectation], timeout seconds: TimeInterval = .infinity, enforceOrder enforceOrderOfFulfillment: Bool = false) async -> XCTWaiter.Result

    /// Creates a waiter that waits on a group of expectations for up to the
    /// specified timeout, optionally enforcing their order of fulfillment.
    ///
    /// - Parameters:
    ///     - expectations: An array of expectations that must be fulfilled.
    ///     - seconds: The number of seconds within which all expectations must
    ///         be fulfilled. The default timeout allows the test to run until
    ///         it reaches its execution time allowance.
    ///     - enforceOrderOfFulfillment: If `true`, the expectations specified
    ///         by the `expectations` parameter must be satisfied in the order
    ///         they appear in the array.
    ///
    /// - Returns: A value describing the outcome of waiting for `expectations`.
    ///
    /// Expectations can only appear in the list once. This function may return
    /// early based on fulfillment of the provided expectations.
    ///
    /// The waiter is discarded when the wait completes.
    ///
    /// - Note: If you do not specify a timeout when calling this function, it
    ///     is recommended that you enable test timeouts to prevent a runaway
    ///     expectation from hanging the test.
    @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
    @nonobjc public class func fulfillment(of expectations: [XCTestExpectation], timeout seconds: TimeInterval = .infinity, enforceOrder enforceOrderOfFulfillment: Bool = false) async -> XCTWaiter.Result
}
tmaly1980 commented 1 year ago

I've also checked Xcode 14.2/iOS 16.2 on an M1 and Intel MacBook Pro, and it keeps saying "Cannot find 'fulfillment' in scope" . Any clue why?

tonyarnold commented 1 year ago

@tmaly1980 this seems like a question better asked on StackOverflow than on a specific issue on GitHub, as it's like to be your project configuration at fault (check your minimum deployment targets).

tonyarnold commented 1 year ago

Thanks, @grynspan :heart:

grynspan commented 1 year ago

Always happy to help!

award999 commented 1 year ago

@grynspan saw this was merged into main, what are the chances of it making into 5.9? Kind of needed to do asynchronous testing on Linux

grynspan commented 1 year ago

@briancroom is looking into it.

joshuawright11 commented 1 year ago

If anyone stumbles upon this before Swift 5.10/whatever release this becomes available, here is the extension I use for support on Linux (based on @grynspan's PR).

When this is officially released, you should just be able to remove the extension.

#if os(Linux)
import XCTest

extension XCTestCase {
    /// Wait on an array of expectations for up to the specified timeout, and optionally specify whether they
    /// must be fulfilled in the given order. May return early based on fulfillment of the waited on expectations.
    ///
    /// - Parameter expectations: The expectations to wait on.
    /// - Parameter timeout: The maximum total time duration to wait on all expectations.
    /// - Parameter enforceOrder: Specifies whether the expectations must be fulfilled in the order
    ///   they are specified in the `expectations` Array. Default is false.
    /// - Parameter file: The file name to use in the error message if
    ///   expectations are not fulfilled before the given timeout. Default is the file
    ///   containing the call to this method. It is rare to provide this
    ///   parameter when calling this method.
    /// - Parameter line: The line number to use in the error message if the
    ///   expectations are not fulfilled before the given timeout. Default is the line
    ///   number of the call to this method in the calling file. It is rare to
    ///   provide this parameter when calling this method.
    ///
    /// - SeeAlso: XCTWaiter
    func fulfillment(of expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false) async {
        return await withCheckedContinuation { continuation in
            // This function operates by blocking a background thread instead of one owned by libdispatch or by the
            // Swift runtime (as used by Swift concurrency.) To ensure we use a thread owned by neither subsystem, use
            // Foundation's Thread.detachNewThread(_:).
            Thread.detachNewThread { [self] in
                wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder)
                continuation.resume()
            }
        }
    }
}
#endif
grynspan commented 1 year ago

For anyone watching at home: if you're developing for a Darwin-based platform (macOS, iOS, etc.) using the version of XCTest that ships with Xcode 14.3 or later, this API is already available and you don't need the category.