make Or() support MatchMayChangeInTheFuture() - simplify tests by using empty And() or Or() instead of Receive() with a closed channel
diff --git a/matchers.go b/matchers.go index 4bdc588..b6110c4 100644 --- a/matchers.go +++ b/matchers.go
@@ -354,7 +354,7 @@ } //SatisfyAll is an alias for And(). -// Ω(foo).Should(SatisfyAll(ContainElement("bar"), HaveLen(3))) +// Ω("hi").Should(SatisfyAll(HaveLen(2), Equal("hi"))) func SatisfyAll(matchers ...types.GomegaMatcher) types.GomegaMatcher { return And(matchers...) } @@ -369,7 +369,7 @@ } //SatisfyAny is an alias for Or(). -// Expect(foo).To(SatisfyAny(ContainElement("bar"), HaveLen(3))) +// Expect("hi").SatisfyAny(Or(HaveLen(3), HaveLen(2)) func SatisfyAny(matchers ...types.GomegaMatcher) types.GomegaMatcher { return Or(matchers...) }
diff --git a/matchers/and.go b/matchers/and.go index 9acf470..5257b76 100644 --- a/matchers/and.go +++ b/matchers/and.go
@@ -45,7 +45,7 @@ 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. + Seems to depend on ANY of them being able to change to F. */ if m.firstFailedMatcher == nil {
diff --git a/matchers/and_test.go b/matchers/and_test.go index 5e281ca..acf778c 100644 --- a/matchers/and_test.go +++ b/matchers/and_test.go
@@ -64,23 +64,19 @@ }) 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 + m := And(Not(BeNil()), Or(), Equal(1)) + Expect(m.Match("hi")).To(BeFalse()) + Expect(m.(*AndMatcher).MatchMayChangeInTheFuture("hi")).To(BeFalse()) // empty Or() indicates 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 + Expect(m.Match("hi")).To(BeFalse()) + Expect(m.(*AndMatcher).MatchMayChangeInTheFuture("hi")).To(BeTrue()) // Equal(5) indicates it might change }) }) })
diff --git a/matchers/not.go b/matchers/not.go index 0cacc94..6aed858 100644 --- a/matchers/not.go +++ b/matchers/not.go
@@ -1,8 +1,8 @@ package matchers import ( - "github.com/onsi/gomega/types" "github.com/onsi/gomega/internal/asyncassertion" + "github.com/onsi/gomega/types" ) type NotMatcher struct {
diff --git a/matchers/not_test.go b/matchers/not_test.go index e745651..b3c1fdb 100644 --- a/matchers/not_test.go +++ b/matchers/not_test.go
@@ -43,14 +43,10 @@ }) Context("MatchMayChangeInTheFuture()", func() { - It("Propogates value from wrapped matcher", func() { - // wrap a Receive matcher, which does implement this method - channel := make(chan int) - close(channel) - var i int - m := Not(Receive(&i)) - Expect(m.Match(channel)).To(BeTrue()) - Expect(m.(*NotMatcher).MatchMayChangeInTheFuture(channel)).To(BeFalse()) + It("Propagates value from wrapped matcher", func() { + m := Not(Or()) // an empty Or() always returns false, and indicates it cannot change + Expect(m.Match("anything")).To(BeTrue()) + Expect(m.(*NotMatcher).MatchMayChangeInTheFuture("anything")).To(BeFalse()) }) It("Defaults to true", func() { m := Not(Equal(1)) // Equal does not have this method
diff --git a/matchers/or.go b/matchers/or.go index 228fce6..29ad5c6 100644 --- a/matchers/or.go +++ b/matchers/or.go
@@ -3,6 +3,7 @@ import ( "fmt" "github.com/onsi/gomega/format" + "github.com/onsi/gomega/internal/asyncassertion" "github.com/onsi/gomega/types" ) @@ -10,17 +11,18 @@ Matchers []types.GomegaMatcher // state - successfulMatcher types.GomegaMatcher + firstSuccessfulMatcher types.GomegaMatcher } func (m *OrMatcher) Match(actual interface{}) (success bool, err error) { + m.firstSuccessfulMatcher = nil for _, matcher := range m.Matchers { success, err := matcher.Match(actual) if err != nil { return false, err } if success { - m.successfulMatcher = matcher + m.firstSuccessfulMatcher = matcher return true, nil } } @@ -33,5 +35,32 @@ } func (m *OrMatcher) NegatedFailureMessage(actual interface{}) (message string) { - return m.successfulMatcher.NegatedFailureMessage(actual) + return m.firstSuccessfulMatcher.NegatedFailureMessage(actual) +} + +func (m *OrMatcher) MatchMayChangeInTheFuture(actual interface{}) bool { + /* + Example with 3 matchers: A, B, C + + Match evaluates them: F, T, <?> => T + So match is currently T, what should MatchMayChangeInTheFuture() return? + Seems like it only depends on B, since currently B MUST change to allow the result to become F + + Match eval: F, F, F => F + So match is currently F, what should MatchMayChangeInTheFuture() return? + Seems to depend on ANY of them being able to change to T. + */ + + if m.firstSuccessfulMatcher != nil { + // one of the matchers succeeded.. it must be able to change in order to affect the result + return asyncassertion.MatchMayChangeInTheFuture(m.firstSuccessfulMatcher, actual) + } else { + // so all matchers failed.. 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 + } }
diff --git a/matchers/or_test.go b/matchers/or_test.go index d9c7b82..9589a17 100644 --- a/matchers/or_test.go +++ b/matchers/or_test.go
@@ -3,6 +3,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/matchers" ) var _ = Describe("OrMatcher", func() { @@ -43,4 +44,42 @@ }) }) }) + + Context("MatchMayChangeInTheFuture", func() { + Context("Match returned false", func() { + It("returns true if any of the matchers could change", func() { + // 3 matchers, all return false, and all could change + m := Or(BeNil(), Equal("hip"), HaveLen(1)) + Expect(m.Match("hi")).To(BeFalse()) + Expect(m.(*OrMatcher).MatchMayChangeInTheFuture("hi")).To(BeTrue()) // all 3 of these matchers default to 'true' + }) + It("returns false if none of the matchers could change", func() { + // empty Or() has the property of never matching, and never can change since there are no sub-matchers that could change + m := Or() + Expect(m.Match("anything")).To(BeFalse()) + Expect(m.(*OrMatcher).MatchMayChangeInTheFuture("anything")).To(BeFalse()) + + // Or() with 3 sub-matchers that return false, and can't change + m = Or(Or(), Or(), Or()) + Expect(m.Match("hi")).To(BeFalse()) + Expect(m.(*OrMatcher).MatchMayChangeInTheFuture("hi")).To(BeFalse()) // the 3 empty Or()'s won't change + }) + }) + Context("Match returned true", func() { + Context("returns value of the successful matcher", func() { + It("false if successful matcher not going to change", func() { + // 3 matchers: 1st returns false, 2nd returns true and is not going to change, 3rd is never called + m := Or(BeNil(), And(), Equal(1)) + Expect(m.Match("hi")).To(BeTrue()) + Expect(m.(*OrMatcher).MatchMayChangeInTheFuture("hi")).To(BeFalse()) + }) + It("true if successful matcher indicates it might change", func() { + // 3 matchers: 1st returns false, 2nd returns true and "might" change, 3rd is never called + m := Or(Not(BeNil()), Equal("hi"), Equal(1)) + Expect(m.Match("hi")).To(BeTrue()) + Expect(m.(*OrMatcher).MatchMayChangeInTheFuture("hi")).To(BeTrue()) // Equal("hi") indicates it might change + }) + }) + }) + }) })
diff --git a/matchers/with_transform_test.go b/matchers/with_transform_test.go index 7ac6f1c..2824df9 100644 --- a/matchers/with_transform_test.go +++ b/matchers/with_transform_test.go
@@ -87,17 +87,10 @@ }) Context("MatchMayChangeInTheFuture()", func() { - It("Propogates value from wrapped matcher on the transformed value", func() { - // dummy struct that holds a channel - type S struct{ C chan int } - getC := func(s S) chan int { return s.C } // extracts channel from struct - // wrap a Receive matcher, which does implement this method - var i int - m := WithTransform(getC, Receive(&i)) - s := S{make(chan int)} - close(s.C) - Expect(m.Match(s)).To(BeFalse()) - Expect(m.(*WithTransformMatcher).MatchMayChangeInTheFuture(s)).To(BeFalse()) // channel closed so Receive return false + It("Propagates value from wrapped matcher on the transformed value", func() { + m := WithTransform(plus1, Or()) // empty Or() always returns false, and indicates it cannot change + Expect(m.Match(1)).To(BeFalse()) + Expect(m.(*WithTransformMatcher).MatchMayChangeInTheFuture(1)).To(BeFalse()) // empty Or() indicates cannot change }) It("Defaults to true", func() { m := WithTransform(plus1, Equal(2)) // Equal does not have this method