Giter VIP home page Giter VIP logo

deep's People

Contributors

anaminus avatar andrewmostello avatar bartleyg avatar bughou avatar daniel-nichter avatar flga avatar gmarik avatar lucapette avatar radeksimko avatar seveas avatar sofuture avatar yalegko avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

deep's Issues

Stringer interface support

Is there any reason to generate diff text using

c.saveDiff(a.Int(), b.Int())

instead of

c.saveDiff(a, b)

At a glance second way is better because it keeps original type and thus if that type implements Stringer interface then diff will contains values in much more readable form than just a number. (With a lot of iota consts reading diff with numbers is really hard.) Of course, same probably apply to other types, not just to integer ones.

uuid is unreadable since it is compared as array

Currently comparing two "github.com/google/uuid" is not really readable since it is checked as array and looks like this:

UUID.array[0]: 93 != 96 UUID.array[1]: 181 != 145 UUID.array[2]: 180 != 187 UUID.array[3]: 80 != 70 UUID.array[4]: 220 != 109 UUID.array[5]: 73 != 90 UUID.array[6]: 73 != 79 UUID.array[7]: 212 != 138 UUID.array[8]: 181 != 149 UUID.array[9]: 244 != 206

or we can get following test:

func TestUUID(t *testing.T) {
	uuid1, _ := uuid.Parse("ac0bd5ef-6f92-4b03-953d-a2c4b9828c8a")
	uuid2, _ := uuid.Parse("de09e3ce-227f-49a1-b54d-9fe13af54375")

	diff := deep.Equal(uuid1, uuid1)
	if len(diff) > 0 {
		t.Error("should be equal:", diff)
	}

	diff = deep.Equal(uuid1, uuid2)
	if diff == nil {
		t.Fatal("no diff")
	}
	if len(diff) != 10 {
		t.Error("uuid expects array of len() == 10:", diff)
	}
	if diff[0] != "array[0]: 172 != 222" {
		t.Error("wrong diff:", diff[0])
	}
	if diff[1] != "array[1]: 11 != 9" {
		t.Error("wrong diff:", diff[1])
	}
	if diff[2] != "array[2]: 213 != 227" {
		t.Error("wrong diff:", diff[2])
	}
	if diff[3] != "array[3]: 239 != 206" {
		t.Error("wrong diff:", diff[3])
	}
	if diff[4] != "array[4]: 111 != 34" {
		t.Error("wrong diff:", diff[4])
	}
	if diff[5] != "array[5]: 146 != 127" {
		t.Error("wrong diff:", diff[5])
	}
	if diff[6] != "array[6]: 75 != 73" {
		t.Error("wrong diff:", diff[6])
	}
	if diff[7] != "array[7]: 3 != 161" {
		t.Error("wrong diff:", diff[7])
	}
	if diff[8] != "array[8]: 149 != 181" {
		t.Error("wrong diff:", diff[8])
	}
	if diff[9] != "array[9]: 61 != 77" {
		t.Error("wrong diff:", diff[9])
	}
}

