Skip to content

Commit

Permalink
Merge 14b0bd4 into 4a7d009
Browse files Browse the repository at this point in the history
  • Loading branch information
maxatome committed Jul 24, 2020
2 parents 4a7d009 + 14b0bd4 commit 3b371bd
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 33 deletions.
44 changes: 44 additions & 0 deletions internal/ctxerr/error.go
Expand Up @@ -8,10 +8,12 @@ package ctxerr

import (
"bytes"
"reflect"
"strings"

"github.com/maxatome/go-testdeep/internal/color"
"github.com/maxatome/go-testdeep/internal/location"
"github.com/maxatome/go-testdeep/internal/types"
"github.com/maxatome/go-testdeep/internal/util"
)

Expand Down Expand Up @@ -48,6 +50,48 @@ var (
}
)

// TypeMismatch returns a "type mismatch" error. It is the caller
// responsibility to check that both types differ.
//
// If they resolve to the same name (via their String method), it
// tries to deeply dump the full package name of each type.
//
// It works pretty well with the exception of identical anomymous
// structs in 2 different packages with the same last name: in this
// case reflect does not allow us to retrieve the package from which
// each type comes.
//
// package foo // in a/
// var Foo struct { a int }
//
// package foo // in b/
// var Foo struct { a int }
//
// package ctxerr
// import(
// a_foo "a/foo"
// b_foo "b/foo"
// )
// …
// TypeMismatch(reflect.TypeOf(a_foo.Foo), reflect.TypeOf(b_foo.Foo))
//
// returns an error producing:
//
// type mismatch
// got: struct { a int }
// expected: struct { a int }
func TypeMismatch(got, expected reflect.Type) *Error {
gs, es := got.String(), expected.String()
if gs == es {
gs, es = util.TypeFullName(got), util.TypeFullName(expected)
}
return &Error{
Message: "type mismatch",
Got: types.RawString(gs),
Expected: types.RawString(es),
}
}

// Error implements error interface.
func (e *Error) Error() string {
buf := bytes.Buffer{}
Expand Down
104 changes: 104 additions & 0 deletions internal/util/string.go
Expand Up @@ -8,6 +8,7 @@ package util

import (
"bytes"
"fmt"
"io"
"reflect"
"strconv"
Expand Down Expand Up @@ -93,3 +94,106 @@ func SliceToBuffer(buf *bytes.Buffer, items []reflect.Value) *bytes.Buffer {

return buf
}

// TypeFullName returns the t type name with packages fully visible
// instead of the last package part in t.String().
func TypeFullName(t reflect.Type) string {
var b bytes.Buffer
typeFullName(&b, t)
return b.String()
}

func typeFullName(b *bytes.Buffer, t reflect.Type) {
if t.Name() != "" {
if pkg := t.PkgPath(); pkg != "" {
fmt.Fprintf(b, "%s.", pkg)
}
b.WriteString(t.Name())
return
}

switch t.Kind() {
case reflect.Ptr:
b.WriteByte('*')
typeFullName(b, t.Elem())

case reflect.Slice:
b.WriteString("[]")
typeFullName(b, t.Elem())

case reflect.Array:
fmt.Fprintf(b, "[%d]", t.Len())
typeFullName(b, t.Elem())

case reflect.Map:
b.WriteString("map[")
typeFullName(b, t.Key())
b.WriteByte(']')
typeFullName(b, t.Elem())

case reflect.Struct:
b.WriteString("struct {")
if num := t.NumField(); num > 0 {
for i := 0; i < num; i++ {
sf := t.Field(i)
if !sf.Anonymous {
b.WriteByte(' ')
b.WriteString(sf.Name)
}
b.WriteByte(' ')
typeFullName(b, sf.Type)
b.WriteByte(';')
}
b.Truncate(b.Len() - 1)
b.WriteByte(' ')
}
b.WriteByte('}')

case reflect.Func:
b.WriteString("func(")
if num := t.NumIn(); num > 0 {
for i := 0; i < num; i++ {
if i == num-1 && t.IsVariadic() {
b.WriteString("...")
typeFullName(b, t.In(i).Elem())
} else {
typeFullName(b, t.In(i))
}
b.WriteString(", ")
}
b.Truncate(b.Len() - 2)
}
b.WriteByte(')')

if num := t.NumOut(); num > 0 {
if num == 1 {
b.WriteByte(' ')
} else {
b.WriteString(" (")
}
for i := 0; i < num; i++ {
typeFullName(b, t.Out(i))
b.WriteString(", ")
}
b.Truncate(b.Len() - 2)
if num > 1 {
b.WriteByte(')')
}
}

case reflect.Chan:
switch t.ChanDir() {
case reflect.RecvDir:
b.WriteString("<-chan ")
case reflect.SendDir:
b.WriteString("chan<- ")
case reflect.BothDir:
b.WriteString("chan ")
}
typeFullName(b, t.Elem())

default:
// Fallback to default implementation
b.WriteString(t.String())
}
}
65 changes: 65 additions & 0 deletions internal/util/string_test.go
Expand Up @@ -9,11 +9,13 @@ package util_test
import (
"bytes"
"reflect"
"runtime"
"testing"

"github.com/maxatome/go-testdeep/internal/test"
"github.com/maxatome/go-testdeep/internal/types"
"github.com/maxatome/go-testdeep/internal/util"
"strings"
)

type myTestDeepStringer struct {
Expand Down Expand Up @@ -111,3 +113,66 @@ items:
curTest.Expected)
}
}

