blob: e910bd107d54cde1da922dbe0270fce64fc12d22 [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"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/go-cmp/cmp/internal/flags"
pb "github.com/google/go-cmp/cmp/internal/testprotos"
ts "github.com/google/go-cmp/cmp/internal/teststructs"
)
func init() {
flags.Deterministic = true
}
var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC)
func intPtr(n int) *int { return &n }
type test struct {
label string // Test name
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
reason string // The reason for the expected outcome
}
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, cycleTests()...)
tests = append(tests, project1Tests()...)
tests = append(tests, project2Tests()...)
tests = append(tests, project3Tests()...)
tests = append(tests, project4Tests()...)
for _, tt := range tests {
tt := tt
t.Run(tt.label, func(t *testing.T) {
t.Parallel()
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...)
}()
// TODO: Require every test case to provide a reason.
if tt.wantPanic == "" {
if gotPanic != "" {
t.Fatalf("unexpected panic message: %s\nreason: %v", gotPanic, tt.reason)
}
tt.wantDiff = strings.TrimPrefix(tt.wantDiff, "\n")
if gotDiff != tt.wantDiff {
t.Fatalf("difference message:\ngot:\n%s\nwant:\n%s\nreason: %v", gotDiff, tt.wantDiff, tt.reason)
}
} else {
if !strings.Contains(gotPanic, tt.wantPanic) {
t.Fatalf("panic message:\ngot: %s\nwant: %s\nreason: %v", gotPanic, tt.wantPanic, tt.reason)
}
}
})
}
}
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
}
type namedWithUnexported struct {
unexported 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: nil,
y: nil,
}, {
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: `
struct{ A int; B int; C int }{
A: 1,
B: 2,
- C: 3,
+ C: 4,
}
`,
}, {
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: `
&struct{ A *int }{
- A: &4,
+ A: &5,
}
`,
}, {
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: `
&struct{ R *bytes.Buffer }{
- R: s"",
+ R: nil,
}
`,
}, {
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{
nil,
- 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: `
struct{ fmt.Stringer }(
- s"hello",
+ s"hello2",
)
`,
}, {
label: label,
x: md5.Sum([]byte{'a'}),
y: md5.Sum([]byte{'b'}),
wantDiff: `
[16]uint8{
- 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61,
+ 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: `
interface{}(
- &fmt.Stringer(nil),
)
`,
}, {
label: label,
x: makeTarHeaders('0'),
y: makeTarHeaders('\x00'),
wantDiff: `
[]cmp_test.tarHeader{
{
... // 4 identical fields
Size: 1,
ModTime: s"2009-11-10 23:00:00 +0000 UTC",
- Typeflag: 0x30,
+ Typeflag: 0x00,
Linkname: "",
Uname: "user",
... // 6 identical fields
},
{
... // 4 identical fields
Size: 2,
ModTime: s"2009-11-11 00:00:00 +0000 UTC",
- Typeflag: 0x30,
+ Typeflag: 0x00,
Linkname: "",
Uname: "user",
... // 6 identical fields
},
{
... // 4 identical fields
Size: 4,
ModTime: s"2009-11-11 01:00:00 +0000 UTC",
- Typeflag: 0x30,
+ Typeflag: 0x00,
Linkname: "",
Uname: "user",
... // 6 identical fields
},
{
... // 4 identical fields
Size: 8,
ModTime: s"2009-11-11 02:00:00 +0000 UTC",
- Typeflag: 0x30,
+ Typeflag: 0x00,
Linkname: "",
Uname: "user",
... // 6 identical fields
},
{
... // 4 identical fields
Size: 16,
ModTime: s"2009-11-11 03:00:00 +0000 UTC",
- Typeflag: 0x30,
+ Typeflag: 0x00,
Linkname: "",
Uname: "user",
... // 6 identical fields
},
}
`,
}, {
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{
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
}
`,
}, {
// 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(
- {s"hello": s"world"},
+ nil,
)
`,
}, {
// Ensure Stringer avoids double-quote escaping if possible.
label: label,
x: []*pb.Stringer{{`multi\nline\nline\nline`}},
wantDiff: strings.Replace(`
interface{}(
- []*testprotos.Stringer{s'multi\nline\nline\nline'},
)
`, "'", "`", -1),
}, {
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: `
[]interface{}{
map[string]interface{}{
"avg": float64(0.278),
- "hr": int(65),
+ "hr": float64(65),
"name": string("Mark McGwire"),
},
map[string]interface{}{
"avg": float64(0.288),
- "hr": int(63),
+ "hr": float64(63),
"name": string("Sammy Sosa"),
},
}
`,
}, {
label: label,
x: map[*int]string{
new(int): "hello",
},
y: map[*int]string{
new(int): "world",
},
wantDiff: `
map[*int]string{
- ⟪0xdeadf00f⟫: "hello",
+ ⟪0xdeadf00f⟫: "world",
}
`,
}, {
label: label,
x: intPtr(0),
y: intPtr(0),
opts: []cmp.Option{
cmp.Comparer(func(x, y *int) bool { return x == y }),
},
// TODO: This output is unhelpful and should show the address.
wantDiff: `
(*int)(
- &0,
+ &0,
)
`,
}, {
label: label,
x: [2][]int{
{0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 7, 8, 0, 9, 0, 0},
{0, 1, 0, 0, 0, 20},
},
y: [2][]int{
{1, 2, 3, 0, 4, 5, 6, 7, 0, 8, 9, 0, 0, 0},
{0, 0, 1, 2, 0, 0, 0},
},
opts: []cmp.Option{
cmp.FilterPath(func(p cmp.Path) bool {
vx, vy := p.Last().Values()
if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
return true
}
if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
return true
}
return false
}, cmp.Ignore()),
},
wantDiff: `
[2][]int{
{..., 1, 2, 3, ..., 4, 5, 6, 7, ..., 8, ..., 9, ...},
{
... // 6 ignored and 1 identical elements
- 20,
+ 2,
... // 3 ignored elements
},
}
`,
reason: "all zero slice elements are ignored (even if missing)",
}, {
label: label,
x: [2]map[string]int{
{"ignore1": 0, "ignore2": 0, "keep1": 1, "keep2": 2, "KEEP3": 3, "IGNORE3": 0},
{"keep1": 1, "ignore1": 0},
},
y: [2]map[string]int{
{"ignore1": 0, "ignore3": 0, "ignore4": 0, "keep1": 1, "keep2": 2, "KEEP3": 3},
{"keep1": 1, "keep2": 2, "ignore2": 0},
},
opts: []cmp.Option{
cmp.FilterPath(func(p cmp.Path) bool {
vx, vy := p.Last().Values()
if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
return true
}
if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
return true
}
return false
}, cmp.Ignore()),
},
wantDiff: `
[2]map[string]int{
{"KEEP3": 3, "keep1": 1, "keep2": 2, ...},
{
... // 2 ignored entries
"keep1": 1,
+ "keep2": 2,
},
}
`,
reason: "all zero map entries are ignored (even if missing)",
}, {
label: label,
x: namedWithUnexported{},
y: namedWithUnexported{},
wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".namedWithUnexported",
reason: "panic on named struct type with unexported field",
}, {
label: label,
x: struct{ a int }{},
y: struct{ a int }{},
wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".(struct { a int })",
reason: "panic on unnamed struct type with unexported field",
}}
}
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(Inverse(λ, uint16(Inverse(λ, uint32(Inverse(λ, uint64(
- 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{
Inverse(λ, int64(0)),
- Inverse(λ, int64(-5)),
+ Inverse(λ, int64(3)),
Inverse(λ, int64(0)),
- Inverse(λ, int64(-1)),
+ Inverse(λ, int64(-5)),
}
`,
}, {
label: label,
x: 0,
y: 1,
opts: []cmp.Option{
cmp.Transformer("λ", func(in int) interface{} {
if in == 0 {
return "zero"
}
return float64(in)
}),
},
wantDiff: `
int(Inverse(λ, interface{}(
- string("zero"),
+ float64(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: `
string(Inverse(ParseJSON, map[string]interface{}{
"address": map[string]interface{}{
- "city": string("Los Angeles"),
+ "city": string("New York"),
"postalCode": string("10021-3100"),
- "state": string("CA"),
+ "state": string("NY"),
"streetAddress": string("21 2nd Street"),
},
"age": float64(25),
"children": []interface{}{},
"firstName": string("John"),
"isAlive": bool(true),
"lastName": string("Smith"),
"phoneNumbers": []interface{}{
map[string]interface{}{
- "number": string("212 555-4321"),
+ "number": string("212 555-1234"),
"type": string("home"),
},
map[string]interface{}{"number": string("646 555-4567"), "type": string("office")},
map[string]interface{}{"number": string("123 456-7890"), "type": string("mobile")},
},
+ "spouse": 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: `
cmp_test.StringBytes{
String: Inverse(SplitString, []string{
"some",
"multi",
- "Line",
+ "line",
"string",
}),
Bytes: []uint8(Inverse(SplitBytes, [][]uint8{
{0x73, 0x6f, 0x6d, 0x65},
{0x6d, 0x75, 0x6c, 0x74, 0x69},
{0x6c, 0x69, 0x6e, 0x65},
{
- 0x62,
+ 0x42,
0x79,
0x74,
... // 2 identical elements
},
})),
}
`,
}, {
x: "a\nb\nc\n",
y: "a\nb\nc\n",
opts: []cmp.Option{
cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }),
},
wantPanic: "recursive set of Transformers detected",
}, {
x: complex64(0),
y: complex64(0),
opts: []cmp.Option{
cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }),
cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }),
cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }),
},
wantPanic: "recursive set of Transformers detected",
}}
}
func reporterTests() []test {
const label = "Reporter"
type (
MyString string
MyByte byte
MyBytes []byte
MyInt int8
MyInts []int8
MyUint int16
MyUints []int16
MyFloat float32
MyFloats []float32
MyComposite struct {
StringA string
StringB MyString
BytesA []byte
BytesB []MyByte
BytesC MyBytes
IntsA []int8
IntsB []MyInt
IntsC MyInts
UintsA []uint16
UintsB []MyUint
UintsC MyUints
FloatsA []float32
FloatsB []MyFloat
FloatsC MyFloats
}
)
return []test{{
label: label,
x: MyComposite{IntsA: []int8{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
y: MyComposite{IntsA: []int8{10, 11, 21, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
wantDiff: `
cmp_test.MyComposite{
... // 3 identical fields
BytesB: nil,
BytesC: nil,
IntsA: []int8{
+ 10,
11,
- 12,
+ 21,
13,
14,
... // 15 identical elements
},
IntsB: nil,
IntsC: nil,
... // 6 identical fields
}
`,
reason: "unbatched diffing desired since few elements differ",
}, {
label: label,
x: MyComposite{IntsA: []int8{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
y: MyComposite{IntsA: []int8{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
wantDiff: `
cmp_test.MyComposite{
... // 3 identical fields
BytesB: nil,
BytesC: nil,
IntsA: []int8{
- 10, 11, 12, 13, 14, 15, 16,
+ 12, 29, 13, 27, 22, 23,
17, 18, 19, 20, 21,
- 22, 23, 24, 25, 26, 27, 28, 29,
+ 10, 26, 16, 25, 28, 11, 15, 24, 14,
},
IntsB: nil,
IntsC: nil,
... // 6 identical fields
}
`,
reason: "batched diffing desired since many elements differ",
}, {
label: label,
x: MyComposite{
BytesA: []byte{1, 2, 3},
BytesB: []MyByte{4, 5, 6},
BytesC: MyBytes{7, 8, 9},
IntsA: []int8{-1, -2, -3},
IntsB: []MyInt{-4, -5, -6},
IntsC: MyInts{-7, -8, -9},
UintsA: []uint16{1000, 2000, 3000},
UintsB: []MyUint{4000, 5000, 6000},
UintsC: MyUints{7000, 8000, 9000},
FloatsA: []float32{1.5, 2.5, 3.5},
FloatsB: []MyFloat{4.5, 5.5, 6.5},
FloatsC: MyFloats{7.5, 8.5, 9.5},
},
y: MyComposite{
BytesA: []byte{3, 2, 1},
BytesB: []MyByte{6, 5, 4},
BytesC: MyBytes{9, 8, 7},
IntsA: []int8{-3, -2, -1},
IntsB: []MyInt{-6, -5, -4},
IntsC: MyInts{-9, -8, -7},
UintsA: []uint16{3000, 2000, 1000},
UintsB: []MyUint{6000, 5000, 4000},
UintsC: MyUints{9000, 8000, 7000},
FloatsA: []float32{3.5, 2.5, 1.5},
FloatsB: []MyFloat{6.5, 5.5, 4.5},
FloatsC: MyFloats{9.5, 8.5, 7.5},
},
wantDiff: `
cmp_test.MyComposite{
StringA: "",
StringB: "",
BytesA: []uint8{
- 0x01, 0x02, 0x03, // -|...|
+ 0x03, 0x02, 0x01, // +|...|
},
BytesB: []cmp_test.MyByte{
- 0x04, 0x05, 0x06,
+ 0x06, 0x05, 0x04,
},
BytesC: cmp_test.MyBytes{
- 0x07, 0x08, 0x09, // -|...|
+ 0x09, 0x08, 0x07, // +|...|
},
IntsA: []int8{
- -1, -2, -3,
+ -3, -2, -1,
},
IntsB: []cmp_test.MyInt{
- -4, -5, -6,
+ -6, -5, -4,
},
IntsC: cmp_test.MyInts{
- -7, -8, -9,
+ -9, -8, -7,
},
UintsA: []uint16{
- 0x03e8, 0x07d0, 0x0bb8,
+ 0x0bb8, 0x07d0, 0x03e8,
},
UintsB: []cmp_test.MyUint{
- 4000, 5000, 6000,
+ 6000, 5000, 4000,
},
UintsC: cmp_test.MyUints{
- 7000, 8000, 9000,
+ 9000, 8000, 7000,
},
FloatsA: []float32{
- 1.5, 2.5, 3.5,
+ 3.5, 2.5, 1.5,
},
FloatsB: []cmp_test.MyFloat{
- 4.5, 5.5, 6.5,
+ 6.5, 5.5, 4.5,
},
FloatsC: cmp_test.MyFloats{
- 7.5, 8.5, 9.5,
+ 9.5, 8.5, 7.5,
},
}
`,
reason: "batched diffing available for both named and unnamed slices",
}, {
label: label,
x: MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeX\x95A\xfd$fX\x8byT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1U~{\xf6\xb3~\x1dWi \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")},
y: MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1u-[]]\xf6\xb3haha~\x1dWI \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")},
wantDiff: `
cmp_test.MyComposite{
StringA: "",
StringB: "",
BytesA: []uint8{
0xf3, 0x0f, 0x8a, 0xa4, 0xd3, 0x12, 0x52, 0x09, 0x24, 0xbe, // |......R.$.|
- 0x58, 0x95, 0x41, 0xfd, 0x24, 0x66, 0x58, 0x8b, 0x79, // -|X.A.$fX.y|
0x54, 0xac, 0x0d, 0xd8, 0x71, 0x77, 0x70, 0x20, 0x6a, 0x5c, 0x73, 0x7f, 0x8c, 0x17, 0x55, 0xc0, // |T...qwp j\s...U.|
0x34, 0xce, 0x6e, 0xf7, 0xaa, 0x47, 0xee, 0x32, 0x9d, 0xc5, 0xca, 0x1e, 0x58, 0xaf, 0x8f, 0x27, // |4.n..G.2....X..'|
0xf3, 0x02, 0x4a, 0x90, 0xed, 0x69, 0x2e, 0x70, 0x32, 0xb4, 0xab, 0x30, 0x20, 0xb6, 0xbd, 0x5c, // |..J..i.p2..0 ..\|
0x62, 0x34, 0x17, 0xb0, 0x00, 0xbb, 0x4f, 0x7e, 0x27, 0x47, 0x06, 0xf4, 0x2e, 0x66, 0xfd, 0x63, // |b4....O~'G...f.c|
0xd7, 0x04, 0xdd, 0xb7, 0x30, 0xb7, 0xd1, // |....0..|
- 0x55, 0x7e, 0x7b, 0xf6, 0xb3, 0x7e, 0x1d, 0x57, 0x69, // -|U~{..~.Wi|
+ 0x75, 0x2d, 0x5b, 0x5d, 0x5d, 0xf6, 0xb3, 0x68, 0x61, 0x68, 0x61, 0x7e, 0x1d, 0x57, 0x49, // +|u-[]]..haha~.WI|
0x20, 0x9e, 0xbc, 0xdf, 0xe1, 0x4d, 0xa9, 0xef, 0xa2, 0xd2, 0xed, 0xb4, 0x47, 0x78, 0xc9, 0xc9, // | ....M......Gx..|
0x27, 0xa4, 0xc6, 0xce, 0xec, 0x44, 0x70, 0x5d, // |'....Dp]|
},
BytesB: nil,
BytesC: nil,
... // 9 identical fields
}
`,
reason: "binary diff in hexdump form since data is binary data",
}, {
label: label,
x: MyComposite{StringB: MyString("readme.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000046\x0000000000000\x00011173\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")},
y: MyComposite{StringB: MyString("gopher.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000043\x0000000000000\x00011217\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")},
wantDiff: `
cmp_test.MyComposite{
StringA: "",
StringB: cmp_test.MyString{
- 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, // -|readme|
+ 0x67, 0x6f, 0x70, 0x68, 0x65, 0x72, // +|gopher|
0x2e, 0x74, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |.txt............|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |................|
... // 64 identical bytes
0x30, 0x30, 0x36, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, // |00600.0000000.00|
0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, // |00000.0000000004|
- 0x36, // -|6|
+ 0x33, // +|3|
0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x31, 0x31, // |.00000000000.011|
- 0x31, 0x37, 0x33, // -|173|
+ 0x32, 0x31, 0x37, // +|217|
0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |. 0.............|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |................|
... // 326 identical bytes
},
BytesA: nil,
BytesB: nil,
... // 10 identical fields
}
`,
reason: "binary diff desired since string looks like binary data",
}, {
label: label,
x: MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"314 54th Avenue","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}`)},
y: MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"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}`)},
wantDiff: strings.Replace(`
cmp_test.MyComposite{
StringA: "",
StringB: "",
BytesA: bytes.Join({
'{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"',
'address":{"streetAddress":"',
- "314 54th Avenue",
+ "21 2nd Street",
'","city":"New York","state":"NY","postalCode":"10021-3100"},"pho',
'neNumbers":[{"type":"home","number":"212 555-1234"},{"type":"off',
... // 101 identical bytes
}, ""),
BytesB: nil,
BytesC: nil,
... // 9 identical fields
}
`, "'", "`", -1),
reason: "batched textual diff desired since bytes looks like textual data",
}, {
label: label,
x: MyComposite{
StringA: strings.TrimPrefix(`
Package cmp determines equality of values.
This package is intended to be a more powerful and safer alternative to
reflect.DeepEqual for comparing whether two values are semantically equal.
The primary features of cmp are:
• When the default behavior of equality does not suit the needs of the test,
custom equality functions can override the equality operation.
For example, an equality function may report floats as equal so long as they
are within some tolerance of each other.
• Types that have an Equal method may use that method to determine equality.
This allows package authors to determine the equality operation for the types
that they define.
• If no custom equality functions are used and no Equal method is defined,
equality is determined by recursively comparing the primitive kinds on both
values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
fields are not compared by default; they result in panics unless suppressed
by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
using the AllowUnexported option.
`, "\n"),
},
y: MyComposite{
StringA: strings.TrimPrefix(`
Package cmp determines equality of value.
This package is intended to be a more powerful and safer alternative to
reflect.DeepEqual for comparing whether two values are semantically equal.
The primary features of cmp are:
• When the default behavior of equality does not suit the needs of the test,
custom equality functions can override the equality operation.
For example, an equality function may report floats as equal so long as they
are within some tolerance of each other.
• If no custom equality functions are used and no Equal method is defined,
equality is determined by recursively comparing the primitive kinds on both
values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
fields are not compared by default; they result in panics unless suppressed
by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
using the AllowUnexported option.`, "\n"),
},
wantDiff: `
cmp_test.MyComposite{
StringA: strings.Join({
- "Package cmp determines equality of values.",
+ "Package cmp determines equality of value.",
"",
"This package is intended to be a more powerful and safer alternative to",
... // 6 identical lines
"For example, an equality function may report floats as equal so long as they",
"are within some tolerance of each other.",
- "",
- "• Types that have an Equal method may use that method to determine equality.",
- "This allows package authors to determine the equality operation for the types",
- "that they define.",
"",
"• If no custom equality functions are used and no Equal method is defined,",
... // 3 identical lines
"by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared",
"using the AllowUnexported option.",
- "",
}, "\n"),
StringB: "",
BytesA: nil,
... // 11 identical fields
}
`,
reason: "batched per-line diff desired since string looks like multi-line textual data",
}}
}
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
}
// TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/21122).
wantPanicNotGo110 := func(s string) string {
if !flags.AtLeastGo110 {
return ""
}
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: teststructs.privateStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 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: teststructs.PublicStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 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: teststructs.privateStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 3,
},
- Public: 3,
+ Public: 4,
- private: 4,
+ private: 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: teststructs.PublicStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 3,
},
- Public: 3,
+ Public: 4,
- private: 4,
+ private: 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: teststructs.privateStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 3,
},
PublicStruct: teststructs.PublicStruct{
- Public: 3,
+ Public: 4,
- private: 4,
+ private: 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: teststructs.privateStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 3,
},
PublicStruct: teststructs.PublicStruct{
- Public: 3,
+ Public: 4,
- private: 4,
+ private: 5,
},
- Public: 5,
+ Public: 6,
- private: 6,
+ private: 7,
}
`,
}, {
label: label + "ParentStructG",
x: ts.ParentStructG{},
y: ts.ParentStructG{},
wantPanic: wantPanicNotGo110("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: &teststructs.privateStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 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: &teststructs.PublicStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 3,
},
}
`,
}, {
label: label + "ParentStructI",
x: ts.ParentStructI{},
y: ts.ParentStructI{},
wantPanic: wantPanicNotGo110("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: &teststructs.privateStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 3,
},
PublicStruct: &teststructs.PublicStruct{
- Public: 3,
+ Public: 4,
- private: 4,
+ private: 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: &teststructs.privateStruct{
- Public: 1,
+ Public: 2,
- private: 2,
+ private: 3,
},
PublicStruct: &teststructs.PublicStruct{
- Public: 3,
+ Public: 4,
- private: 4,
+ private: 5,
},
Public: teststructs.PublicStruct{
- Public: 7,
+ Public: 8,
- private: 8,
+ private: 9,
},
private: teststructs.privateStruct{
- Public: 5,
+ Public: 6,
- private: 6,
+ private: 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",
+ X: "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",
+ X: "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",
+ X: "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",
+ X: "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{
StructA: teststructs.StructA{X: "NotEqual"},
- X: "NotEqual",
+ 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{
StructA: teststructs.StructA{X: "NotEqual"},
- X: "NotEqual",
+ X: "not_equal",
}
`,
}, {
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{
StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})),
- X: "NotEqual",
+ X: "not_equal",
}
`,
}, {
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{
StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})),
- X: "NotEqual",
+ 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 + "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: teststructs.StructD{X: "NotEqual"},
+ StructD: teststructs.StructD{X: "not_equal"},
- X: "NotEqual",
+ 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"},
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: teststructs.StructE{X: "NotEqual"},
+ StructE: teststructs.StructE{X: "not_equal"},
- X: "NotEqual",
+ 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"},
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: teststructs.StructF{X: "NotEqual"},
+ StructF: teststructs.StructF{X: "not_equal"},
- X: "NotEqual",
+ 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"},
}, {
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{
StructA: &teststructs.StructA{X: "NotEqual"},
- X: "NotEqual",
+ 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{
StructA: &teststructs.StructA{X: "NotEqual"},
- X: "NotEqual",
+ X: "not_equal",
}
`,
}, {
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{
StructB: &teststructs.StructB{X: "NotEqual"},
- X: "NotEqual",
+ X: "not_equal",
}
`,
}, {
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{
StructB: &teststructs.StructB{X: "NotEqual"},
- X: "NotEqual",
+ 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 + "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: "NotEqual",
+ X: "not_equal",
}
`,
}, {
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)),
}}
}
type (
CycleAlpha struct {
Name string
Bravos map[string]*CycleBravo
}
CycleBravo struct {
ID int
Name string
Mods int
Alphas map[string]*CycleAlpha
}
)
func cycleTests() []test {
const label = "Cycle"
type (
P *P
S []S
M map[int]M
)
makeGraph := func() map[string]*CycleAlpha {
v := map[string]*CycleAlpha{
"Foo": &CycleAlpha{
Name: "Foo",
Bravos: map[string]*CycleBravo{
"FooBravo": &CycleBravo{
Name: "FooBravo",
ID: 101,
Mods: 100,
Alphas: map[string]*CycleAlpha{
"Foo": nil, // cyclic reference
},
},
},
},
"Bar": &CycleAlpha{
Name: "Bar",
Bravos: map[string]*CycleBravo{
"BarBuzzBravo": &CycleBravo{
Name: "BarBuzzBravo",
ID: 102,
Mods: 2,
Alphas: map[string]*CycleAlpha{
"Bar": nil, // cyclic reference
"Buzz": nil, // cyclic reference
},
},
"BuzzBarBravo": &CycleBravo{
Name: "BuzzBarBravo",
ID: 103,
Mods: 0,
Alphas: map[string]*CycleAlpha{
"Bar": nil, // cyclic reference
"Buzz": nil, // cyclic reference
},
},
},
},
"Buzz": &CycleAlpha{
Name: "Buzz",
Bravos: map[string]*CycleBravo{
"BarBuzzBravo": nil, // cyclic reference
"BuzzBarBravo": nil, // cyclic reference
},
},
}
v["Foo"].Bravos["FooBravo"].Alphas["Foo"] = v["Foo"]
v["Bar"].Bravos["BarBuzzBravo"].Alphas["Bar"] = v["Bar"]
v["Bar"].Bravos["BarBuzzBravo"].Alphas["Buzz"] = v["Buzz"]
v["Bar"].Bravos["BuzzBarBravo"].Alphas["Bar"] = v["Bar"]
v["Bar"].Bravos["BuzzBarBravo"].Alphas["Buzz"] = v["Buzz"]
v["Buzz"].Bravos["BarBuzzBravo"] = v["Bar"].Bravos["BarBuzzBravo"]
v["Buzz"].Bravos["BuzzBarBravo"] = v["Bar"].Bravos["BuzzBarBravo"]
return v
}
var tests []test
type XY struct{ x, y interface{} }
for _, tt := range []struct {
in XY
wantDiff string
reason string
}{{
in: func() XY {
x := new(P)
*x = x
y := new(P)
*y = y
return XY{x, y}
}(),
}, {
in: func() XY {
x := new(P)
*x = x
y1, y2 := new(P), new(P)
*y1 = y2
*y2 = y1
return XY{x, y1}
}(),
wantDiff: `
&&cmp_test.P(
- &⟪0xdeadf00f⟫,
+ &&⟪0xdeadf00f⟫,
)
`,
}, {
in: func() XY {
x := S{nil}
x[0] = x
y := S{nil}
y[0] = y
return XY{x, y}
}(),
}, {
in: func() XY {
x := S{nil}
x[0] = x
y1, y2 := S{nil}, S{nil}
y1[0] = y2
y2[0] = y1
return XY{x, y1}
}(),
wantDiff: `
cmp_test.S{
- {{{*(*cmp_test.S)(⟪0xdeadf00f⟫)}}},
+ {{{{*(*cmp_test.S)(⟪0xdeadf00f⟫)}}}},
}
`,
}, {
in: func() XY {
x := M{0: nil}
x[0] = x
y := M{0: nil}
y[0] = y
return XY{x, y}
}(),
}, {
in: func() XY {
x := M{0: nil}
x[0] = x
y1, y2 := M{0: nil}, M{0: nil}
y1[0] = y2
y2[0] = y1
return XY{x, y1}
}(),
wantDiff: `
cmp_test.M{
- 0: {0: ⟪0xdeadf00f⟫},
+ 0: {0: {0: ⟪0xdeadf00f⟫}},
}
`,
}, {
in: XY{makeGraph(), makeGraph()},
}, {
in: func() XY {
x := makeGraph()
y := makeGraph()
y["Foo"].Bravos["FooBravo"].ID = 0
y["Bar"].Bravos["BarBuzzBravo"].ID = 0
y["Bar"].Bravos["BuzzBarBravo"].ID = 0
return XY{x, y}
}(),
wantDiff: `
map[string]*cmp_test.CycleAlpha{
"Bar": &{
Name: "Bar",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{
- ID: 102,
+ ID: 0,
Name: "BarBuzzBravo",
Mods: 2,
Alphas: map[string]*cmp_test.CycleAlpha{
"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
"Buzz": &{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}},
"BuzzBarBravo": &{
- ID: 103,
+ ID: 0,
Name: "BuzzBarBravo",
Mods: 0,
Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
},
},
},
},
},
"BuzzBarBravo": &{
- ID: 103,
+ ID: 0,
Name: "BuzzBarBravo",
Mods: 0,
Alphas: map[string]*cmp_test.CycleAlpha{
"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
"Buzz": &{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{
- ID: 102,
+ ID: 0,
Name: "BarBuzzBravo",
Mods: 2,
Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
},
"BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": ⟪0xdeadf00f⟫}},
},
},
},
},
},
},
"Buzz": &{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{
- ID: 102,
+ ID: 0,
Name: "BarBuzzBravo",
Mods: 2,
Alphas: map[string]*cmp_test.CycleAlpha{
"Bar": &{
Name: "Bar",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}},
"BuzzBarBravo": &{
- ID: 103,
+ ID: 0,
Name: "BuzzBarBravo",
Mods: 0,
Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
},
},
},
"Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
},
},
"BuzzBarBravo": &{
- ID: 103,
+ ID: 0,
Name: "BuzzBarBravo",
Mods: 0,
Alphas: map[string]*cmp_test.CycleAlpha{
"Bar": &{
Name: "Bar",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{
- ID: 102,
+ ID: 0,
Name: "BarBuzzBravo",
Mods: 2,
Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
},
"BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": ⟪0xdeadf00f⟫}},
},
},
"Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
},
},
},
},
"Foo": &{
Name: "Foo",
Bravos: map[string]*cmp_test.CycleBravo{
"FooBravo": &{
- ID: 101,
+ ID: 0,
Name: "FooBravo",
Mods: 100,
Alphas: map[string]*cmp_test.CycleAlpha{"Foo": &{Name: "Foo", Bravos: map[string]*cmp_test.CycleBravo{"FooBravo": &{Name: "FooBravo", Mods: 100, Alphas: map[string]*cmp_test.CycleAlpha{"Foo": ⟪0xdeadf00f⟫}}}}},
},
},
},
}
`,
}, {
in: func() XY {
x := makeGraph()
y := makeGraph()
x["Buzz"].Bravos["BuzzBarBravo"] = &CycleBravo{
Name: "BuzzBarBravo",
ID: 103,
}
return XY{x, y}
}(),
wantDiff: `
map[string]*cmp_test.CycleAlpha{
"Bar": &{
Name: "Bar",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{
ID: 102,
Name: "BarBuzzBravo",
Mods: 2,
Alphas: map[string]*cmp_test.CycleAlpha{
"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
"Buzz": &{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}},
"BuzzBarBravo": &{
ID: 103,
Name: "BuzzBarBravo",
Mods: 0,
- Alphas: nil,
+ Alphas: map[string]*cmp_test.CycleAlpha{
+ "Bar": &{
+ Name: "Bar",
+ Bravos: map[string]*cmp_test.CycleBravo{
+ "BarBuzzBravo": &{
+ ID: 102,
+ Name: "BarBuzzBravo",
+ Mods: 2,
+ Alphas: map[string]*cmp_test.CycleAlpha{
+ "Bar": ⟪0xdeadf00f⟫,
+ "Buzz": &{
+ Name: "Buzz",
+ Bravos: map[string]*cmp_test.CycleBravo{
+ "BarBuzzBravo": ⟪0xdeadf00f⟫,
+ "BuzzBarBravo": &{
+ ID: 103,
+ Name: "BuzzBarBravo",
+ Alphas: map[string]*cmp_test.CycleAlpha(⟪0xdeadf00f⟫),
+ },
+ },
+ },
+ },
+ },
+ "BuzzBarBravo": ⟪0xdeadf00f⟫,
+ },
+ },
+ "Buzz": ⟪0xdeadf00f⟫,
+ },
},
},
},
},
},
"BuzzBarBravo": &{
ID: 103,
Name: "BuzzBarBravo",
Mods: 0,
Alphas: map[string]*cmp_test.CycleAlpha{
"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
"Buzz": &{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}},
- "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo"},
+ "BuzzBarBravo": &{
+ ID: 103,
+ Name: "BuzzBarBravo",
+ Alphas: map[string]*cmp_test.CycleAlpha{
+ "Bar": &{
+ Name: "Bar",
+ Bravos: map[string]*cmp_test.CycleBravo{
+ "BarBuzzBravo": &{
+ ID: 102,
+ Name: "BarBuzzBravo",
+ Mods: 2,
+ Alphas: map[string]*cmp_test.CycleAlpha{
+ "Bar": ⟪0xdeadf00f⟫,
+ "Buzz": &{
+ Name: "Buzz",
+ Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫},
+ },
+ },
+ },
+ "BuzzBarBravo": ⟪0xdeadf00f⟫,
+ },
+ },
+ "Buzz": ⟪0xdeadf00f⟫,
+ },
+ },
},
},
},
},
},
},
"Buzz": &{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}},
"BuzzBarBravo": &{
ID: 103,
Name: "BuzzBarBravo",
Mods: 0,
- Alphas: nil,
+ Alphas: map[string]*cmp_test.CycleAlpha{
+ "Bar": &{
+ Name: "Bar",
+ Bravos: map[string]*cmp_test.CycleBravo{
+ "BarBuzzBravo": &{
+ ID: 102,
+ Name: "BarBuzzBravo",
+ Mods: 2,
+ Alphas: map[string]*cmp_test.CycleAlpha{
+ "Bar": ⟪0xdeadf00f⟫,
+ "Buzz": &{
+ Name: "Buzz",
+ Bravos: map[string]*cmp_test.CycleBravo{
+ "BarBuzzBravo": ⟪0xdeadf00f⟫,
+ "BuzzBarBravo": &{
+ ID: 103,
+ Name: "BuzzBarBravo",
+ Alphas: map[string]*cmp_test.CycleAlpha(⟪0xdeadf00f⟫),
+ },
+ },
+ },
+ },
+ },
+ "BuzzBarBravo": ⟪0xdeadf00f⟫,
+ },
+ },
+ "Buzz": ⟪0xdeadf00f⟫,
+ },
},
},
},
"Foo": &{Name: "Foo", Bravos: map[string]*cmp_test.CycleBravo{"FooBravo": &{ID: 101, Name: "FooBravo", Mods: 100, Alphas: map[string]*cmp_test.CycleAlpha{"Foo": &{Name: "Foo", Bravos: map[string]*cmp_test.CycleBravo{"FooBravo": &{ID: 101, Name: "FooBravo", Mods: 100, Alphas: map[string]*cmp_test.CycleAlpha{"Foo": ⟪0xdeadf00f⟫}}}}}}}},
}
`,
}} {
tests = append(tests, test{
label: label,
x: tt.in.x,
y: tt.in.y,
wantDiff: tt.wantDiff,
reason: tt.reason,
})
}
return tests
}
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{
... // 4 identical fields
Dreamers: nil,
Prong: 0,
Slaps: []teststructs.Slap{
... // 2 identical elements
{},
{},
{
Name: "",
Desc: "",
DescLong: "",
- Args: s"metadata",
+ Args: s"metadata2",
Tense: 0,
Interval: 0,
... // 3 identical fields
},
},
StateGoverner: "",
PrankRating: "",
... // 2 identical fields
}
`,
}, {
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{
... // 2 identical fields
Desc: "some description",
DescLong: "",
Dreamers: []teststructs.Dreamer{
{},
{
... // 4 identical fields
ContSlaps: nil,
ContSlapsInterval: 0,
Animal: []interface{}{
teststructs.Goat{
Target: "corporation",
Slaps: nil,
FunnyPrank: "",
Immutable: &teststructs.GoatImmutable{
- ID: "southbay2",
+ ID: "southbay",
- State: &6,
+ State: &5,
Started: s"2009-11-10 23:00:00 +0000 UTC",
Stopped: s"0001-01-01 00:00:00 +0000 UTC",
... // 1 ignored and 1 identical fields
},
},
teststructs.Donkey{},
},
Ornamental: false,
Amoeba: 53,
... // 5 identical fields
},
},
Prong: 0,
Slaps: []teststructs.Slap{
{
... // 6 identical fields
Homeland: 0x00,
FunnyPrank: "",
Immutable: &teststructs.SlapImmutable{
ID: "immutableSlap",
Out: nil,
- MildSlap: false,
+ MildSlap: true,
PrettyPrint: "",
State: nil,
Started: s"2009-11-10 23:00:00 +0000 UTC",
Stopped: s"0001-01-01 00:00:00 +0000 UTC",
LastUpdate: s"0001-01-01 00:00:00 +0000 UTC",
LoveRadius: &teststructs.LoveRadius{
Summer: &teststructs.SummerLove{
Summary: &teststructs.SummerLoveSummary{
Devices: []string{
"foo",
- "bar",
- "baz",
},
ChangeType: []testprotos.SummerType{1, 2, 3},
... // 1 ignored field
},
... // 1 ignored field
},
... // 1 ignored field
},
... // 1 ignored field
},
},
},
StateGoverner: "",
PrankRating: "",
... // 2 identical fields
}
`,
}}
}
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: map[int32][]*testprotos.Germ{
17: {s"germ1"},
18: {
- s"germ2",
s"germ3",
s"germ4",
+ s"germ2",
},
},
CleanGerms: nil,
GermMap: map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"},
... // 7 identical fields
}
`,
}, {
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: map[int32][]*testprotos.Germ{
+ 17: {s"germ1"},
18: Inverse(Sort, []*testprotos.Germ{
s"germ2",
s"germ3",
- s"germ4",
}),
},
CleanGerms: nil,
GermMap: map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"},
DishMap: map[int32]*teststructs.Dish{
0: &{err: &errors.errorString{s: "EOF"}},
- 1: nil,
+ 1: &{err: &errors.errorString{s: "unexpected EOF"}},
2: &{pb: &testprotos.Dish{Stringer: testprotos.Stringer{X: "dish"}}},
},
HasPreviousResult: true,
DirtyID: 10,
CleanID: 0,
- GermStrain: 421,
+ GermStrain: 22,
TotalDirtyGerms: 0,
InfectedAt: s"2009-11-10 23:00:00 +0000 UTC",
}
`,
}}
}
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"}},
+ table: &teststructs.MockTable{state: []string{"a", "b", "c"}},
ts: 12345,
- Discord: 554,
+ Discord: 500,
- Proto: testprotos.Dirt(Inverse(λ, s"blah")),
+ Proto: testprotos.Dirt(Inverse(λ, s"proto")),
wizard: map[string]*testprotos.Wizard{
- "albus": s"dumbledore",
- "harry": s"potter",
+ "harry": s"otter",
},
sadistic: nil,
lastTime: 54321,
... // 1 ignored field
}
`,
}}
}
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: teststructs.Headquarter{
id: 0x05,
location: "moon",
subDivisions: []string{
- "alpha",
"bravo",
"charlie",
},
incorporatedDate: s"0001-01-01 00:00:00 +0000 UTC",
metaData: s"metadata",
privateMessage: nil,
publicMessage: []uint8{
0x01,
0x02,
- 0x03,
+ 0x04,
- 0x04,
+ 0x03,
0x05,
},
horseBack: "abcdef",
rattle: "",
... // 5 identical fields
},
source: "mars",
creationDate: s"0001-01-01 00:00:00 +0000 UTC",
boss: "al capone",
lastCrimeDate: s"0001-01-01 00:00:00 +0000 UTC",
poisons: []*teststructs.Poison{
&{
- poisonType: 1,
+ poisonType: 5,
expiration: s"2009-11-10 23:00:00 +0000 UTC",
manufacturer: "acme",
potency: 0,
},
- &{poisonType: 2, manufacturer: "acme2"},
},
}
`,
}}
}
// BenchmarkBytes benchmarks the performance of performing Equal or Diff on
// large slices of bytes.
func BenchmarkBytes(b *testing.B) {
// Create a list of PathFilters that never apply, but are evaluated.
const maxFilters = 5
var filters cmp.Options
errorIface := reflect.TypeOf((*error)(nil)).Elem()
for i := 0; i <= maxFilters; i++ {
filters = append(filters, cmp.FilterPath(func(p cmp.Path) bool {
return p.Last().Type().AssignableTo(errorIface) // Never true
}, cmp.Ignore()))
}
type benchSize struct {
label string
size int64
}
for _, ts := range []benchSize{
{"4KiB", 1 << 12},
{"64KiB", 1 << 16},
{"1MiB", 1 << 20},
{"16MiB", 1 << 24},
} {
bx := append(append(make([]byte, ts.size/2), 'x'), make([]byte, ts.size/2)...)
by := append(append(make([]byte, ts.size/2), 'y'), make([]byte, ts.size/2)...)
b.Run(ts.label, func(b *testing.B) {
// Iteratively add more filters that never apply, but are evaluated
// to measure the cost of simply evaluating each filter.
for i := 0; i <= maxFilters; i++ {
b.Run(fmt.Sprintf("EqualFilter%d", i), func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(2 * ts.size)
for j := 0; j < b.N; j++ {
cmp.Equal(bx, by, filters[:i]...)
}
})
}
for i := 0; i <= maxFilters; i++ {
b.Run(fmt.Sprintf("DiffFilter%d", i), func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(2 * ts.size)
for j := 0; j < b.N; j++ {
cmp.Diff(bx, by, filters[:i]...)
}
})
}
})
}
}