Would it be maybe interesting to implement something like following or is it preferred to not mix other types?

	if aType == reflect.TypeOf(uuid.UUID{}) {
		aUUID := a.Interface().(uuid.UUID)
		bUUID := b.Interface().(uuid.UUID)

		if aUUID.String() != bUUID.String() {
			c.saveDiff(aUUID.String(), bUUID.String())
		}
		return
	}

	switch aKind {

Maybe even more interesting for me would be to have CustomCompare like below which would allow this lib to be kept "pure".

I am also using something like getUUIDByName("my_cool_uuid_name") in my tests and this would allow to match uuids back to "my_cool_uuid_name" for even better readability.

	// NilMapsAreEmpty causes a nil map to be equal to an empty map.
	NilMapsAreEmpty = false

	// CustomCompare allows to implement custom behaviour for specific type
	// and should return if type was handled and what is difference between a and b
	CustomCompare func(abType reflect.Type, a, b reflect.Value) (
		bool, *string, *string) = nil
...

	if CustomCompare != nil {
		handled, aDiff, bDiff := CustomCompare(aType, a, b)
		if handled {
			if aDiff != nil || bDiff != nil {
				c.saveDiff(getNilOrValue(aDiff), getNilOrValue(bDiff))
			}
			return
		}
	}

Proposal: type specific hooks

I understand that it doesn't make sense to go in and try to support all possible core-ish types. E.g. like json.RawMessage as in #62.

Requesting an Equal method for the parent type may not be the best way to solve the culprit, a few caveats:

  • If that parent type was the one to be compared, there's no need anymore to use deep, one could just use the Equal method
  • If the type is used in hundreds of different parent types, there's a lot of Equal methods to write (a generator could be used/created)
  • The type in question as well the parent type(s) may not be in a package you control, so providing an Equal method is actually impossible

What I've seen in different other utilities is to offer a Hook for a type, which could be what is just missing. deep doesn't need to understand the type except to provide the framework for these hooks and this also allows to prototype a compare functions also for foreign types.

Examples:

deep already uses the package reflect, so there's no additional package needed.

Idea very rough from mind and may be incorrect. Good enough to show the idea:

import "reflect"

// signature of an equal func
type EqualFunc func(a, b reflect.Value) (bool, error)

// holds all the provided custom equal funcs
var customEqualFuncs map[reflect.Type]EqualFunc 

// add a custom equal func
func SetEqualFunc(t reflect.Type, f EqualFunc ) {
  customEqualFuncs[t] = f
}

// during equal() check whether a custom equal method exists
func equal(a, b interface{}) ... {
  ...
  // ^ has no Equal method, check if a custom equal func exists
  t := reflect.TypeOf(a)
  if f, exists := customEqualFuncs[t]; exists {
    isequal, err := f(reflect.ValueOf(a),reflect.ValueOf(b))
    ...
    return ...
  }
}

Would be used like:

deep.SetEqualFunc(reflect.TypeOf(json.RawMessage{}), func ... {
   ....
   return isequal, iserr
}

Missing above: diff return, which would need to be incorporated in the idea as well

Options to explore for EqualFunc:

  • type EqualFunc func(a, b interface) (bool, error) and use actual values instead of reflect.Value
  • using generics

Is such a hook a fitting candidate for deep in your opinion?

panic on comparing struct with anonymous time.Time

Comparing on a struct with an embedded time.Time causes a panic. Below is a minimal test case to reproduce the panic

package main

import (
    "time"

    "github.com/go-test/deep"
)

type Time struct {
    time.Time
}

func main() {
    now := time.Now()
    a := Time{now}
    b := Time{now}
    deep.Equal(a, b)
}
panic: reflect: Call using main.Time as type time.Time

goroutine 1 [running]:
panic(0x4bd8e0, 0xc42000a340)
        /usr/lib/go-1.7/src/runtime/panic.go:500 +0x1a1
reflect.Value.call(0x4da060, 0xc42000e2c0, 0x2293, 0x4de107, 0x4, 0xc42003dc90, 0x1, 0x1, 0xc42002a038, 0x13, ...)
        /usr/lib/go-1.7/src/reflect/value.go:371 +0x10c0
reflect.Value.Call(0x4da060, 0xc42000e2c0, 0x2293, 0xc42003dc90, 0x1, 0x1, 0xc42000e2c0, 0x2293, 0x0)
        /usr/lib/go-1.7/src/reflect/value.go:302 +0xa4
github.com/goraxe/deep_test/vendor/github.com/go-test/deep.(*cmp).equals(0xc42003de50, 0x4da060, 0xc42000e2c0, 0x99, 0x4da060, 0xc42000e2e0, 0x99, 0x0)
        /home/goraxe/projects/go/src/github.com/goraxe/deep_test/vendor/github.com/go-test/deep/deep.go:146 +0x2be1
github.com/goraxe/deep_test/vendor/github.com/go-test/deep.Equal(0x4da060, 0xc42000e2c0, 0x4da060, 0xc42000e2e0, 0xc42000e2e0, 0x0, 0x0)
        /home/goraxe/projects/go/src/github.com/goraxe/deep_test/vendor/github.com/go-test/deep/deep.go:77 +0x291
main.main()
        /home/goraxe/projects/go/src/github.com/goraxe/deep_test/main.go:21 +0x16d
exit status 2

Allow ignoring fields using struct tag

Hey, thank you for your time, deep is very handy!

I do have a problem tho, I need to be able to ignore certain struct fields, so I cloned the repo and implemented it locally for now, would you like a PR?

It looks something like this: (briefly)

func TestStructWithTags(t *testing.T) {
	type s1 struct {
		same                    int
		modified                int
		sameIgnored             int `deep:"-"`
		modifiedIgnored         int `deep:"-"`
		ExportedSame            int
		ExportedModified        int
		ExportedSameIgnored     int `deep:"-"`
		ExportedModifiedIgnored int `deep:"-"`
	}
	type s2 struct {
		s1
		same                    int
		modified                int
		sameIgnored             int `deep:"-"`
		modifiedIgnored         int `deep:"-"`
		ExportedSame            int
		ExportedModified        int
		ExportedSameIgnored     int `deep:"-"`
		ExportedModifiedIgnored int `deep:"-"`
		recurseInline           s1
		recursePtr              *s2
	}
	sa := s2{[...]} // omitted, "same" fields are equal, "modified" are multiplied by 10
	sb := s2{[...]} // omitted, "same" fields are equal, "modified" are multiplied by 10

	// with compare unexported = true
	want := []string{
		"s1.modified: 1 != 10",
		"s1.ExportedModified: 5 != 50",
		"modified: 1 != 10",
		"ExportedModified: 5 != 50",
		"recurseInline.modified: 1 != 10",
		"recurseInline.ExportedModified: 5 != 50",
		"recursePtr.modified: 1 != 10",
		"recursePtr.ExportedModified: 5 != 50",
	}
}

Cheers

Strings in struct fields are not compared correctly

As subj states looks like strings in struct fields are not compared:

		if diff := deep.Equal(struct{ foo string }{foo: "aa"}, struct{ foo string }{foo: "aab"}); diff != nil {
			t.Error(diff)
		}

will pass without error

Embedded struct values not inspected

Deep equals doesn't appear to check embedded values:

package main

import (
	"fmt"
	"github.com/go-test/deep"
)

type sub struct{
 subvar string
}

type top struct{
sub
}

func main() {

val1 := top{sub: sub{subvar: "x"}}
val2 := top{sub: sub{subvar: "y"}}

fmt.Println(deep.Equal(val1, val1)) // no diffs, fine
fmt.Println(deep.Equal(val1, val2)) // no diffs, not so fine
}

Panic when inspecting double linked datastructures

If I inspect the double linked datastructure described below:

type CLITree struct {
	Type     argType
	Name     string
	Children []*CLITree
	Parent   *CLITree
}

For a trivial datastructure I get the following error:

$ go test
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc020160358 stack=[0xc020160000, 0xc040160000]
fatal error: stack overflow

runtime stack:
runtime.throw(0x595c2b, 0xe)
        /home/david/opt/go/src/runtime/panic.go:1117 +0x72
runtime.newstack()
        /home/david/opt/go/src/runtime/stack.go:1069 +0x7ed
runtime.morestack()
        /home/david/opt/go/src/runtime/asm_amd64.s:458 +0x8f

goroutine 18 [running]:
fmt.(*pp).printArg(0xc0000a41a0, 0x5632e0, 0x68b5a0, 0x64)
        /home/david/opt/go/src/fmt/print.go:634 +0x9e5 fp=0xc020160368 sp=0xc020160360 pc=0x4ddb85
fmt.(*pp).doPrintf(0xc0000a41a0, 0x594bc5, 0x9, 0xc020160660, 0x1, 0x1)
        /home/david/opt/go/src/fmt/print.go:1026 +0x168 fp=0xc020160458 sp=0xc020160368 pc=0x4e0aa8
fmt.Sprintf(0x594bc5, 0x9, 0xc020160660, 0x1, 0x1, 0x0, 0x0)
        /home/david/opt/go/src/fmt/print.go:219 +0x66 fp=0xc0201604b0 sp=0xc020160458 pc=0x4d9de6
github.com/go-test/deep.(*cmp).equals(0xc04015fe50, 0x55ffa0, 0xc0000b0758, 0x197, 0x55ffa0, 0xc0000b0658, 0x197, 0xcccc9)
        /home/david/go/pkg/mod/github.com/go-test/[email protected]/deep.go:341 +0x183c fp=0xc020160730 sp=0xc0201604b0 pc=0x54c13c
github.com/go-test/deep.(*cmp).equals(0xc04015fe50, 0x57fb00, 0xc0000b0740, 0x199, 0x57fb00, 0xc0000b0640, 0x199, 0xcccc8)
        /home/david/go/pkg/mod/github.com/go-test/[email protected]/deep.go:223 +0x242d fp=0xc0201609b0 sp=0xc020160730 pc=0x54cd2d
github.com/go-test/deep.(*cmp).equals(0xc04015fe50, 0x57fb00, 0xc0000b0740, 0x199, 0x55c700, 0xc0000b06b0, 0x196, 0xcccc7)
        /home/david/go/pkg/mod/github.com/go-test/[email protected]/deep.go:162 +0x31d9 fp=0xc020160c30 sp=0xc0201609b0 pc=0x54dad9
github.com/go-test/deep.(*cmp).equals(0xc04015fe50, 0x57fb00, 0xc0000b0780, 0x199, 0x57fb00, 0xc0000b0680, 0x199, 0xcccc6)
        /home/david/go/pkg/mod/github.com/go-test/[email protected]/deep.go:223 +0x242d fp=0xc020160eb0 sp=0xc020160c30 pc=0x54cd2d
github.com/go-test/deep.(*cmp).equals(0xc04015fe50, 0x57fb00, 0xc0000b0780, 0x199, 0x55c700, 0xc00009e590, 0x196, 0xcccc5)
        /home/david/go/pkg/mod/github.com/go-test/[email protected]/deep.go:162 +0x31d9 fp=0xc020161130 sp=0xc020160eb0 pc=0x54dad9
github.com/go-test/deep.(*cmp).equals(0xc04015fe50, 0x55ffa0, 0xc0000b0758, 0x197, 0x55ffa0, 0xc0000b0658, 0x197, 0xcccc4)
        /home/david/go/pkg/mod/github.com/go-test/[email protected]/deep.go:343 +0x1b78 fp=0xc0201613b0 sp=0xc020161130 pc=0x54c478

...

...additional frames elided...
created by testing.(*T).Run
        /home/david/opt/go/src/testing/testing.go:1238 +0x2b3

goroutine 1 [chan receive]:
testing.(*T).Run(0xc000082600, 0x5957ed, 0xd, 0x59e868, 0x489fe6)
        /home/david/opt/go/src/testing/testing.go:1239 +0x2da
testing.runTests.func1(0xc000082480)
        /home/david/opt/go/src/testing/testing.go:1511 +0x78
testing.tRunner(0xc000082480, 0xc000093de0)
        /home/david/opt/go/src/testing/testing.go:1193 +0xef
testing.runTests(0xc0000bc048, 0x693580, 0x1, 0x1, 0xc01514bef2b74a44, 0x8bb2cc5311, 0x69bc40, 0x595925)
        /home/david/opt/go/src/testing/testing.go:1509 +0x2fe
testing.(*M).Run(0xc0000f0000, 0x0)
        /home/david/opt/go/src/testing/testing.go:1417 +0x1eb
main.main()
        _testmain.go:43 +0x138
exit status 2

NOTE: go-spew handles them properly by displaying already shown:

(*getoptions.CLITree)({
          Type: (getoptions.argType) 0,
          Name: (string) (len=46) "/tmp/go-build309650026/b001/go-getoptions.test",
          Children: ([]*getoptions.CLITree) (len=2) {
            (*getoptions.CLITree)({
              Type: (getoptions.argType) 2,
              Name: (string) (len=4) "opt1",
              Children: ([]*getoptions.CLITree) {
              },
              Parent: (*getoptions.CLITree)(<already shown>)
            }),
            (*getoptions.CLITree)({
              Type: (getoptions.argType) 3,
              Name: (string) (len=3) "cmd",
              Children: ([]*getoptions.CLITree) (len=1) {
                (*getoptions.CLITree)({
                  Type: (getoptions.argType) 2,
                  Name: (string) (len=4) "opt2",
                  Children: ([]*getoptions.CLITree) {
                  },
                  Parent: (*getoptions.CLITree)(<already shown>)
                })
              },
              Parent: (*getoptions.CLITree)(<already shown>)
            })
          },
          Parent: (*getoptions.CLITree)(<nil>)
        })

FLAG_IGNORE_SLICE_ORDER - panic: runtime error: hash of unhashable type map[string]interface {}

Please see the example 5) from this playground: https://go.dev/play/p/M-VZFkXte9T

With FLAG_IGNORE_SLICE_ORDER set, it causes a panic.

It happens at:

func (c *cmp) equals(a, b reflect.Value, level int) {
...
am[a.Index(i).Interface()] += 1
...
}

where the code tries to use the map[string]interface{} as a key when the child is a slice.

When reading the comments in the code:

	// FLAG_IGNORE_SLICE_ORDER causes Equal to ignore slice order so that
	// []int{1, 2} and []int{2, 1} are equal. Only slices of primitive scalars
	// like numbers and strings are supported. Slices of complex types,
	// like []T where T is a struct, are undefined because Equal does not
	// recurse into the slice value when this flag is enabled.

This states it is unsupported to use it with this type of data. I was wondering if it is possible to add this feature in the future.
At least this ticket could save others some time when they do a web search for the cause of the error and did not read the documentation ;)

