Skip to content

[XCTestExpectation] Adding expectationForPredicate constructor #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions Sources/XCTest/XCPredicateExpectation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 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
//
//
// XCPredicateExpectation.swift
// Expectations with a specified predicate and object to evaluate.
//

#if os(Linux) || os(FreeBSD)
import Foundation
#else
import SwiftFoundation
#endif

internal class XCPredicateExpectation: XCTestExpectation {
internal let predicate: NSPredicate
internal let object: AnyObject
internal var timer: NSTimer?
internal let handler: XCPredicateExpectationHandler?
private let evaluationInterval = 0.01

internal init(predicate: NSPredicate, object: AnyObject, description: String, file: StaticString, line: UInt, testCase: XCTestCase, handler: XCPredicateExpectationHandler? = nil) {
self.predicate = predicate
self.object = object
self.handler = handler
self.timer = nil
super.init(description: description, file: file, line: line, testCase: testCase)
}

internal func considerFulfilling() {
self.timer = NSTimer.scheduledTimer(self.evaluationInterval, repeats: true, fire: { [weak self] timer in
guard let strongSelf = self else {
timer.invalidate()
return
}

if strongSelf.predicate.evaluateWithObject(strongSelf.object) {
if let handler = strongSelf.handler {
if handler() {
strongSelf.fulfill()
timer.invalidate()
}
// The timer does not invalidate even if the handler returns
// false. The object is still re-evaluated until timeout.
} else {
strongSelf.fulfill()
timer.invalidate()
}
}
})
self.timer?.fire()
}
}
25 changes: 25 additions & 0 deletions Sources/XCTest/XCPredicateExpectationHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 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
//
//
// XCPredicateExpectationHandler.swift
// A closure invoked by XCTestCase when a predicate for the expectation is
// evaluated with a given object.
//

#if os(Linux) || os(FreeBSD)
import Foundation
#else
import SwiftFoundation
#endif

/// A block to be invoked when evaluating the predicate against the object
/// returns true. If the block is not provided the first successful evaluation
/// will fulfill the expectation. If provided, the handler can override that
/// behavior which leaves the caller responsible for fulfilling the expectation.
public typealias XCPredicateExpectationHandler = (Void) -> (Bool)
36 changes: 36 additions & 0 deletions Sources/XCTest/XCTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -367,5 +367,41 @@ extension XCTestCase {

return expectation
}

/// Creates and returns an expectation that is fulfilled if the predicate
/// returns true when evaluated with the given object. The expectation
/// periodically evaluates the predicate and also may use notifications or
/// other events to optimistically re-evaluate.
///
/// - Parameter predicate: The predicate that will be used to evaluate the
/// object.
/// - Parameter object: The object that is evaluated against the conditions
/// specified by the predicate.
/// - Parameter file: The file name to use in the error message if
/// this expectation is not waited for. 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
/// this expectation is not waited for. 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.
/// - Parameter handler: A block to be invoked when evaluating the predicate
/// against the object returns true. If the block is not provided the
/// first successful evaluation will fulfill the expectation. If provided,
/// the handler can override that behavior which leaves the caller
/// responsible for fulfilling the expectation.
public func expectation(for predicate: NSPredicate, evaluatedWith object: AnyObject, file: StaticString = #file, line: UInt = #line, handler: XCPredicateExpectationHandler? = nil) -> XCTestExpectation {
let expectation = XCPredicateExpectation(
predicate: predicate,
object: object,
description: "Expect `\(predicate)` for object \(object)",
file: file,
line: line,
testCase: self,
handler: handler)
_allExpectations.append(expectation)
expectation.considerFulfilling()
return expectation
}

}
83 changes: 83 additions & 0 deletions Tests/Functional/Asynchronous/Predicates/Expectations/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// RUN: %{swiftc} %s -o %{built_tests_dir}/Asynchronous-Predicates
// RUN: %{built_tests_dir}/Asynchronous-Predicates > %t || true
// RUN: %{xctest_checker} %t %s

