1344 lines
40 KiB
Go
1344 lines
40 KiB
Go
package check
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
pkgerrors "github.com/pkg/errors"
|
|
"github.com/powerman/deepequal"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
)
|
|
|
|
//nolint:gochecknoglobals // Const.
|
|
var (
|
|
typString = reflect.TypeOf("")
|
|
typBytes = reflect.TypeOf([]byte(nil))
|
|
typFloat64 = reflect.TypeOf(0.0)
|
|
)
|
|
|
|
// C wraps *testing.T to make it convenient to call checkers in test.
|
|
type C struct {
|
|
*testing.T
|
|
todo bool
|
|
must bool
|
|
}
|
|
|
|
const (
|
|
nameActual = "Actual"
|
|
nameExpected = "Expected"
|
|
)
|
|
|
|
// Parallel implements an internal workaround which have no visible
|
|
// effect, so you should just call t.Parallel() as you usually do - it
|
|
// will work as expected.
|
|
func (t *C) Parallel() {
|
|
t.Helper()
|
|
// Goconvey anyway doesn't provide -test.cpu= and mixed output of
|
|
// parallel tests result in reporting failed tests at wrong places
|
|
// and with wrong failed tests count in web UI.
|
|
if !flags.detect().conveyJSON {
|
|
t.T.Parallel()
|
|
}
|
|
}
|
|
|
|
// T creates and returns new *C, which wraps given tt and supposed to be
|
|
// used inplace of it, providing you with access to many useful helpers in
|
|
// addition to standard methods of *testing.T.
|
|
//
|
|
// It's convenient to rename Test function's arg from t to something
|
|
// else, create wrapped variable with usual name t and use only t:
|
|
//
|
|
// func TestSomething(tt *testing.T) {
|
|
// t := check.T(tt)
|
|
// // use only t in test and don't touch tt anymore
|
|
// }
|
|
func T(tt *testing.T) *C { //nolint:thelper // With check we name it tt!
|
|
return &C{T: tt}
|
|
}
|
|
|
|
// TODO creates and returns new *C, which have only one difference from
|
|
// original one: every passing check is now handled as failed and vice
|
|
// versa (this doesn't affect boolean value returned by check).
|
|
// You can continue using both old and new *C at same time.
|
|
//
|
|
// Swapping passed/failed gives you ability to temporary mark some failed
|
|
// test as passed. For example, this may be useful to avoid broken builds
|
|
// in CI. This is often better than commenting, deleting or skipping
|
|
// broken test because it will continue to execute, and eventually when
|
|
// reason why it fails will be fixed this test will became failed again -
|
|
// notifying you the mark can and should be removed from this test now.
|
|
//
|
|
// func TestSomething(tt *testing.T) {
|
|
// t := check.T(tt)
|
|
// // Normal tests.
|
|
// t.True(true)
|
|
// // If you need to mark just one/few broken tests:
|
|
// t.TODO().True(false)
|
|
// t.True(true)
|
|
// // If there are several broken tests mixed with working ones:
|
|
// todo := t.TODO()
|
|
// t.True(true)
|
|
// todo.True(false)
|
|
// t.True(true)
|
|
// if todo.True(false) {
|
|
// panic("never here")
|
|
// }
|
|
// // If all tests below this point are broken:
|
|
// t = t.TODO()
|
|
// t.True(false)
|
|
// ...
|
|
// }
|
|
func (t *C) TODO() *C {
|
|
return &C{T: t.T, todo: true, must: t.must}
|
|
}
|
|
|
|
// MustAll creates and returns new *C, which have only one difference from
|
|
// original one: every failed check will interrupt test using t.FailNow.
|
|
// You can continue using both old and new *C at same time.
|
|
//
|
|
// This provides an easy way to turn all checks into assertion.
|
|
func (t *C) MustAll() *C {
|
|
return &C{T: t.T, todo: t.todo, must: true}
|
|
}
|
|
|
|
func (t *C) pass() {
|
|
statsMu.Lock()
|
|
defer statsMu.Unlock()
|
|
|
|
if stats[t.T] == nil {
|
|
stats[t.T] = newTestStat(t.Name(), false)
|
|
}
|
|
if t.todo {
|
|
stats[t.T].forged.value++
|
|
} else {
|
|
stats[t.T].passed.value++
|
|
}
|
|
}
|
|
|
|
func (t *C) fail() {
|
|
statsMu.Lock()
|
|
defer statsMu.Unlock()
|
|
|
|
if stats[t.T] == nil {
|
|
stats[t.T] = newTestStat(t.Name(), false)
|
|
}
|
|
stats[t.T].failed.value++
|
|
}
|
|
|
|
func (t *C) report(ok bool, msg []interface{}, checker string, name []string, args []interface{}) bool { //nolint:revive // False positive.
|
|
t.Helper()
|
|
|
|
if ok != t.todo {
|
|
t.pass()
|
|
return ok
|
|
}
|
|
|
|
if t.todo {
|
|
checker = "TODO " + checker
|
|
}
|
|
|
|
dump := make([]dump, 0, len(args))
|
|
for _, arg := range args {
|
|
dump = append(dump, newDump(arg))
|
|
}
|
|
|
|
failure := new(bytes.Buffer)
|
|
fmt.Fprintf(failure, "%s\nChecker: %s%s%s\n",
|
|
format(msg...),
|
|
ansiYellow, checker, ansiReset,
|
|
)
|
|
failureShort := failure.String()
|
|
// Reverse order to show Actual: last.
|
|
for i := len(dump) - 1; i >= 0; i-- {
|
|
fmt.Fprintf(failure, "%-10s", name[i]+":")
|
|
switch name[i] {
|
|
case nameActual:
|
|
fmt.Fprint(failure, ansiRed)
|
|
default:
|
|
fmt.Fprint(failure, ansiGreen)
|
|
}
|
|
fmt.Fprintf(failure, "%s%s", dump[i], ansiReset)
|
|
}
|
|
failureLong := failure.String()
|
|
|
|
wantDiff := len(dump) == 2 && name[0] == nameActual && name[1] == nameExpected
|
|
if wantDiff { //nolint:nestif // No idea how to simplify.
|
|
if reportToGoConvey(dump[0].String(), dump[1].String(), failureShort) == nil {
|
|
t.Fail()
|
|
} else {
|
|
fmt.Fprintf(failure, "\n%s", colouredDiff(dump[0].diff(dump[1])))
|
|
t.Errorf("%s\n", failure)
|
|
}
|
|
} else {
|
|
if reportToGoConvey("", "", failureLong) == nil {
|
|
t.Fail()
|
|
} else {
|
|
t.Errorf("%s\n", failure)
|
|
}
|
|
}
|
|
|
|
t.fail()
|
|
|
|
if t.must {
|
|
t.FailNow()
|
|
}
|
|
return ok
|
|
}
|
|
|
|
func (t *C) reportShould1(funcName string, actual interface{}, msg []interface{}, ok bool) bool {
|
|
t.Helper()
|
|
return t.report(ok, msg,
|
|
"Should "+funcName,
|
|
[]string{nameActual},
|
|
[]interface{}{actual})
|
|
}
|
|
|
|
func (t *C) reportShould2(funcName string, actual, expected interface{}, msg []interface{}, ok bool) bool {
|
|
t.Helper()
|
|
return t.report(ok, msg,
|
|
"Should "+funcName,
|
|
[]string{nameActual, nameExpected},
|
|
[]interface{}{actual, expected})
|
|
}
|
|
|
|
func (t *C) report0(msg []interface{}, ok bool) bool {
|
|
t.Helper()
|
|
return t.report(ok, msg,
|
|
callerFuncName(1),
|
|
[]string{},
|
|
[]interface{}{})
|
|
}
|
|
|
|
func (t *C) report1(actual interface{}, msg []interface{}, ok bool) bool {
|
|
t.Helper()
|
|
return t.report(ok, msg,
|
|
callerFuncName(1),
|
|
[]string{nameActual},
|
|
[]interface{}{actual})
|
|
}
|
|
|
|
func (t *C) report2(actual, expected interface{}, msg []interface{}, ok bool) bool {
|
|
t.Helper()
|
|
checker, arg2Name := callerFuncName(1), nameExpected
|
|
if strings.Contains(checker, "Match") {
|
|
arg2Name = "Regex"
|
|
}
|
|
return t.report(ok, msg,
|
|
checker,
|
|
[]string{nameActual, arg2Name},
|
|
[]interface{}{actual, expected})
|
|
}
|
|
|
|
func (t *C) report3(actual, expected1, expected2 interface{}, msg []interface{}, ok bool) bool {
|
|
t.Helper()
|
|
checker, arg2Name, arg3Name := callerFuncName(1), "arg1", "arg2"
|
|
switch {
|
|
case strings.Contains(checker, "Between"):
|
|
arg2Name, arg3Name = "Min", "Max"
|
|
case strings.Contains(checker, "Delta"):
|
|
arg2Name, arg3Name = nameExpected, "Delta"
|
|
case strings.Contains(checker, "SMAPE"):
|
|
arg2Name, arg3Name = nameExpected, "SMAPE"
|
|
}
|
|
return t.report(ok, msg,
|
|
checker,
|
|
[]string{nameActual, arg2Name, arg3Name},
|
|
[]interface{}{actual, expected1, expected2})
|
|
}
|
|
|
|
// Must interrupt test using t.FailNow if called with false value.
|
|
//
|
|
// This provides an easy way to turn any check into assertion:
|
|
//
|
|
// t.Must(t.Nil(err))
|
|
func (t *C) Must(continueTest bool, msg ...interface{}) { //nolint:revive // False positive.
|
|
t.Helper()
|
|
t.report0(msg, continueTest)
|
|
if !continueTest {
|
|
t.FailNow()
|
|
}
|
|
}
|
|
|
|
type (
|
|
// ShouldFunc1 is like Nil or Zero.
|
|
ShouldFunc1 func(t *C, actual interface{}) bool
|
|
// ShouldFunc2 is like Equal or Match.
|
|
ShouldFunc2 func(t *C, actual, expected interface{}) bool
|
|
)
|
|
|
|
// Should use user-provided check function to do actual check.
|
|
//
|
|
// anyShouldFunc must have type ShouldFunc1 or ShouldFunc2. It should
|
|
// return true if check was successful. There is no need to call t.Error
|
|
// in anyShouldFunc - this will be done automatically when it returns.
|
|
//
|
|
// args must contain at least 1 element for ShouldFunc1 and at least
|
|
// 2 elements for ShouldFunc2.
|
|
// Rest of elements will be processed as usual msg ...interface{} param.
|
|
//
|
|
// Example:
|
|
//
|
|
// func bePositive(_ *check.C, actual interface{}) bool {
|
|
// return actual.(int) > 0
|
|
// }
|
|
// func TestCustomCheck(tt *testing.T) {
|
|
// t := check.T(tt)
|
|
// t.Should(bePositive, 42, "custom check!!!")
|
|
// }
|
|
func (t *C) Should(anyShouldFunc interface{}, args ...interface{}) bool {
|
|
t.Helper()
|
|
switch f := anyShouldFunc.(type) {
|
|
case func(t *C, actual interface{}) bool:
|
|
return t.should1(f, args...)
|
|
case func(t *C, actual, expected interface{}) bool:
|
|
return t.should2(f, args...)
|
|
default:
|
|
panic("anyShouldFunc is not a ShouldFunc1 or ShouldFunc2")
|
|
}
|
|
}
|
|
|
|
func (t *C) should1(f ShouldFunc1, args ...interface{}) bool {
|
|
t.Helper()
|
|
if len(args) < 1 {
|
|
panic("not enough params for " + funcName(f))
|
|
}
|
|
actual, msg := args[0], args[1:]
|
|
return t.reportShould1(funcName(f), actual, msg,
|
|
f(t, actual))
|
|
}
|
|
|
|
func (t *C) should2(f ShouldFunc2, args ...interface{}) bool {
|
|
t.Helper()
|
|
const minArgs = 2
|
|
if len(args) < minArgs {
|
|
panic("not enough params for " + funcName(f))
|
|
}
|
|
actual, expected, msg := args[0], args[1], args[2:]
|
|
return t.reportShould2(funcName(f), actual, expected, msg,
|
|
f(t, actual, expected))
|
|
}
|
|
|
|
// Nil checks for actual == nil.
|
|
//
|
|
// There is one subtle difference between this check and Go `== nil` (if
|
|
// this surprises you then you should read
|
|
// https://golang.org/doc/faq#nil_error first):
|
|
//
|
|
// var intPtr *int
|
|
// var empty interface{}
|
|
// var notEmpty interface{} = intPtr
|
|
// t.True(intPtr == nil) // TRUE
|
|
// t.True(empty == nil) // TRUE
|
|
// t.True(notEmpty == nil) // FALSE
|
|
//
|
|
// When you call this function your actual value will be stored in
|
|
// interface{} argument, and this makes any typed nil pointer value `!=
|
|
// nil` inside this function (just like in example above happens with
|
|
// notEmpty variable).
|
|
//
|
|
// As it is very common case to check some typed pointer using Nil this
|
|
// check has to work around and detect nil even if usual `== nil` return
|
|
// false. But this has nasty side effect: if actual value already was of
|
|
// interface type and contains some typed nil pointer (which is usually
|
|
// bad thing and should be avoid) then Nil check will pass (which may be
|
|
// not what you want/expect):
|
|
//
|
|
// t.Nil(nil) // TRUE
|
|
// t.Nil(intPtr) // TRUE
|
|
// t.Nil(empty) // TRUE
|
|
// t.Nil(notEmpty) // WARNING: also TRUE!
|
|
//
|
|
// Second subtle case is less usual: uintptr(0) is sorta nil, but not
|
|
// really, so Nil(uintptr(0)) will fail. Nil(unsafe.Pointer(nil)) will
|
|
// also fail, for the same reason. Please do not use this and consider
|
|
// this behaviour undefined, because it may change in the future.
|
|
func (t *C) Nil(actual interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report1(actual, msg,
|
|
isNil(actual))
|
|
}
|
|
|
|
func isNil(actual interface{}) bool {
|
|
switch val := reflect.ValueOf(actual); val.Kind() {
|
|
case reflect.Invalid:
|
|
return actual == nil
|
|
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
return val.IsNil()
|
|
case reflect.Uintptr, reflect.UnsafePointer: // Subtle cases documented above.
|
|
case reflect.Interface: // ???
|
|
// Can't be nil:
|
|
case reflect.Struct, reflect.Array, reflect.Bool, reflect.String:
|
|
case reflect.Complex128, reflect.Complex64, reflect.Float32, reflect.Float64:
|
|
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
|
|
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint8:
|
|
}
|
|
return false
|
|
}
|
|
|
|
// NotNil checks for actual != nil.
|
|
//
|
|
// See Nil about subtle case in check logic.
|
|
func (t *C) NotNil(actual interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report0(msg,
|
|
!isNil(actual))
|
|
}
|
|
|
|
// Error is equivalent to Log followed by Fail.
|
|
//
|
|
// It is like t.Errorf with TODO() and statistics support.
|
|
func (t *C) Error(msg ...interface{}) {
|
|
t.Helper()
|
|
t.report0(msg, false)
|
|
}
|
|
|
|
// True checks for cond == true.
|
|
//
|
|
// This can be useful to use your own custom checks, but this way you
|
|
// won't get nice dump/diff for actual/expected values. You'll still have
|
|
// statistics about passed/failed checks and it's shorter than usual:
|
|
//
|
|
// if !cond {
|
|
// t.Errorf(msg...)
|
|
// }
|
|
func (t *C) True(cond bool, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report0(msg,
|
|
cond)
|
|
}
|
|
|
|
// False checks for cond == false.
|
|
func (t *C) False(cond bool, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report0(msg,
|
|
!cond)
|
|
}
|
|
|
|
// Equal checks for actual == expected.
|
|
//
|
|
// Note: For time.Time it uses actual.Equal(expected) instead.
|
|
func (t *C) Equal(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
isEqual(actual, expected))
|
|
}
|
|
|
|
func isEqual(actual, expected interface{}) bool {
|
|
switch actual := actual.(type) {
|
|
case time.Time:
|
|
return actual.Equal(expected.(time.Time))
|
|
default:
|
|
return actual == expected
|
|
}
|
|
}
|
|
|
|
// EQ is a synonym for Equal.
|
|
func (t *C) EQ(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.Equal(actual, expected, msg...)
|
|
}
|
|
|
|
// NotEqual checks for actual != expected.
|
|
func (t *C) NotEqual(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
!isEqual(actual, expected))
|
|
}
|
|
|
|
// NE is a synonym for NotEqual.
|
|
func (t *C) NE(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.NotEqual(actual, expected, msg...)
|
|
}
|
|
|
|
// BytesEqual checks for bytes.Equal(actual, expected).
|
|
//
|
|
// Hint: BytesEqual([]byte{}, []byte(nil)) is true (unlike DeepEqual).
|
|
func (t *C) BytesEqual(actual, expected []byte, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
bytes.Equal(actual, expected))
|
|
}
|
|
|
|
// NotBytesEqual checks for !bytes.Equal(actual, expected).
|
|
//
|
|
// Hint: NotBytesEqual([]byte{}, []byte(nil)) is false (unlike NotDeepEqual).
|
|
func (t *C) NotBytesEqual(actual, expected []byte, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report1(actual, msg,
|
|
!bytes.Equal(actual, expected))
|
|
}
|
|
|
|
// DeepEqual checks for reflect.DeepEqual(actual, expected).
|
|
// It will also use Equal method for types which implements it
|
|
// (e.g. time.Time, decimal.Decimal, etc.).
|
|
// It will use proto.Equal for protobuf messages.
|
|
func (t *C) DeepEqual(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
protoActual, proto1 := actual.(protoreflect.ProtoMessage)
|
|
protoExpected, proto2 := expected.(protoreflect.ProtoMessage)
|
|
if proto1 && proto2 {
|
|
return t.report2(actual, expected, msg,
|
|
proto.Equal(protoActual, protoExpected))
|
|
}
|
|
return t.report2(actual, expected, msg,
|
|
deepequal.DeepEqual(actual, expected))
|
|
}
|
|
|
|
// NotDeepEqual checks for !reflect.DeepEqual(actual, expected).
|
|
// It will also use Equal method for types which implements it
|
|
// (e.g. time.Time, decimal.Decimal, etc.).
|
|
// It will use proto.Equal for protobuf messages.
|
|
func (t *C) NotDeepEqual(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
protoActual, proto1 := actual.(protoreflect.ProtoMessage)
|
|
protoExpected, proto2 := expected.(protoreflect.ProtoMessage)
|
|
if proto1 && proto2 {
|
|
return t.report1(actual, msg,
|
|
!proto.Equal(protoActual, protoExpected))
|
|
}
|
|
return t.report1(actual, msg,
|
|
!deepequal.DeepEqual(actual, expected))
|
|
}
|
|
|
|
// Match checks for regex.MatchString(actual).
|
|
//
|
|
// Regex type can be either *regexp.Regexp or string.
|
|
//
|
|
// Actual type can be:
|
|
// - string - will match with actual
|
|
// - []byte - will match with string(actual)
|
|
// - []rune - will match with string(actual)
|
|
// - fmt.Stringer - will match with actual.String()
|
|
// - error - will match with actual.Error()
|
|
// - nil - will not match (even with empty regex)
|
|
func (t *C) Match(actual, regex interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
ok := isMatch(&actual, regex)
|
|
return t.report2(actual, regex, msg,
|
|
ok)
|
|
}
|
|
|
|
// isMatch updates actual to be a real string used for matching, to make
|
|
// dump easier to understand, but this result in losing type information.
|
|
func isMatch(actual *interface{}, regex interface{}) bool { //nolint:gocritic // False positive.
|
|
if *actual == nil {
|
|
return false
|
|
}
|
|
if !stringify(actual) {
|
|
panic("actual is not a string, []byte, []rune, fmt.Stringer, error or nil")
|
|
}
|
|
s := (*actual).(string) //nolint:forcetypeassert // False positive.
|
|
|
|
switch v := regex.(type) {
|
|
case *regexp.Regexp:
|
|
return v.MatchString(s)
|
|
case string:
|
|
return regexp.MustCompile(v).MatchString(s)
|
|
}
|
|
panic("regex is not a *regexp.Regexp or string")
|
|
}
|
|
|
|
func stringify(arg *interface{}) bool { //nolint:gocritic // False positive.
|
|
switch v := (*arg).(type) {
|
|
case nil:
|
|
return false
|
|
case error:
|
|
*arg = v.Error()
|
|
case fmt.Stringer:
|
|
*arg = v.String()
|
|
default:
|
|
typ := reflect.TypeOf(*arg)
|
|
switch typ.Kind() { //nolint:exhaustive // Covered by default case.
|
|
case reflect.String:
|
|
case reflect.Slice:
|
|
switch typ.Elem().Kind() { //nolint:exhaustive // Covered by default case.
|
|
case reflect.Uint8, reflect.Int32:
|
|
default:
|
|
return false
|
|
}
|
|
default:
|
|
return false
|
|
}
|
|
*arg = reflect.ValueOf(*arg).Convert(typString).Interface()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// NotMatch checks for !regex.MatchString(actual).
|
|
//
|
|
// See Match about supported actual/regex types and check logic.
|
|
func (t *C) NotMatch(actual, regex interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
ok := !isMatch(&actual, regex)
|
|
return t.report2(actual, regex, msg,
|
|
ok)
|
|
}
|
|
|
|
// Contains checks is actual contains substring/element expected.
|
|
//
|
|
// Element of array/slice/map is checked using == expected.
|
|
//
|
|
// Type of expected depends on type of actual:
|
|
// - if actual is a string, then expected should be a string
|
|
// - if actual is an array, then expected should have array's element type
|
|
// - if actual is a slice, then expected should have slice's element type
|
|
// - if actual is a map, then expected should have map's value type
|
|
//
|
|
// Hint: In a map it looks for a value, if you need to look for a key -
|
|
// use HasKey instead.
|
|
func (t *C) Contains(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
isContains(actual, expected))
|
|
}
|
|
|
|
func isContains(actual, expected interface{}) (found bool) {
|
|
switch valActual := reflect.ValueOf(actual); valActual.Kind() { //nolint:exhaustive // Covered by default case.
|
|
case reflect.String:
|
|
strActual := valActual.Convert(typString).Interface().(string) //nolint:forcetypeassert // False positive.
|
|
valExpected := reflect.ValueOf(expected)
|
|
if valExpected.Kind() != reflect.String {
|
|
panic("expected underlying type is not a string")
|
|
}
|
|
strExpected := valExpected.Convert(typString).Interface().(string) //nolint:forcetypeassert // False positive.
|
|
found = strings.Contains(strActual, strExpected)
|
|
|
|
case reflect.Map:
|
|
if valActual.Type().Elem() != reflect.TypeOf(expected) {
|
|
panic("expected type not match actual element type")
|
|
}
|
|
keys := valActual.MapKeys()
|
|
for i := 0; i < len(keys) && !found; i++ {
|
|
found = valActual.MapIndex(keys[i]).Interface() == expected
|
|
}
|
|
|
|
case reflect.Slice, reflect.Array:
|
|
if valActual.Type().Elem() != reflect.TypeOf(expected) {
|
|
panic("expected type not match actual element type")
|
|
}
|
|
for i := 0; i < valActual.Len() && !found; i++ {
|
|
found = valActual.Index(i).Interface() == expected
|
|
}
|
|
|
|
default:
|
|
panic("actual is not a string, array, slice or map")
|
|
}
|
|
return found
|
|
}
|
|
|
|
// NotContains checks is actual not contains substring/element expected.
|
|
//
|
|
// See Contains about supported actual/expected types and check logic.
|
|
func (t *C) NotContains(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
!isContains(actual, expected))
|
|
}
|
|
|
|
// HasKey checks is actual has key expected.
|
|
func (t *C) HasKey(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
hasKey(actual, expected))
|
|
}
|
|
|
|
func hasKey(actual, expected interface{}) bool {
|
|
return reflect.ValueOf(actual).MapIndex(reflect.ValueOf(expected)).IsValid()
|
|
}
|
|
|
|
// NotHasKey checks is actual has no key expected.
|
|
func (t *C) NotHasKey(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
!hasKey(actual, expected))
|
|
}
|
|
|
|
// Zero checks is actual is zero value of it's type.
|
|
func (t *C) Zero(actual interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report1(actual, msg,
|
|
isZero(actual))
|
|
}
|
|
|
|
func isZero(actual interface{}) bool {
|
|
if isNil(actual) {
|
|
return true
|
|
} else if typ := reflect.TypeOf(actual); typ.Comparable() {
|
|
// Not Func, Map, Slice, Array with non-comparable
|
|
// elements, Struct with non-comparable fields.
|
|
return actual == reflect.Zero(typ).Interface()
|
|
} else if typ.Kind() == reflect.Array {
|
|
zero := true
|
|
val := reflect.ValueOf(actual)
|
|
for i := 0; i < val.Len() && zero; i++ {
|
|
zero = isZero(val.Index(i).Interface())
|
|
}
|
|
return zero
|
|
}
|
|
// Func, Struct with non-comparable fields.
|
|
// Non-nil Map, Slice.
|
|
return false
|
|
}
|
|
|
|
// NotZero checks is actual is not zero value of it's type.
|
|
func (t *C) NotZero(actual interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report1(actual, msg,
|
|
!isZero(actual))
|
|
}
|
|
|
|
// Len checks is len(actual) == expected.
|
|
func (t *C) Len(actual interface{}, expected int, msg ...interface{}) bool {
|
|
t.Helper()
|
|
l := reflect.ValueOf(actual).Len()
|
|
return t.report2(l, expected, msg,
|
|
l == expected)
|
|
}
|
|
|
|
// NotLen checks is len(actual) != expected.
|
|
func (t *C) NotLen(actual interface{}, expected int, msg ...interface{}) bool {
|
|
t.Helper()
|
|
l := reflect.ValueOf(actual).Len()
|
|
return t.report2(l, expected, msg,
|
|
l != expected)
|
|
}
|
|
|
|
// Err checks is actual error is the same as expected error.
|
|
//
|
|
// It tries to recursively unwrap actual before checking using
|
|
// errors.Unwrap() and github.com/pkg/errors.Cause().
|
|
//
|
|
// It will use proto.Equal for gRPC status errors.
|
|
//
|
|
// They may be a different instances, but must have same type and value.
|
|
//
|
|
// Checking for nil is okay, but using Nil(actual) instead is more clean.
|
|
func (t *C) Err(actual, expected error, msg ...interface{}) bool {
|
|
t.Helper()
|
|
actual2 := unwrapErr(actual)
|
|
equal := fmt.Sprintf("%#v", actual2) == fmt.Sprintf("%#v", expected)
|
|
_, proto1 := actual2.(interface{ GRPCStatus() *status.Status }) //nolint:errorlint // False positive.
|
|
_, proto2 := expected.(interface{ GRPCStatus() *status.Status }) //nolint:errorlint // False positive.
|
|
if proto1 || proto2 {
|
|
equal = proto.Equal(status.Convert(actual2).Proto(), status.Convert(expected).Proto())
|
|
}
|
|
return t.report2(actual, expected, msg, equal)
|
|
}
|
|
|
|
func unwrapErr(err error) (actual error) {
|
|
defer func() { _ = recover() }()
|
|
actual = err
|
|
for {
|
|
actual = pkgerrors.Cause(actual)
|
|
wrapped, ok := actual.(interface{ Unwrap() error }) //nolint:errorlint // False positive.
|
|
if !ok {
|
|
break
|
|
}
|
|
unwrapped := wrapped.Unwrap()
|
|
if unwrapped == nil {
|
|
break
|
|
}
|
|
actual = unwrapped
|
|
}
|
|
return actual
|
|
}
|
|
|
|
// NotErr checks is actual error is not the same as expected error.
|
|
//
|
|
// It tries to recursively unwrap actual before checking using
|
|
// errors.Unwrap() and github.com/pkg/errors.Cause().
|
|
//
|
|
// It will use !proto.Equal for gRPC status errors.
|
|
//
|
|
// They must have either different types or values (or one should be nil).
|
|
// Different instances with same type and value will be considered the
|
|
// same error, and so is both nil.
|
|
func (t *C) NotErr(actual, expected error, msg ...interface{}) bool {
|
|
t.Helper()
|
|
actual2 := unwrapErr(actual)
|
|
notEqual := fmt.Sprintf("%#v", actual2) != fmt.Sprintf("%#v", expected)
|
|
_, proto1 := actual2.(interface{ GRPCStatus() *status.Status }) //nolint:errorlint // False positive.
|
|
_, proto2 := expected.(interface{ GRPCStatus() *status.Status }) //nolint:errorlint // False positive.
|
|
if proto1 || proto2 {
|
|
notEqual = !proto.Equal(status.Convert(actual2).Proto(), status.Convert(expected).Proto())
|
|
}
|
|
return t.report1(actual, msg, notEqual)
|
|
}
|
|
|
|
// Panic checks is actual() panics.
|
|
//
|
|
// It is able to detect panic(nil)… but you should try to avoid using this.
|
|
func (t *C) Panic(actual func(), msg ...interface{}) bool {
|
|
t.Helper()
|
|
didPanic := true
|
|
func() {
|
|
defer func() { _ = recover() }()
|
|
actual()
|
|
didPanic = false
|
|
}()
|
|
return t.report0(msg,
|
|
didPanic)
|
|
}
|
|
|
|
// NotPanic checks is actual() don't panics.
|
|
//
|
|
// It is able to detect panic(nil)… but you should try to avoid using this.
|
|
func (t *C) NotPanic(actual func(), msg ...interface{}) bool {
|
|
t.Helper()
|
|
didPanic := true
|
|
func() {
|
|
defer func() { _ = recover() }()
|
|
actual()
|
|
didPanic = false
|
|
}()
|
|
return t.report0(msg,
|
|
!didPanic)
|
|
}
|
|
|
|
// PanicMatch checks is actual() panics and panic text match regex.
|
|
//
|
|
// Regex type can be either *regexp.Regexp or string.
|
|
//
|
|
// In case of panic(nil) it will match like panic("<nil>").
|
|
func (t *C) PanicMatch(actual func(), regex interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
var panicVal interface{}
|
|
didPanic := true
|
|
func() {
|
|
defer func() { panicVal = recover() }()
|
|
actual()
|
|
didPanic = false
|
|
}()
|
|
if !didPanic {
|
|
return t.report0(msg,
|
|
false)
|
|
}
|
|
|
|
switch panicVal.(type) {
|
|
case string, error:
|
|
default:
|
|
panicVal = fmt.Sprintf("%#v", panicVal)
|
|
}
|
|
|
|
ok := isMatch(&panicVal, regex)
|
|
return t.report2(panicVal, regex, msg,
|
|
ok)
|
|
}
|
|
|
|
// PanicNotMatch checks is actual() panics and panic text not match regex.
|
|
//
|
|
// Regex type can be either *regexp.Regexp or string.
|
|
//
|
|
// In case of panic(nil) it will match like panic("<nil>").
|
|
func (t *C) PanicNotMatch(actual func(), regex interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
var panicVal interface{}
|
|
didPanic := true
|
|
func() {
|
|
defer func() { panicVal = recover() }()
|
|
actual()
|
|
didPanic = false
|
|
}()
|
|
if !didPanic {
|
|
return t.report0(msg,
|
|
false)
|
|
}
|
|
|
|
switch panicVal.(type) {
|
|
case string, error:
|
|
default:
|
|
panicVal = fmt.Sprintf("%#v", panicVal)
|
|
}
|
|
|
|
ok := !isMatch(&panicVal, regex)
|
|
return t.report2(panicVal, regex, msg,
|
|
ok)
|
|
}
|
|
|
|
// Less checks for actual < expected.
|
|
//
|
|
// Both actual and expected must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - strings
|
|
// - time.Time
|
|
func (t *C) Less(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
isLess(actual, expected))
|
|
}
|
|
|
|
func isLess(actual, expected interface{}) bool {
|
|
switch v1, v2 := reflect.ValueOf(actual), reflect.ValueOf(expected); v1.Kind() { //nolint:exhaustive // Covered by default case.
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return v1.Int() < v2.Int()
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
return v1.Uint() < v2.Uint()
|
|
case reflect.Float32, reflect.Float64:
|
|
return v1.Float() < v2.Float()
|
|
case reflect.String:
|
|
return v1.String() < v2.String()
|
|
default:
|
|
if actualTime, ok := actual.(time.Time); ok {
|
|
return actualTime.Before(expected.(time.Time))
|
|
}
|
|
}
|
|
panic("actual is not a number, string or time.Time")
|
|
}
|
|
|
|
// LT is a synonym for Less.
|
|
func (t *C) LT(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.Less(actual, expected, msg...)
|
|
}
|
|
|
|
// LessOrEqual checks for actual <= expected.
|
|
//
|
|
// Both actual and expected must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - strings
|
|
// - time.Time
|
|
func (t *C) LessOrEqual(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
!isGreater(actual, expected))
|
|
}
|
|
|
|
func isGreater(actual, expected interface{}) bool {
|
|
switch v1, v2 := reflect.ValueOf(actual), reflect.ValueOf(expected); v1.Kind() { //nolint:exhaustive // Covered by default case.
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return v1.Int() > v2.Int()
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
return v1.Uint() > v2.Uint()
|
|
case reflect.Float32, reflect.Float64:
|
|
return v1.Float() > v2.Float()
|
|
case reflect.String:
|
|
return v1.String() > v2.String()
|
|
default:
|
|
if actualTime, ok := actual.(time.Time); ok {
|
|
return actualTime.After(expected.(time.Time))
|
|
}
|
|
}
|
|
panic("actual is not a number, string or time.Time")
|
|
}
|
|
|
|
// LE is a synonym for LessOrEqual.
|
|
func (t *C) LE(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.LessOrEqual(actual, expected, msg...)
|
|
}
|
|
|
|
// Greater checks for actual > expected.
|
|
//
|
|
// Both actual and expected must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - strings
|
|
// - time.Time
|
|
func (t *C) Greater(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
isGreater(actual, expected))
|
|
}
|
|
|
|
// GT is a synonym for Greater.
|
|
func (t *C) GT(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.Greater(actual, expected, msg...)
|
|
}
|
|
|
|
// GreaterOrEqual checks for actual >= expected.
|
|
//
|
|
// Both actual and expected must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - strings
|
|
// - time.Time
|
|
func (t *C) GreaterOrEqual(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
!isLess(actual, expected))
|
|
}
|
|
|
|
// GE is a synonym for GreaterOrEqual.
|
|
func (t *C) GE(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.GreaterOrEqual(actual, expected, msg...)
|
|
}
|
|
|
|
// Between checks for min < actual < max.
|
|
//
|
|
// All three actual, min and max must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - strings
|
|
// - time.Time
|
|
func (t *C) Between(actual, min, max interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report3(actual, min, max, msg,
|
|
isBetween(actual, min, max))
|
|
}
|
|
|
|
func isBetween(actual, min, max interface{}) bool {
|
|
switch v, vmin, vmax := reflect.ValueOf(actual), reflect.ValueOf(min), reflect.ValueOf(max); v.Kind() { //nolint:exhaustive // Covered by default case.
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return vmin.Int() < v.Int() && v.Int() < vmax.Int()
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
return vmin.Uint() < v.Uint() && v.Uint() < vmax.Uint()
|
|
case reflect.Float32, reflect.Float64:
|
|
return vmin.Float() < v.Float() && v.Float() < vmax.Float()
|
|
case reflect.String:
|
|
return vmin.String() < v.String() && v.String() < vmax.String()
|
|
default:
|
|
if actualTime, ok := actual.(time.Time); ok {
|
|
minTime := min.(time.Time) //nolint:forcetypeassert // Want panic.
|
|
maxTime := max.(time.Time) //nolint:forcetypeassert // Want panic.
|
|
return minTime.Before(actualTime) && actualTime.Before(maxTime)
|
|
}
|
|
}
|
|
panic("actual is not a number, string or time.Time")
|
|
}
|
|
|
|
// NotBetween checks for actual <= min or max <= actual.
|
|
//
|
|
// All three actual, min and max must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - strings
|
|
// - time.Time
|
|
func (t *C) NotBetween(actual, min, max interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report3(actual, min, max, msg,
|
|
!isBetween(actual, min, max))
|
|
}
|
|
|
|
// BetweenOrEqual checks for min <= actual <= max.
|
|
//
|
|
// All three actual, min and max must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - strings
|
|
// - time.Time
|
|
func (t *C) BetweenOrEqual(actual, min, max interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report3(actual, min, max, msg,
|
|
isBetween(actual, min, max) || isEqual(actual, min) || isEqual(actual, max))
|
|
}
|
|
|
|
// NotBetweenOrEqual checks for actual < min or max < actual.
|
|
//
|
|
// All three actual, min and max must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - strings
|
|
// - time.Time
|
|
func (t *C) NotBetweenOrEqual(actual, min, max interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report3(actual, min, max, msg,
|
|
!(isBetween(actual, min, max) || isEqual(actual, min) || isEqual(actual, max)))
|
|
}
|
|
|
|
// InDelta checks for expected-delta <= actual <= expected+delta.
|
|
//
|
|
// All three actual, expected and delta must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - time.Time (in this case delta must be time.Duration)
|
|
func (t *C) InDelta(actual, expected, delta interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report3(actual, expected, delta, msg,
|
|
isInDelta(actual, expected, delta))
|
|
}
|
|
|
|
func isInDelta(actual, expected, delta interface{}) bool {
|
|
switch v, e, d := reflect.ValueOf(actual), reflect.ValueOf(expected), reflect.ValueOf(delta); v.Kind() { //nolint:exhaustive // Covered by default case.
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
min, max := e.Int()-d.Int(), e.Int()+d.Int()
|
|
return min <= v.Int() && v.Int() <= max
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
min, max := e.Uint()-d.Uint(), e.Uint()+d.Uint()
|
|
return min <= v.Uint() && v.Uint() <= max
|
|
case reflect.Float32, reflect.Float64:
|
|
min, max := e.Float()-d.Float(), e.Float()+d.Float()
|
|
return min <= v.Float() && v.Float() <= max
|
|
default:
|
|
if actualTime, ok := actual.(time.Time); ok {
|
|
expectedTime := expected.(time.Time) //nolint:forcetypeassert // Want panic.
|
|
dur := delta.(time.Duration) //nolint:forcetypeassert // Want panic.
|
|
minTime, maxTime := expectedTime.Add(-dur), expectedTime.Add(dur)
|
|
return minTime.Before(actualTime) && actualTime.Before(maxTime) ||
|
|
actualTime.Equal(minTime) ||
|
|
actualTime.Equal(maxTime)
|
|
}
|
|
}
|
|
panic("actual is not a number or time.Time")
|
|
}
|
|
|
|
// NotInDelta checks for actual < expected-delta or expected+delta < actual.
|
|
//
|
|
// All three actual, expected and delta must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
// - time.Time (in this case delta must be time.Duration)
|
|
func (t *C) NotInDelta(actual, expected, delta interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report3(actual, expected, delta, msg,
|
|
!isInDelta(actual, expected, delta))
|
|
}
|
|
|
|
// InSMAPE checks that actual and expected have a symmetric mean absolute
|
|
// percentage error (SMAPE) is less than given smape.
|
|
//
|
|
// Both actual and expected must be either:
|
|
// - signed integers
|
|
// - unsigned integers
|
|
// - floats
|
|
//
|
|
// Allowed smape values are: 0.0 < smape < 100.0.
|
|
//
|
|
// Used formula returns SMAPE value between 0 and 100 (percents):
|
|
// - 0.0 when actual == expected
|
|
// - ~0.5 when they differs in ~1%
|
|
// - ~5 when they differs in ~10%
|
|
// - ~20 when they differs in 1.5 times
|
|
// - ~33 when they differs in 2 times
|
|
// - 50.0 when they differs in 3 times
|
|
// - ~82 when they differs in 10 times
|
|
// - 99.0+ when actual and expected differs in 200+ times
|
|
// - 100.0 when only one of actual or expected is 0 or one of them is
|
|
// positive while another is negative
|
|
func (t *C) InSMAPE(actual, expected interface{}, smape float64, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report3(actual, expected, smape, msg,
|
|
isInSMAPE(actual, expected, smape))
|
|
}
|
|
|
|
func isInSMAPE(actual, expected interface{}, smape float64) bool {
|
|
if !(0 < smape && smape < 100) {
|
|
panic("smape is not in allowed range: 0 < smape < 100")
|
|
}
|
|
a := reflect.ValueOf(actual).Convert(typFloat64).Float()
|
|
e := reflect.ValueOf(expected).Convert(typFloat64).Float()
|
|
if a == 0 && e == 0 {
|
|
return true // avoid division by zero in legal use case
|
|
}
|
|
return 100*math.Abs(e-a)/(math.Abs(e)+math.Abs(a)) < smape
|
|
}
|
|
|
|
// NotInSMAPE checks that actual and expected have a symmetric mean
|
|
// absolute percentage error (SMAPE) is greater than or equal to given
|
|
// smape.
|
|
//
|
|
// See InSMAPE about supported actual/expected types and check logic.
|
|
func (t *C) NotInSMAPE(actual, expected interface{}, smape float64, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report3(actual, expected, smape, msg,
|
|
!isInSMAPE(actual, expected, smape))
|
|
}
|
|
|
|
// HasPrefix checks for strings.HasPrefix(actual, expected).
|
|
//
|
|
// Both actual and expected may have any of these types:
|
|
// - string - will use as is
|
|
// - []byte - will convert with string()
|
|
// - []rune - will convert with string()
|
|
// - fmt.Stringer - will convert with actual.String()
|
|
// - error - will convert with actual.Error()
|
|
// - nil - check will always fail
|
|
func (t *C) HasPrefix(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
ok := isHasPrefix(&actual, &expected)
|
|
return t.report2(actual, expected, msg,
|
|
ok)
|
|
}
|
|
|
|
// isHasPrefix updates actual and expected to be a real string used for check,
|
|
// to make dump easier to understand, but this result in losing type information.
|
|
func isHasPrefix(actual, expected *interface{}) bool { //nolint:gocritic // False positive.
|
|
if *actual == nil || *expected == nil {
|
|
return false
|
|
}
|
|
if !stringify(actual) {
|
|
panic("actual is not a string, []byte, []rune, fmt.Stringer, error or nil")
|
|
}
|
|
if !stringify(expected) {
|
|
panic("expected is not a string, []byte, []rune, fmt.Stringer, error or nil")
|
|
}
|
|
return strings.HasPrefix((*actual).(string), (*expected).(string))
|
|
}
|
|
|
|
// NotHasPrefix checks for !strings.HasPrefix(actual, expected).
|
|
//
|
|
// See HasPrefix about supported actual/expected types and check logic.
|
|
func (t *C) NotHasPrefix(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
ok := !isHasPrefix(&actual, &expected)
|
|
return t.report2(actual, expected, msg,
|
|
ok)
|
|
}
|
|
|
|
// HasSuffix checks for strings.HasSuffix(actual, expected).
|
|
//
|
|
// Both actual and expected may have any of these types:
|
|
// - string - will use as is
|
|
// - []byte - will convert with string()
|
|
// - []rune - will convert with string()
|
|
// - fmt.Stringer - will convert with actual.String()
|
|
// - error - will convert with actual.Error()
|
|
// - nil - check will always fail
|
|
func (t *C) HasSuffix(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
ok := isHasSuffix(&actual, &expected)
|
|
return t.report2(actual, expected, msg,
|
|
ok)
|
|
}
|
|
|
|
// isHasSuffix updates actual and expected to be a real string used for check,
|
|
// to make dump easier to understand, but this result in losing type information.
|
|
func isHasSuffix(actual, expected *interface{}) bool { //nolint:gocritic // False positive.
|
|
if *actual == nil || *expected == nil {
|
|
return false
|
|
}
|
|
if !stringify(actual) {
|
|
panic("actual is not a string, []byte, []rune, fmt.Stringer, error or nil")
|
|
}
|
|
if !stringify(expected) {
|
|
panic("expected is not a string, []byte, []rune, fmt.Stringer, error or nil")
|
|
}
|
|
return strings.HasSuffix((*actual).(string), (*expected).(string))
|
|
}
|
|
|
|
// NotHasSuffix checks for !strings.HasSuffix(actual, expected).
|
|
//
|
|
// See HasSuffix about supported actual/expected types and check logic.
|
|
func (t *C) NotHasSuffix(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
ok := !isHasSuffix(&actual, &expected)
|
|
return t.report2(actual, expected, msg,
|
|
ok)
|
|
}
|
|
|
|
// JSONEqual normalize formatting of actual and expected (if they're valid
|
|
// JSON) and then checks for bytes.Equal(actual, expected).
|
|
//
|
|
// Both actual and expected may have any of these types:
|
|
// - string
|
|
// - []byte
|
|
// - json.RawMessage
|
|
// - *json.RawMessage
|
|
// - nil
|
|
//
|
|
// In case any of actual or expected is nil or empty or (for string or
|
|
// []byte) is invalid JSON - check will fail.
|
|
func (t *C) JSONEqual(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
ok := isJSONEqual(actual, expected)
|
|
if !ok {
|
|
if buf := jsonify(actual); len(buf) != 0 {
|
|
actual = buf
|
|
}
|
|
if buf := jsonify(expected); len(buf) != 0 {
|
|
expected = buf
|
|
}
|
|
}
|
|
return t.report2(actual, expected, msg,
|
|
ok)
|
|
}
|
|
|
|
func isJSONEqual(actual, expected interface{}) bool {
|
|
jsonActual, jsonExpected := jsonify(actual), jsonify(expected)
|
|
return len(jsonActual) != 0 && len(jsonExpected) != 0 &&
|
|
bytes.Equal(jsonActual, jsonExpected)
|
|
}
|
|
|
|
func jsonify(arg interface{}) json.RawMessage {
|
|
switch v := (arg).(type) {
|
|
case nil:
|
|
return nil
|
|
case json.RawMessage:
|
|
return v
|
|
case *json.RawMessage:
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
return *v
|
|
}
|
|
buf := reflect.ValueOf(arg).Convert(typBytes).Interface().([]byte) //nolint:forcetypeassert // Want panic.
|
|
|
|
var v interface{}
|
|
err := json.Unmarshal(buf, &v)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
buf, err = json.Marshal(v)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return buf
|
|
}
|
|
|
|
// HasType checks is actual has same type as expected.
|
|
func (t *C) HasType(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
reflect.TypeOf(actual) == reflect.TypeOf(expected))
|
|
}
|
|
|
|
// NotHasType checks is actual has not same type as expected.
|
|
func (t *C) NotHasType(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
reflect.TypeOf(actual) != reflect.TypeOf(expected))
|
|
}
|
|
|
|
// Implements checks is actual implements interface pointed by expected.
|
|
//
|
|
// You must use pointer to interface type in expected:
|
|
//
|
|
// t.Implements(os.Stdin, (*io.Reader)(nil))
|
|
func (t *C) Implements(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
isImplements(actual, expected))
|
|
}
|
|
|
|
func isImplements(actual, expected interface{}) bool {
|
|
typActual := reflect.TypeOf(actual)
|
|
if typActual.Kind() != reflect.Ptr {
|
|
typActual = reflect.PtrTo(typActual)
|
|
}
|
|
return typActual.Implements(reflect.TypeOf(expected).Elem())
|
|
}
|
|
|
|
// NotImplements checks is actual does not implements interface pointed by expected.
|
|
//
|
|
// You must use pointer to interface type in expected:
|
|
//
|
|
// t.NotImplements(os.Stdin, (*fmt.Stringer)(nil))
|
|
func (t *C) NotImplements(actual, expected interface{}, msg ...interface{}) bool {
|
|
t.Helper()
|
|
return t.report2(actual, expected, msg,
|
|
!isImplements(actual, expected))
|
|
}
|