Panic when comparing errors in unexported fields

#33 broke the comparison of structs with errors in an unexported fields. THis code used to work just fine with deep 1.0.4, but panics with 1.0.7:

package main

import (
	"fmt"
	"github.com/go-test/deep"
)

type foo struct {
	bar error
}

func main() {
	deep.CompareUnexportedFields = true
	e1 := foo{bar: fmt.Errorf("error")}
	e2 := foo{bar: fmt.Errorf("error")}
	deep.Equal(e1, e2)
}

The panic:

$ go run deep_panic.go
panic: reflect: reflect.flag.mustBeExported using value obtained using unexported field

goroutine 1 [running]:
reflect.flag.mustBeExportedSlow(0x2b3)
	/usr/local/Cellar/go/1.14.3/libexec/src/reflect/value.go:225 +0x12e
reflect.flag.mustBeExported(...)
	/usr/local/Cellar/go/1.14.3/libexec/src/reflect/value.go:216
reflect.Value.Call(0x10e1ca0, 0xc00008e230, 0x2b3, 0x0, 0x0, 0x0, 0xc00008e230, 0x2b3, 0x0)
	/usr/local/Cellar/go/1.14.3/libexec/src/reflect/value.go:320 +0x4d
github.com/go-test/deep.(*cmp).equals(0xc00009aeb0, 0x10e1ca0, 0xc00008e230, 0xb4, 0x10e1ca0, 0xc00008e240, 0xb4, 0x1)
	/Users/seveas/go/pkg/mod/github.com/go-test/[email protected]/deep.go:145 +0x335b