#if os(Linux) || os(FreeBSD)
import XCTest
import Foundation
#else
import SwiftXCTest
import SwiftFoundation
#endif

// CHECK: Test Suite 'All tests' started at \d+:\d+:\d+\.\d+
// CHECK: Test Suite '.*\.xctest' started at \d+:\d+:\d+\.\d+

// CHECK: Test Suite 'PredicateExpectationsTestCase' started at \d+:\d+:\d+\.\d+
class PredicateExpectationsTestCase: XCTestCase {
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyTruePredicateAndObject_passes' started at \d+:\d+:\d+\.\d+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyTruePredicateAndObject_passes' passed \(\d+\.\d+ seconds\).
func test_immediatelyTruePredicateAndObject_passes() {
let predicate = NSPredicate(value: true)
let object = NSObject()
expectation(for: predicate, evaluatedWith: object)
waitForExpectations(withTimeout: 0.1)
}

// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyFalsePredicateAndObject_fails' started at \d+:\d+:\d+\.\d+
// 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}>
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyFalsePredicateAndObject_fails' failed \(\d+\.\d+ seconds\).
func test_immediatelyFalsePredicateAndObject_fails() {
let predicate = NSPredicate(value: false)
let object = NSObject()
expectation(for: predicate, evaluatedWith: object)
waitForExpectations(withTimeout: 0.1)
}

// CHECK: Test Case 'PredicateExpectationsTestCase.test_delayedTruePredicateAndObject_passes' started at \d+:\d+:\d+\.\d+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_delayedTruePredicateAndObject_passes' passed \(\d+\.\d+ seconds\).
func test_delayedTruePredicateAndObject_passes() {
let halfSecLaterDate = NSDate(timeIntervalSinceNow: 0.01)
let predicate = NSPredicate(block: {
evaluatedObject, bindings in
if let evaluatedDate = evaluatedObject as? NSDate {
return evaluatedDate.compare(NSDate()) == NSComparisonResult.OrderedAscending
}
return false
})
expectation(for: predicate, evaluatedWith: halfSecLaterDate)
waitForExpectations(withTimeout: 0.1)
}

// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyTrueDelayedFalsePredicateAndObject_passes' started at \d+:\d+:\d+\.\d+
// CHECK: Test Case 'PredicateExpectationsTestCase.test_immediatelyTrueDelayedFalsePredicateAndObject_passes' passed \(\d+\.\d+ seconds\).
func test_immediatelyTrueDelayedFalsePredicateAndObject_passes() {
let halfSecLaterDate = NSDate(timeIntervalSinceNow: 0.01)
let predicate = NSPredicate(block: { evaluatedObject, bindings in
if let evaluatedDate = evaluatedObject as? NSDate {
return evaluatedDate.compare(NSDate()) == NSComparisonResult.OrderedDescending
}
return false
})
expectation(for: predicate, evaluatedWith: halfSecLaterDate)
waitForExpectations(withTimeout: 0.1)
}

static var allTests: [(String, PredicateExpectationsTestCase -> () throws -> Void)] {
return [
("test_immediatelyTruePredicateAndObject_passes", test_immediatelyTruePredicateAndObject_passes),
("test_immediatelyFalsePredicateAndObject_fails", test_immediatelyFalsePredicateAndObject_fails),
("test_delayedTruePredicateAndObject_passes", test_delayedTruePredicateAndObject_passes),
("test_immediatelyTrueDelayedFalsePredicateAndObject_passes", test_immediatelyTrueDelayedFalsePredicateAndObject_passes),
]
}
}

// CHECK: Test Suite 'PredicateExpectationsTestCase' failed at \d+:\d+:\d+\.\d+
// CHECK: \t Executed 4 tests, with 1 failure \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
XCTMain([testCase(PredicateExpectationsTestCase.allTests)])