func TestTypeFullName(t *testing.T) {
// our full package name
pc, _, _, _ := runtime.Caller(0)
pkg := strings.TrimSuffix(runtime.FuncForPC(pc).Name(), ".TestTypeFullName")

test.EqualStr(t, util.TypeFullName(reflect.TypeOf(123)), "int")
test.EqualStr(t, util.TypeFullName(reflect.TypeOf([]int{})), "[]int")
test.EqualStr(t, util.TypeFullName(reflect.TypeOf([3]int{})), "[3]int")
test.EqualStr(t, util.TypeFullName(reflect.TypeOf((**float64)(nil))), "**float64")
test.EqualStr(t, util.TypeFullName(reflect.TypeOf(map[int]float64{})), "map[int]float64")
test.EqualStr(t, util.TypeFullName(reflect.TypeOf(struct{}{})), "struct {}")
test.EqualStr(t, util.TypeFullName(reflect.TypeOf(struct {
a int
b bool
}{})), "struct { a int; b bool }")
test.EqualStr(t, util.TypeFullName(reflect.TypeOf(struct {
s struct{ a []int }
b bool
}{})), "struct { s struct { a []int }; b bool }")

type anon struct{ a []int }
test.EqualStr(t, util.TypeFullName(reflect.TypeOf(struct {
anon
b bool
}{})), "struct { "+pkg+".anon; b bool }")

test.EqualStr(t, util.TypeFullName(reflect.TypeOf(func() {})), "func()")
test.EqualStr(t,
util.TypeFullName(reflect.TypeOf(func(a int) {})),
"func(int)")
test.EqualStr(t,
util.TypeFullName(reflect.TypeOf(func(a int, b ...bool) rune { return 0 })),
"func(int, ...bool) int32")
test.EqualStr(t,
util.TypeFullName(reflect.TypeOf(func() (int, bool, int) { return 0, true, 0 })),
"func() (int, bool, int)")

test.EqualStr(t, util.TypeFullName(reflect.TypeOf(func() {})), "func()")
test.EqualStr(t,
util.TypeFullName(reflect.TypeOf(func(a int) {})),
"func(int)")
test.EqualStr(t,
util.TypeFullName(reflect.TypeOf(func(a int, b ...bool) rune { return 0 })),
"func(int, ...bool) int32")
test.EqualStr(t,
util.TypeFullName(reflect.TypeOf(func() (int, bool, int) { return 0, true, 0 })),
"func() (int, bool, int)")

test.EqualStr(t,
util.TypeFullName(reflect.TypeOf((<-chan []int)(nil))),
"<-chan []int")
test.EqualStr(t,
util.TypeFullName(reflect.TypeOf((chan<- []int)(nil))),
"chan<- []int")
test.EqualStr(t,
util.TypeFullName(reflect.TypeOf((chan []int)(nil))),
"chan []int")

test.EqualStr(t,
util.TypeFullName(reflect.TypeOf((*interface{})(nil))),
"*interface {}")
}
6 changes: 1 addition & 5 deletions td/equal.go
Expand Up @@ -173,11 +173,7 @@ func deepValueEqual(ctx ctxerr.Context, got, expected reflect.Value) (err *ctxer
if ctx.BooleanError {
return ctxerr.BooleanError
}
return ctx.CollectError(&ctxerr.Error{
Message: "type mismatch",
Got: types.RawString(got.Type().String()),
Expected: types.RawString(expected.Type().String()),
})
return ctx.CollectError(ctxerr.TypeMismatch(got.Type(), expected.Type()))
}

