Skip to content
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

Add block/boolean NSPredicates, NSCompoundPredicate, and NSArray predicate method #127

Merged
merged 1 commit into from Apr 14, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 16 additions & 8 deletions Foundation.xcodeproj/project.pbxproj
Expand Up @@ -254,6 +254,8 @@
61E011811C1B5998000037DD /* CFMessagePort.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D88DC1BBC9AEC00234F36 /* CFMessagePort.c */; };
61E011821C1B599A000037DD /* CFMachPort.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D88D01BBC9AAC00234F36 /* CFMachPort.c */; };
AE35A1861CBAC85E0042DB84 /* SwiftFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = AE35A1851CBAC85E0042DB84 /* SwiftFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; };
7900433B1CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 790043391CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift */; };
7900433C1CACD33E00ECCBF1 /* TestNSPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900433A1CACD33E00ECCBF1 /* TestNSPredicate.swift */; };
CE19A88C1C23AA2300B4CB6A /* NSStringTestData.txt in Resources */ = {isa = PBXBuildFile; fileRef = CE19A88B1C23AA2300B4CB6A /* NSStringTestData.txt */; };
D31302011C30CEA900295652 /* NSConcreteValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31302001C30CEA900295652 /* NSConcreteValue.swift */; };
D370696E1C394FBF00295652 /* NSKeyedUnarchiver-RangeTest.plist in Resources */ = {isa = PBXBuildFile; fileRef = D370696D1C394FBF00295652 /* NSKeyedUnarchiver-RangeTest.plist */; };
Expand Down Expand Up @@ -547,15 +549,15 @@
5B5D89A51BBDC06800234F36 /* CFStringEncodingDatabase.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFStringEncodingDatabase.c; sourceTree = "<group>"; };
5B5D89A71BBDC09700234F36 /* CFStringScanner.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFStringScanner.c; sourceTree = "<group>"; };
5B5D89A91BBDC11100234F36 /* CFLocaleKeys.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFLocaleKeys.c; sourceTree = "<group>"; };
5B6228BA1C179041009587FE /* CFRunArray.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFRunArray.c; sourceTree = "<group>"; };
5B6228BC1C179049009587FE /* CFRunArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFRunArray.h; sourceTree = "<group>"; };
5B6228BE1C179052009587FE /* CFAttributedString.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFAttributedString.c; sourceTree = "<group>"; };
5B6228C01C17905B009587FE /* CFAttributedString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFAttributedString.h; sourceTree = "<group>"; };
5B6F17921C48631C00935030 /* TestNSNull.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSNull.swift; sourceTree = "<group>"; };
5B6F17931C48631C00935030 /* TestNSNumberFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSNumberFormatter.swift; sourceTree = "<group>"; };
5B6F17941C48631C00935030 /* TestNSTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSTask.swift; sourceTree = "<group>"; };
5B6F17951C48631C00935030 /* TestNSXMLDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSXMLDocument.swift; sourceTree = "<group>"; };
5B6F17961C48631C00935030 /* TestUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = "<group>"; };
5B6228BA1C179041009587FE /* CFRunArray.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFRunArray.c; sourceTree = "<group>"; };
5B6228BC1C179049009587FE /* CFRunArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFRunArray.h; sourceTree = "<group>"; };
5B6228BE1C179052009587FE /* CFAttributedString.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CFAttributedString.c; sourceTree = "<group>"; };
5B6228C01C17905B009587FE /* CFAttributedString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CFAttributedString.h; sourceTree = "<group>"; };
5B7C8A6E1BEA7F8F00C5B690 /* libCoreFoundation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCoreFoundation.a; sourceTree = BUILT_PRODUCTS_DIR; };
5B94E8811C430DE70055C035 /* String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
5BC1D8BC1BF3ADFE009D3973 /* TestNSCharacterSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSCharacterSet.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -610,6 +612,8 @@
61E0117B1C1B554D000037DD /* TestNSRunLoop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSRunLoop.swift; sourceTree = "<group>"; };
61F8AE7C1C180FC600FB62F0 /* TestNSNotificationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSNotificationCenter.swift; sourceTree = "<group>"; };
6E203B8C1C1303BB003B2576 /* TestNSBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSBundle.swift; sourceTree = "<group>"; };
790043391CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSCompoundPredicate.swift; sourceTree = "<group>"; };
7900433A1CACD33E00ECCBF1 /* TestNSPredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSPredicate.swift; sourceTree = "<group>"; };
7A7D6FBA1C16439400957E2E /* TestNSURLResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSURLResponse.swift; sourceTree = "<group>"; };
83712C8D1C1684900049AD49 /* TestNSURLRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSURLRequest.swift; sourceTree = "<group>"; };
844DC3321C17584F005611F9 /* TestNSScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSScanner.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1159,10 +1163,13 @@
C93559281C12C49F009FD6A9 /* TestNSAffineTransform.swift */,
EA66F63C1BF1619600136161 /* TestNSArray.swift */,
6E203B8C1C1303BB003B2576 /* TestNSBundle.swift */,
A5A34B551C18C85D00FD972B /* TestNSByteCountFormatter.swift */,
52829AD61C160D64003BC4EF /* TestNSCalendar.swift */,
5BC1D8BC1BF3ADFE009D3973 /* TestNSCharacterSet.swift */,
790043391CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift */,
DCDBB8321C1768AC00313299 /* TestNSData.swift */,
22B9C1E01C165D7A00DECFF9 /* TestNSDate.swift */,
2EBE67A31C77BF05006583D5 /* TestNSDateFormatter.swift */,
EA66F63D1BF1619600136161 /* TestNSDictionary.swift */,
525AECEB1BF2C96400D15BB0 /* TestNSFileManager.swift */,
88D28DE61C13AE9000494606 /* TestNSGeometry.swift */,
Expand All @@ -1180,6 +1187,7 @@
5B6F17931C48631C00935030 /* TestNSNumberFormatter.swift */,
D834F9931C31C4060023812A /* TestNSOrderedSet.swift */,
4DC1D07F1C12EEEF00B5948A /* TestNSPipe.swift */,
7900433A1CACD33E00ECCBF1 /* TestNSPredicate.swift */,
400E22641C1A4E58007C5933 /* TestNSProcessInfo.swift */,
EA66F6401BF1619600136161 /* TestNSPropertyList.swift */,
E876A73D1C1180E000F279EC /* TestNSRange.swift */,
Expand All @@ -1195,15 +1203,13 @@
EA66F6431BF1619600136161 /* TestNSURL.swift */,
83712C8D1C1684900049AD49 /* TestNSURLRequest.swift */,
7A7D6FBA1C16439400957E2E /* TestNSURLResponse.swift */,
555683BC1C1250E70041D4C6 /* TestNSUserDefaults.swift */,
C2A9D75B1C15C08B00993803 /* TestNSUUID.swift */,
D3047AEB1C38BC3300295652 /* TestNSValue.swift */,
5B6F17951C48631C00935030 /* TestNSXMLDocument.swift */,
5B40F9F11C125187000E72E3 /* TestNSXMLParser.swift */,
555683BC1C1250E70041D4C6 /* TestNSUserDefaults.swift */,
5B6F17961C48631C00935030 /* TestUtils.swift */,
A5A34B551C18C85D00FD972B /* TestNSByteCountFormatter.swift */,
D3047AEB1C38BC3300295652 /* TestNSValue.swift */,
E19E17DB1C2225930023AF4D /* TestNSXMLDocument.swift */,
2EBE67A31C77BF05006583D5 /* TestNSDateFormatter.swift */,
);
name = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -1952,6 +1958,7 @@
5B13B33E1C582D4C00651CE2 /* TestNSProcessInfo.swift in Sources */,
5B13B33F1C582D4C00651CE2 /* TestNSPropertyList.swift in Sources */,
5B13B32C1C582D4C00651CE2 /* TestNSDate.swift in Sources */,
7900433C1CACD33E00ECCBF1 /* TestNSPredicate.swift in Sources */,
5B13B33B1C582D4C00651CE2 /* TestNSNumberFormatter.swift in Sources */,
5B13B3301C582D4C00651CE2 /* TestNSHTTPCookie.swift in Sources */,
5B13B3361C582D4C00651CE2 /* TestNSLocale.swift in Sources */,
Expand Down Expand Up @@ -1984,6 +1991,7 @@
5B13B3271C582D4C00651CE2 /* TestNSArray.swift in Sources */,
5B13B3461C582D4C00651CE2 /* TestNSTask.swift in Sources */,
555683BD1C1250E70041D4C6 /* TestNSUserDefaults.swift in Sources */,
7900433B1CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
43 changes: 36 additions & 7 deletions Foundation/NSCompoundPredicate.swift
Expand Up @@ -19,14 +19,43 @@ public enum NSCompoundPredicateType : UInt {

public class NSCompoundPredicate : NSPredicate {

public init(type: NSCompoundPredicateType, subpredicates: [NSPredicate]) { NSUnimplemented() }
public init(type: NSCompoundPredicateType, subpredicates: [NSPredicate]) {
if type == .NotPredicateType && subpredicates.count == 0 {
preconditionFailure("Unsupported predicate count of \(subpredicates.count) for \(type)")
}
self.compoundPredicateType = type
self.subpredicates = subpredicates
super.init(value: false)
}
public required init?(coder: NSCoder) { NSUnimplemented() }

public var compoundPredicateType: NSCompoundPredicateType { NSUnimplemented() }
public var subpredicates: [AnyObject] { NSUnimplemented() }
public let compoundPredicateType: NSCompoundPredicateType
public let subpredicates: [NSPredicate]

/*** Convenience Methods ***/
public init(andPredicateWithSubpredicates subpredicates: [NSPredicate]) { NSUnimplemented() }
public init(orPredicateWithSubpredicates subpredicates: [NSPredicate]) { NSUnimplemented() }
public init(notPredicateWithSubpredicate predicate: NSPredicate) { NSUnimplemented() }
public convenience init(andPredicateWithSubpredicates subpredicates: [NSPredicate]) {
self.init(type: .AndPredicateType, subpredicates: subpredicates)
}
public convenience init(orPredicateWithSubpredicates subpredicates: [NSPredicate]) {
self.init(type: .OrPredicateType, subpredicates: subpredicates)
}
public convenience init(notPredicateWithSubpredicate predicate: NSPredicate) {
self.init(type: .NotPredicateType, subpredicates: [predicate])
}

override public func evaluateWithObject(_ object: AnyObject?, substitutionVariables bindings: [String : AnyObject]?) -> Bool {
switch compoundPredicateType {
case .AndPredicateType:
return subpredicates.reduce(true, combine: {
$0 && $1.evaluateWithObject(object, substitutionVariables: bindings)
})
case .OrPredicateType:
return subpredicates.reduce(false, combine: {
$0 || $1.evaluateWithObject(object, substitutionVariables: bindings)
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using reduce doesn't maintain the short-circuiting behavior that the ObjC Foundation version exhibits. As a test, the following will not crash, indicating that NSCompoundPredicate is short-circuiting predicate evaluation once it knows that it will return false:

let bOK = NSPredicate(value: false)
let bCrash = NSPredicate(block: { _ in
    fatalError("whoops")
    return false
})

let both = NSCompoundPredicate(andPredicateWithSubpredicates: [bOK, bCrash])
let eval = both.evaluateWithObject("hi") // evaluates to false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how that's true. I added test cases for both "and" and "or predicates based on your example code here, and I did not get any test failures or crashes. The && and || operators short circuit properly on their own, and I don't see how reduce() could alter that behavior.

case .NotPredicateType:
// safe to get the 0th item here since we trap if there's not at least one on init
return !(subpredicates[0].evaluateWithObject(object, substitutionVariables: bindings))
}
}
}
46 changes: 39 additions & 7 deletions Foundation/NSPredicate.swift
Expand Up @@ -11,7 +11,16 @@
// Predicates wrap some combination of expressions and operators and when evaluated return a BOOL.

public class NSPredicate : NSObject, NSSecureCoding, NSCopying {


private enum PredicateKind {
case Boolean(Bool)
case Block((AnyObject?, [String : AnyObject]?) -> Bool)
// TODO: case for init(format:argumentArray:)
// TODO: case for init(fromMetadataQueryString:)
}

private let kind: PredicateKind

public static func supportsSecureCoding() -> Bool {
return true
}
Expand All @@ -37,23 +46,46 @@ public class NSPredicate : NSObject, NSSecureCoding, NSCopying {

public init?(fromMetadataQueryString queryString: String) { NSUnimplemented() }

public init(value: Bool) { NSUnimplemented() } // return predicates that always evaluate to true/false

public init(block: (AnyObject, [String : AnyObject]?) -> Bool) { NSUnimplemented() }
public init(value: Bool) {
kind = .Boolean(value)
super.init()
} // return predicates that always evaluate to true/false

public init(block: (AnyObject?, [String : AnyObject]?) -> Bool) {
kind = .Block(block)
super.init()
}

public var predicateFormat: String { NSUnimplemented() } // returns the format string of the predicate

public func predicateWithSubstitutionVariables(_ variables: [String : AnyObject]) -> Self { NSUnimplemented() } // substitute constant values for variables

public func evaluateWithObject(_ object: AnyObject?) -> Bool { NSUnimplemented() } // evaluate a predicate against a single object
public func evaluateWithObject(_ object: AnyObject?) -> Bool {
return evaluateWithObject(object, substitutionVariables: nil)
} // evaluate a predicate against a single object

public func evaluateWithObject(_ object: AnyObject?, substitutionVariables bindings: [String : AnyObject]?) -> Bool { NSUnimplemented() } // single pass evaluation substituting variables from the bindings dictionary for any variable expressions encountered
public func evaluateWithObject(_ object: AnyObject?, substitutionVariables bindings: [String : AnyObject]?) -> Bool {
if bindings != nil {
NSUnimplemented()
}

switch kind {
case let .Boolean(value):
return value
case let .Block(block):
return block(object, bindings)
}
} // single pass evaluation substituting variables from the bindings dictionary for any variable expressions encountered

public func allowEvaluation() { NSUnimplemented() } // Force a predicate which was securely decoded to allow evaluation
}

extension NSArray {
public func filteredArrayUsingPredicate(_ predicate: NSPredicate) -> [AnyObject] { NSUnimplemented() } // evaluate a predicate against an array of objects and return a filtered array
public func filteredArrayUsingPredicate(_ predicate: NSPredicate) -> [AnyObject] {
return bridge().filter({ object in
return predicate.evaluateWithObject(object)
})
} // evaluate a predicate against an array of objects and return a filtered array
}

extension NSMutableArray {
Expand Down
118 changes: 118 additions & 0 deletions TestFoundation/TestNSCompoundPredicate.swift
@@ -0,0 +1,118 @@
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 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
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should look at another Swift file in this project and add a correct legal notice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, missed that, good call. I'll handle that in the morning.

Kevin Lundberg

On Dec 11, 2015, at 1:44 AM, Thi notifications@github.com wrote:

In TestFoundation/TestNSCompoundPredicate.swift:

@@ -0,0 +1,87 @@
+//
+// TestNSCompoundPredicate.swift
+// Foundation
+//
+// Created by Kevin Lundberg on 12/11/15.
+// Copyright © 2015 Apple. All rights reserved.
+//
You should look at another Swift file in this project and add the same legal notice.


Reply to this email directly or view it on GitHub.


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

class TestNSCompoundPredicate: XCTestCase {

static var allTests: [(String, TestNSCompoundPredicate -> () throws -> Void)] {
return [
("test_NotPredicate", test_NotPredicate),
("test_AndPredicateWithNoSubpredicates", test_AndPredicateWithNoSubpredicates),
("test_AndPredicateWithOneSubpredicate", test_AndPredicateWithOneSubpredicate),
("test_AndPredicateWithMultipleSubpredicates", test_AndPredicateWithMultipleSubpredicates),
("test_OrPredicateWithNoSubpredicates", test_OrPredicateWithNoSubpredicates),
("test_OrPredicateWithOneSubpredicate", test_OrPredicateWithOneSubpredicate),
("test_OrPredicateWithMultipleSubpredicates", test_OrPredicateWithMultipleSubpredicates),
("test_OrPredicateShortCircuits", test_OrPredicateShortCircuits),
("test_AndPredicateShortCircuits", test_AndPredicateShortCircuits),
]
}

private func eval(_ predicate: NSPredicate, object: NSObject = NSObject()) -> Bool {
return predicate.evaluateWithObject(object, substitutionVariables: nil)
}

func test_NotPredicate() {
let notTruePredicate = NSCompoundPredicate(notPredicateWithSubpredicate: NSPredicate(value: true))
let notFalsePredicate = NSCompoundPredicate(notPredicateWithSubpredicate: NSPredicate(value: false))

XCTAssertFalse(eval(notTruePredicate))
XCTAssertTrue(eval(notFalsePredicate))
}

func test_AndPredicateWithNoSubpredicates() {
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [])

XCTAssertTrue(eval(predicate))
}

func test_AndPredicateWithOneSubpredicate() {
let truePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [NSPredicate(value: true)])
let falsePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [NSPredicate(value: false)])

XCTAssertTrue(eval(truePredicate))
XCTAssertFalse(eval(falsePredicate))
}

func test_AndPredicateWithMultipleSubpredicates() {
let truePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [NSPredicate(value: true), NSPredicate(value: true)])
let falsePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [NSPredicate(value: true), NSPredicate(value: false)])

XCTAssertTrue(eval(truePredicate))
XCTAssertFalse(eval(falsePredicate))
}


func test_OrPredicateWithNoSubpredicates() {
let predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [])

XCTAssertFalse(eval(predicate))
}

