diff --git a/assertion.go b/assertion.go index 9c9e662..e9cbacb 100644 --- a/assertion.go +++ b/assertion.go @@ -5,13 +5,9 @@ package assert import ( - "errors" "fmt" - "io/fs" - "os" "reflect" "regexp" - "strings" "testing" "time" ) @@ -108,89 +104,6 @@ func (a *Assertion) NotEmpty(expr interface{}, msg ...interface{}) *Assertion { return a.Assert(!isEmpty(expr), NewFailure("NotEmpty", msg, map[string]interface{}{"v": expr})) } -// Error 断言有错误发生 -// -// 传递未初始化的 error 值(var err error = nil),将断言失败 -// -// [Assertion.NotNil] 的特化版本,限定了类型为 error。 -func (a *Assertion) Error(expr error, msg ...interface{}) *Assertion { - a.TB().Helper() - return a.Assert(!isNil(expr), NewFailure("Error", msg, map[string]interface{}{"v": expr})) -} - -// ErrorString 断言有错误发生且错误信息中包含指定的字符串 str -// -// 传递未初始化的 error 值(var err error = nil),将断言失败 -func (a *Assertion) ErrorString(expr error, str string, msg ...interface{}) *Assertion { - a.TB().Helper() - - if isNil(expr) { // 空值,必定没有错误 - return a.Assert(false, NewFailure("ErrorString", msg, map[string]interface{}{"v": expr})) - } - return a.Assert(strings.Contains(expr.Error(), str), NewFailure("ErrorString", msg, map[string]interface{}{"v": expr})) -} - -// ErrorIs 断言 expr 为 target 类型 -// -// 相当于 a.True(errors.Is(expr, target)) -func (a *Assertion) ErrorIs(expr, target error, msg ...interface{}) *Assertion { - a.TB().Helper() - return a.Assert(errors.Is(expr, target), NewFailure("ErrorIs", msg, map[string]interface{}{"err": expr})) -} - -// NotError 断言没有错误 -// -// [Assertion.Nil] 的特化版本,限定了类型为 error。 -func (a *Assertion) NotError(expr error, msg ...interface{}) *Assertion { - a.TB().Helper() - return a.Assert(isNil(expr), NewFailure("NotError", msg, map[string]interface{}{"v": expr})) -} - -// Panic 断言函数会发生 panic -func (a *Assertion) Panic(fn func(), msg ...interface{}) *Assertion { - a.TB().Helper() - has, _ := hasPanic(fn) - return a.Assert(has, NewFailure("Panic", msg, nil)) -} - -// PanicString 断言函数会发生 panic 且 panic 信息中包含指定的字符串内容 -func (a *Assertion) PanicString(fn func(), str string, msg ...interface{}) *Assertion { - a.TB().Helper() - - if has, m := hasPanic(fn); has { - return a.Assert(strings.Contains(fmt.Sprint(m), str), NewFailure("PanicString", msg, map[string]interface{}{"msg": m})) - } - return a.Assert(false, NewFailure("PanicString", msg, nil)) -} - -// PanicType 断言函数会发生 panic 且抛出指定的类型 -func (a *Assertion) PanicType(fn func(), typ interface{}, msg ...interface{}) *Assertion { - a.TB().Helper() - - if has, m := hasPanic(fn); has { - t1, t2 := getType(true, m, typ) - return a.Assert(t1 == t2, NewFailure("PanicType", msg, map[string]interface{}{"v1": t1, "v2": t2})) - } - return a.Assert(false, NewFailure("PanicType", msg, nil)) -} - -// PanicValue 断言函数会抛出与 v 相同的信息 -func (a *Assertion) PanicValue(fn func(), v interface{}, msg ...interface{}) *Assertion { - a.TB().Helper() - - if has, m := hasPanic(fn); has { - return a.Assert(isEqual(m, v), NewFailure("PanicValue", msg, map[string]interface{}{"v": m})) - } - return a.Assert(false, NewFailure("PanicType", msg, nil)) -} - -// NotPanic 断言 fn 不会 panic -func (a *Assertion) NotPanic(fn func(), msg ...interface{}) *Assertion { - a.TB().Helper() - has, m := hasPanic(fn) - return a.Assert(!has, NewFailure("NotPanic", msg, map[string]interface{}{"err": m})) -} - // Contains 断言 container 包含 item 或是包含 item 中的所有项 // // 若 container string、[]byte 和 []rune 类型, @@ -224,40 +137,6 @@ func (a *Assertion) NotZero(v interface{}, msg ...interface{}) *Assertion { return a.Assert(!isZero(v), NewFailure("NotZero", msg, map[string]interface{}{"v": v})) } -// Length 断言长度是否为指定的值 -// -// v 可以是以下类型: -// - map -// - string -// - slice -// - array -func (a *Assertion) Length(v interface{}, l int, msg ...interface{}) *Assertion { - a.TB().Helper() - - rl, err := getLen(v) - if err != "" { - a.Assert(false, NewFailure("Length", msg, map[string]interface{}{"err": err})) - } - return a.Assert(rl == l, NewFailure("Length", msg, map[string]interface{}{"l1": rl, "l2": l})) -} - -// NotLength 断言长度不是指定的值 -// -// v 可以是以下类型: -// - map -// - string -// - slice -// - array -func (a *Assertion) NotLength(v interface{}, l int, msg ...interface{}) *Assertion { - a.TB().Helper() - - rl, err := getLen(v) - if err != "" { - a.Assert(false, NewFailure("NotLength", msg, map[string]interface{}{"err": err})) - } - return a.Assert(rl != l, NewFailure("NotLength", msg, map[string]interface{}{"l": rl})) -} - // TypeEqual 断言两个值的类型是否相同 // // ptr 如果为 true,则会在对象为指针时,查找其指向的对象。 diff --git a/assertion_test.go b/assertion_test.go index c8d8560..0baf882 100644 --- a/assertion_test.go +++ b/assertion_test.go @@ -6,8 +6,6 @@ package assert import ( "database/sql" - "errors" - "fmt" "regexp" "testing" "time" @@ -66,51 +64,6 @@ func TestAssertion_Equal_NotEqual_Nil_NotNil(t *testing.T) { NotNil(v6, "a.NotNil(v6) failed") } -func TestAssertion_Error(t *testing.T) { - a := New(t, false) - - err := errors.New("test") - a.Error(err, "a.Error(err) failed") - a.ErrorString(err, "test", "ErrorString(err) failed") - - err2 := &errorImpl{msg: "msg"} - a.Error(err2, "ErrorString(errorImpl) failed") - a.ErrorString(err2, "msg", "ErrorString(errorImpl) failed") - - var err3 error - a.NotError(err3, "var err1 error failed") - - err4 := errors.New("err4") - err5 := fmt.Errorf("err5 with %w", err4) - a.ErrorIs(err5, err4) -} - -func TestAssertion_Panic(t *testing.T) { - a := New(t, false) - - f1 := func() { - panic("panic message") - } - - a.Panic(f1) - a.PanicString(f1, "message") - a.PanicType(f1, "abc") - a.PanicValue(f1, "panic message") - - f1 = func() { - panic(errors.New("panic")) - } - a.PanicType(f1, errors.New("abc")) - - f1 = func() { - panic(&errorImpl{msg: "panic"}) - } - a.PanicType(f1, &errorImpl{msg: "abc"}) - - f1 = func() {} - a.NotPanic(f1) -} - func TestAssertion_Zero_NotZero(t *testing.T) { a := New(t, false) @@ -128,24 +81,6 @@ func TestAssertion_Zero_NotZero(t *testing.T) { a.NotZero([]int{}) } -func TestAssertion_Length_NotLength(t *testing.T) { - a := New(t, false) - - a.Length(nil, 0) - a.Length([]int{1, 2}, 2) - a.Length([3]int{1, 2, 3}, 3) - a.NotLength([3]int{1, 2, 3}, 2) - a.Length(map[string]string{"1": "1", "2": "2"}, 2) - a.NotLength(map[string]string{"1": "1", "2": "2"}, 3) - slices := []rune{'a', 'b', 'c'} - ps := &slices - pps := &ps - a.Length(pps, 3) - a.NotLength(pps, 2) - a.Length("string", 6) - a.NotLength("string", 4) -} - func TestAssertion_Contains(t *testing.T) { a := New(t, false) diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..e0a1200 --- /dev/null +++ b/errors.go @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2014-2024 caixw +// +// SPDX-License-Identifier: MIT + +package assert + +import ( + "errors" + "fmt" + "strings" +) + +// Error 断言有错误发生 +// +// 传递未初始化的 error 值(var err error = nil),将断言失败 +// +// [Assertion.NotNil] 的特化版本,限定了类型为 error。 +func (a *Assertion) Error(expr error, msg ...interface{}) *Assertion { + a.TB().Helper() + return a.Assert(!isNil(expr), NewFailure("Error", msg, map[string]interface{}{"v": expr})) +} + +// ErrorString 断言有错误发生且错误信息中包含指定的字符串 str +// +// 传递未初始化的 error 值(var err error = nil),将断言失败 +func (a *Assertion) ErrorString(expr error, str string, msg ...interface{}) *Assertion { + a.TB().Helper() + + if isNil(expr) { // 空值,必定没有错误 + return a.Assert(false, NewFailure("ErrorString", msg, map[string]interface{}{"v": expr})) + } + return a.Assert(strings.Contains(expr.Error(), str), NewFailure("ErrorString", msg, map[string]interface{}{"v": expr})) +} + +// ErrorIs 断言 expr 为 target 类型 +// +// 相当于 a.True(errors.Is(expr, target)) +func (a *Assertion) ErrorIs(expr, target error, msg ...interface{}) *Assertion { + a.TB().Helper() + return a.Assert(errors.Is(expr, target), NewFailure("ErrorIs", msg, map[string]interface{}{"err": expr})) +} + +// NotError 断言没有错误 +// +// [Assertion.Nil] 的特化版本,限定了类型为 error。 +func (a *Assertion) NotError(expr error, msg ...interface{}) *Assertion { + a.TB().Helper() + return a.Assert(isNil(expr), NewFailure("NotError", msg, map[string]interface{}{"v": expr})) +} + +// Panic 断言函数会发生 panic +func (a *Assertion) Panic(fn func(), msg ...interface{}) *Assertion { + a.TB().Helper() + has, _ := hasPanic(fn) + return a.Assert(has, NewFailure("Panic", msg, nil)) +} + +// PanicString 断言函数会发生 panic 且 panic 信息中包含指定的字符串内容 +func (a *Assertion) PanicString(fn func(), str string, msg ...interface{}) *Assertion { + a.TB().Helper() + + if has, m := hasPanic(fn); has { + return a.Assert(strings.Contains(fmt.Sprint(m), str), NewFailure("PanicString", msg, map[string]interface{}{"msg": m})) + } + return a.Assert(false, NewFailure("PanicString", msg, nil)) +} + +// PanicType 断言函数会发生 panic 且抛出指定的类型 +func (a *Assertion) PanicType(fn func(), typ interface{}, msg ...interface{}) *Assertion { + a.TB().Helper() + + if has, m := hasPanic(fn); has { + t1, t2 := getType(true, m, typ) + return a.Assert(t1 == t2, NewFailure("PanicType", msg, map[string]interface{}{"v1": t1, "v2": t2})) + } + return a.Assert(false, NewFailure("PanicType", msg, nil)) +} + +// PanicValue 断言函数会抛出与 v 相同的信息 +func (a *Assertion) PanicValue(fn func(), v interface{}, msg ...interface{}) *Assertion { + a.TB().Helper() + + if has, m := hasPanic(fn); has { + return a.Assert(isEqual(m, v), NewFailure("PanicValue", msg, map[string]interface{}{"v": m})) + } + return a.Assert(false, NewFailure("PanicType", msg, nil)) +} + +// NotPanic 断言 fn 不会 panic +func (a *Assertion) NotPanic(fn func(), msg ...interface{}) *Assertion { + a.TB().Helper() + has, m := hasPanic(fn) + return a.Assert(!has, NewFailure("NotPanic", msg, map[string]interface{}{"err": m})) +} + +// hasPanic 判断 fn 函数是否会发生 panic +// 若发生了 panic,将把 msg 一起返回。 +func hasPanic(fn func()) (has bool, msg interface{}) { + defer func() { + if msg = recover(); msg != nil { + has = true + } + }() + fn() + + return +} diff --git a/errors_test.go b/errors_test.go new file mode 100644 index 0000000..f9e8093 --- /dev/null +++ b/errors_test.go @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2014-2024 caixw +// +// SPDX-License-Identifier: MIT + +package assert + +import ( + "errors" + "fmt" + "testing" +) + +func TestAssertion_Error(t *testing.T) { + a := New(t, false) + + err := errors.New("test") + a.Error(err, "a.Error(err) failed") + a.ErrorString(err, "test", "ErrorString(err) failed") + + err2 := &errorImpl{msg: "msg"} + a.Error(err2, "ErrorString(errorImpl) failed") + a.ErrorString(err2, "msg", "ErrorString(errorImpl) failed") + + var err3 error + a.NotError(err3, "var err1 error failed") + + err4 := errors.New("err4") + err5 := fmt.Errorf("err5 with %w", err4) + a.ErrorIs(err5, err4) +} + +func TestAssertion_Panic(t *testing.T) { + a := New(t, false) + + f1 := func() { + panic("panic message") + } + + a.Panic(f1) + a.PanicString(f1, "message") + a.PanicType(f1, "abc") + a.PanicValue(f1, "panic message") + + f1 = func() { + panic(errors.New("panic")) + } + a.PanicType(f1, errors.New("abc")) + + f1 = func() { + panic(&errorImpl{msg: "panic"}) + } + a.PanicType(f1, &errorImpl{msg: "abc"}) + + f1 = func() {} + a.NotPanic(f1) +} + +func TestHasPanic(t *testing.T) { + f1 := func() { + panic("panic") + } + + if has, _ := hasPanic(f1); !has { + t.Error("f1未发生panic") + } + + f2 := func() { + f1() + } + + if has, msg := hasPanic(f2); !has { + t.Error("f2未发生panic") + } else if msg != "panic" { + t.Errorf("f2发生了panic,但返回信息不正确,应为[panic],但其实返回了%v", msg) + } + + f3 := func() { + defer func() { + if msg := recover(); msg != nil { + t.Logf("TestHasPanic.f3 recover msg:[%v]", msg) + } + }() + + f1() + } + + if has, msg := hasPanic(f3); has { + t.Errorf("f3发生了panic,其信息为:[%v]", msg) + } + + f4 := func() { + //todo + } + + if has, msg := hasPanic(f4); has { + t.Errorf("f4发生panic,其信息为[%v]", msg) + } +} diff --git a/number.go b/number.go new file mode 100644 index 0000000..08a7f37 --- /dev/null +++ b/number.go @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: 2014-2024 caixw +// +// SPDX-License-Identifier: MIT + +package assert + +import ( + "fmt" + "reflect" +) + +// Length 断言长度是否为指定的值 +// +// v 可以是以下类型: +// - map +// - string +// - slice +// - array +func (a *Assertion) Length(v interface{}, l int, msg ...interface{}) *Assertion { + a.TB().Helper() + + rl, err := getLen(v) + if err != "" { + a.Assert(false, NewFailure("Length", msg, map[string]interface{}{"err": err})) + } + return a.Assert(rl == l, NewFailure("Length", msg, map[string]interface{}{"l1": rl, "l2": l})) +} + +// NotLength 断言长度不是指定的值 +// +// v 可以是以下类型: +// - map +// - string +// - slice +// - array +func (a *Assertion) NotLength(v interface{}, l int, msg ...interface{}) *Assertion { + a.TB().Helper() + + rl, err := getLen(v) + if err != "" { + a.Assert(false, NewFailure("NotLength", msg, map[string]interface{}{"err": err})) + } + return a.Assert(rl != l, NewFailure("NotLength", msg, map[string]interface{}{"l": rl})) +} + +func (a *Assertion) Greater(v interface{}, val float64, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("Greater", msg, nil)) + } + return a.Assert(vv > val, NewFailure("Greater", msg, nil)) +} + +func (a *Assertion) Less(v interface{}, val float64, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("Less", msg, nil)) + } + return a.Assert(vv < val, NewFailure("Less", msg, nil)) +} + +func (a *Assertion) GreaterEqual(v interface{}, val float64, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("GreaterEqual", msg, nil)) + } + return a.Assert(vv >= val, NewFailure("GreaterEqual", msg, nil)) +} + +func (a *Assertion) LessEqual(v interface{}, val float64, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("LessEqual", msg, nil)) + } + return a.Assert(vv <= val, NewFailure("LessEqual", msg, nil)) +} + +// Positive 断言 v 为正数 +// +// NOTE: 不包含 0 +func (a *Assertion) Positive(v interface{}, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("Positive", msg, nil)) + } + return a.Assert(vv > 0, NewFailure("Positive", msg, nil)) +} + +// Negative 断言 v 为负数 +// +// NOTE: 不包含 0 +func (a *Assertion) Negative(v interface{}, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("Negative", msg, nil)) + } + return a.Assert(vv < 0, NewFailure("Negative", msg, nil)) +} + +// Between 断言 v 是否存在于 (min,max) 之间 +func (a *Assertion) Between(v interface{}, min, max float64, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("Between", msg, nil)) + } + + return a.Assert(vv > min && vv < max, NewFailure("Between", msg, nil)) +} + +// BetweenEqual 断言 v 是否存在于 [min,max] 之间 +func (a *Assertion) BetweenEqual(v interface{}, min, max float64, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("BetweenEqual", msg, nil)) + } + + return a.Assert(vv >= min && vv <= max, NewFailure("BetweenEqual", msg, nil)) +} + +// BetweenEqualMin 断言 v 是否存在于 [min,max) 之间 +func (a *Assertion) BetweenEqualMin(v interface{}, min, max float64, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("BetweenEqualMin", msg, nil)) + } + + return a.Assert(vv >= min && vv < max, NewFailure("BetweenEqualMin", msg, nil)) +} + +// BetweenEqualMax 断言 v 是否存在于 (min,max] 之间 +func (a *Assertion) BetweenEqualMax(v interface{}, min, max float64, msg ...interface{}) *Assertion { + vv, ok := getNumber(v) + if !ok { + return a.Assert(false, NewFailure("BetweenEqualMax", msg, nil)) + } + + return a.Assert(vv > min && vv <= max, NewFailure("BetweenEqualMax", msg, nil)) +} + +// bool 表示是否成功转换 +func getNumber(v interface{}) (float64, bool) { + switch val := v.(type) { + case int: + return float64(val), true + case int8: + return float64(val), true + case int16: + return float64(val), true + case int32: + return float64(val), true + case int64: + return float64(val), true + case uint: + return float64(val), true + case uint8: + return float64(val), true + case uint16: + return float64(val), true + case uint32: + return float64(val), true + case uint64: + return float64(val), true + case float32: + return float64(val), true + case float64: + return float64(val), true + } + + return 0, false +} + +func getLen(v interface{}) (l int, msg string) { + r := reflect.ValueOf(v) + for r.Kind() == reflect.Ptr { + r = r.Elem() + } + + if v == nil { + return 0, "" + } + + switch r.Kind() { + case reflect.Array, reflect.String, reflect.Slice, reflect.Map, reflect.Chan: + return r.Len(), "" + } + return 0, fmt.Sprintf("无法获取 %s 类型的长度信息", r.Kind()) +} diff --git a/number_test.go b/number_test.go new file mode 100644 index 0000000..5458655 --- /dev/null +++ b/number_test.go @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2014-2024 caixw +// +// SPDX-License-Identifier: MIT + +package assert + +import "testing" + +func TestAssertion_Length_NotLength(t *testing.T) { + a := New(t, false) + + a.Length(nil, 0) + a.Length([]int{1, 2}, 2) + a.Length([3]int{1, 2, 3}, 3) + a.NotLength([3]int{1, 2, 3}, 2) + a.Length(map[string]string{"1": "1", "2": "2"}, 2) + a.NotLength(map[string]string{"1": "1", "2": "2"}, 3) + slices := []rune{'a', 'b', 'c'} + ps := &slices + pps := &ps + a.Length(pps, 3) + a.NotLength(pps, 2) + a.Length("string", 6) + a.NotLength("string", 4) +} + +func TestAssertion_Greater_Less(t *testing.T) { + a := New(t, false) + + a.Greater(uint16(5), 3).Less(uint8(5), 6).GreaterEqual(uint64(5), 5).LessEqual(uint(5), 5) +} + +func TestAssertion_Positive_Negative(t *testing.T) { + a := New(t, false) + + a.Positive(float32(5)).Negative(float64(-5)) +} + +func TestAssertion_Between(t *testing.T) { + a := New(t, false) + + a.Between(int8(5), 1, 6). + BetweenEqual(int16(5), 5, 6). + BetweenEqual(int32(6), 5, 6). + BetweenEqualMin(int64(5), 5, 6). + BetweenEqualMax(uint32(5), 4, 5) +} diff --git a/util.go b/util.go index 6ac5088..daeeb54 100644 --- a/util.go +++ b/util.go @@ -6,7 +6,6 @@ package assert import ( "bytes" - "fmt" "reflect" "strings" ) @@ -168,19 +167,6 @@ func isEqual(v1, v2 interface{}) bool { return false } -// hasPanic 判断 fn 函数是否会发生 panic -// 若发生了 panic,将把 msg 一起返回。 -func hasPanic(fn func()) (has bool, msg interface{}) { - defer func() { - if msg = recover(); msg != nil { - has = true - } - }() - fn() - - return -} - // isContains 判断 container 是否包含了 item 的内容。若是指针,会判断指针指向的内容 func isContains(container, item interface{}) bool { if container == nil { // nil不包含任何东西 @@ -321,23 +307,6 @@ func isContains(container, item interface{}) bool { return false } -func getLen(v interface{}) (l int, msg string) { - r := reflect.ValueOf(v) - for r.Kind() == reflect.Ptr { - r = r.Elem() - } - - if v == nil { - return 0, "" - } - - switch r.Kind() { - case reflect.Array, reflect.String, reflect.Slice, reflect.Map, reflect.Chan: - return r.Len(), "" - } - return 0, fmt.Sprintf("无法获取 %s 类型的长度信息", r.Kind()) -} - func getType(ptr bool, v1, v2 interface{}) (t1, t2 reflect.Type) { t1 = reflect.TypeOf(v1) t2 = reflect.TypeOf(v2) diff --git a/util_test.go b/util_test.go index 9171fc4..30bd9d1 100644 --- a/util_test.go +++ b/util_test.go @@ -201,48 +201,6 @@ func TestIsNil(t *testing.T) { } } -func TestHasPanic(t *testing.T) { - f1 := func() { - panic("panic") - } - - if has, _ := hasPanic(f1); !has { - t.Error("f1未发生panic") - } - - f2 := func() { - f1() - } - - if has, msg := hasPanic(f2); !has { - t.Error("f2未发生panic") - } else if msg != "panic" { - t.Errorf("f2发生了panic,但返回信息不正确,应为[panic],但其实返回了%v", msg) - } - - f3 := func() { - defer func() { - if msg := recover(); msg != nil { - t.Logf("TestHasPanic.f3 recover msg:[%v]", msg) - } - }() - - f1() - } - - if has, msg := hasPanic(f3); has { - t.Errorf("f3发生了panic,其信息为:[%v]", msg) - } - - f4 := func() { - //todo - } - - if has, msg := hasPanic(f4); has { - t.Errorf("f4发生panic,其信息为[%v]", msg) - } -} - func TestIsContains(t *testing.T) { fn := func(result bool, container, item interface{}) { t.Helper()