// CHECK: Test Suite '.*\.xctest' failed at \d+:\d+:\d+\.\d+
// CHECK: \t Executed 4 tests, with 1 failure \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: Test Suite 'All tests' failed at \d+:\d+:\d+\.\d+
// CHECK: \t Executed 4 tests, with 1 failure \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
74 changes: 74 additions & 0 deletions Tests/Functional/Asynchronous/Predicates/Handler/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// RUN: %{swiftc} %s -o %{built_tests_dir}/Asynchronous-Predicates-Handler
// RUN: %{built_tests_dir}/Asynchronous-Predicates-Handler > %t || true
// RUN: %{xctest_checker} %t %s

#if os(Linux) || os(FreeBSD)
import XCTest
import Foundation
#else
import SwiftXCTest
import SwiftFoundation
#endif

// CHECK: Test Suite 'All tests' started at \d+:\d+:\d+\.\d+
// CHECK: Test Suite '.*\.xctest' started at \d+:\d+:\d+\.\d+

// CHECK: Test Suite 'PredicateHandlerTestCase' started at \d+:\d+:\d+\.\d+
class PredicateHandlerTestCase: XCTestCase {
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrue_handlerReturnsTrue_passes' started at \d+:\d+:\d+\.\d+
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrue_handlerReturnsTrue_passes' passed \(\d+\.\d+ seconds\).
func test_predicateIsTrue_handlerReturnsTrue_passes() {
let predicate = NSPredicate(value: true)
let object = NSObject()
self.expectation(for: predicate, evaluatedWith: object, handler: { _ in
return true
})
waitForExpectations(withTimeout: 0.1)
}
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrue_handlerReturnsFalse_fails' started at \d+:\d+:\d+\.\d+
// 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}>
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrue_handlerReturnsFalse_fails' failed \(\d+\.\d+ seconds\).
func test_predicateIsTrue_handlerReturnsFalse_fails() {
let predicate = NSPredicate(value: true)
let object = NSObject()
self.expectation(for: predicate, evaluatedWith: object, handler: { _ in
return false
})
waitForExpectations(withTimeout: 0.1)
}

// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails' started at \d+:\d+:\d+\.\d+
// 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+
// CHECK: Test Case 'PredicateHandlerTestCase.test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails' failed \(\d+\.\d+ seconds\).
func test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails() {
let halfSecLaterDate = NSDate(timeIntervalSinceNow: 0.2)
let predicate = NSPredicate(block: { evaluatedObject, bindings in
if let evaluatedDate = evaluatedObject as? NSDate {
return evaluatedDate.compare(NSDate()) == NSComparisonResult.OrderedAscending
}
return false
})
expectation(for: predicate, evaluatedWith: halfSecLaterDate, handler: { _ in
XCTFail("Should not call the predicate expectation handler")
return true
})
waitForExpectations(withTimeout: 0.1, handler: nil)
}

static var allTests: [(String, PredicateHandlerTestCase -> () throws -> Void)] {
return [
("test_predicateIsTrue_handlerReturnsTrue_passes", test_predicateIsTrue_handlerReturnsTrue_passes),
("test_predicateIsTrue_handlerReturnsFalse_fails", test_predicateIsTrue_handlerReturnsFalse_fails),
("test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails", test_predicateIsTrueAfterTimeout_handlerIsNotCalled_fails),
]
}
}

// CHECK: Test Suite 'PredicateHandlerTestCase' failed at \d+:\d+:\d+\.\d+
// CHECK: \t Executed 3 tests, with 2 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
XCTMain([testCase(PredicateHandlerTestCase.allTests)])

