Skip to content

Commit

Permalink
make Or() support MatchMayChangeInTheFuture()
Browse files Browse the repository at this point in the history
- simplify tests by using empty And() or Or() instead of Receive() with a closed channel
  • Loading branch information
slatteryjim committed Sep 20, 2015
1 parent 1dc8c2e commit 61953c6
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 35 deletions.
4 changes: 2 additions & 2 deletions matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ func And(ms ...types.GomegaMatcher) types.GomegaMatcher {
}

//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...)
}
Expand All @@ -369,7 +369,7 @@ func Or(ms ...types.GomegaMatcher) types.GomegaMatcher {
}

//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...)
}
Expand Down
2 changes: 1 addition & 1 deletion matchers/and.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (m *AndMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
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 {
Expand Down
14 changes: 5 additions & 9 deletions matchers/and_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,19 @@ var _ = Describe("AndMatcher", func() {
})

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
})
})
})
Expand Down
2 changes: 1 addition & 1 deletion matchers/not.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
12 changes: 4 additions & 8 deletions matchers/not_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,10 @@ var _ = Describe("NotMatcher", func() {
})

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
Expand Down
35 changes: 32 additions & 3 deletions matchers/or.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,26 @@ package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/internal/asyncassertion"
"github.com/onsi/gomega/types"
)

type OrMatcher struct {
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
}
}
Expand All @@ -33,5 +35,32 @@ func (m *OrMatcher) FailureMessage(actual interface{}) (message string) {
}

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
}
}
39 changes: 39 additions & 0 deletions matchers/or_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package matchers_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/matchers"
)

var _ = Describe("OrMatcher", func() {
Expand Down Expand Up @@ -43,4 +44,42 @@ var _ = Describe("OrMatcher", func() {
})
})
})

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
})
})
})
})
})
15 changes: 4 additions & 11 deletions matchers/with_transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,10 @@ var _ = Describe("WithTransformMatcher", func() {
})

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
Expand Down

0 comments on commit 61953c6

Please sign in to comment.