You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ntool/ntest/assert/asserts.go

731 lines
18 KiB
Go

package assert
import (
"errors"
"fmt"
"git.noahlan.cn/noahlan/ntool/narr"
"git.noahlan.cn/noahlan/ntool/nmap"
"git.noahlan.cn/noahlan/ntool/nmath"
"git.noahlan.cn/noahlan/ntool/nreflect"
"git.noahlan.cn/noahlan/ntool/nstd"
"github.com/gookit/color"
"reflect"
"runtime/debug"
"strings"
)
// Nil asserts that the given is a nil value
func Nil(t TestingT, give any, fmtAndArgs ...any) bool {
if nstd.IsNil(give) {
return true
}
t.Helper()
return fail(t, fmt.Sprintf("Expected nil, but got: %#v", give), fmtAndArgs)
}
// NotNil asserts that the given is a not nil value
func NotNil(t TestingT, give any, fmtAndArgs ...any) bool {
if !nstd.IsNil(give) {
return true
}
t.Helper()
return fail(t, "Should not nil value", fmtAndArgs)
}
// True asserts that the given is a bool true
func True(t TestingT, give bool, fmtAndArgs ...any) bool {
if !give {
t.Helper()
return fail(t, "Result should be True", fmtAndArgs)
}
return true
}
// False asserts that the given is a bool false
func False(t TestingT, give bool, fmtAndArgs ...any) bool {
if give {
t.Helper()
return fail(t, "Result should be False", fmtAndArgs)
}
return true
}
// Empty asserts that the give should be empty
func Empty(t TestingT, give any, fmtAndArgs ...any) bool {
empty := isEmpty(give)
if !empty {
t.Helper()
return fail(t, fmt.Sprintf("Should be empty, but was:\n%#v", give), fmtAndArgs)
}
return empty
}
// NotEmpty asserts that the give should not be empty
func NotEmpty(t TestingT, give any, fmtAndArgs ...any) bool {
nEmpty := !isEmpty(give)
if !nEmpty {
t.Helper()
return fail(t, fmt.Sprintf("Should not be empty, but was:\n%#v", give), fmtAndArgs)
}
return nEmpty
}
// PanicRunFunc define
type PanicRunFunc func()
// didPanic returns true if the function passed to it panics. Otherwise, it returns false.
func runPanicFunc(f PanicRunFunc) (didPanic bool, message any, stack string) {
didPanic = true
defer func() {
message = recover()
if didPanic {
stack = string(debug.Stack())
}
}()
// call the target function
f()
didPanic = false
return
}
// Panics asserts that the code inside the specified func panics.
func Panics(t TestingT, fn PanicRunFunc, fmtAndArgs ...any) bool {
if hasPanic, panicVal, _ := runPanicFunc(fn); !hasPanic {
t.Helper()
return fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", fn, panicVal), fmtAndArgs)
}
return true
}
// NotPanics asserts that the code inside the specified func NOT panics.
func NotPanics(t TestingT, fn PanicRunFunc, fmtAndArgs ...any) bool {
if hasPanic, panicVal, stackMsg := runPanicFunc(fn); hasPanic {
t.Helper()
return fail(t, fmt.Sprintf(
"func %#v should not panic\n\tPanic value:\t%#v\n\tPanic stack:\t%s",
fn, panicVal, stackMsg,
), fmtAndArgs,
)
}
return true
}
// PanicsMsg should panic and with a value
func PanicsMsg(t TestingT, fn PanicRunFunc, wantVal any, fmtAndArgs ...any) bool {
hasPanic, panicVal, stackMsg := runPanicFunc(fn)
if !hasPanic {
t.Helper()
return fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", fn, panicVal), fmtAndArgs)
}
if panicVal != wantVal {
t.Helper()
return fail(t, fmt.Sprintf(
"func %#v should panic.\n\tWant value:\t%#v\n\tPanic value:\t%#v\n\tPanic stack:\t%s",
fn, wantVal, panicVal, stackMsg),
fmtAndArgs,
)
}
return true
}
// PanicsErrMsg should panic and with error message
func PanicsErrMsg(t TestingT, fn PanicRunFunc, errMsg string, fmtAndArgs ...any) bool {
hasPanic, panicVal, stackMsg := runPanicFunc(fn)
if !hasPanic {
t.Helper()
return fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", fn, panicVal), fmtAndArgs)
}
err, ok := panicVal.(error)
if !ok {
t.Helper()
return fail(t, fmt.Sprintf("func %#v should panic and is error type,\nbut type was: %T", fn, panicVal), fmtAndArgs)
}
if err.Error() != errMsg {
t.Helper()
return fail(t, fmt.Sprintf(
"func %#v should panic.\n\tWant error:\t%#v\n\tPanic value:\t%#v\n\tPanic stack:\t%s",
fn, errMsg, panicVal, stackMsg),
fmtAndArgs,
)
}
return true
}
// Contains asserts that the given data(string,slice,map) should contain element
//
// TIP: only support types: string, map, array, slice
//
// map - check key exists
// string - check sub-string exists
// array,slice - check sub-element exists
func Contains(t TestingT, src, elem any, fmtAndArgs ...any) bool {
valid, found := nstd.CheckContains(src, elem)
if valid && found {
return true
}
t.Helper()
// src invalid
if !valid {
return fail(t, fmt.Sprintf("%#v could not be applied builtin len()", src), fmtAndArgs)
}
// not found
return fail(t, fmt.Sprintf("%#v\nShould contain: %#v", src, elem), fmtAndArgs)
}
// NotContains asserts that the given data(string,slice,map) should not contain element
//
// TIP: only support types: string, map, array, slice
//
// map - check key exists
// string - check sub-string exists
// array,slice - check sub-element exists
func NotContains(t TestingT, src, elem any, fmtAndArgs ...any) bool {
valid, found := nstd.CheckContains(src, elem)
if valid && !found {
return true
}
t.Helper()
// src invalid
if !valid {
return fail(t, fmt.Sprintf("%#v could not be applied builtin len()", src), fmtAndArgs)
}
// found
return fail(t, fmt.Sprintf("%#v\nShould not contain: %#v", src, elem), fmtAndArgs)
}
// ContainsKey asserts that the given map is contains key
func ContainsKey(t TestingT, mp, key any, fmtAndArgs ...any) bool {
if !nmap.HasKey(mp, key) {
t.Helper()
return fail(t,
fmt.Sprintf(
"Map should contains the key: %#v\nMap data:\n%v",
key,
nmap.FormatIndent(mp, " "),
),
fmtAndArgs,
)
}
return true
}
// NotContainsKey asserts that the given map is not contains key
func NotContainsKey(t TestingT, mp, key any, fmtAndArgs ...any) bool {
if nmap.HasKey(mp, key) {
t.Helper()
return fail(t,
fmt.Sprintf(
"Map should not contains the key: %#v\nMap data:\n%v",
key,
nmap.FormatIndent(mp, " "),
),
fmtAndArgs,
)
}
return true
}
// ContainsKeys asserts that the map is contains all given keys
//
// Usage:
//
// ContainsKeys(t, map[string]any{...}, []string{"key1", "key2"})
func ContainsKeys(t TestingT, mp any, keys any, fmtAndArgs ...any) bool {
anyKeys, err := narr.AnyToSlice(keys)
if err != nil {
t.Helper()
return fail(t, err.Error(), fmtAndArgs)
}
ok, noKey := nmap.HasAllKeys(mp, anyKeys...)
if !ok {
t.Helper()
return fail(t,
fmt.Sprintf(
"Map should contains the key: %#v\nMap data:\n%v",
noKey,
nmap.FormatIndent(mp, " "),
),
fmtAndArgs,
)
}
return true
}
// NotContainsKeys asserts that the map is not contains all given keys
//
// Usage:
//
// NotContainsKeys(t, map[string]any{...}, []string{"key1", "key2"})
func NotContainsKeys(t TestingT, mp any, keys any, fmtAndArgs ...any) bool {
anyKeys, err := narr.AnyToSlice(keys)
if err != nil {
t.Helper()
return fail(t, err.Error(), fmtAndArgs)
}
ok, hasKey := nmap.HasOneKey(mp, anyKeys...)
if ok {
t.Helper()
return fail(t,
fmt.Sprintf(
"Map should not contains the key: %#v\nMap data:\n%v",
hasKey,
nmap.FormatIndent(mp, " "),
),
fmtAndArgs,
)
}
return true
}
// StrContains asserts that the given strings is contains sub-string
func StrContains(t TestingT, s, sub string, fmtAndArgs ...any) bool {
if strings.Contains(s, sub) {
return true
}
t.Helper()
return fail(t,
fmt.Sprintf("String check fail:\nGiven string: %#v\nNot contains: %#v", s, sub),
fmtAndArgs,
)
}
// StrCount asserts that the given strings is contains sub-string and count
func StrCount(t TestingT, s, sub string, count int, fmtAndArgs ...any) bool {
if strings.Count(s, sub) == count {
return true
}
t.Helper()
return fail(t,
fmt.Sprintf("String check fail:\nGiven string: %s\nNot contains %q count: %d", s, sub, count),
fmtAndArgs,
)
}
//
// -------------------- error --------------------
//
// NoError asserts that the given is a nil error. alias of NoError()
func NoError(t TestingT, err error, fmtAndArgs ...any) bool {
t.Helper()
return NoErr(t, err, fmtAndArgs...)
}
// NoErr asserts that the given is a nil error
func NoErr(t TestingT, err error, fmtAndArgs ...any) bool {
if err != nil {
t.Helper()
return fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), fmtAndArgs)
}
return true
}
// Error asserts that the given is a not nil error. alias of Error()
func Error(t TestingT, err error, fmtAndArgs ...any) bool {
t.Helper()
return Err(t, err, fmtAndArgs...)
}
// Err asserts that the given is a not nil error
func Err(t TestingT, err error, fmtAndArgs ...any) bool {
if err == nil {
t.Helper()
return fail(t, "An error is expected but got nil.", fmtAndArgs)
}
return true
}
// ErrIs asserts that the given error is equals wantErr
func ErrIs(t TestingT, err, wantErr error, fmtAndArgs ...any) bool {
if err == nil {
t.Helper()
return fail(t, "An error is expected but got nil.", fmtAndArgs)
}
if !errors.Is(err, wantErr) {
t.Helper()
return fail(t, fmt.Sprintf("Expect given err is equals %#v.", wantErr), fmtAndArgs)
}
return true
}
// ErrMsg asserts that the given is a not nil error and error message equals wantMsg
func ErrMsg(t TestingT, err error, wantMsg string, fmtAndArgs ...any) bool {
if err == nil {
t.Helper()
return fail(t, "An error is expected but got nil.", fmtAndArgs)
}
errMsg := err.Error()
if errMsg != wantMsg {
t.Helper()
return fail(t, fmt.Sprintf("Error message not equal:\n"+
"expect: %q\n"+
"actual: %q", wantMsg, errMsg), fmtAndArgs)
}
return true
}
// ErrSubMsg asserts that the given is a not nil error and the error message contains subMsg
func ErrSubMsg(t TestingT, err error, subMsg string, fmtAndArgs ...any) bool {
if err == nil {
t.Helper()
return fail(t, "An error is expected but got nil.", fmtAndArgs)
}
errMsg := err.Error()
if !strings.Contains(errMsg, subMsg) {
t.Helper()
return fail(t, fmt.Sprintf("Error message check fail:\n"+
"error message : %q\n"+
"should contains: %q", errMsg, subMsg), fmtAndArgs)
}
return true
}
//
// -------------------- Len --------------------
//
// Len assert given length is equals to wantLn
func Len(t TestingT, give any, wantLn int, fmtAndArgs ...any) bool {
gln := nreflect.Len(reflect.ValueOf(give))
if gln < 0 {
t.Helper()
return fail(t, fmt.Sprintf("\"%s\" could not be calc length", give), fmtAndArgs)
}
if gln != wantLn {
t.Helper()
return fail(t, fmt.Sprintf("\"%s\" should have %d item(s), but has %d", give, wantLn, gln), fmtAndArgs)
}
return false
}
// LenGt assert given length is greater than to minLn
func LenGt(t TestingT, give any, minLn int, fmtAndArgs ...any) bool {
gln := nreflect.Len(reflect.ValueOf(give))
if gln < 0 {
t.Helper()
return fail(t, fmt.Sprintf("\"%s\" could not be calc length", give), fmtAndArgs)
}
if gln < minLn {
t.Helper()
return fail(t, fmt.Sprintf("\"%s\" should less have %d item(s), but has %d", give, minLn, gln), fmtAndArgs)
}
return false
}
//
// -------------------- compare --------------------
//
// Equal asserts that the want should equal to the given.
//
// alias of Eq()
func Equal(t TestingT, want, give any, fmtAndArgs ...any) bool {
t.Helper()
return Eq(t, want, give, fmtAndArgs...)
}
// Eq asserts that the want should equal to the given
func Eq(t TestingT, want, give any, fmtAndArgs ...any) bool {
t.Helper()
if err := checkEqualArgs(want, give); err != nil {
return fail(t,
fmt.Sprintf("Cannot compare: %#v == %#v (%s)", want, give, err),
fmtAndArgs,
)
}
if !nreflect.IsEqual(want, give) {
// TODO diff := diff(want, give)
want, give = formatUnequalValues(want, give)
return fail(t, fmt.Sprintf("Not equal: \n"+
"expect: %s\n"+
"actual: %s", want, give), fmtAndArgs)
}
return true
}
// Neq asserts that the want should not be equal to the given.
//
// alias of NotEq()
func Neq(t TestingT, want, give any, fmtAndArgs ...any) bool {
t.Helper()
return NotEq(t, want, give, fmtAndArgs...)
}
// NotEqual asserts that the want should not be equal to the given.
//
// alias of NotEq()
func NotEqual(t TestingT, want, give any, fmtAndArgs ...any) bool {
t.Helper()
return NotEq(t, want, give, fmtAndArgs...)
}
// NotEq asserts that the want should not be equal to the given
func NotEq(t TestingT, want, give any, fmtAndArgs ...any) bool {
t.Helper()
if err := checkEqualArgs(want, give); err != nil {
return fail(t,
fmt.Sprintf("Cannot compare: %#v == %#v (%s)", want, give, err),
fmtAndArgs,
)
}
if nreflect.IsEqual(want, give) {
return fail(t, fmt.Sprintf("Given should not be: %#v\n", give), fmtAndArgs)
}
return true
}
// EqualValues asserts that two objects are equal or convertable to the same types
// and equal.
//
// assert.EqualValues(t, uint32(123), int32(123))
func EqualValues(t TestingT, expected, actual any, fmtAndArgs ...any) bool {
t.Helper()
if !nreflect.IsEqualValues(expected, actual) {
//diff := diff(expected, actual)
expected, actual = formatUnequalValues(expected, actual)
return fail(t, fmt.Sprintf("Not equal: \n"+
"expected: %s\n"+
"actual : %s", expected, actual), fmtAndArgs)
}
return true
}
// Lt asserts that the give(intX,uintX,floatX) should not be less than max
func Lt(t TestingT, give, max any, fmtAndArgs ...any) bool {
if nmath.Compare(give, max, "lt") {
return true
}
t.Helper()
return fail(t, fmt.Sprintf("Given %v should later than %v", give, max), fmtAndArgs)
}
// Lte asserts that the give(intX,uintX,floatX) should not be less than or equals to max
func Lte(t TestingT, give, max any, fmtAndArgs ...any) bool {
if nmath.Compare(give, max, "lte") {
return true
}
t.Helper()
return fail(t, fmt.Sprintf("Given %v should later than %v", give, max), fmtAndArgs)
}
// Gt asserts that the give(intX,uintX,floatX) should not be greater than min
func Gt(t TestingT, give, min any, fmtAndArgs ...any) bool {
if nmath.Compare(give, min, "gt") {
return true
}
t.Helper()
return fail(t, fmt.Sprintf("Given %v should gater than %v", give, min), fmtAndArgs)
}
// Gte asserts that the give(intX,uintX,floatX) should not be greater than or equals to min
func Gte(t TestingT, give, min any, fmtAndArgs ...any) bool {
if nmath.Compare(give, min, "gte") {
return true
}
t.Helper()
return fail(t, fmt.Sprintf("Given %v should gater than or equal %v", give, min), fmtAndArgs)
}
// IsType assert data type equals
//
// Usage:
//
// assert.IsType(t, 0, val) // assert type is int
func IsType(t TestingT, wantType, give any, fmtAndArgs ...any) bool {
if nreflect.IsEqual(reflect.TypeOf(wantType), reflect.TypeOf(give)) {
return true
}
t.Helper()
return fail(t,
fmt.Sprintf("Expected to be of type %v, but was %v", reflect.TypeOf(wantType), reflect.TypeOf(give)),
fmtAndArgs,
)
}
// IsKind assert data reflect.Kind equals.
// If `give` is ptr or interface, will get real kind.
//
// Usage:
//
// assert.IsKind(t, reflect.Int, val) // assert type is int kind.
func IsKind(t TestingT, wantKind reflect.Kind, give any, fmtAndArgs ...any) bool {
giveKind := nreflect.Elem(reflect.ValueOf(give)).Kind()
if wantKind == giveKind {
return true
}
t.Helper()
return fail(t,
fmt.Sprintf("Expected to be of kind %v, but was %v", wantKind, giveKind),
fmtAndArgs,
)
}
// Same asserts that two pointers reference the same object.
//
// assert.Same(t, ptr1, ptr2)
//
// Both arguments must be pointer variables. Pointer variable sameness is
// determined based on the equality of both type and value.
func Same(t TestingT, wanted, actual any, fmtAndArgs ...any) bool {
if samePointers(wanted, actual) {
return true
}
return fail(t, fmt.Sprintf("Not same: \n"+
"wanted: %p %#v\n"+
"actual: %p %#v", wanted, wanted, actual, actual), fmtAndArgs)
}
// NotSame asserts that two pointers do not reference the same object.
//
// assert.NotSame(t, ptr1, ptr2)
//
// Both arguments must be pointer variables. Pointer variable sameness is
// determined based on the equality of both type and value.
func NotSame(t TestingT, want, actual any, fmtAndArgs ...any) bool {
if !samePointers(want, actual) {
return true
}
t.Helper()
return fail(t, fmt.Sprintf("Expect and actual point to the same object: %p %#v", want, want), fmtAndArgs)
}
// samePointers compares two generic interface objects and returns whether
// they point to the same object
func samePointers(first, second any) bool {
firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second)
if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr {
return false
}
firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second)
if firstType != secondType {
return false
}
// compare pointer addresses
return first == second
}
//
// -------------------- fail --------------------
//
// Fail reports a failure through
func Fail(t TestingT, failMsg string, fmtAndArgs ...any) bool {
t.Helper()
return fail(t, failMsg, fmtAndArgs)
}
type failNower interface {
FailNow()
}
// FailNow fails test
func FailNow(t TestingT, failMsg string, fmtAndArgs ...any) bool {
t.Helper()
fail(t, failMsg, fmtAndArgs)
if fnr, ok := t.(failNower); ok {
fnr.FailNow()
}
return false
}
//
// -------------------- render error --------------------
//
var (
// ShowFullPath on show error trace
ShowFullPath = true
// EnableColor on show error trace
EnableColor = true
)
// DisableColor render
func DisableColor() {
EnableColor = false
}
// HideFullPath render
func HideFullPath() {
ShowFullPath = false
}
// fail reports a failure through
func fail(t TestingT, failMsg string, fmtAndArgs []any) bool {
t.Helper()
tName := t.Name()
if EnableColor {
tName = color.Red.Sprint(tName)
}
labeledTexts := []labeledText{
{"Test Name", tName},
{"Error At", strings.Join(callerInfos(), "\n")},
{"Error Msg", failMsg},
}
// user custom message
if userMsg := formatTplAndArgs(fmtAndArgs...); len(userMsg) > 0 {
labeledTexts = append(labeledTexts, labeledText{"User Msg", userMsg})
}
t.Error("\n" + formatLabeledTexts(labeledTexts))
return false
}