// CHECK: Test Suite '.*\.xctest' failed at \d+:\d+:\d+\.\d+
// CHECK: \t Executed 3 tests, with 2 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: Test Suite 'All tests' failed at \d+:\d+:\d+\.\d+
// CHECK: \t Executed 3 tests, with 2 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
8 changes: 8 additions & 0 deletions XCTest.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
510957A91CA878410091D1A1 /* XCNotificationExpectationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510957A81CA878410091D1A1 /* XCNotificationExpectationHandler.swift */; };
AE33888B1CD3D72500C39B1C /* XCTestCaseSuite.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE33888A1CD3D72500C39B1C /* XCTestCaseSuite.swift */; };
51DA1EA41CC19C9000F75EB7 /* XCPredicateExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DA1EA31CC19C9000F75EB7 /* XCPredicateExpectation.swift */; };
5A3952F11CC173380053159C /* XCPredicateExpectationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A3952F01CC173380053159C /* XCPredicateExpectationHandler.swift */; };
AE7DD6091C8E81A0006FC722 /* ArgumentParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7DD6071C8E81A0006FC722 /* ArgumentParser.swift */; };
AE7DD60A1C8E81A0006FC722 /* TestFiltering.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7DD6081C8E81A0006FC722 /* TestFiltering.swift */; };
AE7DD60C1C8F0513006FC722 /* XCTestObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7DD60B1C8F0513006FC722 /* XCTestObservation.swift */; };
Expand Down Expand Up @@ -41,6 +43,8 @@

/* Begin PBXFileReference section */
510957A81CA878410091D1A1 /* XCNotificationExpectationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCNotificationExpectationHandler.swift; sourceTree = "<group>"; };
51DA1EA31CC19C9000F75EB7 /* XCPredicateExpectation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCPredicateExpectation.swift; sourceTree = "<group>"; };
5A3952F01CC173380053159C /* XCPredicateExpectationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCPredicateExpectationHandler.swift; sourceTree = "<group>"; };
5B5D86DB1BBC74AD00234F36 /* SwiftXCTest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftXCTest.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AE33888A1CD3D72500C39B1C /* XCTestCaseSuite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestCaseSuite.swift; sourceTree = "<group>"; };
AE7DD6061C8DC6C0006FC722 /* Functional */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Functional; sourceTree = "<group>"; };
Expand Down Expand Up @@ -136,12 +140,14 @@
DA7714FA1CA87DFB001EA745 /* XCTestCaseRun.swift */,
AED59FF51CB5394800F49260 /* XCTestErrors.swift */,
DADB979B1C51BDA2005E68B6 /* XCTestExpectation.swift */,
51DA1EA31CC19C9000F75EB7 /* XCPredicateExpectation.swift */,
C265F66C1C3AEB6A00520CF9 /* XCTestMain.swift */,
AE7DD60B1C8F0513006FC722 /* XCTestObservation.swift */,
AE9596E01C9692B8001A9EF0 /* XCTestObservationCenter.swift */,
DACC94411C8B87B900EC85F5 /* XCWaitCompletionHandler.swift */,
510957A81CA878410091D1A1 /* XCNotificationExpectationHandler.swift */,
AE33888A1CD3D72500C39B1C /* XCTestCaseSuite.swift */,
5A3952F01CC173380053159C /* XCPredicateExpectationHandler.swift */,
);
path = XCTest;
sourceTree = "<group>";
Expand Down Expand Up @@ -278,8 +284,10 @@
DA7FB38F1CA4EE3800F024F9 /* XCAbstractTest.swift in Sources */,
C265F6701C3AEB6A00520CF9 /* XCTestCase.swift in Sources */,
DADB979C1C51BDA2005E68B6 /* XCTestExpectation.swift in Sources */,
51DA1EA41CC19C9000F75EB7 /* XCPredicateExpectation.swift in Sources */,
DA7714FD1CA8D057001EA745 /* PrintObserver.swift in Sources */,
AE7DD60A1C8E81A0006FC722 /* TestFiltering.swift in Sources */,
5A3952F11CC173380053159C /* XCPredicateExpectationHandler.swift in Sources */,
AE7DD6091C8E81A0006FC722 /* ArgumentParser.swift in Sources */,
DA7732481CA87278007E31FD /* XCTestRun.swift in Sources */,
AE9596E11C9692B8001A9EF0 /* XCTestObservationCenter.swift in Sources */,
Expand Down