Skip to content

Commit 361f295

Browse files
author
Kyle Yoon
committed
[XCTestExpectation] Adding expectationForPredicate constructor
- Added class XCPredicateExpectation - Added typealias for XCPredicateExpectationHandler - Added tests for the expectation and handler
1 parent e3b502b commit 361f295

File tree

6 files changed

+284
-0
lines changed

6 files changed

+284
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
4+
// Licensed under Apache License v2.0 with Runtime Library Exception
5+
//
6+
// See http://swift.org/LICENSE.txt for license information
7+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8+
//
9+
//
10+
// XCPredicateExpectation.swift
11+
// Expectations with a specified predicate and object to evaluate.
12+
//
13+
14+
#if os(Linux) || os(FreeBSD)
15+
import Foundation
16+
#else
17+
import SwiftFoundation
18+
#endif
19+
20+
public class XCPredicateExpectation: XCTestExpectation {
21+
internal let predicate: NSPredicate
22+
internal let object: AnyObject
23+
internal var timer: NSTimer?
24+
internal let handler: XCPredicateExpectationHandler?
25+
private let evaluationInterval = 0.01
26+
27+
internal init(predicate: NSPredicate, object: AnyObject, description: String, file: StaticString, line: UInt, testCase: XCTestCase, handler: XCPredicateExpectationHandler? = nil) {
28+
self.predicate = predicate
29+
self.object = object
30+
self.handler = handler
31+
self.timer = nil
32+
super.init(description: description, file: file, line: line, testCase: testCase)
33+
}
34+
35+
internal func considerFulfilling() {
36+
self.timer = NSTimer.scheduledTimer(self.evaluationInterval, repeats: true, fire: { [weak self] timer in
37+
guard let strongSelf = self else {
38+
timer.invalidate()
39+
return
40+
}
41+
42+
if strongSelf.predicate.evaluateWithObject(strongSelf.object) {
43+
if let handler = strongSelf.handler {
44+
if handler() {
45+
strongSelf.fulfill()
46+
timer.invalidate()
47+
}
48+
// The timer does not invalidate even if the handler returns
49+
// false. The object is still re-evaluated until timeout.
50+
} else {
51+
strongSelf.fulfill()
52+
timer.invalidate()
53+
}
54+
}
55+
})
56+
self.timer?.fire()
57+
}
58+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
4+
// Licensed under Apache License v2.0 with Runtime Library Exception
5+
//
6+
// See http://swift.org/LICENSE.txt for license information
7+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8+
//
9+
//
10+
// XCPredicateExpectationHandler.swift
11+
// A closure invoked by XCTestCase when a predicate for the expectation is
12+
// evaluated with a given object.
13+
//
14+
15+
#if os(Linux) || os(FreeBSD)
16+
import Foundation
17+
#else
18+
import SwiftFoundation
19+
#endif
20+
21+
/// A block to be invoked when evaluating the predicate against the object
22+
/// returns true. If the block is not provided the first successful evaluation
23+
/// will fulfill the expectation. If provided, the handler can override that
24+
/// behavior which leaves the caller responsible for fulfilling the expectation.
25+
public typealias XCPredicateExpectationHandler = (Void) -> (Bool)

Sources/XCTest/XCTestCase.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,5 +367,41 @@ extension XCTestCase {
367367

368368
return expectation
369369
}
370+
371+
/// Creates and returns an expectation that is fulfilled if the predicate
372+
/// returns true when evaluated with the given object. The expectation
373+
/// periodically evaluates the predicate and also may use notifications or
374+
/// other events to optimistically re-evaluate.
375+
///
376+
/// - Parameter predicate: The predicate that will be used to evaluate the
377+
/// object.
378+
/// - Parameter object: The object that is evaluated against the conditions
379+
/// specified by the predicate.
380+
/// - Parameter file: The file name to use in the error message if
381+
/// this expectation is not waited for. Default is the file
382+
/// containing the call to this method. It is rare to provide this
383+
/// parameter when calling this method.
384+
/// - Parameter line: The line number to use in the error message if the
385+
/// this expectation is not waited for. Default is the line
386+
/// number of the call to this method in the calling file. It is rare to
387+
/// provide this parameter when calling this method.
388+
/// - Parameter handler: A block to be invoked when evaluating the predicate
389+
/// against the object returns true. If the block is not provided the
390+
/// first successful evaluation will fulfill the expectation. If provided,
391+
/// the handler can override that behavior which leaves the caller
392+
/// responsible for fulfilling the expectation.
393+
public func expectation(for predicate: NSPredicate, evaluatedWith object: AnyObject, file: StaticString = #file, line: UInt = #line, handler: XCPredicateExpectationHandler? = nil) -> XCPredicateExpectation {
394+
let expectation = XCPredicateExpectation(
395+
predicate: predicate,
396+
object: object,
397+
description: "Expect `\(predicate)` for object \(object)",
398+
file: file,
399+
line: line,
400+
testCase: self,
401+
handler: handler)
402+
_allExpectations.append(expectation)
403+
expectation.considerFulfilling()
404+
return expectation
405+
}
370406

371407
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// RUN: %{swiftc} %s -o %{built_tests_dir}/Asynchronous-Predicates
2+
// RUN: %{built_tests_dir}/Asynchronous-Predicates > %t || true
3+
// RUN: %{xctest_checker} %t %s
4+
5+
#if os(Linux) || os(FreeBSD)
6+
import XCTest
7+
import Foundation
8+
#else
9+
import SwiftXCTest
10+
import SwiftFoundation
11+
#endif
12+
13+
// CHECK: Test Suite 'All tests' started at \d+:\d+:\d+\.\d+
14+
// CHECK: Test Suite '.*\.xctest' started at \d+:\d+:\d+\.\d+
15+
16+
// CHECK: Test Suite 'PredicateExpectationsTestCase' started at \d+:\d+:\d+\.\d+
17+
class PredicateExpectationsTestCase: XCTestCase {
18+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyTruePredicateAndObject_passes' started at \d+:\d+:\d+\.\d+
19+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyTruePredicateAndObject_passes' passed \(\d+\.\d+ seconds\).
20+
func test_immediatelyTruePredicateAndObject_passes() {
21+
let predicate = NSPredicate(value: true)
22+
let object = NSObject()
23+
expectation(for: predicate, evaluatedWith: object)
24+
waitForExpectations(withTimeout: 0.1)
25+
}
26+
27+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyFalsePredicateAndObject_fails' started at \d+:\d+:\d+\.\d+
28+
// CHECK: .*/Tests/Functional/Asynchronous/Predicates/Expectations/main.swift:34: error: PredicateExpectationsTestCase.test_immediatelyFalsePredicateAndObject_fails : Asynchronous wait failed - Exceeded timeout of 0.1 seconds, with unfulfilled expectations: Expect `<NSPredicate: 0x[0-9A-Fa-f]{1,16}>` for object <NSObject: 0x[0-9A-Fa-f]{1,16}>
29+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyFalsePredicateAndObject_fails' failed \(\d+\.\d+ seconds\).
30+
func test_immediatelyFalsePredicateAndObject_fails() {
31+
let predicate = NSPredicate(value: false)
32+
let object = NSObject()
33+
expectation(for: predicate, evaluatedWith: object)
34+
waitForExpectations(withTimeout: 0.1)
35+
}
36+
37+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_delayedTruePredicateAndObject_passes' started at \d+:\d+:\d+\.\d+
38+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_delayedTruePredicateAndObject_passes' passed \(\d+\.\d+ seconds\).
39+
func test_delayedTruePredicateAndObject_passes() {
40+
let halfSecLaterDate = NSDate(timeIntervalSinceNow: 0.01)
41+
let predicate = NSPredicate(block: {
42+
evaluatedObject, bindings in
43+
if let evaluatedDate = evaluatedObject as? NSDate {
44+
return evaluatedDate.compare(NSDate()) == NSComparisonResult.OrderedAscending
45+
}
46+
return false
47+
})
48+
expectation(for: predicate, evaluatedWith: halfSecLaterDate)
49+
waitForExpectations(withTimeout: 0.1)
50+
}
51+
52+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyTrueDelayedFalsePredicateAndObject_passes' started at \d+:\d+:\d+\.\d+
53+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyTrueDelayedFalsePredicateAndObject_passes' passed \(\d+\.\d+ seconds\).
54+
func test_immediatelyTrueDelayedFalsePredicateAndObject_passes() {
55+
let halfSecLaterDate = NSDate(timeIntervalSinceNow: 0.01)
56+
let predicate = NSPredicate(block: { evaluatedObject, bindings in
57+
if let evaluatedDate = evaluatedObject as? NSDate {
58+
return evaluatedDate.compare(NSDate()) == NSComparisonResult.OrderedDescending
59+
}
60+
return false
61+
})
62+
expectation(for: predicate, evaluatedWith: halfSecLaterDate)
63+
waitForExpectations(withTimeout: 0.1)
64+
}
65+
66+
static var allTests: [(String, PredicateExpectationsTestCase -> () throws -> Void)] {
67+
return [
68+
("test_immediatelyTruePredicateAndObject_passes", test_immediatelyTruePredicateAndObject_passes),
69+
("test_immediatelyFalsePredicateAndObject_fails", test_immediatelyFalsePredicateAndObject_fails),
70+
("test_delayedTruePredicateAndObject_passes", test_delayedTruePredicateAndObject_passes),
71+
("test_immediatelyTrueDelayedFalsePredicateAndObject_passes", test_immediatelyTrueDelayedFalsePredicateAndObject_passes),
72+
]
73+
}
74+
}
75+
76+
// CHECK: Test Suite 'PredicateExpectationsTestCase' failed at \d+:\d+:\d+\.\d+
77+
// CHECK: \t Executed 4 tests, with 1 failure \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
78+
XCTMain([testCase(PredicateExpectationsTestCase.allTests)])
79+
80+
// CHECK: Test Suite '.*\.xctest' failed at \d+:\d+:\d+\.\d+
81+
// CHECK: \t Executed 4 tests, with 1 failure \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
82+
// CHECK: Test Suite 'All tests' failed at \d+:\d+:\d+\.\d+
83+
// CHECK: \t Executed 4 tests, with 1 failure \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// RUN: %{swiftc} %s -o %{built_tests_dir}/Asynchronous-Predicates-Handler
2+
// RUN: %{built_tests_dir}/Asynchronous-Predicates-Handler > %t || true
3+
// RUN: %{xctest_checker} %t %s
4+
5+
#if os(Linux) || os(FreeBSD)
6+
import XCTest
7+
import Foundation
8+
#else
9+
import SwiftXCTest
10+
import SwiftFoundation
11+
#endif
12+
13+
// CHECK: Test Suite 'All tests' started at \d+:\d+:\d+\.\d+
14+
// CHECK: Test Suite '.*\.xctest' started at \d+:\d+:\d+\.\d+
15+
16+
// CHECK: Test Suite 'PredicateHandlerTestCase' started at \d+:\d+:\d+\.\d+
17+
class PredicateHandlerTestCase: XCTestCase {
18+
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrue_handlerReturnsTrue_passes' started at \d+:\d+:\d+\.\d+
19+
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrue_handlerReturnsTrue_passes' passed \(\d+\.\d+ seconds\).
20+
func test_predicateIsTrue_handlerReturnsTrue_passes() {
21+
let predicate = NSPredicate(value: true)
22+
let object = NSObject()
23+
self.expectation(for: predicate, evaluatedWith: object, handler: { _ in
24+
return true
25+
})
26+
waitForExpectations(withTimeout: 0.1)
27+
}
28+
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrue_handlerReturnsFalse_fails' started at \d+:\d+:\d+\.\d+
29+
// CHECK: .*/Tests/Functional/Asynchronous/Predicates/Handler/main.swift:37: error: PredicateHandlerTestCase.test_predicateIsTrue_handlerReturnsFalse_fails : Asynchronous wait failed - Exceeded timeout of 0.1 seconds, with unfulfilled expectations: Expect `<NSPredicate: 0x[0-9a-fA-F]{1,16}>` for object <NSObject: 0x[0-9a-fA-F]{1,16}>
30+
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrue_handlerReturnsFalse_fails' failed \(\d+\.\d+ seconds\).
31+
func test_predicateIsTrue_handlerReturnsFalse_fails() {
32+
let predicate = NSPredicate(value: true)
33+
let object = NSObject()
34+
self.expectation(for: predicate, evaluatedWith: object, handler: { _ in
35+
return false
36+
})
37+
waitForExpectations(withTimeout: 0.1)
38+
}
39+
40+
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails' started at \d+:\d+:\d+\.\d+
41+
// CHECK: .*/Tests/Functional/Asynchronous/Predicates/Handler/main.swift:55: error: PredicateHandlerTestCase.test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails : Asynchronous wait failed - Exceeded timeout of 0.1 seconds, with unfulfilled expectations: Expect `<NSPredicate: 0x[0-9a-fA-F]{1,16}>` for object \d{4}-\d{2}-\d{2} \d+:\d+:\d+ \+\d+
42+
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails' failed \(\d+\.\d+ seconds\).
43+
func test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails() {
44+
let halfSecLaterDate = NSDate(timeIntervalSinceNow: 0.2)
45+
let predicate = NSPredicate(block: { evaluatedObject, bindings in
46+
if let evaluatedDate = evaluatedObject as? NSDate {
47+
return evaluatedDate.compare(NSDate()) == NSComparisonResult.OrderedAscending
48+
}
49+
return false
50+
})
51+
expectation(for: predicate, evaluatedWith: halfSecLaterDate, handler: { _ in
52+
XCTFail("Should not call the predicate expectation handler")
53+
return true
54+
})
55+
waitForExpectations(withTimeout: 0.1, handler: nil)
56+
}
57+
58+
static var allTests: [(String, PredicateHandlerTestCase -> () throws -> Void)] {
59+
return [
60+
("test_predicateIsTrue_handlerReturnsTrue_passes", test_predicateIsTrue_handlerReturnsTrue_passes),
61+
("test_predicateIsTrue_handlerReturnsFalse_fails", test_predicateIsTrue_handlerReturnsFalse_fails),
62+
("test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails", test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails),
63+
]
64+
}
65+
}
66+
67+
// CHECK: Test Suite 'PredicateHandlerTestCase' failed at \d+:\d+:\d+\.\d+
68+
// CHECK: \t Executed 3 tests, with 2 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
69+
XCTMain([testCase(PredicateHandlerTestCase.allTests)])
70+
71+
// CHECK: Test Suite '.*\.xctest' failed at \d+:\d+:\d+\.\d+
72+
// CHECK: \t Executed 3 tests, with 2 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
73+
// CHECK: Test Suite 'All tests' failed at \d+:\d+:\d+\.\d+
74+
// CHECK: \t Executed 3 tests, with 2 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds

XCTest.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
/* Begin PBXBuildFile section */
1010
510957A91CA878410091D1A1 /* XCNotificationExpectationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510957A81CA878410091D1A1 /* XCNotificationExpectationHandler.swift */; };
1111
AE33888B1CD3D72500C39B1C /* XCTestCaseSuite.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE33888A1CD3D72500C39B1C /* XCTestCaseSuite.swift */; };
12+
51DA1EA41CC19C9000F75EB7 /* XCPredicateExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DA1EA31CC19C9000F75EB7 /* XCPredicateExpectation.swift */; };
13+
5A3952F11CC173380053159C /* XCPredicateExpectationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A3952F01CC173380053159C /* XCPredicateExpectationHandler.swift */; };
1214
AE7DD6091C8E81A0006FC722 /* ArgumentParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7DD6071C8E81A0006FC722 /* ArgumentParser.swift */; };
1315
AE7DD60A1C8E81A0006FC722 /* TestFiltering.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7DD6081C8E81A0006FC722 /* TestFiltering.swift */; };
1416
AE7DD60C1C8F0513006FC722 /* XCTestObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7DD60B1C8F0513006FC722 /* XCTestObservation.swift */; };
@@ -41,6 +43,8 @@
4143

