blob: 08d0cf26b2913344a7bda9b1cf62c860dd23da2e [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//
// XCTNSPredicateExpectation.swift
//
/// Expectation subclass for waiting on a condition defined by an NSPredicate and an optional object.
open class XCTNSPredicateExpectation: XCTestExpectation {
/// A closure to be invoked whenever evaluating the predicate against the object returns true.
///
/// - Returns: `true` if the expectation should be fulfilled, `false` if it should not.
///
/// - SeeAlso: `XCTNSPredicateExpectation.handler`
public typealias Handler = () -> Bool
private let queue = DispatchQueue(label: "org.swift.XCTest.XCTNSPredicateExpectation")
/// The predicate used by the expectation.
open private(set) var predicate: NSPredicate
/// The object against which the predicate is evaluated, if any. Default is nil.
open private(set) var object: Any?
private var _handler: Handler?
/// Handler called when evaluating the predicate against the object returns true. If the handler is not
/// provided, the first successful evaluation will fulfill the expectation. If the handler provided, the
/// handler will be queried each time the notification is received to determine whether the expectation
/// should be fulfilled or not.
open var handler: Handler? {
get {
return queue.sync { _handler }
}
set {
dispatchPrecondition(condition: .notOnQueue(queue))
queue.async { self._handler = newValue }
}
}
private let runLoop = RunLoop.current
private var timer: Timer?
private let evaluationInterval = 0.01
/// Initializes an expectation that waits for a predicate to evaluate as true with an optionally specified object.
///
/// - Parameter predicate: The predicate to evaluate.
/// - Parameter object: An optional object to evaluate `predicate` with. Default is nil.
/// - Parameter file: The file name to use in the error message if
/// expectations are not met before the wait 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 met before the wait 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.
public init(predicate: NSPredicate, object: Any? = nil, file: StaticString = #file, line: Int = #line) {
self.predicate = predicate
self.object = object
let description = "Expect predicate `\(predicate)`" + (object.map { " for object \($0)" } ?? "")
super.init(description: description, file: file, line: line)
}
deinit {
assert(timer == nil, "timer should be nil, indicates failure to call cleanUp() internally")
}
override func didBeginWaiting() {
runLoop.perform {
if self.shouldFulfill() {
self.fulfill()
} else {
self.startPolling()
}
}
}
private func startPolling() {
let timer = Timer(timeInterval: evaluationInterval, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
if self.shouldFulfill() {
self.fulfill()
timer.invalidate()
}
}
runLoop.add(timer, forMode: .default)
queue.async {
self.timer = timer
}
}
private func shouldFulfill() -> Bool {
if predicate.evaluate(with: object) {
if let handler = handler {
if handler() {
return true
}
// We do not fulfill or invalidate the timer if the handler returns
// false. The object is still re-evaluated until timeout.
} else {
return true
}
}
return false
}
override func cleanUp() {
queue.sync {
if let timer = timer {
timer.invalidate()
self.timer = nil
}
}
}
}
/// A closure to be invoked whenever evaluating the predicate against the object returns true.
///
/// - SeeAlso: `XCTNSPredicateExpectation.handler`
@available(*, deprecated, renamed: "XCTNSPredicateExpectation.Handler")
public typealias XCPredicateExpectationHandler = XCTNSPredicateExpectation.Handler