uassert.gno
14.73 Kb ยท 619 lines
1// uassert is an adapted lighter version of https://github.com/stretchr/testify/assert.
2package uassert
3
4import (
5 "std"
6 "strconv"
7 "strings"
8
9 "gno.land/p/demo/diff"
10 "gno.land/p/demo/ufmt"
11)
12
13// NoError asserts that a function returned no error (i.e. `nil`).
14func NoError(t TestingT, err error, msgs ...string) bool {
15 t.Helper()
16 if err != nil {
17 return fail(t, msgs, "unexpected error: %s", err.Error())
18 }
19 return true
20}
21
22// Error asserts that a function returned an error (i.e. not `nil`).
23func Error(t TestingT, err error, msgs ...string) bool {
24 t.Helper()
25 if err == nil {
26 return fail(t, msgs, "an error is expected but got nil")
27 }
28 return true
29}
30
31// ErrorContains asserts that a function returned an error (i.e. not `nil`)
32// and that the error contains the specified substring.
33func ErrorContains(t TestingT, err error, contains string, msgs ...string) bool {
34 t.Helper()
35
36 if !Error(t, err, msgs...) {
37 return false
38 }
39
40 actual := err.Error()
41 if !strings.Contains(actual, contains) {
42 return fail(t, msgs, "error %q does not contain %q", actual, contains)
43 }
44
45 return true
46}
47
48// True asserts that the specified value is true.
49func True(t TestingT, value bool, msgs ...string) bool {
50 t.Helper()
51 if !value {
52 return fail(t, msgs, "should be true")
53 }
54 return true
55}
56
57// False asserts that the specified value is false.
58func False(t TestingT, value bool, msgs ...string) bool {
59 t.Helper()
60 if value {
61 return fail(t, msgs, "should be false")
62 }
63 return true
64}
65
66// ErrorIs asserts the given error matches the target error
67func ErrorIs(t TestingT, err, target error, msgs ...string) bool {
68 t.Helper()
69
70 if err == nil || target == nil {
71 return err == target
72 }
73
74 // XXX: if errors.Is(err, target) return true
75
76 if err.Error() != target.Error() {
77 return fail(t, msgs, "error mismatch, expected %s, got %s", target.Error(), err.Error())
78 }
79
80 return true
81}
82
83// AbortsWithMessage asserts that the code inside the specified func aborts
84// (panics when crossing another realm).
85// Use PanicsWithMessage for asserting local panics within the same realm.
86//
87// NOTE: This relies on gno's `revive` mechanism to catch aborts.
88func AbortsWithMessage(t TestingT, msg string, f any, msgs ...string) bool {
89 t.Helper()
90
91 var didAbort bool
92 var abortValue any
93 var r any
94
95 if fn, ok := f.(func()); ok {
96 r = revive(fn)
97 } else if fn, ok := f.(func(realm)); ok {
98 r = revive(func() { fn(cross) })
99 } else {
100 panic("f must be of type func() or func(realm)")
101 }
102 if r != nil {
103 didAbort = true
104 abortValue = r
105 }
106
107 if !didAbort {
108 // If the function didn't abort as expected
109 return fail(t, msgs, "func should abort")
110 }
111
112 // Check if the abort value matches the expected message string
113 abortStr := ufmt.Sprintf("%v", abortValue)
114 if abortStr != msg {
115 return fail(t, msgs, "func should abort with message:\t%q\n\tActual abort value:\t%q", msg, abortStr)
116 }
117
118 // Success: function aborted with the expected message
119 return true
120}
121
122// NotAborts asserts that the code inside the specified func does NOT abort
123// when crossing an execution boundary.
124// Note: Consider using NotPanics which checks for both panics and aborts.
125func NotAborts(t TestingT, f any, msgs ...string) bool {
126 t.Helper()
127
128 var didAbort bool
129 var abortValue any
130 var r any
131
132 if fn, ok := f.(func()); ok {
133 r = revive(fn) // revive() captures the value passed to panic()
134 } else if fn, ok := f.(func(realm)); ok {
135 r = revive(func() { fn(cross) })
136 } else {
137 panic("f must be of type func() or func(realm)")
138 }
139 if r != nil {
140 didAbort = true
141 abortValue = r
142 }
143
144 if didAbort {
145 // Fail if the function aborted when it shouldn't have
146 // Attempt to format the abort value in the error message
147 return fail(t, msgs, "func should not abort\\n\\tAbort value:\\t%v", abortValue)
148 }
149
150 // Success: function did not abort
151 return true
152}
153
154// PanicsWithMessage asserts that the code inside the specified func panics
155// locally within the same execution realm.
156// Use AbortsWithMessage for asserting panics that cross execution boundaries (aborts).
157func PanicsWithMessage(t TestingT, msg string, f any, msgs ...string) bool {
158 t.Helper()
159
160 didPanic, panicValue := checkDidPanic(f)
161 if !didPanic {
162 return fail(t, msgs, "func should panic\n\tPanic value:\t%v", panicValue)
163 }
164
165 // Check if the abort value matches the expected message string
166 panicStr := ufmt.Sprintf("%v", panicValue)
167 if panicStr != msg {
168 return fail(t, msgs, "func should panic with message:\t%q\n\tActual panic value:\t%q", msg, panicStr)
169 }
170 return true
171}
172
173// NotPanics asserts that the code inside the specified func does NOT panic
174// (within the same realm) or abort (due to a cross-realm panic).
175func NotPanics(t TestingT, f any, msgs ...string) bool {
176 t.Helper()
177
178 var panicVal any
179 var didPanic bool
180 var abortVal any
181
182 // Use revive to catch cross-realm aborts
183 abortVal = revive(func() {
184 // Use defer+recover to catch same-realm panics
185 defer func() {
186 if r := recover(); r != nil {
187 didPanic = true
188 panicVal = r
189 }
190 }()
191 // Execute the function
192 if fn, ok := f.(func()); ok {
193 fn()
194 } else if fn, ok := f.(func(realm)); ok {
195 fn(cross)
196 } else {
197 panic("f must be of type func() or func(realm)")
198 }
199 })
200
201 // Check if revive caught an abort
202 if abortVal != nil {
203 return fail(t, msgs, "func should not abort\n\tAbort value:\t%+v", abortVal)
204 }
205
206 // Check if recover caught a panic
207 if didPanic {
208 // Format panic value for message
209 panicMsg := ""
210 if panicVal == nil {
211 panicMsg = "nil"
212 } else if err, ok := panicVal.(error); ok {
213 panicMsg = err.Error()
214 } else if str, ok := panicVal.(string); ok {
215 panicMsg = str
216 } else {
217 // Fallback for other types
218 panicMsg = "panic: unsupported type"
219 }
220 return fail(t, msgs, "func should not panic\n\tPanic value:\t%s", panicMsg)
221 }
222
223 return true // No panic or abort occurred
224}
225
226// Equal asserts that two objects are equal.
227func Equal(t TestingT, expected, actual any, msgs ...string) bool {
228 t.Helper()
229
230 if expected == nil || actual == nil {
231 return expected == actual
232 }
233
234 // XXX: errors
235 // XXX: slices
236 // XXX: pointers
237
238 equal := false
239 ok_ := false
240 es, as := "unsupported type", "unsupported type"
241
242 switch ev := expected.(type) {
243 case string:
244 if av, ok := actual.(string); ok {
245 equal = ev == av
246 ok_ = true
247 es, as = ev, av
248 if !equal {
249 dif := diff.MyersDiff(ev, av)
250 return fail(t, msgs, "uassert.Equal: strings are different\n\tDiff: %s", diff.Format(dif))
251 }
252 }
253 case std.Address:
254 if av, ok := actual.(std.Address); ok {
255 equal = ev == av
256 ok_ = true
257 es, as = string(ev), string(av)
258 }
259 case int:
260 if av, ok := actual.(int); ok {
261 equal = ev == av
262 ok_ = true
263 es, as = strconv.Itoa(ev), strconv.Itoa(av)
264 }
265 case int8:
266 if av, ok := actual.(int8); ok {
267 equal = ev == av
268 ok_ = true
269 es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))
270 }
271 case int16:
272 if av, ok := actual.(int16); ok {
273 equal = ev == av
274 ok_ = true
275 es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))
276 }
277 case int32:
278 if av, ok := actual.(int32); ok {
279 equal = ev == av
280 ok_ = true
281 es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))
282 }
283 case int64:
284 if av, ok := actual.(int64); ok {
285 equal = ev == av
286 ok_ = true
287 es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))
288 }
289 case uint:
290 if av, ok := actual.(uint); ok {
291 equal = ev == av
292 ok_ = true
293 es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)
294 }
295 case uint8:
296 if av, ok := actual.(uint8); ok {
297 equal = ev == av
298 ok_ = true
299 es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)
300 }
301 case uint16:
302 if av, ok := actual.(uint16); ok {
303 equal = ev == av
304 ok_ = true
305 es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)
306 }
307 case uint32:
308 if av, ok := actual.(uint32); ok {
309 equal = ev == av
310 ok_ = true
311 es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)
312 }
313 case uint64:
314 if av, ok := actual.(uint64); ok {
315 equal = ev == av
316 ok_ = true
317 es, as = strconv.FormatUint(ev, 10), strconv.FormatUint(av, 10)
318 }
319 case bool:
320 if av, ok := actual.(bool); ok {
321 equal = ev == av
322 ok_ = true
323 if ev {
324 es, as = "true", "false"
325 } else {
326 es, as = "false", "true"
327 }
328 }
329 case float32:
330 if av, ok := actual.(float32); ok {
331 equal = ev == av
332 ok_ = true
333 }
334 case float64:
335 if av, ok := actual.(float64); ok {
336 equal = ev == av
337 ok_ = true
338 }
339 default:
340 return fail(t, msgs, "uassert.Equal: unsupported type")
341 }
342
343 /*
344 // XXX: implement stringer and other well known similar interfaces
345 type stringer interface{ String() string }
346 if ev, ok := expected.(stringer); ok {
347 if av, ok := actual.(stringer); ok {
348 equal = ev.String() == av.String()
349 ok_ = true
350 }
351 }
352 */
353
354 if !ok_ {
355 return fail(t, msgs, "uassert.Equal: different types") // XXX: display the types
356 }
357 if !equal {
358 return fail(t, msgs, "uassert.Equal: same type but different value\n\texpected: %s\n\tactual: %s", es, as)
359 }
360
361 return true
362}
363
364// NotEqual asserts that two objects are not equal.
365func NotEqual(t TestingT, expected, actual any, msgs ...string) bool {
366 t.Helper()
367
368 if expected == nil || actual == nil {
369 return expected != actual
370 }
371
372 // XXX: errors
373 // XXX: slices
374 // XXX: pointers
375
376 notEqual := false
377 ok_ := false
378 es, as := "unsupported type", "unsupported type"
379
380 switch ev := expected.(type) {
381 case string:
382 if av, ok := actual.(string); ok {
383 notEqual = ev != av
384 ok_ = true
385 es, as = ev, av
386 }
387 case std.Address:
388 if av, ok := actual.(std.Address); ok {
389 notEqual = ev != av
390 ok_ = true
391 es, as = string(ev), string(av)
392 }
393 case int:
394 if av, ok := actual.(int); ok {
395 notEqual = ev != av
396 ok_ = true
397 es, as = strconv.Itoa(ev), strconv.Itoa(av)
398 }
399 case int8:
400 if av, ok := actual.(int8); ok {
401 notEqual = ev != av
402 ok_ = true
403 es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))
404 }
405 case int16:
406 if av, ok := actual.(int16); ok {
407 notEqual = ev != av
408 ok_ = true
409 es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))
410 }
411 case int32:
412 if av, ok := actual.(int32); ok {
413 notEqual = ev != av
414 ok_ = true
415 es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))
416 }
417 case int64:
418 if av, ok := actual.(int64); ok {
419 notEqual = ev != av
420 ok_ = true
421 es, as = strconv.Itoa(int(ev)), strconv.Itoa(int(av))
422 }
423 case uint:
424 if av, ok := actual.(uint); ok {
425 notEqual = ev != av
426 ok_ = true
427 es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)
428 }
429 case uint8:
430 if av, ok := actual.(uint8); ok {
431 notEqual = ev != av
432 ok_ = true
433 es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)
434 }
435 case uint16:
436 if av, ok := actual.(uint16); ok {
437 notEqual = ev != av
438 ok_ = true
439 es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)
440 }
441 case uint32:
442 if av, ok := actual.(uint32); ok {
443 notEqual = ev != av
444 ok_ = true
445 es, as = strconv.FormatUint(uint64(ev), 10), strconv.FormatUint(uint64(av), 10)
446 }
447 case uint64:
448 if av, ok := actual.(uint64); ok {
449 notEqual = ev != av
450 ok_ = true
451 es, as = strconv.FormatUint(ev, 10), strconv.FormatUint(av, 10)
452 }
453 case bool:
454 if av, ok := actual.(bool); ok {
455 notEqual = ev != av
456 ok_ = true
457 if ev {
458 es, as = "true", "false"
459 } else {
460 es, as = "false", "true"
461 }
462 }
463 case float32:
464 if av, ok := actual.(float32); ok {
465 notEqual = ev != av
466 ok_ = true
467 }
468 case float64:
469 if av, ok := actual.(float64); ok {
470 notEqual = ev != av
471 ok_ = true
472 }
473 default:
474 return fail(t, msgs, "uassert.NotEqual: unsupported type")
475 }
476
477 /*
478 // XXX: implement stringer and other well known similar interfaces
479 type stringer interface{ String() string }
480 if ev, ok := expected.(stringer); ok {
481 if av, ok := actual.(stringer); ok {
482 notEqual = ev.String() != av.String()
483 ok_ = true
484 }
485 }
486 */
487
488 if !ok_ {
489 return fail(t, msgs, "uassert.NotEqual: different types") // XXX: display the types
490 }
491 if !notEqual {
492 return fail(t, msgs, "uassert.NotEqual: same type and same value\n\texpected: %s\n\tactual: %s", es, as)
493 }
494
495 return true
496}
497
498func isNumberEmpty(n any) (isNumber, isEmpty bool) {
499 switch n := n.(type) {
500 // NOTE: the cases are split individually, so that n becomes of the
501 // asserted type; the type of '0' was correctly inferred and converted
502 // to the corresponding type, int, int8, etc.
503 case int:
504 return true, n == 0
505 case int8:
506 return true, n == 0
507 case int16:
508 return true, n == 0
509 case int32:
510 return true, n == 0
511 case int64:
512 return true, n == 0
513 case uint:
514 return true, n == 0
515 case uint8:
516 return true, n == 0
517 case uint16:
518 return true, n == 0
519 case uint32:
520 return true, n == 0
521 case uint64:
522 return true, n == 0
523 case float32:
524 return true, n == 0
525 case float64:
526 return true, n == 0
527 }
528 return false, false
529}
530
531func Empty(t TestingT, obj any, msgs ...string) bool {
532 t.Helper()
533
534 isNumber, isEmpty := isNumberEmpty(obj)
535 if isNumber {
536 if !isEmpty {
537 return fail(t, msgs, "uassert.Empty: not empty number: %d", obj)
538 }
539 } else {
540 switch val := obj.(type) {
541 case string:
542 if val != "" {
543 return fail(t, msgs, "uassert.Empty: not empty string: %s", val)
544 }
545 case std.Address:
546 var zeroAddr std.Address
547 if val != zeroAddr {
548 return fail(t, msgs, "uassert.Empty: not empty std.Address: %s", string(val))
549 }
550 default:
551 return fail(t, msgs, "uassert.Empty: unsupported type")
552 }
553 }
554 return true
555}
556
557func NotEmpty(t TestingT, obj any, msgs ...string) bool {
558 t.Helper()
559 isNumber, isEmpty := isNumberEmpty(obj)
560 if isNumber {
561 if isEmpty {
562 return fail(t, msgs, "uassert.NotEmpty: empty number: %d", obj)
563 }
564 } else {
565 switch val := obj.(type) {
566 case string:
567 if val == "" {
568 return fail(t, msgs, "uassert.NotEmpty: empty string: %s", val)
569 }
570 case std.Address:
571 var zeroAddr std.Address
572 if val == zeroAddr {
573 return fail(t, msgs, "uassert.NotEmpty: empty std.Address: %s", string(val))
574 }
575 default:
576 return fail(t, msgs, "uassert.NotEmpty: unsupported type")
577 }
578 }
579 return true
580}
581
582// Nil asserts that the value is nil.
583func Nil(t TestingT, value any, msgs ...string) bool {
584 t.Helper()
585 if value != nil {
586 return fail(t, msgs, "should be nil")
587 }
588 return true
589}
590
591// NotNil asserts that the value is not nil.
592func NotNil(t TestingT, value any, msgs ...string) bool {
593 t.Helper()
594 if value == nil {
595 return fail(t, msgs, "should not be nil")
596 }
597 return true
598}
599
600// TypedNil asserts that the value is a typed-nil (nil pointer) value.
601func TypedNil(t TestingT, value any, msgs ...string) bool {
602 t.Helper()
603 if value == nil {
604 return fail(t, msgs, "should be typed-nil but got nil instead")
605 }
606 if !istypednil(value) {
607 return fail(t, msgs, "should be typed-nil")
608 }
609 return true
610}
611
612// NotTypedNil asserts that the value is not a typed-nil (nil pointer) value.
613func NotTypedNil(t TestingT, value any, msgs ...string) bool {
614 t.Helper()
615 if istypednil(value) {
616 return fail(t, msgs, "should not be typed-nil")
617 }
618 return true
619}