118 lines
3.3 KiB
Swift
118 lines
3.3 KiB
Swift
//
|
|
// Copyright Amazon.com Inc. or its affiliates.
|
|
// All Rights Reserved.
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
import Foundation
|
|
import XCTest
|
|
|
|
extension Task where Success == Never, Failure == Never {
|
|
static func sleep(seconds: Double) async throws {
|
|
let nanoseconds = UInt64(seconds * Double(NSEC_PER_SEC))
|
|
try await Task.sleep(nanoseconds: nanoseconds)
|
|
}
|
|
}
|
|
|
|
public actor AsyncExpectation {
|
|
enum State {
|
|
case pending
|
|
case fulfilled
|
|
case timedOut
|
|
}
|
|
public typealias AsyncExpectationContinuation = CheckedContinuation<Void, Error>
|
|
public let expectationDescription: String
|
|
public var isInverted: Bool
|
|
public var expectedFulfillmentCount: Int
|
|
|
|
private var fulfillmentCount: Int = 0
|
|
private var continuation: AsyncExpectationContinuation?
|
|
private var state: State = .pending
|
|
|
|
public var isFulfilled: Bool {
|
|
state == .fulfilled
|
|
}
|
|
|
|
public func setShouldTrigger(_ shouldTrigger: Bool) {
|
|
self.isInverted = !shouldTrigger
|
|
}
|
|
|
|
public func setExpectedFulfillmentCount(_ count: Int) {
|
|
self.expectedFulfillmentCount = count
|
|
}
|
|
|
|
public init(description: String,
|
|
isInverted: Bool = false,
|
|
expectedFulfillmentCount: Int = 1) {
|
|
expectationDescription = description
|
|
self.isInverted = isInverted
|
|
self.expectedFulfillmentCount = expectedFulfillmentCount
|
|
}
|
|
|
|
/// Marks the expectation as having been met.
|
|
///
|
|
/// It is an error to call this method on an expectation that has already been fulfilled,
|
|
/// or when the test case that vended the expectation has already completed.
|
|
public func fulfill(file: StaticString = #filePath, line: UInt = #line) {
|
|
guard state != .fulfilled else { return }
|
|
|
|
if isInverted {
|
|
if state != .timedOut {
|
|
XCTFail("Inverted expectation fulfilled: \(expectationDescription)", file: file, line: line)
|
|
state = .fulfilled
|
|
finish()
|
|
}
|
|
return
|
|
}
|
|
|
|
fulfillmentCount += 1
|
|
if fulfillmentCount == expectedFulfillmentCount {
|
|
state = .fulfilled
|
|
finish()
|
|
}
|
|
}
|
|
|
|
internal nonisolated func wait() async throws {
|
|
try await withTaskCancellationHandler {
|
|
try await handleWait()
|
|
} onCancel: {
|
|
Task {
|
|
await cancel()
|
|
}
|
|
}
|
|
}
|
|
|
|
internal func timeOut(file: StaticString = #filePath,
|
|
line: UInt = #line) async {
|
|
if isInverted {
|
|
state = .timedOut
|
|
} else if state != .fulfilled {
|
|
state = .timedOut
|
|
XCTFail("Expectation timed out: \(expectationDescription)", file: file, line: line)
|
|
}
|
|
finish()
|
|
}
|
|
|
|
private func handleWait() async throws {
|
|
if state == .fulfilled {
|
|
return
|
|
} else {
|
|
try await withCheckedThrowingContinuation { (continuation: AsyncExpectationContinuation) in
|
|
self.continuation = continuation
|
|
}
|
|
}
|
|
}
|
|
|
|
private func cancel() {
|
|
continuation?.resume(throwing: CancellationError())
|
|
continuation = nil
|
|
}
|
|
|
|
private func finish() {
|
|
continuation?.resume(returning: ())
|
|
continuation = nil
|
|
}
|
|
|
|
}
|