// if ctx.Depth > 10 { panic("deepValueEqual") } // for debugging
Expand Down
12 changes: 2 additions & 10 deletions td/td_between.go
Expand Up @@ -488,11 +488,7 @@ func (b *tdBetween) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {
if ctx.BooleanError {
return ctxerr.BooleanError
}
return ctx.CollectError(&ctxerr.Error{
Message: "type mismatch",
Got: types.RawString(got.Type().String()),
Expected: types.RawString(b.expectedMin.Type().String()),
})
return ctx.CollectError(ctxerr.TypeMismatch(got.Type(), b.expectedMin.Type()))
}
}

Expand Down Expand Up @@ -596,11 +592,7 @@ func (b *tdBetweenTime) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Err
if ctx.BooleanError {
return ctxerr.BooleanError
}
return ctx.CollectError(&ctxerr.Error{
Message: "type mismatch",
Got: types.RawString(got.Type().String()),
Expected: types.RawString(b.expectedType.String()),
})
return ctx.CollectError(ctxerr.TypeMismatch(got.Type(), b.expectedType))
}
}

Expand Down
7 changes: 1 addition & 6 deletions td/td_catch.go
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/maxatome/go-testdeep/internal/color"
"github.com/maxatome/go-testdeep/internal/ctxerr"
"github.com/maxatome/go-testdeep/internal/types"
"github.com/maxatome/go-testdeep/internal/util"
)

Expand Down Expand Up @@ -80,11 +79,7 @@ func (c *tdCatch) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {
if ctx.BooleanError {
return ctxerr.BooleanError
}
return ctx.CollectError(&ctxerr.Error{
Message: "type mismatch",
Got: types.RawString(got.Type().String()),
Expected: types.RawString(c.target.Elem().Type().String()),
})
return ctx.CollectError(ctxerr.TypeMismatch(got.Type(), c.target.Elem().Type()))
}

c.target.Elem().Set(got.Convert(targetType))
Expand Down
19 changes: 9 additions & 10 deletions td/td_expected_type.go
Expand Up @@ -19,12 +19,12 @@ type tdExpectedType struct {
isPtr bool
}

func (t *tdExpectedType) errorTypeMismatch(gotType types.RawString) *ctxerr.Error {
return &ctxerr.Error{
Message: "type mismatch",
Got: gotType,
Expected: types.RawString(t.expectedTypeStr()),
func (t *tdExpectedType) errorTypeMismatch(gotType reflect.Type) *ctxerr.Error {
expectedType := t.expectedType
if t.isPtr {
expectedType = reflect.PtrTo(expectedType)
}
return ctxerr.TypeMismatch(gotType, expectedType)
}

func (t *tdExpectedType) checkPtr(ctx ctxerr.Context, pGot *reflect.Value, nilAllowed bool) *ctxerr.Error {
Expand All @@ -34,7 +34,7 @@ func (t *tdExpectedType) checkPtr(ctx ctxerr.Context, pGot *reflect.Value, nilAl
if ctx.BooleanError {
return ctxerr.BooleanError
}
return t.errorTypeMismatch(types.RawString(got.Type().String()))
return t.errorTypeMismatch(got.Type())
}

if !nilAllowed && got.IsNil() {
Expand Down Expand Up @@ -62,12 +62,11 @@ func (t *tdExpectedType) checkType(ctx ctxerr.Context, got reflect.Value) *ctxer
if ctx.BooleanError {
return ctxerr.BooleanError
}
var gotType types.RawString
gt := got.Type()
if t.isPtr {
gotType = "*"
gt = reflect.PtrTo(gt)
}
gotType += types.RawString(got.Type().String())
return t.errorTypeMismatch(gotType)
return t.errorTypeMismatch(gt)
}
return nil
}
Expand Down
3 changes: 1 addition & 2 deletions td/td_isa.go
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/maxatome/go-testdeep/internal/color"
"github.com/maxatome/go-testdeep/internal/ctxerr"
"github.com/maxatome/go-testdeep/internal/types"
)

type tdIsa struct {
Expand Down Expand Up @@ -81,7 +80,7 @@ func (i *tdIsa) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {
if ctx.BooleanError {
return ctxerr.BooleanError
}
return ctx.CollectError(i.errorTypeMismatch(types.RawString(gotType.String())))
return ctx.CollectError(i.errorTypeMismatch(gotType))
}

func (i *tdIsa) String() string {
Expand Down

0 comments on commit 3b371bd

Please sign in to comment.