From 26606feed7f373975e2a74d39c68904501b19153 Mon Sep 17 00:00:00 2001 From: Rachel Brindle Date: Mon, 3 Apr 2023 23:29:58 -0700 Subject: [PATCH] satisfyAllOf with toEventually should only evaluate the expression once each poll Fixes #529 --- Sources/Nimble/Expression.swift | 9 +++++++++ Sources/Nimble/Matchers/PostNotification.swift | 2 +- Sources/Nimble/Matchers/SatisfyAllOf.swift | 5 +++-- Tests/NimbleTests/Matchers/SatisfyAllOfTest.swift | 12 ++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Sources/Nimble/Expression.swift b/Sources/Nimble/Expression.swift index 1ac29b254..bae39e451 100644 --- a/Sources/Nimble/Expression.swift +++ b/Sources/Nimble/Expression.swift @@ -94,4 +94,13 @@ public struct Expression { isClosure: isClosure ) } + + public func withCaching() -> Expression { + return Expression( + memoizedExpression: memoizedClosure { try self.evaluate() }, + location: self.location, + withoutCaching: false, + isClosure: isClosure + ) + } } diff --git a/Sources/Nimble/Matchers/PostNotification.swift b/Sources/Nimble/Matchers/PostNotification.swift index e04bf556e..750b46a88 100644 --- a/Sources/Nimble/Matchers/PostNotification.swift +++ b/Sources/Nimble/Matchers/PostNotification.swift @@ -61,7 +61,7 @@ private func _postNotifications( withoutCaching: true ) - assert(pthread_equal(mainThread, pthread_self()) != 0, "Only expecting closure to be evaluated on main thread.") + assert(Thread.isMainThread, "Only expecting closure to be evaluated on main thread.") if !once { once = true _ = try actualExpression.evaluate() diff --git a/Sources/Nimble/Matchers/SatisfyAllOf.swift b/Sources/Nimble/Matchers/SatisfyAllOf.swift index 50ab64150..2df6a7091 100644 --- a/Sources/Nimble/Matchers/SatisfyAllOf.swift +++ b/Sources/Nimble/Matchers/SatisfyAllOf.swift @@ -8,10 +8,11 @@ public func satisfyAllOf(_ predicates: Predicate...) -> Predicate { /// provided in the array of matchers. public func satisfyAllOf(_ predicates: [Predicate]) -> Predicate { return Predicate.define { actualExpression in + let cachedExpression = actualExpression.withCaching() var postfixMessages = [String]() var status: PredicateStatus = .matches for predicate in predicates { - let result = try predicate.satisfies(actualExpression) + let result = try predicate.satisfies(cachedExpression) if result.status == .fail { status = .fail } else if result.status == .doesNotMatch, status != .fail { @@ -21,7 +22,7 @@ public func satisfyAllOf(_ predicates: [Predicate]) -> Predicate { } var msg: ExpectationMessage - if let actualValue = try actualExpression.evaluate() { + if let actualValue = try cachedExpression.evaluate() { msg = .expectedCustomValueTo( "match all of: " + postfixMessages.joined(separator: ", and "), actual: "\(actualValue)" diff --git a/Tests/NimbleTests/Matchers/SatisfyAllOfTest.swift b/Tests/NimbleTests/Matchers/SatisfyAllOfTest.swift index caae73501..e57626329 100644 --- a/Tests/NimbleTests/Matchers/SatisfyAllOfTest.swift +++ b/Tests/NimbleTests/Matchers/SatisfyAllOfTest.swift @@ -48,4 +48,16 @@ final class SatisfyAllOfTest: XCTestCase { expect(false).toNot(beTrue() && beFalse()) expect(true).toNot(beTruthy() && beFalsy()) } + + func testSatisfyAllOfCachesExpressionBeforePassingToPredicates() { + // This is not a great example of assertion writing - functions being asserted on in Expressions should not have side effects. + // But we should still handle those cases anyway. + var value: Int = 0 + func testFunction() -> Int { + value += 1 + return value + } + + expect(testFunction()).toEventually(satisfyAllOf(equal(1), equal(1))) + } }