func test_OrPredicateWithOneSubpredicate() {
let truePredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [NSPredicate(value: true)])
let falsePredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [NSPredicate(value: false)])

XCTAssertTrue(eval(truePredicate))
XCTAssertFalse(eval(falsePredicate))
}

func test_OrPredicateWithMultipleSubpredicates() {
let truePredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [NSPredicate(value: true), NSPredicate(value: false)])
let falsePredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [NSPredicate(value: false), NSPredicate(value: false)])

XCTAssertTrue(eval(truePredicate))
XCTAssertFalse(eval(falsePredicate))
}

func test_AndPredicateShortCircuits() {
var shortCircuited = true

let bOK = NSPredicate(value: false)
let bDontEval = NSPredicate(block: { _ in
shortCircuited = false
return true
})

let both = NSCompoundPredicate(andPredicateWithSubpredicates: [bOK, bDontEval])
XCTAssertFalse(eval(both))
XCTAssertTrue(shortCircuited)
}

func test_OrPredicateShortCircuits() {
var shortCircuited = true

let bOK = NSPredicate(value: true)
let bDontEval = NSPredicate(block: { _ in
shortCircuited = false
return true
})

let both = NSCompoundPredicate(orPredicateWithSubpredicates: [bOK, bDontEval])
XCTAssertTrue(eval(both))
XCTAssertTrue(shortCircuited)
}
}