1 package gcustom_test
2
3 import (
4 "errors"
5 "runtime"
6 "strings"
7
8 . "github.com/onsi/ginkgo/v2"
9 . "github.com/onsi/gomega"
10 "github.com/onsi/gomega/gcustom"
11 "github.com/onsi/gomega/internal"
12 )
13
14
15 type InstrumentedGomega struct {
16 G *internal.Gomega
17 FailureMessage string
18 FailureSkip []int
19 RegisteredHelpers []string
20 }
21
22 func NewInstrumentedGomega() *InstrumentedGomega {
23 out := &InstrumentedGomega{}
24
25 out.G = internal.NewGomega(internal.FetchDefaultDurationBundle())
26 out.G.Fail = func(message string, skip ...int) {
27 out.FailureMessage = message
28 out.FailureSkip = skip
29 }
30 out.G.THelper = func() {
31 pc, _, _, _ := runtime.Caller(1)
32 f := runtime.FuncForPC(pc)
33 funcName := strings.TrimPrefix(f.Name(), "github.com/onsi/gomega/internal.")
34 out.RegisteredHelpers = append(out.RegisteredHelpers, funcName)
35 }
36
37 return out
38 }
39
40 type someType struct {
41 Name string
42 }
43
44 var _ = Describe("MakeMatcher", func() {
45 It("generatees a custom matcher that satisfies the GomegaMatcher interface and renders correct failure messages", func() {
46 m := gcustom.MakeMatcher(func(a int) (bool, error) {
47 if a == 0 {
48 return true, nil
49 }
50 if a == 1 {
51 return false, nil
52 }
53 return false, errors.New("bam")
54 }).WithMessage("match")
55
56 Ω(0).Should(m)
57 Ω(1).ShouldNot(m)
58
59 ig := NewInstrumentedGomega()
60 ig.G.Ω(1).Should(m)
61 Ω(ig.FailureMessage).Should(Equal("Expected:\n <int>: 1\nto match"))
62
63 ig.G.Ω(0).ShouldNot(m)
64 Ω(ig.FailureMessage).Should(Equal("Expected:\n <int>: 0\nnot to match"))
65
66 ig.G.Ω(2).Should(m)
67 Ω(ig.FailureMessage).Should(Equal("bam"))
68
69 ig.G.Ω(2).ShouldNot(m)
70 Ω(ig.FailureMessage).Should(Equal("bam"))
71 })
72
73 Describe("validating and wrapping the MatchFunc", func() {
74 DescribeTable("it panics when passed an invalid function", func(f any) {
75 Expect(func() {
76 gcustom.MakeMatcher(f)
77 }).To(PanicWith("MakeMatcher must be passed a function that takes one argument and returns (bool, error)"))
78 },
79 Entry("a non-function", "foo"),
80 Entry("a non-function", 1),
81 Entry("a function with no input", func() (bool, error) { return false, nil }),
82 Entry("a function with too many inputs", func(a int, b string) (bool, error) { return false, nil }),
83 Entry("a function with no outputs", func(a any) {}),
84 Entry("a function with insufficient outputs", func(a any) bool { return false }),
85 Entry("a function with insufficient outputs", func(a any) error { return nil }),
86 Entry("a function with too many outputs", func(a any) (bool, error, string) { return false, nil, "" }),
87 Entry("a function with the wrong types of outputs", func(a any) (int, error) { return 1, nil }),
88 Entry("a function with the wrong types of outputs", func(a any) (bool, int) { return false, 1 }),
89 )
90
91 Context("when the match func accepts any actual", func() {
92 It("always passes in the actual, regardless of type", func() {
93 var passedIn any
94 m := gcustom.MakeMatcher(func(a any) (bool, error) {
95 passedIn = a
96 return true, nil
97 })
98
99 m.Match(1)
100 Ω(passedIn).Should(Equal(1))
101
102 m.Match("foo")
103 Ω(passedIn).Should(Equal("foo"))
104
105 m.Match(someType{"foo"})
106 Ω(passedIn).Should(Equal(someType{"foo"}))
107
108 c := make(chan bool)
109 m.Match(c)
110 Ω(passedIn).Should(Equal(c))
111 })
112 })
113
114 Context("when the match func accepts a specific type", func() {
115 It("ensure the type matches before calling func", func() {
116 var passedIn any
117 m := gcustom.MakeMatcher(func(a int) (bool, error) {
118 passedIn = a
119 return true, nil
120 })
121
122 success, err := m.Match(1)
123 Ω(success).Should(BeTrue())
124 Ω(err).ShouldNot(HaveOccurred())
125 Ω(passedIn).Should(Equal(1))
126
127 passedIn = nil
128 success, err = m.Match(1.2)
129 Ω(success).Should(BeFalse())
130 Ω(err).Should(MatchError(ContainSubstring("Matcher expected actual of type <int>. Got:\n <float64>: 1.2")))
131 Ω(passedIn).Should(BeNil())
132
133 m = gcustom.MakeMatcher(func(a someType) (bool, error) {
134 passedIn = a
135 return true, nil
136 })
137
138 success, err = m.Match(someType{"foo"})
139 Ω(success).Should(BeTrue())
140 Ω(err).ShouldNot(HaveOccurred())
141 Ω(passedIn).Should(Equal(someType{"foo"}))
142
143 passedIn = nil
144 success, err = m.Match("foo")
145 Ω(success).Should(BeFalse())
146 Ω(err).Should(MatchError(ContainSubstring("Matcher expected actual of type <gcustom_test.someType>. Got:\n <string>: foo")))
147 Ω(passedIn).Should(BeNil())
148
149 })
150 })
151
152 Context("when the match func accepts a nil-able type", func() {
153 It("ensure nil matches the type", func() {
154 var passedIn any
155 m := gcustom.MakeMatcher(func(a *someType) (bool, error) {
156 passedIn = a
157 return true, nil
158 })
159
160 success, err := m.Match(nil)
161 Ω(success).Should(BeTrue())
162 Ω(err).ShouldNot(HaveOccurred())
163 Ω(passedIn).Should(BeNil())
164 })
165 })
166 })
167
168 It("calls the matchFunc and returns whatever it returns when Match is called", func() {
169 m := gcustom.MakeMatcher(func(a int) (bool, error) {
170 if a == 0 {
171 return true, nil
172 }
173 if a == 1 {
174 return false, nil
175 }
176 return false, errors.New("bam")
177 })
178
179 Ω(m.Match(0)).Should(BeTrue())
180 Ω(m.Match(1)).Should(BeFalse())
181 success, err := m.Match(2)
182 Ω(success).Should(BeFalse())
183 Ω(err).Should(MatchError("bam"))
184 })
185
186 Describe("rendering messages", func() {
187 var m gcustom.CustomGomegaMatcher
188 BeforeEach(func() {
189 m = gcustom.MakeMatcher(func(a any) (bool, error) { return false, nil })
190 })
191
192 Context("when no message is configured", func() {
193 It("renders a simple canned message", func() {
194 Ω(m.FailureMessage(3)).Should(Equal("Custom matcher failed for:\n <int>: 3"))
195 Ω(m.NegatedFailureMessage(3)).Should(Equal("Custom matcher succeeded (but was expected to fail) for:\n <int>: 3"))
196 })
197 })
198
199 Context("when a simple message is configured", func() {
200 It("tacks that message onto the end of a formatted string", func() {
201 m = m.WithMessage("have been confabulated")
202 Ω(m.FailureMessage(3)).Should(Equal("Expected:\n <int>: 3\nto have been confabulated"))
203 Ω(m.NegatedFailureMessage(3)).Should(Equal("Expected:\n <int>: 3\nnot to have been confabulated"))
204
205 m = gcustom.MakeMatcher(func(a any) (bool, error) { return false, nil }, "have been confabulated")
206 Ω(m.FailureMessage(3)).Should(Equal("Expected:\n <int>: 3\nto have been confabulated"))
207 Ω(m.NegatedFailureMessage(3)).Should(Equal("Expected:\n <int>: 3\nnot to have been confabulated"))
208
209 })
210 })
211
212 Context("when a template is registered", func() {
213 It("uses that template", func() {
214 m = m.WithTemplate("{{.Failure}} {{.NegatedFailure}} {{.To}} {{.FormattedActual}} {{.Actual.Name}}")
215 Ω(m.FailureMessage(someType{"foo"})).Should(Equal("true false to <gcustom_test.someType>: {Name: \"foo\"} foo"))
216 Ω(m.NegatedFailureMessage(someType{"foo"})).Should(Equal("false true not to <gcustom_test.someType>: {Name: \"foo\"} foo"))
217
218 })
219 })
220
221 Context("when a template with custom data is registered", func() {
222 It("provides that custom data", func() {
223 m = m.WithTemplate("{{.Failure}} {{.NegatedFailure}} {{.To}} {{.FormattedActual}} {{.Actual.Name}} {{.Data}}", 17)
224
225 Ω(m.FailureMessage(someType{"foo"})).Should(Equal("true false to <gcustom_test.someType>: {Name: \"foo\"} foo 17"))
226 Ω(m.NegatedFailureMessage(someType{"foo"})).Should(Equal("false true not to <gcustom_test.someType>: {Name: \"foo\"} foo 17"))
227 })
228
229 It("provides a mechanism for formatting custom data", func() {
230 m = m.WithTemplate("{{format .Data}}", 17)
231
232 Ω(m.FailureMessage(0)).Should(Equal("<int>: 17"))
233 Ω(m.NegatedFailureMessage(0)).Should(Equal("<int>: 17"))
234
235 m = m.WithTemplate("{{format .Data 1}}", 17)
236
237 Ω(m.FailureMessage(0)).Should(Equal(" <int>: 17"))
238 Ω(m.NegatedFailureMessage(0)).Should(Equal(" <int>: 17"))
239
240 })
241 })
242
243 Context("when a precompiled template is registered", func() {
244 It("uses that template", func() {
245 templ, err := gcustom.ParseTemplate("{{.Failure}} {{.NegatedFailure}} {{.To}} {{.FormattedActual}} {{.Actual.Name}} {{format .Data}}")
246 Ω(err).ShouldNot(HaveOccurred())
247
248 m = m.WithPrecompiledTemplate(templ, 17)
249 Ω(m.FailureMessage(someType{"foo"})).Should(Equal("true false to <gcustom_test.someType>: {Name: \"foo\"} foo <int>: 17"))
250 Ω(m.NegatedFailureMessage(someType{"foo"})).Should(Equal("false true not to <gcustom_test.someType>: {Name: \"foo\"} foo <int>: 17"))
251 })
252
253 It("can also take a template as an argument upon construction", func() {
254 templ, err := gcustom.ParseTemplate("{{.To}} {{format .Data}}")
255 Ω(err).ShouldNot(HaveOccurred())
256 m = gcustom.MakeMatcher(func(a any) (bool, error) { return false, nil }, templ)
257
258 Ω(m.FailureMessage(0)).Should(Equal("to <nil>: nil"))
259 Ω(m.NegatedFailureMessage(0)).Should(Equal("not to <nil>: nil"))
260
261 m = m.WithTemplateData(17)
262 Ω(m.FailureMessage(0)).Should(Equal("to <int>: 17"))
263 Ω(m.NegatedFailureMessage(0)).Should(Equal("not to <int>: 17"))
264 })
265 })
266 })
267 })
268
View as plain text