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:
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user