github.com/go-test/deep.(*cmp).equals(0xc00009aeb0, 0x10e3040, 0xc00008e230, 0x99, 0x10e3040, 0xc00008e240, 0x99, 0x0)
	/Users/seveas/go/pkg/mod/github.com/go-test/[email protected]/deep.go:223 +0x246c
github.com/go-test/deep.Equal(0x10e3040, 0xc00008e230, 0x10e3040, 0xc00008e240, 0x0, 0x11203e0, 0xc00008e220)
	/Users/seveas/go/pkg/mod/github.com/go-test/[email protected]/deep.go:88 +0x2b4
main.main()
	/Users/seveas/code/katyusha/deep_panic.go:16 +0x118
exit status 2

Panic when comparing two maps

This program

package main

import (
	"database/sql"

	"github.com/go-test/deep"
)

type NullString struct{ sql.NullString }

func main() {
	a := map[string]interface{}{
		"a": NullString{sql.NullString{"foo", true}},
	}
	b := map[string]interface{}{
		"a": sql.NullString{"foo", true},
	}
	deep.Equal(b, a)
}

panics with

panic: reflect: Field index out of range

goroutine 1 [running]:
reflect.Value.Field(0x10e1880, 0xc42000a060, 0x99, 0x1, 0x10d1a60, 0xc42000a090, 0x81)
	/usr/local/Cellar/go/1.9.2/libexec/src/reflect/value.go:769 +0x14c
