File: XCTNSPredicateExpectation.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (136 lines) | stat: -rw-r--r-- 4,996 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// 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, @unchecked Sendable {

    /// 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 = @Sendable () -> 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)
        nonisolated(unsafe) let t = timer
        queue.async {
            self.timer = t
        }
    }

    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