Skip to content

Commit 8e37808

Browse files
authored
vet optional description args in assertions, fixing #560 (#566)
1 parent 5f26371 commit 8e37808

File tree

5 files changed

+89
-0
lines changed

5 files changed

+89
-0
lines changed

internal/assertion.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,26 +45,31 @@ func (assertion *Assertion) Error() types.Assertion {
4545

4646
func (assertion *Assertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
4747
assertion.g.THelper()
48+
vetOptionalDescription("Assertion", optionalDescription...)
4849
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
4950
}
5051

5152
func (assertion *Assertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
5253
assertion.g.THelper()
54+
vetOptionalDescription("Assertion", optionalDescription...)
5355
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
5456
}
5557

5658
func (assertion *Assertion) To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
5759
assertion.g.THelper()
60+
vetOptionalDescription("Assertion", optionalDescription...)
5861
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
5962
}
6063

6164
func (assertion *Assertion) ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
6265
assertion.g.THelper()
66+
vetOptionalDescription("Assertion", optionalDescription...)
6367
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
6468
}
6569

6670
func (assertion *Assertion) NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
6771
assertion.g.THelper()
72+
vetOptionalDescription("Assertion", optionalDescription...)
6873
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
6974
}
7075

internal/assertion_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package internal_test
22

33
import (
44
"errors"
5+
"reflect"
56

67
. "github.com/onsi/ginkgo/v2"
78
. "github.com/onsi/gomega"
@@ -189,4 +190,33 @@ var _ = Describe("Making Synchronous Assertions", func() {
189190
),
190191
)
191192

193+
When("vetting optional description parameters", func() {
194+
195+
It("panics when Gomega matcher is at the beginning of optional description parameters", func() {
196+
ig := NewInstrumentedGomega()
197+
for _, expectator := range []string{
198+
"To", "NotTo", "ToNot",
199+
"Should", "ShouldNot",
200+
} {
201+
Expect(func() {
202+
expect := ig.G.Expect(42) // sic!
203+
meth := reflect.ValueOf(expect).MethodByName(expectator)
204+
Expect(meth.IsValid()).To(BeTrue())
205+
meth.Call([]reflect.Value{
206+
reflect.ValueOf(HaveLen(1)),
207+
reflect.ValueOf(ContainElement(42)),
208+
})
209+
}).To(PanicWith(MatchRegexp("Assertion has a GomegaMatcher as the first element of optionalDescription")))
210+
}
211+
})
212+
213+
It("accepts Gomega matchers in optional description parameters after the first", func() {
214+
Expect(func() {
215+
ig := NewInstrumentedGomega()
216+
ig.G.Expect(42).To(HaveLen(1), "foo", ContainElement(42))
217+
}).NotTo(Panic())
218+
})
219+
220+
})
221+
192222
})

internal/async_assertion.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,13 @@ func (assertion *AsyncAssertion) WithPolling(interval time.Duration) types.Async
104104

105105
func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
106106
assertion.g.THelper()
107+
vetOptionalDescription("Asynchronous assertion", optionalDescription...)
107108
return assertion.match(matcher, true, optionalDescription...)
108109
}
109110

110111
func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
111112
assertion.g.THelper()
113+
vetOptionalDescription("Asynchronous assertion", optionalDescription...)
112114
return assertion.match(matcher, false, optionalDescription...)
113115
}
114116

internal/async_assertion_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package internal_test
22

33
import (
44
"errors"
5+
"reflect"
56
"runtime"
67
"time"
78

@@ -712,4 +713,33 @@ var _ = Describe("Asynchronous Assertions", func() {
712713
Ω(ig.FailureMessage).Should(ContainSubstring("Timed out after"))
713714
})
714715
})
716+
717+
When("vetting optional description parameters", func() {
718+
719+
It("panics when Gomega matcher is at the beginning of optional description parameters", func() {
720+
ig := NewInstrumentedGomega()
721+
for _, expectator := range []string{
722+
"Should", "ShouldNot",
723+
} {
724+
Expect(func() {
725+
eventually := ig.G.Eventually(42) // sic!
726+
meth := reflect.ValueOf(eventually).MethodByName(expectator)
727+
Expect(meth.IsValid()).To(BeTrue())
728+
meth.Call([]reflect.Value{
729+
reflect.ValueOf(HaveLen(1)),
730+
reflect.ValueOf(ContainElement(42)),
731+
})
732+
}).To(PanicWith(MatchRegexp("Asynchronous assertion has a GomegaMatcher as the first element of optionalDescription")))
733+
}
734+
})
735+
736+
It("accepts Gomega matchers in optional description parameters after the first", func() {
737+
Expect(func() {
738+
ig := NewInstrumentedGomega()
739+
ig.G.Eventually(42).Should(HaveLen(1), "foo", ContainElement(42))
740+
}).NotTo(Panic())
741+
})
742+
743+
})
744+
715745
})

internal/vetoptdesc.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package internal
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/onsi/gomega/types"
7+
)
8+
9+
// vetOptionalDescription vets the optional description args: if it finds any
10+
// Gomega matcher at the beginning it panics. This allows for rendering Gomega
11+
// matchers as part of an optional Description, as long as they're not in the
12+
// first slot.
13+
func vetOptionalDescription(assertion string, optionalDescription ...interface{}) {
14+
if len(optionalDescription) == 0 {
15+
return
16+
}
17+
if _, isGomegaMatcher := optionalDescription[0].(types.GomegaMatcher); isGomegaMatcher {
18+
panic(fmt.Sprintf("%s has a GomegaMatcher as the first element of optionalDescription.\n\t"+
19+
"Do you mean to use And/Or/SatisfyAll/SatisfyAny to combine multiple matchers?",
20+
assertion))
21+
}
22+
}

0 commit comments

Comments
 (0)