blob: 84c645b4ae047f09077aa943d8170ad4d4726edc [file] [log] [blame]
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.
package cmp_test
import (
"bytes"
"crypto/md5"
"encoding/json"
"fmt"
"io"
"math"
"math/rand"
"reflect"
"regexp"
"sort"
"strings"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
pb "github.com/google/go-cmp/cmp/internal/testprotos"
ts "github.com/google/go-cmp/cmp/internal/teststructs"
)
var now = time.Now()
func intPtr(n int) *int { return &n }
type test struct {
label string // Test description
x, y interface{} // Input values to compare
opts []cmp.Option // Input options
wantDiff string // The exact difference string
wantPanic string // Sub-string of an expected panic message
}
func TestDiff(t *testing.T) {
var tests []test
tests = append(tests, comparerTests()...)
tests = append(tests, transformerTests()...)
tests = append(tests, embeddedTests()...)
tests = append(tests, methodTests()...)
tests = append(tests, project1Tests()...)
tests = append(tests, project2Tests()...)
tests = append(tests, project3Tests()...)
tests = append(tests, project4Tests()...)
for _, tt := range tests {
tt := tt
tRunParallel(t, tt.label, func(t *testing.T) {
var gotDiff, gotPanic string
func() {
defer func() {
if ex := recover(); ex != nil {
if s, ok := ex.(string); ok {
gotPanic = s
} else {
panic(ex)
}
}
}()
gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...)
}()
if tt.wantPanic == "" {
if gotPanic != "" {
t.Fatalf("unexpected panic message: %s", gotPanic)
}
if got, want := strings.TrimSpace(gotDiff), strings.TrimSpace(tt.wantDiff); got != want {
t.Fatalf("difference message:\ngot:\n%s\n\nwant:\n%s", got, want)
}
} else {
if !strings.Contains(gotPanic, tt.wantPanic) {
t.Fatalf("panic message:\ngot: %s\nwant: %s", gotPanic, tt.wantPanic)
}
}
})
}
}
func comparerTests() []test {
const label = "Comparer"
type Iface1 interface {
Method()
}
type Iface2 interface {
Method()
}
type tarHeader struct {
Name string
Mode int64
Uid int
Gid int
Size int64
ModTime time.Time
Typeflag byte
Linkname string
Uname string
Gname string
Devmajor int64
Devminor int64
AccessTime time.Time
ChangeTime time.Time
Xattrs map[string]string
}
makeTarHeaders := func(tf byte) (hs []tarHeader) {
for i := 0; i < 5; i++ {
hs = append(hs, tarHeader{
Name: fmt.Sprintf("some/dummy/test/file%d", i),
Mode: 0664, Uid: i * 1000, Gid: i * 1000, Size: 1 << uint(i),
ModTime: now.Add(time.Duration(i) * time.Hour),
Uname: "user", Gname: "group",
Typeflag: tf,
})
}
return hs
}
return []test{{
label: label,
x: 1,
y: 1,
}, {
label: label,
x: 1,
y: 1,
opts: []cmp.Option{cmp.Ignore()},
wantPanic: "cannot use an unfiltered option",
}, {
label: label,
x: 1,
y: 1,
opts: []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })},
wantPanic: "cannot use an unfiltered option",
}, {
label: label,
x: 1,
y: 1,
opts: []cmp.Option{cmp.Transformer("", func(x interface{}) interface{} { return x })},
wantPanic: "cannot use an unfiltered option",
}, {
label: label,
x: 1,
y: 1,
opts: []cmp.Option{
cmp.Comparer(func(x, y int) bool { return true }),
cmp.Transformer("", func(x int) float64 { return float64(x) }),
},
wantPanic: "ambiguous set of applicable options",
}, {
label: label,
x: 1,
y: 1,
opts: []cmp.Option{
cmp.FilterPath(func(p cmp.Path) bool {
return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int
}, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}),
cmp.Comparer(func(x, y int) bool { return true }),
cmp.Transformer("", func(x int) float64 { return float64(x) }),
},
}, {
label: label,
opts: []cmp.Option{struct{ cmp.Option }{}},
wantPanic: "unknown option",
}, {
label: label,
x: struct{ A, B, C int }{1, 2, 3},
y: struct{ A, B, C int }{1, 2, 3},
}, {
label: label,
x: struct{ A, B, C int }{1, 2, 3},
y: struct{ A, B, C int }{1, 2, 4},
wantDiff: "root.C:\n\t-: 3\n\t+: 4\n",
}, {
label: label,
x: struct{ a, b, c int }{1, 2, 3},
y: struct{ a, b, c int }{1, 2, 4},
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: &struct{ A *int }{intPtr(4)},
y: &struct{ A *int }{intPtr(4)},
}, {
label: label,
x: &struct{ A *int }{intPtr(4)},
y: &struct{ A *int }{intPtr(5)},
wantDiff: "*root.A:\n\t-: 4\n\t+: 5\n",
}, {
label: label,
x: &struct{ A *int }{intPtr(4)},
y: &struct{ A *int }{intPtr(5)},
opts: []cmp.Option{
cmp.Comparer(func(x, y int) bool { return true }),
},
}, {
label: label,
x: &struct{ A *int }{intPtr(4)},
y: &struct{ A *int }{intPtr(5)},
opts: []cmp.Option{
cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }),
},
}, {
label: label,
x: &struct{ R *bytes.Buffer }{},
y: &struct{ R *bytes.Buffer }{},
}, {
label: label,
x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
y: &struct{ R *bytes.Buffer }{},
wantDiff: "root.R:\n\t-: s\"\"\n\t+: <nil>\n",
}, {
label: label,
x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
y: &struct{ R *bytes.Buffer }{},
opts: []cmp.Option{
cmp.Comparer(func(x, y io.Reader) bool { return true }),
},
}, {
label: label,
x: &struct{ R bytes.Buffer }{},
y: &struct{ R bytes.Buffer }{},
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: &struct{ R bytes.Buffer }{},
y: &struct{ R bytes.Buffer }{},
opts: []cmp.Option{
cmp.Comparer(func(x, y io.Reader) bool { return true }),
},
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: &struct{ R bytes.Buffer }{},
y: &struct{ R bytes.Buffer }{},
opts: []cmp.Option{
cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }),
cmp.Comparer(func(x, y io.Reader) bool { return true }),
},
}, {
label: label,
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
if x == nil || y == nil {
return x == nil && y == nil
}
return x.String() == y.String()
})},
}, {
label: label,
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
if x == nil || y == nil {
return x == nil && y == nil
}
return x.String() == y.String()
})},
wantDiff: `
{[]*regexp.Regexp}[1]:
-: s"a*b*c*"
+: s"a*b*d*"`,
}, {
label: label,
x: func() ***int {
a := 0
b := &a
c := &b
return &c
}(),
y: func() ***int {
a := 0
b := &a
c := &b
return &c
}(),
}, {
label: label,
x: func() ***int {
a := 0
b := &a
c := &b
return &c
}(),
y: func() ***int {
a := 1
b := &a
c := &b
return &c
}(),
wantDiff: `
***{***int}:
-: 0
+: 1`,
}, {
label: label,
x: []int{1, 2, 3, 4, 5}[:3],
y: []int{1, 2, 3},
}, {
label: label,
x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
y: struct{ fmt.Stringer }{regexp.MustCompile("hello")},
opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
}, {
label: label,
x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
y: struct{ fmt.Stringer }{regexp.MustCompile("hello2")},
opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
wantDiff: `
root:
-: s"hello"
+: s"hello2"`,
}, {
label: label,
x: md5.Sum([]byte{'a'}),
y: md5.Sum([]byte{'b'}),
wantDiff: `
{[16]uint8}:
-: [16]uint8{0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61}
+: [16]uint8{0x92, 0xeb, 0x5f, 0xfe, 0xe6, 0xae, 0x2f, 0xec, 0x3a, 0xd7, 0x1c, 0x77, 0x75, 0x31, 0x57, 0x8f}`,
}, {
label: label,
x: new(fmt.Stringer),
y: nil,
wantDiff: `
:
-: &<nil>
+: <non-existent>`,
}, {
label: label,
x: makeTarHeaders('0'),
y: makeTarHeaders('\x00'),
wantDiff: `
{[]cmp_test.tarHeader}[0].Typeflag:
-: 0x30
+: 0x00
{[]cmp_test.tarHeader}[1].Typeflag:
-: 0x30
+: 0x00
{[]cmp_test.tarHeader}[2].Typeflag:
-: 0x30
+: 0x00
{[]cmp_test.tarHeader}[3].Typeflag:
-: 0x30
+: 0x00
{[]cmp_test.tarHeader}[4].Typeflag:
-: 0x30
+: 0x00`,
}, {
label: label,
x: make([]int, 1000),
y: make([]int, 1000),
opts: []cmp.Option{
cmp.Comparer(func(_, _ int) bool {
return rand.Intn(2) == 0
}),
},
wantPanic: "non-deterministic or non-symmetric function detected",
}, {
label: label,
x: make([]int, 1000),
y: make([]int, 1000),
opts: []cmp.Option{
cmp.FilterValues(func(_, _ int) bool {
return rand.Intn(2) == 0
}, cmp.Ignore()),
},
wantPanic: "non-deterministic or non-symmetric function detected",
}, {
label: label,
x: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
y: []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
opts: []cmp.Option{
cmp.Comparer(func(x, y int) bool {
return x < y
}),
},
wantPanic: "non-deterministic or non-symmetric function detected",
}, {
label: label,
x: make([]string, 1000),
y: make([]string, 1000),
opts: []cmp.Option{
cmp.Transformer("", func(x string) int {
return rand.Int()
}),
},
wantPanic: "non-deterministic function detected",
}, {
// Make sure the dynamic checks don't raise a false positive for
// non-reflexive comparisons.
label: label,
x: make([]int, 10),
y: make([]int, 10),
opts: []cmp.Option{
cmp.Transformer("", func(x int) float64 {
return math.NaN()
}),
},
wantDiff: `
{[]int}:
-: []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+: []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}`,
}, {
// Ensure reasonable Stringer formatting of map keys.
label: label,
x: map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}},
y: map[*pb.Stringer]*pb.Stringer(nil),
wantDiff: `
{map[*testprotos.Stringer]*testprotos.Stringer}:
-: map[*testprotos.Stringer]*testprotos.Stringer{s"hello": s"world"}
+: map[*testprotos.Stringer]*testprotos.Stringer(nil)`,
}, {
// Ensure Stringer avoids double-quote escaping if possible.
label: label,
x: []*pb.Stringer{{`multi\nline\nline\nline`}},
wantDiff: ":\n\t-: []*testprotos.Stringer{s`multi\\nline\\nline\\nline`}\n\t+: <non-existent>",
}, {
label: label,
x: struct{ I Iface2 }{},
y: struct{ I Iface2 }{},
opts: []cmp.Option{
cmp.Comparer(func(x, y Iface1) bool {
return x == nil && y == nil
}),
},
}, {
label: label,
x: struct{ I Iface2 }{},
y: struct{ I Iface2 }{},
opts: []cmp.Option{
cmp.Transformer("", func(v Iface1) bool {
return v == nil
}),
},
}, {
label: label,
x: struct{ I Iface2 }{},
y: struct{ I Iface2 }{},
opts: []cmp.Option{
cmp.FilterValues(func(x, y Iface1) bool {
return x == nil && y == nil
}, cmp.Ignore()),
},
}, {
label: label,
x: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63, "name": "Sammy Sosa"}},
y: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65.0, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63.0, "name": "Sammy Sosa"}},
wantDiff: `
root[0]["hr"]:
-: int(65)
+: float64(65)
root[1]["hr"]:
-: int(63)
+: float64(63)`,
}}
}
func transformerTests() []test {
type StringBytes struct {
String string
Bytes []byte
}
const label = "Transformer"
transformOnce := func(name string, f interface{}) cmp.Option {
xform := cmp.Transformer(name, f)
return cmp.FilterPath(func(p cmp.Path) bool {
for _, ps := range p {
if tr, ok := ps.(cmp.Transform); ok && tr.Option() == xform {
return false
}
}
return true
}, xform)
}
return []test{{
label: label,
x: uint8(0),
y: uint8(1),
opts: []cmp.Option{
cmp.Transformer("", func(in uint8) uint16 { return uint16(in) }),
cmp.Transformer("", func(in uint16) uint32 { return uint32(in) }),
cmp.Transformer("", func(in uint32) uint64 { return uint64(in) }),
},
wantDiff: `
λ(λ(λ({uint8}))):
-: 0x00
+: 0x01`,
}, {
label: label,
x: 0,
y: 1,
opts: []cmp.Option{
cmp.Transformer("", func(in int) int { return in / 2 }),
cmp.Transformer("", func(in int) int { return in }),
},
wantPanic: "ambiguous set of applicable options",
}, {
label: label,
x: []int{0, -5, 0, -1},
y: []int{1, 3, 0, -5},
opts: []cmp.Option{
cmp.FilterValues(
func(x, y int) bool { return x+y >= 0 },
cmp.Transformer("", func(in int) int64 { return int64(in / 2) }),
),
cmp.FilterValues(
func(x, y int) bool { return x+y < 0 },
cmp.Transformer("", func(in int) int64 { return int64(in) }),
),
},
wantDiff: `
λ({[]int}[1]):
-: -5
+: 3
λ({[]int}[3]):
-: -1
+: -5`,
}, {
label: label,
x: 0,
y: 1,
opts: []cmp.Option{
cmp.Transformer("", func(in int) interface{} {
if in == 0 {
return "string"
}
return float64(in)
}),
},
wantDiff: `
λ({int}):
-: "string"
+: 1`,
}, {
label: label,
x: `{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"isAlive": true,
"address": {
"city": "Los Angeles",
"postalCode": "10021-3100",
"state": "CA",
"streetAddress": "21 2nd Street"
},
"phoneNumbers": [{
"type": "home",
"number": "212 555-4321"
},{
"type": "office",
"number": "646 555-4567"
},{
"number": "123 456-7890",
"type": "mobile"
}],
"children": []
}`,
y: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":25,
"address":{"streetAddress":"21 2nd Street","city":"New York",
"state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home",
"number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{
"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`,
opts: []cmp.Option{
transformOnce("ParseJSON", func(s string) (m map[string]interface{}) {
if err := json.Unmarshal([]byte(s), &m); err != nil {
panic(err)
}
return m
}),
},
wantDiff: `
ParseJSON({string})["address"]["city"]:
-: "Los Angeles"
+: "New York"
ParseJSON({string})["address"]["state"]:
-: "CA"
+: "NY"
ParseJSON({string})["phoneNumbers"][0]["number"]:
-: "212 555-4321"
+: "212 555-1234"
ParseJSON({string})["spouse"]:
-: <non-existent>
+: interface {}(nil)`,
}, {
label: label,
x: StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")},
y: StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")},
opts: []cmp.Option{
transformOnce("SplitString", func(s string) []string { return strings.Split(s, "\n") }),
transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }),
},
wantDiff: `
SplitString({cmp_test.StringBytes}.String)[2]:
-: "Line"
+: "line"
SplitBytes({cmp_test.StringBytes}.Bytes)[3][0]:
-: 0x62
+: 0x42`,
}}
}
func embeddedTests() []test {
const label = "EmbeddedStruct/"
privateStruct := *new(ts.ParentStructA).PrivateStruct()
createStructA := func(i int) ts.ParentStructA {
s := ts.ParentStructA{}
s.PrivateStruct().Public = 1 + i
s.PrivateStruct().SetPrivate(2 + i)
return s
}
createStructB := func(i int) ts.ParentStructB {
s := ts.ParentStructB{}
s.PublicStruct.Public = 1 + i
s.PublicStruct.SetPrivate(2 + i)
return s
}
createStructC := func(i int) ts.ParentStructC {
s := ts.ParentStructC{}
s.PrivateStruct().Public = 1 + i
s.PrivateStruct().SetPrivate(2 + i)
s.Public = 3 + i
s.SetPrivate(4 + i)
return s
}
createStructD := func(i int) ts.ParentStructD {
s := ts.ParentStructD{}
s.PublicStruct.Public = 1 + i
s.PublicStruct.SetPrivate(2 + i)
s.Public = 3 + i
s.SetPrivate(4 + i)
return s
}
createStructE := func(i int) ts.ParentStructE {
s := ts.ParentStructE{}
s.PrivateStruct().Public = 1 + i
s.PrivateStruct().SetPrivate(2 + i)
s.PublicStruct.Public = 3 + i
s.PublicStruct.SetPrivate(4 + i)
return s
}
createStructF := func(i int) ts.ParentStructF {
s := ts.ParentStructF{}
s.PrivateStruct().Public = 1 + i
s.PrivateStruct().SetPrivate(2 + i)
s.PublicStruct.Public = 3 + i
s.PublicStruct.SetPrivate(4 + i)
s.Public = 5 + i
s.SetPrivate(6 + i)
return s
}
createStructG := func(i int) *ts.ParentStructG {
s := ts.NewParentStructG()
s.PrivateStruct().Public = 1 + i
s.PrivateStruct().SetPrivate(2 + i)
return s
}
createStructH := func(i int) *ts.ParentStructH {
s := ts.NewParentStructH()
s.PublicStruct.Public = 1 + i
s.PublicStruct.SetPrivate(2 + i)
return s
}
createStructI := func(i int) *ts.ParentStructI {
s := ts.NewParentStructI()
s.PrivateStruct().Public = 1 + i
s.PrivateStruct().SetPrivate(2 + i)
s.PublicStruct.Public = 3 + i
s.PublicStruct.SetPrivate(4 + i)
return s
}
createStructJ := func(i int) *ts.ParentStructJ {
s := ts.NewParentStructJ()
s.PrivateStruct().Public = 1 + i
s.PrivateStruct().SetPrivate(2 + i)
s.PublicStruct.Public = 3 + i
s.PublicStruct.SetPrivate(4 + i)
s.Private().Public = 5 + i
s.Private().SetPrivate(6 + i)
s.Public.Public = 7 + i
s.Public.SetPrivate(8 + i)
return s
}
return []test{{
label: label + "ParentStructA",
x: ts.ParentStructA{},
y: ts.ParentStructA{},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructA",
x: ts.ParentStructA{},
y: ts.ParentStructA{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructA{}),
},
}, {
label: label + "ParentStructA",
x: createStructA(0),
y: createStructA(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructA{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructA",
x: createStructA(0),
y: createStructA(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
},
}, {
label: label + "ParentStructA",
x: createStructA(0),
y: createStructA(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
},
wantDiff: `
{teststructs.ParentStructA}.privateStruct.Public:
-: 1
+: 2
{teststructs.ParentStructA}.privateStruct.private:
-: 2
+: 3`,
}, {
label: label + "ParentStructB",
x: ts.ParentStructB{},
y: ts.ParentStructB{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructB{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructB",
x: ts.ParentStructB{},
y: ts.ParentStructB{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructB{}),
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
},
}, {
label: label + "ParentStructB",
x: createStructB(0),
y: createStructB(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructB{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructB",
x: createStructB(0),
y: createStructB(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
},
}, {
label: label + "ParentStructB",
x: createStructB(0),
y: createStructB(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
},
wantDiff: `
{teststructs.ParentStructB}.PublicStruct.Public:
-: 1
+: 2
{teststructs.ParentStructB}.PublicStruct.private:
-: 2
+: 3`,
}, {
label: label + "ParentStructC",
x: ts.ParentStructC{},
y: ts.ParentStructC{},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructC",
x: ts.ParentStructC{},
y: ts.ParentStructC{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructC{}),
},
}, {
label: label + "ParentStructC",
x: createStructC(0),
y: createStructC(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructC{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructC",
x: createStructC(0),
y: createStructC(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
},
}, {
label: label + "ParentStructC",
x: createStructC(0),
y: createStructC(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
},
wantDiff: `
{teststructs.ParentStructC}.privateStruct.Public:
-: 1
+: 2
{teststructs.ParentStructC}.privateStruct.private:
-: 2
+: 3
{teststructs.ParentStructC}.Public:
-: 3
+: 4
{teststructs.ParentStructC}.private:
-: 4
+: 5`,
}, {
label: label + "ParentStructD",
x: ts.ParentStructD{},
y: ts.ParentStructD{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructD{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructD",
x: ts.ParentStructD{},
y: ts.ParentStructD{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructD{}),
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
},
}, {
label: label + "ParentStructD",
x: createStructD(0),
y: createStructD(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructD{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructD",
x: createStructD(0),
y: createStructD(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
},
}, {
label: label + "ParentStructD",
x: createStructD(0),
y: createStructD(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
},
wantDiff: `
{teststructs.ParentStructD}.PublicStruct.Public:
-: 1
+: 2
{teststructs.ParentStructD}.PublicStruct.private:
-: 2
+: 3
{teststructs.ParentStructD}.Public:
-: 3
+: 4
{teststructs.ParentStructD}.private:
-: 4
+: 5`,
}, {
label: label + "ParentStructE",
x: ts.ParentStructE{},
y: ts.ParentStructE{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructE{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructE",
x: ts.ParentStructE{},
y: ts.ParentStructE{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructE{}),
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
},
}, {
label: label + "ParentStructE",
x: createStructE(0),
y: createStructE(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructE{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructE",
x: createStructE(0),
y: createStructE(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructE",
x: createStructE(0),
y: createStructE(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
},
}, {
label: label + "ParentStructE",
x: createStructE(0),
y: createStructE(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
},
wantDiff: `
{teststructs.ParentStructE}.privateStruct.Public:
-: 1
+: 2
{teststructs.ParentStructE}.privateStruct.private:
-: 2
+: 3
{teststructs.ParentStructE}.PublicStruct.Public:
-: 3
+: 4
{teststructs.ParentStructE}.PublicStruct.private:
-: 4
+: 5`,
}, {
label: label + "ParentStructF",
x: ts.ParentStructF{},
y: ts.ParentStructF{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructF{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructF",
x: ts.ParentStructF{},
y: ts.ParentStructF{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructF{}),
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
},
}, {
label: label + "ParentStructF",
x: createStructF(0),
y: createStructF(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructF{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructF",
x: createStructF(0),
y: createStructF(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructF",
x: createStructF(0),
y: createStructF(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
},
}, {
label: label + "ParentStructF",
x: createStructF(0),
y: createStructF(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
},
wantDiff: `
{teststructs.ParentStructF}.privateStruct.Public:
-: 1
+: 2
{teststructs.ParentStructF}.privateStruct.private:
-: 2
+: 3
{teststructs.ParentStructF}.PublicStruct.Public:
-: 3
+: 4
{teststructs.ParentStructF}.PublicStruct.private:
-: 4
+: 5
{teststructs.ParentStructF}.Public:
-: 5
+: 6
{teststructs.ParentStructF}.private:
-: 6
+: 7`,
}, {
label: label + "ParentStructG",
x: ts.ParentStructG{},
y: ts.ParentStructG{},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructG",
x: ts.ParentStructG{},
y: ts.ParentStructG{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructG{}),
},
}, {
label: label + "ParentStructG",
x: createStructG(0),
y: createStructG(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructG{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructG",
x: createStructG(0),
y: createStructG(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
},
}, {
label: label + "ParentStructG",
x: createStructG(0),
y: createStructG(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
},
wantDiff: `
{*teststructs.ParentStructG}.privateStruct.Public:
-: 1
+: 2
{*teststructs.ParentStructG}.privateStruct.private:
-: 2
+: 3`,
}, {
label: label + "ParentStructH",
x: ts.ParentStructH{},
y: ts.ParentStructH{},
}, {
label: label + "ParentStructH",
x: createStructH(0),
y: createStructH(0),
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructH",
x: ts.ParentStructH{},
y: ts.ParentStructH{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructH{}),
},
}, {
label: label + "ParentStructH",
x: createStructH(0),
y: createStructH(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructH{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructH",
x: createStructH(0),
y: createStructH(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
},
}, {
label: label + "ParentStructH",
x: createStructH(0),
y: createStructH(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
},
wantDiff: `
{*teststructs.ParentStructH}.PublicStruct.Public:
-: 1
+: 2
{*teststructs.ParentStructH}.PublicStruct.private:
-: 2
+: 3`,
}, {
label: label + "ParentStructI",
x: ts.ParentStructI{},
y: ts.ParentStructI{},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructI",
x: ts.ParentStructI{},
y: ts.ParentStructI{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructI{}),
},
}, {
label: label + "ParentStructI",
x: createStructI(0),
y: createStructI(0),
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructI{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructI",
x: createStructI(0),
y: createStructI(0),
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
},
}, {
label: label + "ParentStructI",
x: createStructI(0),
y: createStructI(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructI{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructI",
x: createStructI(0),
y: createStructI(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
},
}, {
label: label + "ParentStructI",
x: createStructI(0),
y: createStructI(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
},
wantDiff: `
{*teststructs.ParentStructI}.privateStruct.Public:
-: 1
+: 2
{*teststructs.ParentStructI}.privateStruct.private:
-: 2
+: 3
{*teststructs.ParentStructI}.PublicStruct.Public:
-: 3
+: 4
{*teststructs.ParentStructI}.PublicStruct.private:
-: 4
+: 5`,
}, {
label: label + "ParentStructJ",
x: ts.ParentStructJ{},
y: ts.ParentStructJ{},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructJ",
x: ts.ParentStructJ{},
y: ts.ParentStructJ{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructJ",
x: ts.ParentStructJ{},
y: ts.ParentStructJ{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
},
}, {
label: label + "ParentStructJ",
x: createStructJ(0),
y: createStructJ(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
},
wantPanic: "cannot handle unexported field",
}, {
label: label + "ParentStructJ",
x: createStructJ(0),
y: createStructJ(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
},
}, {
label: label + "ParentStructJ",
x: createStructJ(0),
y: createStructJ(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
},
wantDiff: `
{*teststructs.ParentStructJ}.privateStruct.Public:
-: 1
+: 2
{*teststructs.ParentStructJ}.privateStruct.private:
-: 2
+: 3
{*teststructs.ParentStructJ}.PublicStruct.Public:
-: 3
+: 4
{*teststructs.ParentStructJ}.PublicStruct.private:
-: 4
+: 5
{*teststructs.ParentStructJ}.Public.Public:
-: 7
+: 8
{*teststructs.ParentStructJ}.Public.private:
-: 8
+: 9
{*teststructs.ParentStructJ}.private.Public:
-: 5
+: 6
{*teststructs.ParentStructJ}.private.private:
-: 6
+: 7`,
}}
}
func methodTests() []test {
const label = "EqualMethod/"
// A common mistake that the Equal method is on a pointer receiver,
// but only a non-pointer value is present in the struct.
// A transform can be used to forcibly reference the value.
derefTransform := cmp.FilterPath(func(p cmp.Path) bool {
if len(p) == 0 {
return false
}
t := p[len(p)-1].Type()
if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr {
return false
}
if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
tf := m.Func.Type()
return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true)
}
return false
}, cmp.Transformer("Ref", func(x interface{}) interface{} {
v := reflect.ValueOf(x)
vp := reflect.New(v.Type())
vp.Elem().Set(v)
return vp.Interface()
}))
// For each of these types, there is an Equal method defined, which always
// returns true, while the underlying data are fundamentally different.
// Since the method should be called, these are expected to be equal.
return []test{{
label: label + "StructA",
x: ts.StructA{X: "NotEqual"},
y: ts.StructA{X: "not_equal"},
}, {
label: label + "StructA",
x: &ts.StructA{X: "NotEqual"},
y: &ts.StructA{X: "not_equal"},
}, {
label: label + "StructB",
x: ts.StructB{X: "NotEqual"},
y: ts.StructB{X: "not_equal"},
wantDiff: `
{teststructs.StructB}.X:
-: "NotEqual"
+: "not_equal"`,
}, {
label: label + "StructB",
x: ts.StructB{X: "NotEqual"},
y: ts.StructB{X: "not_equal"},
opts: []cmp.Option{derefTransform},
}, {
label: label + "StructB",
x: &ts.StructB{X: "NotEqual"},
y: &ts.StructB{X: "not_equal"},
}, {
label: label + "StructC",
x: ts.StructC{X: "NotEqual"},
y: ts.StructC{X: "not_equal"},
}, {
label: label + "StructC",
x: &ts.StructC{X: "NotEqual"},
y: &ts.StructC{X: "not_equal"},
}, {
label: label + "StructD",
x: ts.StructD{X: "NotEqual"},
y: ts.StructD{X: "not_equal"},
wantDiff: `
{teststructs.StructD}.X:
-: "NotEqual"
+: "not_equal"`,
}, {
label: label + "StructD",
x: ts.StructD{X: "NotEqual"},
y: ts.StructD{X: "not_equal"},
opts: []cmp.Option{derefTransform},
}, {
label: label + "StructD",
x: &ts.StructD{X: "NotEqual"},
y: &ts.StructD{X: "not_equal"},
}, {
label: label + "StructE",
x: ts.StructE{X: "NotEqual"},
y: ts.StructE{X: "not_equal"},
wantDiff: `
{teststructs.StructE}.X:
-: "NotEqual"
+: "not_equal"`,
}, {
label: label + "StructE",
x: ts.StructE{X: "NotEqual"},
y: ts.StructE{X: "not_equal"},
opts: []cmp.Option{derefTransform},
}, {
label: label + "StructE",
x: &ts.StructE{X: "NotEqual"},
y: &ts.StructE{X: "not_equal"},
}, {
label: label + "StructF",
x: ts.StructF{X: "NotEqual"},
y: ts.StructF{X: "not_equal"},
wantDiff: `
{teststructs.StructF}.X:
-: "NotEqual"
+: "not_equal"`,
}, {
label: label + "StructF",
x: &ts.StructF{X: "NotEqual"},
y: &ts.StructF{X: "not_equal"},
}, {
label: label + "StructA1",
x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
}, {
label: label + "StructA1",
x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
wantDiff: "{teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
}, {
label: label + "StructA1",
x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
}, {
label: label + "StructA1",
x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
wantDiff: "{*teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
}, {
label: label + "StructB1",
x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
opts: []cmp.Option{derefTransform},
}, {
label: label + "StructB1",
x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
opts: []cmp.Option{derefTransform},
wantDiff: "{teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
}, {
label: label + "StructB1",
x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
opts: []cmp.Option{derefTransform},
}, {
label: label + "StructB1",
x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
opts: []cmp.Option{derefTransform},
wantDiff: "{*teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
}, {
label: label + "StructC1",
x: ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructC1",
x: &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructD1",
x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
wantDiff: `
{teststructs.StructD1}.StructD.X:
-: "NotEqual"
+: "not_equal"
{teststructs.StructD1}.X:
-: "NotEqual"
+: "not_equal"`,
}, {
label: label + "StructD1",
x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
opts: []cmp.Option{derefTransform},
}, {
label: label + "StructD1",
x: &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructE1",
x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
wantDiff: `
{teststructs.StructE1}.StructE.X:
-: "NotEqual"
+: "not_equal"
{teststructs.StructE1}.X:
-: "NotEqual"
+: "not_equal"`,
}, {
label: label + "StructE1",
x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
opts: []cmp.Option{derefTransform},
}, {
label: label + "StructE1",
x: &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructF1",
x: ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
wantDiff: `
{teststructs.StructF1}.StructF.X:
-: "NotEqual"
+: "not_equal"
{teststructs.StructF1}.X:
-: "NotEqual"
+: "not_equal"`,
}, {
label: label + "StructF1",
x: &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructA2",
x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
}, {
label: label + "StructA2",
x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
wantDiff: "{teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
}, {
label: label + "StructA2",
x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
}, {
label: label + "StructA2",
x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
wantDiff: "{*teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
}, {
label: label + "StructB2",
x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
}, {
label: label + "StructB2",
x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
wantDiff: "{teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
}, {
label: label + "StructB2",
x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
}, {
label: label + "StructB2",
x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
wantDiff: "{*teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
}, {
label: label + "StructC2",
x: ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructC2",
x: &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructD2",
x: ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructD2",
x: &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructE2",
x: ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructE2",
x: &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructF2",
x: ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructF2",
x: &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
}, {
label: label + "StructNo",
x: ts.StructNo{X: "NotEqual"},
y: ts.StructNo{X: "not_equal"},
wantDiff: "{teststructs.StructNo}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
}, {
label: label + "AssignA",
x: ts.AssignA(func() int { return 0 }),
y: ts.AssignA(func() int { return 1 }),
}, {
label: label + "AssignB",
x: ts.AssignB(struct{ A int }{0}),
y: ts.AssignB(struct{ A int }{1}),
}, {
label: label + "AssignC",
x: ts.AssignC(make(chan bool)),
y: ts.AssignC(make(chan bool)),
}, {
label: label + "AssignD",
x: ts.AssignD(make(chan bool)),
y: ts.AssignD(make(chan bool)),
}}
}
func project1Tests() []test {
const label = "Project1"
ignoreUnexported := cmpopts.IgnoreUnexported(
ts.EagleImmutable{},
ts.DreamerImmutable{},
ts.SlapImmutable{},
ts.GoatImmutable{},
ts.DonkeyImmutable{},
ts.LoveRadius{},
ts.SummerLove{},
ts.SummerLoveSummary{},
)
createEagle := func() ts.Eagle {
return ts.Eagle{
Name: "eagle",
Hounds: []string{"buford", "tannen"},
Desc: "some description",
Dreamers: []ts.Dreamer{{}, {
Name: "dreamer2",
Animal: []interface{}{
ts.Goat{
Target: "corporation",
Immutable: &ts.GoatImmutable{
ID: "southbay",
State: (*pb.Goat_States)(intPtr(5)),
Started: now,
},
},
ts.Donkey{},
},
Amoeba: 53,
}},
Slaps: []ts.Slap{{
Name: "slapID",
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
Immutable: &ts.SlapImmutable{
ID: "immutableSlap",
MildSlap: true,
Started: now,
LoveRadius: &ts.LoveRadius{
Summer: &ts.SummerLove{
Summary: &ts.SummerLoveSummary{
Devices: []string{"foo", "bar", "baz"},
ChangeType: []pb.SummerType{1, 2, 3},
},
},
},
},
}},
Immutable: &ts.EagleImmutable{
ID: "eagleID",
Birthday: now,
MissingCall: (*pb.Eagle_MissingCalls)(intPtr(55)),
},
}
}
return []test{{
label: label,
x: ts.Eagle{Slaps: []ts.Slap{{
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
}}},
y: ts.Eagle{Slaps: []ts.Slap{{
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
}}},
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: ts.Eagle{Slaps: []ts.Slap{{
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
}}},
y: ts.Eagle{Slaps: []ts.Slap{{
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
}}},
opts: []cmp.Option{cmp.Comparer(pb.Equal)},
}, {
label: label,
x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
}}},
y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata2"}},
}}},
opts: []cmp.Option{cmp.Comparer(pb.Equal)},
wantDiff: "{teststructs.Eagle}.Slaps[4].Args:\n\t-: s\"metadata\"\n\t+: s\"metadata2\"\n",
}, {
label: label,
x: createEagle(),
y: createEagle(),
opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
}, {
label: label,
x: func() ts.Eagle {
eg := createEagle()
eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2"
eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(intPtr(6))
eg.Slaps[0].Immutable.MildSlap = false
return eg
}(),
y: func() ts.Eagle {
eg := createEagle()
devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices
eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1]
return eg
}(),
opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
wantDiff: `
{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.ID:
-: "southbay2"
+: "southbay"
*{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.State:
-: testprotos.Goat_States(6)
+: testprotos.Goat_States(5)
{teststructs.Eagle}.Slaps[0].Immutable.MildSlap:
-: false
+: true
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[1->?]:
-: "bar"
+: <non-existent>
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[2->?]:
-: "baz"
+: <non-existent>`,
}}
}
type germSorter []*pb.Germ
func (gs germSorter) Len() int { return len(gs) }
func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() }
func (gs germSorter) Swap(i, j int) { gs[i], gs[j] = gs[j], gs[i] }
func project2Tests() []test {
const label = "Project2"
sortGerms := cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ {
out := append([]*pb.Germ(nil), in...) // Make copy
sort.Sort(germSorter(out))
return out
})
equalDish := cmp.Comparer(func(x, y *ts.Dish) bool {
if x == nil || y == nil {
return x == nil && y == nil
}
px, err1 := x.Proto()
py, err2 := y.Proto()
if err1 != nil || err2 != nil {
return err1 == err2
}
return pb.Equal(px, py)
})
createBatch := func() ts.GermBatch {
return ts.GermBatch{
DirtyGerms: map[int32][]*pb.Germ{
17: {
{Stringer: pb.Stringer{X: "germ1"}},
},
18: {
{Stringer: pb.Stringer{X: "germ2"}},
{Stringer: pb.Stringer{X: "germ3"}},
{Stringer: pb.Stringer{X: "germ4"}},
},
},
GermMap: map[int32]*pb.Germ{
13: {Stringer: pb.Stringer{X: "germ13"}},
21: {Stringer: pb.Stringer{X: "germ21"}},
},
DishMap: map[int32]*ts.Dish{
0: ts.CreateDish(nil, io.EOF),
1: ts.CreateDish(nil, io.ErrUnexpectedEOF),
2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{X: "dish"}}, nil),
},
HasPreviousResult: true,
DirtyID: 10,
GermStrain: 421,
InfectedAt: now,
}
}
return []test{{
label: label,
x: createBatch(),
y: createBatch(),
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: createBatch(),
y: createBatch(),
opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
}, {
label: label,
x: createBatch(),
y: func() ts.GermBatch {
gb := createBatch()
s := gb.DirtyGerms[18]
s[0], s[1], s[2] = s[1], s[2], s[0]
return gb
}(),
opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
wantDiff: `
{teststructs.GermBatch}.DirtyGerms[18][0->?]:
-: s"germ2"
+: <non-existent>
{teststructs.GermBatch}.DirtyGerms[18][?->2]:
-: <non-existent>
+: s"germ2"`,
}, {
label: label,
x: createBatch(),
y: func() ts.GermBatch {
gb := createBatch()
s := gb.DirtyGerms[18]
s[0], s[1], s[2] = s[1], s[2], s[0]
return gb
}(),
opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
}, {
label: label,
x: func() ts.GermBatch {
gb := createBatch()
delete(gb.DirtyGerms, 17)
gb.DishMap[1] = nil
return gb
}(),
y: func() ts.GermBatch {
gb := createBatch()
gb.DirtyGerms[18] = gb.DirtyGerms[18][:2]
gb.GermStrain = 22
return gb
}(),
opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
wantDiff: `
{teststructs.GermBatch}.DirtyGerms[17]:
-: <non-existent>
+: []*testprotos.Germ{s"germ1"}
Sort({teststructs.GermBatch}.DirtyGerms[18])[2->?]:
-: s"germ4"
+: <non-existent>
{teststructs.GermBatch}.DishMap[1]:
-: (*teststructs.Dish)(nil)
+: &teststructs.Dish{err: &errors.errorString{s: "unexpected EOF"}}
{teststructs.GermBatch}.GermStrain:
-: 421
+: 22`,
}}
}
func project3Tests() []test {
const label = "Project3"
allowVisibility := cmp.AllowUnexported(ts.Dirt{})
ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})
transformProtos := cmp.Transformer("", func(x pb.Dirt) *pb.Dirt {
return &x
})
equalTable := cmp.Comparer(func(x, y ts.Table) bool {
tx, ok1 := x.(*ts.MockTable)
ty, ok2 := y.(*ts.MockTable)
if !ok1 || !ok2 {
panic("table type must be MockTable")
}
return cmp.Equal(tx.State(), ty.State())
})
createDirt := func() (d ts.Dirt) {
d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"}))
d.SetTimestamp(12345)
d.Discord = 554
d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "proto"}}
d.SetWizard(map[string]*pb.Wizard{
"harry": {Stringer: pb.Stringer{X: "potter"}},
"albus": {Stringer: pb.Stringer{X: "dumbledore"}},
})
d.SetLastTime(54321)
return d
}
return []test{{
label: label,
x: createDirt(),
y: createDirt(),
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: createDirt(),
y: createDirt(),
opts: []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: createDirt(),
y: createDirt(),
opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
}, {
label: label,
x: func() ts.Dirt {
d := createDirt()
d.SetTable(ts.CreateMockTable([]string{"a", "c"}))
d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "blah"}}
return d
}(),
y: func() ts.Dirt {
d := createDirt()
d.Discord = 500
d.SetWizard(map[string]*pb.Wizard{
"harry": {Stringer: pb.Stringer{X: "otter"}},
})
return d
}(),
opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
wantDiff: `
{teststructs.Dirt}.table:
-: &teststructs.MockTable{state: []string{"a", "c"}}
+: &teststructs.MockTable{state: []string{"a", "b", "c"}}
{teststructs.Dirt}.Discord:
-: teststructs.DiscordState(554)
+: teststructs.DiscordState(500)
λ({teststructs.Dirt}.Proto):
-: s"blah"
+: s"proto"
{teststructs.Dirt}.wizard["albus"]:
-: s"dumbledore"
+: <non-existent>
{teststructs.Dirt}.wizard["harry"]:
-: s"potter"
+: s"otter"`,
}}
}
func project4Tests() []test {
const label = "Project4"
allowVisibility := cmp.AllowUnexported(
ts.Cartel{},
ts.Headquarter{},
ts.Poison{},
)
transformProtos := cmp.Transformer("", func(x pb.Restrictions) *pb.Restrictions {
return &x
})
createCartel := func() ts.Cartel {
var p ts.Poison
p.SetPoisonType(5)
p.SetExpiration(now)
p.SetManufacturer("acme")
var hq ts.Headquarter
hq.SetID(5)
hq.SetLocation("moon")
hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"})
hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{X: "metadata"}})
hq.SetPublicMessage([]byte{1, 2, 3, 4, 5})
hq.SetHorseBack("abcdef")
hq.SetStatus(44)
var c ts.Cartel
c.Headquarter = hq
c.SetSource("mars")
c.SetCreationTime(now)
c.SetBoss("al capone")
c.SetPoisons([]*ts.Poison{&p})
return c
}
return []test{{
label: label,
x: createCartel(),
y: createCartel(),
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: createCartel(),
y: createCartel(),
opts: []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)},
wantPanic: "cannot handle unexported field",
}, {
label: label,
x: createCartel(),
y: createCartel(),
opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
}, {
label: label,
x: func() ts.Cartel {
d := createCartel()
var p1, p2 ts.Poison
p1.SetPoisonType(1)
p1.SetExpiration(now)
p1.SetManufacturer("acme")
p2.SetPoisonType(2)
p2.SetManufacturer("acme2")
d.SetPoisons([]*ts.Poison{&p1, &p2})
return d
}(),
y: func() ts.Cartel {
d := createCartel()
d.SetSubDivisions([]string{"bravo", "charlie"})
d.SetPublicMessage([]byte{1, 2, 4, 3, 5})
return d
}(),
opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
wantDiff: `
{teststructs.Cartel}.Headquarter.subDivisions[0->?]:
-: "alpha"
+: <non-existent>
{teststructs.Cartel}.Headquarter.publicMessage[2]:
-: 0x03
+: 0x04
{teststructs.Cartel}.Headquarter.publicMessage[3]:
-: 0x04
+: 0x03
{teststructs.Cartel}.poisons[0].poisonType:
-: testprotos.PoisonType(1)
+: testprotos.PoisonType(5)
{teststructs.Cartel}.poisons[1->?]:
-: &teststructs.Poison{poisonType: testprotos.PoisonType(2), manufacturer: "acme2"}
+: <non-existent>`,
}}
}
// TODO: Delete this hack when we drop Go1.6 support.
func tRunParallel(t *testing.T, name string, f func(t *testing.T)) {
type runner interface {
Run(string, func(t *testing.T)) bool
}
var ti interface{} = t
if r, ok := ti.(runner); ok {
r.Run(name, func(t *testing.T) {
t.Parallel()
f(t)
})
} else {
// Cannot run sub-tests in parallel in Go1.6.
t.Logf("Test: %s", name)
f(t)
}
}