1
16
17 package framework
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "strings"
24 "time"
25
26 ginkgotypes "github.com/onsi/ginkgo/v2/types"
27 "github.com/onsi/gomega"
28 "github.com/onsi/gomega/format"
29 "github.com/onsi/gomega/types"
30 )
31
32
33
34
35
36
37
38
39
40
41
42
43 func MakeMatcher[T interface{}](match func(actual T) (failure func() string, err error)) types.GomegaMatcher {
44 return &matcher[T]{
45 match: match,
46 }
47 }
48
49 type matcher[T interface{}] struct {
50 match func(actual T) (func() string, error)
51 failure func() string
52 }
53
54 func (m *matcher[T]) Match(actual interface{}) (success bool, err error) {
55 if actual, ok := actual.(T); ok {
56 failure, err := m.match(actual)
57 if err != nil {
58 return false, err
59 }
60 m.failure = failure
61 if failure != nil {
62 return false, nil
63 }
64 return true, nil
65 }
66 var empty T
67 return false, gomega.StopTrying(fmt.Sprintf("internal error: expected %T, got:\n%s", empty, format.Object(actual, 1)))
68 }
69
70 func (m *matcher[T]) FailureMessage(actual interface{}) string {
71 return m.failure()
72 }
73
74 func (m matcher[T]) NegatedFailureMessage(actual interface{}) string {
75 return m.failure()
76 }
77
78 var _ types.GomegaMatcher = &matcher[string]{}
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 func Gomega() GomegaInstance {
100 return gomegaInstance{}
101 }
102
103 type GomegaInstance interface {
104 Expect(actual interface{}) Assertion
105 Eventually(ctx context.Context, args ...interface{}) AsyncAssertion
106 Consistently(ctx context.Context, args ...interface{}) AsyncAssertion
107 }
108
109 type Assertion interface {
110 Should(matcher types.GomegaMatcher) error
111 ShouldNot(matcher types.GomegaMatcher) error
112 To(matcher types.GomegaMatcher) error
113 ToNot(matcher types.GomegaMatcher) error
114 NotTo(matcher types.GomegaMatcher) error
115 }
116
117 type AsyncAssertion interface {
118 Should(matcher types.GomegaMatcher) error
119 ShouldNot(matcher types.GomegaMatcher) error
120
121 WithTimeout(interval time.Duration) AsyncAssertion
122 WithPolling(interval time.Duration) AsyncAssertion
123 }
124
125 type gomegaInstance struct{}
126
127 var _ GomegaInstance = gomegaInstance{}
128
129 func (g gomegaInstance) Expect(actual interface{}) Assertion {
130 return assertion{actual: actual}
131 }
132
133 func (g gomegaInstance) Eventually(ctx context.Context, args ...interface{}) AsyncAssertion {
134 return newAsyncAssertion(ctx, args, false)
135 }
136
137 func (g gomegaInstance) Consistently(ctx context.Context, args ...interface{}) AsyncAssertion {
138 return newAsyncAssertion(ctx, args, true)
139 }
140
141 func newG() (*FailureError, gomega.Gomega) {
142 var failure FailureError
143 g := gomega.NewGomega(func(msg string, callerSkip ...int) {
144 failure = FailureError{
145 msg: msg,
146 }
147 })
148
149 return &failure, g
150 }
151
152 type assertion struct {
153 actual interface{}
154 }
155
156 func (a assertion) Should(matcher types.GomegaMatcher) error {
157 err, g := newG()
158 if !g.Expect(a.actual).Should(matcher) {
159 err.backtrace()
160 return *err
161 }
162 return nil
163 }
164
165 func (a assertion) ShouldNot(matcher types.GomegaMatcher) error {
166 err, g := newG()
167 if !g.Expect(a.actual).ShouldNot(matcher) {
168 err.backtrace()
169 return *err
170 }
171 return nil
172 }
173
174 func (a assertion) To(matcher types.GomegaMatcher) error {
175 err, g := newG()
176 if !g.Expect(a.actual).To(matcher) {
177 err.backtrace()
178 return *err
179 }
180 return nil
181 }
182
183 func (a assertion) ToNot(matcher types.GomegaMatcher) error {
184 err, g := newG()
185 if !g.Expect(a.actual).ToNot(matcher) {
186 err.backtrace()
187 return *err
188 }
189 return nil
190 }
191
192 func (a assertion) NotTo(matcher types.GomegaMatcher) error {
193 err, g := newG()
194 if !g.Expect(a.actual).NotTo(matcher) {
195 err.backtrace()
196 return *err
197 }
198 return nil
199 }
200
201 type asyncAssertion struct {
202 ctx context.Context
203 args []interface{}
204 timeout time.Duration
205 interval time.Duration
206 consistently bool
207 }
208
209 func newAsyncAssertion(ctx context.Context, args []interface{}, consistently bool) asyncAssertion {
210 return asyncAssertion{
211 ctx: ctx,
212 args: args,
213
214
215 timeout: TestContext.timeouts.PodStart,
216 interval: TestContext.timeouts.Poll,
217 consistently: consistently,
218 }
219 }
220
221 func (a asyncAssertion) newAsync() (*FailureError, gomega.AsyncAssertion) {
222 err, g := newG()
223 var assertion gomega.AsyncAssertion
224 if a.consistently {
225 assertion = g.Consistently(a.ctx, a.args...)
226 } else {
227 assertion = g.Eventually(a.ctx, a.args...)
228 }
229 assertion = assertion.WithTimeout(a.timeout).WithPolling(a.interval)
230 return err, assertion
231 }
232
233 func (a asyncAssertion) Should(matcher types.GomegaMatcher) error {
234 err, assertion := a.newAsync()
235 if !assertion.Should(matcher) {
236 err.backtrace()
237 return *err
238 }
239 return nil
240 }
241
242 func (a asyncAssertion) ShouldNot(matcher types.GomegaMatcher) error {
243 err, assertion := a.newAsync()
244 if !assertion.ShouldNot(matcher) {
245 err.backtrace()
246 return *err
247 }
248 return nil
249 }
250
251 func (a asyncAssertion) WithTimeout(timeout time.Duration) AsyncAssertion {
252 a.timeout = timeout
253 return a
254 }
255
256 func (a asyncAssertion) WithPolling(interval time.Duration) AsyncAssertion {
257 a.interval = interval
258 return a
259 }
260
261
262
263
264 type FailureError struct {
265 msg string
266 fullStackTrace string
267 }
268
269 func (f FailureError) Error() string {
270 return f.msg
271 }
272
273 func (f FailureError) Backtrace() string {
274 return f.fullStackTrace
275 }
276
277 func (f FailureError) Is(target error) bool {
278 return target == ErrFailure
279 }
280
281 func (f *FailureError) backtrace() {
282 f.fullStackTrace = ginkgotypes.NewCodeLocationWithStackTrace(2).FullStackTrace
283 }
284
285
286
287
288
289
290
291
292
293
294 var ErrFailure error = FailureError{}
295
296
297
298
299
300
301 func ExpectError(err error, explain ...interface{}) {
302 gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
303 }
304
305
306 func ExpectNoError(err error, explain ...interface{}) {
307 ExpectNoErrorWithOffset(1, err, explain...)
308 }
309
310
311
312 func ExpectNoErrorWithOffset(offset int, err error, explain ...interface{}) {
313 if err == nil {
314 return
315 }
316
317
318
319 prefix := ""
320 if len(explain) > 0 {
321 if str, ok := explain[0].(string); ok {
322 prefix = fmt.Sprintf(str, explain[1:]...) + ": "
323 } else {
324 prefix = fmt.Sprintf("unexpected explain arguments, need format string: %v", explain)
325 }
326 }
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344 var failure FailureError
345 if errors.As(err, &failure) && failure.Backtrace() != "" {
346 log(offset+1, fmt.Sprintf("Failed inside E2E framework:\n %s", strings.ReplaceAll(failure.Backtrace(), "\n", "\n ")))
347 } else if !errors.Is(err, ErrFailure) {
348 log(offset+1, fmt.Sprintf("Unexpected error: %s\n%s", prefix, format.Object(err, 1)))
349 }
350 Fail(prefix+err.Error(), 1+offset)
351 }
352
View as plain text