Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delay type checks #264

Merged
merged 6 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions internal/util/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,27 @@ import (
"github.com/maxatome/go-testdeep/internal/types"
)

// ToString does its best to stringify val.
func ToString(val any) string {
// ToString does its best to stringify val. inReflectValue is used
// internally to avoid treating specifically reflect.Value type.
func ToString(val any, inReflectValue ...bool) string {
if val == nil {
return "nil"
}

switch tval := val.(type) {
case reflect.Value:
if len(inReflectValue) > 0 && inReflectValue[0] {
break
}
newVal, ok := dark.GetInterface(tval, true)
if ok {
return ToString(newVal)
return ToString(newVal, true)
}

case []reflect.Value:
if len(inReflectValue) > 0 && inReflectValue[0] {
break
}
var buf strings.Builder
SliceToString(&buf, tval)
return buf.String()
Expand All @@ -55,7 +62,10 @@ func ToString(val any) string {

// no "(bool) " prefix for booleans
case bool:
return TernStr(tval, "true", "false")
if tval {
return "true"
}
return "false"

case types.TestDeepStringer:
return tval.String()
Expand Down
8 changes: 8 additions & 0 deletions internal/util/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,19 @@ func TestToString(t *testing.T) {
{paramGot: "foo\n`\"bar", expected: "(string) (len=9) \"foo\\n`\\\"bar\""},
{paramGot: "foo\n\"`bar", expected: "(string) (len=9) \"foo\\n\\\"`bar\""},
{paramGot: reflect.ValueOf("foobar"), expected: `"foobar"`},
{
paramGot: reflect.ValueOf(reflect.ValueOf(42)),
expected: `(reflect.Value) <int Value>`,
},
{
paramGot: []reflect.Value{reflect.ValueOf("foo"), reflect.ValueOf("bar")},
expected: `("foo",
"bar")`,
},
{
paramGot: reflect.ValueOf([]reflect.Value{}),
expected: "([]reflect.Value) {\n}",
},
{paramGot: types.RawString("test"), expected: "test"},
{paramGot: types.RawInt(42), expected: "42"},
{paramGot: myTestDeepStringer{}, expected: "TesT!"},
Expand Down
8 changes: 0 additions & 8 deletions internal/util/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,3 @@ func TernRune(cond bool, a, b rune) rune {
}
return b
}

// TernStr returns a if cond is true, b otherwise.
func TernStr(cond bool, a, b string) string {
if cond {
return a
}
return b
}
3 changes: 0 additions & 3 deletions internal/util/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ import (
)

func TestTern(t *testing.T) {
test.EqualStr(t, util.TernStr(true, "A", "B"), "A")
test.EqualStr(t, util.TernStr(false, "A", "B"), "B")

test.EqualInt(t, int(util.TernRune(true, 'A', 'B')), int('A'))
test.EqualInt(t, int(util.TernRune(false, 'A', 'B')), int('B'))
}
20 changes: 10 additions & 10 deletions td/equal.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,9 @@ func deepValueEqual(ctx ctxerr.Context, got, expected reflect.Value) (err *ctxer
expected = op
}

if !got.IsValid() || !expected.IsValid() {
if got.IsValid() == expected.IsValid() {
return
}
return nilHandler(ctx, got, expected)
}

// Check if a Smuggle hook matches got type
if handled, e := ctx.Hooks.Smuggle(&got); handled {
if e != nil {
if got.IsValid() {
// Check if a Smuggle hook matches got type
if handled, e := ctx.Hooks.Smuggle(&got); handled && e != nil {
// ctx.BooleanError is always false here as hooks cannot be set globally
return ctx.CollectError(&ctxerr.Error{
Message: e.Error(),
Expand All @@ -159,6 +152,13 @@ func deepValueEqual(ctx ctxerr.Context, got, expected reflect.Value) (err *ctxer
}
}

if !got.IsValid() || !expected.IsValid() {
if got.IsValid() == expected.IsValid() {
return
}
return nilHandler(ctx, got, expected)
}

// Check if a Cmp hook matches got & expected types
if handled, e := ctx.Hooks.Cmp(got, expected); handled {
if e == nil {
Expand Down
4 changes: 2 additions & 2 deletions td/t_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (
// Each function in fns has to be a function with the following
// possible signatures:
//
// func (got A, expected A) bool
// func (got A, expected A) error
// func (got, expected A) bool
// func (got, expected A) error
//
// First arg is always got, and second is always expected.
//
Expand Down
115 changes: 115 additions & 0 deletions td/t_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,121 @@ func TestWithSmuggleHooks(tt *testing.T) {
})
}

tt.Run("Array", func(tt *testing.T) {
ttt := test.NewTestingTB(tt.Name())

t := td.NewT(ttt).WithSmuggleHooks(func(got reflect.Value) any {
if got.IsValid() {
return got.Interface()
}
return nil
})

got := [3]reflect.Value{1: reflect.ValueOf(42)}

td.CmpTrue(tt, td.Cmp(t, got, td.Array([3]reflect.Value{}, td.ArrayEntries{
0: nil, // if omitted, td.Array sets it to reflect.Value{}
1: 42,
2: nil, // if omitted, td.Array sets it to reflect.Value{}
})))
test.EqualStr(tt, ttt.LastMessage(), "")
ttt.ResetMessages()

td.CmpTrue(tt, td.Cmp(t, got[:], td.Slice([]reflect.Value{}, td.ArrayEntries{
0: nil, // if omitted, td.Array sets it to reflect.Value{}
1: 42,
2: nil, // if omitted, td.Array sets it to reflect.Value{}
})))
test.EqualStr(tt, ttt.LastMessage(), "")
ttt.ResetMessages()

td.CmpTrue(tt, td.Cmp(t, got[:], td.SuperSliceOf([]reflect.Value{}, td.ArrayEntries{
1: 42,
})))
test.EqualStr(tt, ttt.LastMessage(), "")
ttt.ResetMessages()
})

tt.Run("Map", func(tt *testing.T) {
ttt := test.NewTestingTB(tt.Name())

t := td.NewT(ttt).WithSmuggleHooks(func(got reflect.Value) any {
if got.IsValid() {
return got.Interface()
}
return nil
})

got := map[string]reflect.Value{
"pipo": reflect.ValueOf(42),
"bingo": reflect.ValueOf(666),
"zip": {},
}

td.CmpTrue(tt, td.Cmp(t, got, td.Map(map[string]reflect.Value{}, td.MapEntries{
"pipo": 42,
"bingo": 666,
"zip": nil,
})))
test.EqualStr(tt, ttt.LastMessage(), "")
ttt.ResetMessages()

td.CmpTrue(tt, td.Cmp(t, got, td.SubMapOf(map[string]reflect.Value{}, td.MapEntries{
"pipo": 42,
"bingo": 666,
"zip": nil,
"test": "more",
})))
test.EqualStr(tt, ttt.LastMessage(), "")
ttt.ResetMessages()

td.CmpTrue(tt, td.Cmp(t, got, td.SuperMapOf(map[string]reflect.Value{}, td.MapEntries{
"pipo": 42,
"zip": nil,
})))
test.EqualStr(tt, ttt.LastMessage(), "")
ttt.ResetMessages()
})

tt.Run("Struct", func(tt *testing.T) {
ttt := test.NewTestingTB(tt.Name())

t := td.NewT(ttt).WithSmuggleHooks(func(got reflect.Value) any {
if got.IsValid() {
return got.Interface()
}
return nil
})

type Rstruct struct {
V1 reflect.Value
V2 reflect.Value
}

got := Rstruct{V1: reflect.ValueOf(42)}

td.CmpTrue(tt, td.Cmp(t, got, td.Struct(Rstruct{}, td.StructFields{
"V1": 42,
"V2": nil,
})))
test.EqualStr(tt, ttt.LastMessage(), "")
ttt.ResetMessages()

td.CmpTrue(tt, td.Cmp(t, got, td.SStruct(Rstruct{}, td.StructFields{
"V1": 42,
"V2": nil,
})))
test.EqualStr(tt, ttt.LastMessage(), "")
ttt.ResetMessages()

td.CmpTrue(tt, td.Cmp(t, got, td.Struct(nil, td.StructFields{
"V1": 42,
"V2": nil,
})))
test.EqualStr(tt, ttt.LastMessage(), "")
ttt.ResetMessages()
})

tt.Run("Error", func(tt *testing.T) {
ttt := test.NewTestingTB(tt.Name())

Expand Down
24 changes: 5 additions & 19 deletions td/td_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,28 +242,14 @@ func (a *tdArray) populateExpectedEntries(expectedEntries ArrayEntries, expected
reflect.Ptr, reflect.Slice:
vexpectedValue = reflect.Zero(elemType) // change to a typed nil
default:
a.err = ctxerr.OpBad(
a.GetLocation().Func,
"expected value of #%d cannot be nil as items type is %s",
index,
elemType)
return
// Don't raise an error if item cannot be nil as a smuggle hook can
// change it at fly during the comparison
vexpectedValue = reflect.Value{}
}
} else {
vexpectedValue = reflect.ValueOf(expectedValue)

if _, ok := expectedValue.(TestDeep); !ok {
if !vexpectedValue.Type().AssignableTo(elemType) {
a.err = ctxerr.OpBad(
a.GetLocation().Func,
"type %s of #%d expected value differs from %s contents (%s)",
vexpectedValue.Type(),
index,
util.TernStr(array, "array", "slice"),
elemType)
return
}
}
// Don't check vexpectedValue type against slice/array item one as a
// smuggle hook can change it at fly during the comparison
}

a.expectedEntries[index] = vexpectedValue
Expand Down
Loading
Loading