github.com/go-test/deep.(*cmp).equals(0xc42003de98, 0x10db460, 0xc42000e2c0, 0x94, 0x10db460, 0xc42000e2d0, 0x94, 0x1)
	/Users/mibk/src/github.com/go-test/deep/deep.go:179 +0x648
github.com/go-test/deep.(*cmp).equals(0xc42003de98, 0x10dd240, 0xc4200721e0, 0x15, 0x10dd240, 0xc4200721b0, 0x15, 0x0)
	/Users/mibk/src/github.com/go-test/deep/deep.go:225 +0x1810
github.com/go-test/deep.Equal(0x10dd240, 0xc4200721e0, 0x10dd240, 0xc4200721b0, 0xc4200801a8, 0xc42003df70, 0x1004324)
	/Users/mibk/src/github.com/go-test/deep/deep.go:77 +0x284
main.main()
	/Users/mibk/src/github.com/mibk/junk/deep/main.go:18 +0x211
exit status 2

Note that if I flip the arguments (deep.Equal(a, b)), the program works just fine.

go version go1.9.2 darwin/amd64
GOARCH="amd64"
GOOS="darwin"

fatal error: concurrent map read and map write

Is recursion causing this?

fatal error: concurrent map read and map write

goroutine 213 [running]:
runtime.throw({0x9fc28f, 0x45d4d9})
C:/Program Files/Go/src/runtime/panic.go:1198 +0x71 fp=0xc000546af8 sp=0xc000546ac8 pc=0x4337b1
runtime.mapaccess2(0xc0b963e1a0, 0xc015228750, 0xc0092a2f00)
C:/Program Files/Go/src/runtime/map.go:469 +0x205 fp=0xc000546b38 sp=0xc000546af8 pc=0x40e2e5
reflect.mapaccess(0x943740, 0xc0b963e1a0, 0xc0bfa7db80)
C:/Program Files/Go/src/runtime/map.go:1318 +0x19 fp=0xc000546b60 sp=0xc000546b38 pc=0x45d259
reflect.Value.MapIndex({0x962de0, 0xc009245ad0, 0xc000546e60}, {0x943740, 0xc0b963e1a0, 0x98})
C:/Program Files/Go/src/reflect/value.go:1530 +0xe5 fp=0xc000546bd0 sp=0xc000546b60 pc=0x495aa5
github.com/go-test/deep.(*cmp).equals(0xc000547430, {0x962de0, 0xc0092459e0, 0x95}, {0x962de0, 0xc009245ad0, 0x95}, 0x1)
C:/Users/work/go/pkg/mod/github.com/go-test/[email protected]/deep.go:350 +0x32a8 fp=0xc000546f98 sp=0xc000546bd0 pc=0x8c6a48
github.com/go-test/deep.(*cmp).equals(0xc000547430, {0x9d9ac0, 0xc009245950, 0x99}, {0x9d9ac0, 0xc009245a40, 0x99}, 0x0)
C:/Users/work/go/pkg/mod/github.com/go-test/[email protected]/deep.go:287 +0x2893 fp=0xc000547360 sp=0xc000546f98 pc=0x8c6033
github.com/go-test/deep.EqualNew({0x9d9ac0, 0xc009245950}, {0x9d9ac0, 0xc009245a40})
C:/Users/work/go/pkg/mod/github.com/go-test/[email protected]/deep.go:141 +0x347 fp=0xc0005474a0 sp=0xc000547360 pc=0x8c35c7

