Replace stretchr/testify with an internal test suite (#981)

As recommended, an `internal/assert` package was added with a reduced set of assertions. All tests were then refactored to use the internal assertions. When more complex assertions were used, they have been rewritten using logic and the simplified assertions.

Fancy formatting for failures was omitted. The `internal/assert/assertions.diff` function could be overwritten for better formatting. That is where diff libraries are used in other test suites.

Refs: #872

Co-authored-by: Alex Mikitik <alex.mikitik@oracle.com>
This commit is contained in:
Alex Mikitik
2025-04-07 03:36:37 -07:00
committed by GitHub
parent 923b2ab478
commit 014204cfb7
22 changed files with 681 additions and 352 deletions
+135
View File
@@ -0,0 +1,135 @@
package assert
import (
"bytes"
"fmt"
"reflect"
"strings"
"testing"
)
// True asserts that an expression is true.
func True(t testing.TB, ok bool, msgAndArgs ...any) {
if ok {
return
}
t.Helper()
t.Fatal(formatMsgAndArgs("Expected expression to be true", msgAndArgs...))
}
// False asserts that an expression is false.
func False(t testing.TB, ok bool, msgAndArgs ...any) {
if !ok {
return
}
t.Helper()
t.Fatal(formatMsgAndArgs("Expected expression to be false", msgAndArgs...))
}
// Equal asserts that "expected" and "actual" are equal.
func Equal[T any](t testing.TB, expected, actual T, msgAndArgs ...any) {
if objectsAreEqual(expected, actual) {
return
}
t.Helper()
msg := formatMsgAndArgs("Expected values to be equal:", msgAndArgs...)
t.Fatalf("%s\n%s", msg, diff(expected, actual))
}
// Error asserts that an error is not nil.
func Error(t testing.TB, err error, msgAndArgs ...any) {
if err != nil {
return
}
t.Helper()
t.Fatal(formatMsgAndArgs("Expected an error", msgAndArgs...))
}
// NoError asserts that an error is nil.
func NoError(t testing.TB, err error, msgAndArgs ...any) {
if err == nil {
return
}
t.Helper()
msg := formatMsgAndArgs("Unexpected error:", msgAndArgs...)
t.Fatalf("%s\n%+v", msg, err)
}
// Panics asserts that the given function panics.
func Panics(t testing.TB, fn func(), msgAndArgs ...any) {
t.Helper()
defer func() {
if recover() == nil {
msg := formatMsgAndArgs("Expected function to panic", msgAndArgs...)
t.Fatal(msg)
}
}()
fn()
}
// Zero asserts that a value is its zero value.
func Zero[T any](t testing.TB, value T, msgAndArgs ...any) {
var zero T
if objectsAreEqual(value, zero) {
return
}
val := reflect.ValueOf(value)
if (val.Kind() == reflect.Slice || val.Kind() == reflect.Map || val.Kind() == reflect.Array) && val.Len() == 0 {
return
}
t.Helper()
msg := formatMsgAndArgs("Expected zero value but got:", msgAndArgs...)
t.Fatalf("%s\n%s", msg, fmt.Sprintf("%v", value))
}
func NotZero[T any](t testing.TB, value T, msgAndArgs ...any) {
var zero T
if !objectsAreEqual(value, zero) {
val := reflect.ValueOf(value)
if !((val.Kind() == reflect.Slice || val.Kind() == reflect.Map || val.Kind() == reflect.Array) && val.Len() == 0) {
return
}
}
t.Helper()
msg := formatMsgAndArgs("Unexpected zero value:", msgAndArgs...)
t.Fatalf("%s\n%s", msg, fmt.Sprintf("%v", value))
}
func formatMsgAndArgs(msg string, args ...any) string {
if len(args) == 0 {
return msg
}
format, ok := args[0].(string)
if !ok {
panic("message argument must be a fmt string")
}
return fmt.Sprintf(format, args[1:]...)
}
func diff(expected, actual any) string {
lines := []string{
"expected:",
fmt.Sprintf("%v", expected),
"actual:",
fmt.Sprintf("%v", actual),
}
return strings.Join(lines, "\n")
}
func objectsAreEqual(expected, actual any) bool {
if expected == nil || actual == nil {
return expected == actual
}
if exp, eok := expected.([]byte); eok {
if act, aok := actual.([]byte); aok {
return bytes.Equal(exp, act)
}
}
if exp, eok := expected.(string); eok {
if act, aok := actual.(string); aok {
return exp == act
}
}
return reflect.DeepEqual(expected, actual)
}
+184
View File
@@ -0,0 +1,184 @@
package assert
import (
"fmt"
"testing"
)
type Data struct {
Label string
Value int64
}
func TestBadMessage(t *testing.T) {
invalidMessage := func() { True(t, false, 1234) }
assertOk(t, "Non-fmt message value", func(t testing.TB) {
Panics(t, invalidMessage)
})
assertFail(t, "Non-fmt message value", func(t testing.TB) {
True(t, false, "example %s", "message")
})
}
func TestTrue(t *testing.T) {
assertOk(t, "Succeed", func(t testing.TB) {
True(t, 1 > 0)
})
assertFail(t, "Fail", func(t testing.TB) {
True(t, 1 < 0)
})
}
func TestFalse(t *testing.T) {
assertOk(t, "Succeed", func(t testing.TB) {
False(t, 1 < 0)
})
assertFail(t, "Fail", func(t testing.TB) {
False(t, 1 > 0)
})
}
func TestEqual(t *testing.T) {
assertOk(t, "Nil", func(t testing.TB) {
Equal(t, interface{}(nil), interface{}(nil))
})
assertOk(t, "Identical structs", func(t testing.TB) {
Equal(t, Data{"expected", 1234}, Data{"expected", 1234})
})
assertFail(t, "Different structs", func(t testing.TB) {
Equal(t, Data{"expected", 1234}, Data{"actual", 1234})
})
assertOk(t, "Identical numbers", func(t testing.TB) {
Equal(t, 1234, 1234)
})
assertFail(t, "Identical numbers", func(t testing.TB) {
Equal(t, 1234, 1324)
})
assertOk(t, "Zero-length byte arrays", func(t testing.TB) {
Equal(t, []byte(nil), []byte(""))
})
assertOk(t, "Identical byte arrays", func(t testing.TB) {
Equal(t, []byte{1, 2, 3, 4}, []byte{1, 2, 3, 4})
})
assertFail(t, "Different byte arrays", func(t testing.TB) {
Equal(t, []byte{1, 2, 3, 4}, []byte{1, 3, 2, 4})
})
assertOk(t, "Identical strings", func(t testing.TB) {
Equal(t, "example", "example")
})
assertFail(t, "Identical strings", func(t testing.TB) {
Equal(t, "example", "elpmaxe")
})
}
func TestError(t *testing.T) {
assertOk(t, "Error", func(t testing.TB) {
Error(t, fmt.Errorf("example"))
})
assertFail(t, "Nil", func(t testing.TB) {
Error(t, nil)
})
}
func TestNoError(t *testing.T) {
assertFail(t, "Error", func(t testing.TB) {
NoError(t, fmt.Errorf("example"))
})
assertOk(t, "Nil", func(t testing.TB) {
NoError(t, nil)
})
}
func TestPanics(t *testing.T) {
willPanic := func() { panic("example") }
wontPanic := func() {}
assertOk(t, "Will panic", func(t testing.TB) {
Panics(t, willPanic)
})
assertFail(t, "Won't panic", func(t testing.TB) {
Panics(t, wontPanic)
})
}
func TestZero(t *testing.T) {
assertOk(t, "Empty struct", func(t testing.TB) {
Zero(t, Data{})
})
assertFail(t, "Non-empty struct", func(t testing.TB) {
Zero(t, Data{Label: "example"})
})
assertOk(t, "Nil slice", func(t testing.TB) {
var slice []int
Zero(t, slice)
})
assertFail(t, "Non-empty slice", func(t testing.TB) {
slice := []int{1, 2, 3, 4}
Zero(t, slice)
})
assertOk(t, "Zero-length slice", func(t testing.TB) {
slice := []int{}
Zero(t, slice)
})
}
func TestNotZero(t *testing.T) {
assertFail(t, "Empty struct", func(t testing.TB) {
zero := Data{}
NotZero(t, zero)
})
assertOk(t, "Non-empty struct", func(t testing.TB) {
notZero := Data{Label: "example"}
NotZero(t, notZero)
})
assertFail(t, "Nil slice", func(t testing.TB) {
var slice []int
NotZero(t, slice)
})
assertFail(t, "Zero-length slice", func(t testing.TB) {
slice := []int{}
NotZero(t, slice)
})
assertOk(t, "Non-empty slice", func(t testing.TB) {
slice := []int{1, 2, 3, 4}
NotZero(t, slice)
})
}
type testCase struct {
*testing.T
failed string
}
func (t *testCase) Fatal(args ...interface{}) {
t.failed = fmt.Sprint(args...)
}
func (t *testCase) Fatalf(message string, args ...interface{}) {
t.failed = fmt.Sprintf(message, args...)
}
func assertFail(t *testing.T, name string, fn func(t testing.TB)) {
t.Helper()
t.Run(name, func(t *testing.T) {
t.Helper()
test := &testCase{T: t}
fn(test)
if test.failed == "" {
t.Fatal("Test expected to fail but did not")
} else {
t.Log(test.failed)
}
})
}
func assertOk(t *testing.T, name string, fn func(t testing.TB)) {
t.Helper()
t.Run(name, func(t *testing.T) {
t.Helper()
test := &testCase{T: t}
fn(test)
if test.failed != "" {
t.Fatal("Test expected to succeed but did not:\n", test.failed)
}
})
}