make And() propagate MatchMayChangeInTheFuture()
- considers both Match() cases and considers the appropriate matcher(s) in making decision
- also added a comment to WithTransform.MatchMayChangeInTheFuture(), as it will not have correct behavior with non-deterministic transformer
diff --git a/matchers/and.go b/matchers/and.go
index 5092463..9acf470 100644
--- a/matchers/and.go
+++ b/matchers/and.go
@@ -3,6 +3,7 @@
import (
"fmt"
"github.com/onsi/gomega/format"
+ "github.com/onsi/gomega/internal/asyncassertion"
"github.com/onsi/gomega/types"
)
@@ -10,25 +11,53 @@
Matchers []types.GomegaMatcher
// state
- firstFailedMatchErrMsg string
+ firstFailedMatcher types.GomegaMatcher
}
func (m *AndMatcher) Match(actual interface{}) (success bool, err error) {
+ m.firstFailedMatcher = nil
for _, matcher := range m.Matchers {
success, err := matcher.Match(actual)
if !success || err != nil {
- m.firstFailedMatchErrMsg = matcher.FailureMessage(actual)
+ m.firstFailedMatcher = matcher
return false, err
}
}
return true, nil
}
-func (m *AndMatcher) FailureMessage(_ interface{}) (message string) {
- return m.firstFailedMatchErrMsg
+func (m *AndMatcher) FailureMessage(actual interface{}) (message string) {
+ return m.firstFailedMatcher.FailureMessage(actual)
}
func (m *AndMatcher) NegatedFailureMessage(actual interface{}) (message string) {
// not the most beautiful list of matchers, but not bad either...
return format.Message(actual, fmt.Sprintf("To not satisfy all of these matchers: %s", m.Matchers))
}
+
+func (m *AndMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
+ /*
+ Example with 3 matchers: A, B, C
+
+ Match evaluates them: T, F, <?> => F
+ So match is currently F, what should MatchMayChangeInTheFuture() return?
+ Seems like it only depends on B, since currently B MUST change to allow the result to become T
+
+ Match eval: T, T, T => T
+ So match is currently T, what should MatchMayChangeInTheFuture() return?
+ Answer: Seems to depend on ANY of them being able to change to F.
+ */
+
+ if m.firstFailedMatcher == nil {
+ // so all matchers succeeded.. Any one of them changing would change the result.
+ for _, matcher := range m.Matchers {
+ if asyncassertion.MatchMayChangeInTheFuture(matcher, actual) {
+ return true
+ }
+ }
+ return false // none of were going to change
+ } else {
+ // one of the matchers failed.. it must be able to change in order to affect the result
+ return asyncassertion.MatchMayChangeInTheFuture(m.firstFailedMatcher, actual)
+ }
+}
diff --git a/matchers/and_test.go b/matchers/and_test.go
index 955e4ae..5e281ca 100644
--- a/matchers/and_test.go
+++ b/matchers/and_test.go
@@ -3,6 +3,7 @@
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/matchers"
"github.com/onsi/gomega/types"
)
@@ -61,4 +62,46 @@
})
})
})
+
+ Context("MatchMayChangeInTheFuture", func() {
+ // setup a closed channel
+ closedChannel := make(chan int)
+ close(closedChannel)
+ var i int
+ Context("Match returned false", func() {
+ Context("returns value of the failed matcher", func() {
+ It("false if failed matcher not going to change", func() {
+ // 3 matchers: 1st returns true, 2nd returns false and is not going to change, 3rd is never called
+ m := And(Not(BeNil()), Receive(&i), Equal(1))
+ Expect(m.Match(closedChannel)).To(BeFalse())
+ Expect(m.(*AndMatcher).MatchMayChangeInTheFuture(closedChannel)).To(BeFalse()) // closed channel, so not going to change
+ })
+ It("true if failed matcher indicates it might change", func() {
+ // 3 matchers: 1st returns true, 2nd returns false and "might" change, 3rd is never called
+ m := And(Not(BeNil()), Equal(5), Equal(1))
+ Expect(m.Match(closedChannel)).To(BeFalse())
+ Expect(m.(*AndMatcher).MatchMayChangeInTheFuture(closedChannel)).To(BeTrue()) // Equal(5) indicates it might change
+ })
+ })
+ })
+ Context("Match returned true", func() {
+ It("returns true if any of the matchers could change", func() {
+ // 3 matchers, all return true, and all could change
+ m := And(Not(BeNil()), Equal("hi"), HaveLen(2))
+ Expect(m.Match("hi")).To(BeTrue())
+ Expect(m.(*AndMatcher).MatchMayChangeInTheFuture("hi")).To(BeTrue()) // all 3 of these matchers default to 'true'
+ })
+ It("returns false if none of the matchers could change", func() {
+ // empty And() has the property of always matching, and never can change since there are no sub-matchers that could change
+ m := And()
+ Expect(m.Match("anything")).To(BeTrue())
+ Expect(m.(*AndMatcher).MatchMayChangeInTheFuture("anything")).To(BeFalse())
+
+ // And() with 3 sub-matchers that return true, and can't change
+ m = And(And(), And(), And())
+ Expect(m.Match("hi")).To(BeTrue())
+ Expect(m.(*AndMatcher).MatchMayChangeInTheFuture("hi")).To(BeFalse()) // the 3 empty And()'s won't change
+ })
+ })
+ })
})
diff --git a/matchers/with_transform.go b/matchers/with_transform.go
index a8d2a7c..21d9fe0 100644
--- a/matchers/with_transform.go
+++ b/matchers/with_transform.go
@@ -62,5 +62,10 @@
}
func (m *WithTransformMatcher) MatchMayChangeInTheFuture(_ interface{}) bool {
+ // TODO: Maybe this should always just return true? (Only an issue for non-deterministic transformers.)
+ //
+ // Querying the next matcher is fine if the transformer always will return the same value.
+ // But if the transformer is non-deterministic and returns a different value each time, then there
+ // is no point in querying the next matcher, since it can only comment on the last transformed value.
return asyncassertion.MatchMayChangeInTheFuture(m.Matcher, m.transformedValue)
}