default Depth

Not sure if there is a limitation on the depth with the default package

reflect.DeepEqual(got, tt.want)

but changing the code out with just

if diff := deep.Equal(got, tt.want); diff != nil {
	t.Error(diff)
}

is kind of dangerous. The dept default is set to just 10.
I had to set it to

deep.MaxDepth = 100

to find my issue.

Maybe generate a warning when the struct is deeper and you stop checking?
Or increase the default much higher ?

Probably the best is

if !reflect.DeepEqual(got, tt.want) {
        //try a friendly deep error message
	if diff := deep.Equal(got, tt.want); diff != nil {
		t.Error(diff)
	} else {
		t.Errorf("deepEqual failed \n%v\nbut I  want \n%v", got, tt.want)	
	}
}

Not detecting drift in a struct within a nested slice

Hi,

Ive started using this library its great!

I have been playing around testing this and works well. We have some big JSON that gets marshalled into structs, ive found changing something fairly deeply nested wasnt being picked up.

So orderLines is a slice, it DOES pick up a change to a field in the top of the slice like Id

e.g OrderEvent.Order.OrderLines.slice[0].Id

but if its another struct within the slice, it doesnt pick this up, like 'Gross' below

e.g OrderEvent.Order.OrderLines.slice[0].UnitPrice.Gross (float64)

Is this a know thing? Is there a limit how deep you can go?

Thanks!

False positive on error field with different real types but same string

    var err1 primKindError = "abc"
    var err2 error = fmt.Errorf("abc")
    t1 := tWithError{
        Error: err1,
    }
    t2 := tWithError{
        Error: err2,
    }

Those should be equal, but as of v1.0.5 they're not:

--- FAIL: TestErrorDifferentTypesSameString (0.00s)
    deep_test.go:1336: expected zero diffs, got 1: [Error: deep_test.primKindError != *errors.errorString]

because,

	if aType.Implements(errorType) && bType.Implements(errorType) {
		if (!aElem || !a.IsNil()) && (!bElem || !b.IsNil()) {
			aString := a.MethodByName("Error").Call(nil)[0].String()
			bString := b.MethodByName("Error").Call(nil)[0].String()
			if aString != bString {
				c.saveDiff(aString, bString)
				return
			}
		}
	}

we don't return from that block if they're equal, we keep comparing which results in the false positive on their different underlying type.

internal struct don't work

`import (
"fmt"
"testing"

"github.com/go-test/deep"

. "github.com/smartystreets/goconvey/convey"

)

func TestStructCompare(t *testing.T) {

type BB struct {
    i_BB int
    I_BB int
    S_BB string
    M_BB map[string]string
    A_BB []int
}

type AA struct {
    i_AA int
    I_AA int
    S_AA string
    M_AA map[string]string
    A_AA []int
    P_AA *BB
    B_AA BB
}

Convey("test struct compare", t, func() {
    a1 := AA{}
    a2 := AA{}

    a1.I_AA = 1
    a1.S_AA = "cc"
    a1.M_AA = map[string]string{"1": "2"}
    a1.A_AA = []int{3, 4}
    a1.P_AA = &BB{I_BB: 21, M_BB: map[string]string{"15": "6"}}
    a1.B_AA = BB{I_BB: 21, M_BB: map[string]string{"15": "6"}, A_BB: []int{1, 2}}

    a2.I_AA = 2
    a2.S_AA = "dd"
    a2.M_AA = map[string]string{"5": "6"}
    a2.A_AA = []int{7, 8}
    a2.P_AA = &BB{I_BB: 21, M_BB: map[string]string{"25": "6"}}

    a2.B_AA = BB{I_BB: 21, M_BB: map[string]string{"25": "6"}, A_BB: []int{3, 4}}

    fmt.Println()
    fmt.Printf("%+v \n", a1)
    fmt.Printf("%+v \n", a2)

    if diff := deep.Equal(a1, a2); diff != nil {
        for i, s := range diff {
            fmt.Println(i, s)

        }
        t.Error(diff)
    }
})

}`
output:

{i_AA:0 I_AA:1 S_AA:cc M_AA:map[1:2] A_AA:[3 4] P_AA:0x140009aaf00 B_AA:{i_BB:0 I_BB:21 S_BB: M_BB:map[15:6] A_BB:[1 2]}}
{i_AA:0 I_AA:2 S_AA:dd M_AA:map[5:6] A_AA:[7 8] P_AA:0x140009aaf40 B_AA:{i_BB:0 I_BB:21 S_BB: M_BB:map[25:6] A_BB:[3 4]}}
0 I_AA: 1 != 2
1 S_AA: cc != dd
2 M_AA.map[1]: 2 !=
3 M_AA.map[5]: != 6
4 A_AA.slice[0]: 3 != 7
5 A_AA.slice[1]: 4 != 8
6 P_AA.M_BB.map[15]: 6 !=
7 P_AA.M_BB.map[25]: != 6
8 B_AA.M_BB.map[15]: 6 !=
9 B_AA.M_BB.map[25]: != 6