4244
/* Begin PBXFileReference section */
4345
510957A81CA878410091D1A1 /* XCNotificationExpectationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCNotificationExpectationHandler.swift; sourceTree = "<group>"; };
46+
51DA1EA31CC19C9000F75EB7 /* XCPredicateExpectation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCPredicateExpectation.swift; sourceTree = "<group>"; };
47+
5A3952F01CC173380053159C /* XCPredicateExpectationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCPredicateExpectationHandler.swift; sourceTree = "<group>"; };
4448
5B5D86DB1BBC74AD00234F36 /* SwiftXCTest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftXCTest.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4549
AE33888A1CD3D72500C39B1C /* XCTestCaseSuite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestCaseSuite.swift; sourceTree = "<group>"; };
4650
AE7DD6061C8DC6C0006FC722 /* Functional */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Functional; sourceTree = "<group>"; };
@@ -136,12 +140,14 @@
136140
DA7714FA1CA87DFB001EA745 /* XCTestCaseRun.swift */,
137141
AED59FF51CB5394800F49260 /* XCTestErrors.swift */,
138142
DADB979B1C51BDA2005E68B6 /* XCTestExpectation.swift */,
143+
51DA1EA31CC19C9000F75EB7 /* XCPredicateExpectation.swift */,
139144
C265F66C1C3AEB6A00520CF9 /* XCTestMain.swift */,
140145
AE7DD60B1C8F0513006FC722 /* XCTestObservation.swift */,
141146
AE9596E01C9692B8001A9EF0 /* XCTestObservationCenter.swift */,
142147
DACC94411C8B87B900EC85F5 /* XCWaitCompletionHandler.swift */,
143148
510957A81CA878410091D1A1 /* XCNotificationExpectationHandler.swift */,
144149
AE33888A1CD3D72500C39B1C /* XCTestCaseSuite.swift */,
150+
5A3952F01CC173380053159C /* XCPredicateExpectationHandler.swift */,
145151
);
146152
path = XCTest;
147153
sourceTree = "<group>";
@@ -278,8 +284,10 @@
278284
DA7FB38F1CA4EE3800F024F9 /* XCAbstractTest.swift in Sources */,
279285
C265F6701C3AEB6A00520CF9 /* XCTestCase.swift in Sources */,
280286
DADB979C1C51BDA2005E68B6 /* XCTestExpectation.swift in Sources */,
287+
51DA1EA41CC19C9000F75EB7 /* XCPredicateExpectation.swift in Sources */,
281288
DA7714FD1CA8D057001EA745 /* PrintObserver.swift in Sources */,
282289
AE7DD60A1C8E81A0006FC722 /* TestFiltering.swift in Sources */,
290+
5A3952F11CC173380053159C /* XCPredicateExpectationHandler.swift in Sources */,
283291
AE7DD6091C8E81A0006FC722 /* ArgumentParser.swift in Sources */,
284292
DA7732481CA87278007E31FD /* XCTestRun.swift in Sources */,
285293
AE9596E11C9692B8001A9EF0 /* XCTestObservationCenter.swift in Sources */,

0 commit comments

Comments
 (0)