BB.A_BB is diff,but no.

wrong comparison for slices types with Equal() method

in commit 929fce9 the check of Equal() function was moved under the reflect.Struct case.
and here is an example why this is wrong:
https://play.golang.org/p/AlgXufFrO1g

package main

import (
	"fmt"
	"net"
	"github.com/go-test/deep"
)

func main() {
	ipA := net.ParseIP("1.2.3.4")
	ipB := net.ParseIP("1.2.3.4").To4()
	
	fmt.Println(deep.Equal(ipA, ipB))
	
	fmt.Println(ipA.Equal(ipB))
}

ipA and ipB has different len of their slice inside, but they still represents the same ip. however deep.Equal() say there is a diff between ipA and ipB.

not only structs may have an Equal() method.

support for json.RawMessage?

import "encoding/json"

func TestJsonRawMessage(t *testing.T) {
	type T json.RawMessage

	a := T(`{"foo":1,"bar":2}`)
	b := T(`{"bar":2,"foo":1}`)

	// just informative begin
	aI := map[string]interface{}{}
	bI := map[string]interface{}{}

	_ = json.Unmarshal(a, &aI)
	_ = json.Unmarshal(b, &bI)

	fmt.Println(aI)
	fmt.Println(bI)
	// end

	diff := deep.Equal(a, b)
	if len(diff) != 0 {
		t.Fatalf("expected 0 diff, got %d: %s", len(diff), diff)
	}
}
go test -run ^TestJsonRawMessage$ github.com/go-test/deep

map[bar:2 foo:1]
map[bar:2 foo:1]
--- FAIL: TestJsonRawMessage (0.00s)
    .../deep/deep_test.go:1603: expected 0 diff, got 8: [slice[2]: 102 != 98 slice[3]: 111 != 97 slice[4]: 111 != 114 slice[7]: 49 != 50 slice[10]: 98 != 102 slice[11]: 97 != 111 slice[12]: 114 != 111 slice[15]: 50 != 49]
FAIL
FAIL	github.com/go-test/deep	0.002s
FAIL

might be tricky tho, since the actual type within there can be any json supported type

Panic when comparing structs with time.Time value and CompareUnexportedFields is true

I have a large struct I am comparing which contains unexported fields and time.Time values.
When comparing this struct, I get the following error:

panic: reflect: reflect.Value.Call using value obtained using unexported field [recovered]

By reading martini-contrib/encoder#9 and some hacky logging, I've narrowed it down to structs that contain time.Time values when using the CompareUnexportedFields option.

Reproduction code:

package main

import (
	"testing"
	"time"

	"github.com/go-test/deep"
)

func init() {
	deep.CompareUnexportedFields = true
}

type Foo struct {
	t time.Time
}

func TestFoo(t *testing.T) {
	a := &Foo{
		t: time.Now(),
	}

	b := &Foo{
		t: time.Now(),
	}

	if diff := deep.Equal(a, b); diff != nil {
		t.Errorf("Diff: \n%v", diff)
	}
}

Full stacktrace:

--- FAIL: TestFoo (0.00s)
panic: reflect: reflect.Value.Call using value obtained using unexported field [recovered]
	panic: reflect: reflect.Value.Call using value obtained using unexported field

goroutine 6 [running]:
testing.tRunner.func1(0xc4200c00f0)
	/usr/local/Cellar/go/1.10/libexec/src/testing/testing.go:742 +0x29d
panic(0x114a060, 0xc420064570)
	/usr/local/Cellar/go/1.10/libexec/src/runtime/panic.go:505 +0x229
reflect.flag.mustBeExported(0x22b3)
	/usr/local/Cellar/go/1.10/libexec/src/reflect/value.go:218 +0x145
reflect.Value.Call(0x117fe80, 0xc42000a0e0, 0x22b3, 0xc4200cf238, 0x1, 0x1, 0xc42000a0e0, 0x22b3, 0x0)
	/usr/local/Cellar/go/1.10/libexec/src/reflect/value.go:307 +0x46
foo/vendor/github.com/go-test/deep.(*cmp).equals(0xc4200cfec8, 0x117fe80, 0xc42000a0e0, 0x1b9, 0x117fe80, 0xc42000a100, 0x1b9, 0x2)
	/Users/amu19/go/src/foo/vendor/github.com/go-test/deep/deep.go:147 +0x28ed
foo/vendor/github.com/go-test/deep.(*cmp).equals(0xc4200cfec8, 0x115e3c0, 0xc42000a0e0, 0x199, 0x115e3c0, 0xc42000a100, 0x199, 0x1)
	/Users/amu19/go/src/foo/vendor/github.com/go-test/deep/deep.go:184 +0x1598
foo/vendor/github.com/go-test/deep.(*cmp).equals(0xc420060ec8, 0x115e3c0, 0xc42000a0e0, 0x199, 0x1144800, 0xc42000a100, 0x16, 0x0)
	/Users/amu19/go/src/foo/vendor/github.com/go-test/deep/deep.go:139 +0x394
foo/vendor/github.com/go-test/deep.Equal(0x1144800, 0xc42000a0e0, 0x1144800, 0xc42000a100, 0x1241681, 0x38, 0x2d2)
	/Users/amu19/go/src/foo/vendor/github.com/go-test/deep/deep.go:77 +0x262
foo/internal/model/customer.TestFoo(0xc4200c00f0)
	/Users/amu19/go/src/foo/internal/model/customer/customer_test.go:27 +0x11e
testing.tRunner(0xc4200c00f0, 0x118e280)
	/usr/local/Cellar/go/1.10/libexec/src/testing/testing.go:777 +0xd0
created by testing.(*T).Run
	/usr/local/Cellar/go/1.10/libexec/src/testing/testing.go:824 +0x2e0

Inside if statement

Seems that something strange happening when wrapping inside

if len(equal) > 1 {
  t.Fatal(spew.Sdump(equal))
}

new fields with errors added which are actually equal

Functions are handled differently from reflect.DeepEqual

deep.Equal does behave differently from reflect.DeepEqual when comparing functions.

The documentation of reflect.DeepEqual states for functions:

Func values are deeply equal if both are nil; otherwise they are not deeply equal.

Example Code:

func testFunc(i int) int {
	return i
}
func TestEqualForFunctions() {
	type TestStruct struct {
		Function func(int) int
	}
	t1 := TestStruct{
		Function: testFunc,
	}
	t2 := TestStruct{
		Function: testFunc,
	}
	fmt.Println("reflect", reflect.DeepEqual(t1, t2))
	fmt.Println("deep", deep.Equal(t1, t2))

	t1.Function = nil
	fmt.Println("reflect", reflect.DeepEqual(t1, t2))
	fmt.Println("deep", deep.Equal(t1, t2))

	t1.Function = nil
	t2.Function = nil
	fmt.Println("reflect", reflect.DeepEqual(t1, t2))
	fmt.Println("deep", deep.Equal(t1, t2))
}

Output:

reflect false 
deep []  // should state that both functions are != nil
reflect false
deep [] // should state that function 1 is not nil but function 2 is nil
reflect true
deep []

Confusing diff when comparing distinct types with the same name

I'm using v1.0.4.

This program:

package main

import (
	"fmt"

	"github.com/go-test/deep"
	v6 "github.com/olivere/elastic"
	v7 "github.com/olivere/elastic/v7"
)

func main() {
	errv6 := v6.Error{}
	errv7 := v7.Error{}

	fmt.Printf("diff: %v\n", deep.Equal(errv6, errv7))
}

produces the output diff: [elastic.Error != elastic.Error] which gives little indication for why there is a difference between errv6 and errv7. I have used github.com/olivere/elastic here but the problem is not specific to that module. Perhaps the full import path can be included in the diff if two distinct types have the same name?

Time Precision

Hi,

would it be possible to add an option "TimePrecision" analog to "FloatPrecision" which would permit to Truncate the times before comparison.

Reason:
I use deep.Equal to test my Database Interface. I compare the stored Struct with the original one (which contains timestamps). The DB I use (postgres) has a time resolution of Microsecond whereas the golang time.Time has a resolution of Nanosecond. Being able to truncate the timestamps would permit to compare those two.

thanks for considering

A number of edge-cases

I’m documenting a few edge-case bugs that came up while I was tinkering with the package. I have a branch with fixes for most of the currently open issues as well as the ones below. I just want to have them written down some where just in case I don’t get the PR open, and my code falls into the void.

The weirds:

  • the code will panic if a type implements the wrong return value for Equal. Say func (Type) Equal(Type) int.
  • the code will erroneously report zero and negative zero as inequal, but these values == as true.
  • the code is treating value.IsValid() as a definitive dereferenced nil-pointer, but this is not guaranteed. (Fixing this requires checking for the nil-pointers before derefing with .Elem(). When fixed, this lets us be a little more specific between <invalid value> and <nil pointer>)
  • deep.Equal(nil, bType) is not strictly comparing a <nil pointer> to a bType, but rather an “untyped nil”, which compares as inequal to a typed nil-pointer. (The bane of everyone who wonders how their return nil function could possible not compare equal to nil in the caller)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.