1 // Copyright 2017, The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
27 "github.com/google/go-cmp/cmp"
28 "github.com/google/go-cmp/cmp/cmpopts"
29 "github.com/google/go-cmp/cmp/internal/flags"
31 pb "github.com/google/go-cmp/cmp/internal/testprotos"
32 ts "github.com/google/go-cmp/cmp/internal/teststructs"
33 foo1 "github.com/google/go-cmp/cmp/internal/teststructs/foo1"
34 foo2 "github.com/google/go-cmp/cmp/internal/teststructs/foo2"
38 flags.Deterministic = true
41 var update = flag.Bool("update", false, "update golden test files")
43 const goldenHeaderPrefix = "<<< "
44 const goldenFooterPrefix = ">>> "
46 /// mustParseGolden parses a file as a set of key-value pairs.
48 // The syntax is simple and looks something like:
58 // It is the user's responsibility to choose a sufficiently unique key name
59 // such that it never appears in the body of the value itself.
60 func mustParseGolden(path string) map[string]string {
61 b, err := ioutil.ReadFile(path)
67 out := map[string]string{}
69 // Identify the next header.
70 i := strings.Index(s, "\n") + len("\n")
72 if !strings.HasPrefix(header, goldenHeaderPrefix) {
73 panic(fmt.Sprintf("invalid header: %q", header))
76 // Locate the next footer.
77 footer := goldenFooterPrefix + header[len(goldenHeaderPrefix):]
78 j := strings.Index(s, footer)
80 panic(fmt.Sprintf("missing footer: %q", footer))
83 // Store the name and data.
84 name := header[len(goldenHeaderPrefix) : len(header)-len("\n")]
85 if _, ok := out[name]; ok {
86 panic(fmt.Sprintf("duplicate name: %q", name))
88 out[name] = s[len(header):j]
93 func mustFormatGolden(path string, in []struct{ Name, Data string }) {
95 for _, v := range in {
96 b = append(b, goldenHeaderPrefix+v.Name+"\n"...)
97 b = append(b, v.Data...)
98 b = append(b, goldenFooterPrefix+v.Name+"\n"...)
100 if err := ioutil.WriteFile(path, b, 0664); err != nil {
105 var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC)
107 func newInt(n int) *int { return &n }
111 func newStringer(s string) fmt.Stringer { return (*Stringer)(&s) }
112 func (s Stringer) String() string { return string(s) }
115 label string // Test name
116 x, y interface{} // Input values to compare
117 opts []cmp.Option // Input options
118 wantEqual bool // Whether any difference is expected
119 wantPanic string // Sub-string of an expected panic message
120 reason string // The reason for the expected outcome
123 func TestDiff(t *testing.T) {
125 tests = append(tests, comparerTests()...)
126 tests = append(tests, transformerTests()...)
127 tests = append(tests, reporterTests()...)
128 tests = append(tests, embeddedTests()...)
129 tests = append(tests, methodTests()...)
130 tests = append(tests, cycleTests()...)
131 tests = append(tests, project1Tests()...)
132 tests = append(tests, project2Tests()...)
133 tests = append(tests, project3Tests()...)
134 tests = append(tests, project4Tests()...)
136 const goldenFile = "testdata/diffs"
137 gotDiffs := []struct{ Name, Data string }{}
138 wantDiffs := mustParseGolden(goldenFile)
139 for _, tt := range tests {
141 t.Run(tt.label, func(t *testing.T) {
145 var gotDiff, gotPanic string
148 if ex := recover(); ex != nil {
149 if s, ok := ex.(string); ok {
156 gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...)
160 case strings.Contains(t.Name(), "#"):
161 panic("unique test name must be provided")
162 case tt.reason == "":
163 panic("reason must be provided")
164 case tt.wantPanic == "":
166 t.Fatalf("unexpected panic message: %s\nreason: %v", gotPanic, tt.reason)
170 gotDiffs = append(gotDiffs, struct{ Name, Data string }{t.Name(), gotDiff})
173 wantDiff := wantDiffs[t.Name()]
174 if diff := cmp.Diff(wantDiff, gotDiff); diff != "" {
175 t.Fatalf("Diff:\ngot:\n%s\nwant:\n%s\ndiff: (-want +got)\n%s\nreason: %v", gotDiff, wantDiff, diff, tt.reason)
178 gotEqual := gotDiff == ""
179 if gotEqual != tt.wantEqual {
180 t.Fatalf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
183 if !strings.Contains(gotPanic, tt.wantPanic) {
184 t.Fatalf("panic message:\ngot: %s\nwant: %s\nreason: %v", gotPanic, tt.wantPanic, tt.reason)
191 mustFormatGolden(goldenFile, gotDiffs)
195 func comparerTests() []test {
196 const label = "Comparer"
198 type Iface1 interface {
201 type Iface2 interface {
205 type tarHeader struct {
220 Xattrs map[string]string
223 type namedWithUnexported struct {
227 makeTarHeaders := func(tf byte) (hs []tarHeader) {
228 for i := 0; i < 5; i++ {
229 hs = append(hs, tarHeader{
230 Name: fmt.Sprintf("some/dummy/test/file%d", i),
231 Mode: 0664, Uid: i * 1000, Gid: i * 1000, Size: 1 << uint(i),
232 ModTime: now.Add(time.Duration(i) * time.Hour),
233 Uname: "user", Gname: "group",
241 label: label + "/Nil",
245 reason: "nils are equal",
247 label: label + "/Integer",
251 reason: "identical integers are equal",
253 label: label + "/UnfilteredIgnore",
256 opts: []cmp.Option{cmp.Ignore()},
257 wantPanic: "cannot use an unfiltered option",
258 reason: "unfiltered options are functionally useless",
260 label: label + "/UnfilteredCompare",
263 opts: []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })},
264 wantPanic: "cannot use an unfiltered option",
265 reason: "unfiltered options are functionally useless",
267 label: label + "/UnfilteredTransform",
270 opts: []cmp.Option{cmp.Transformer("λ", func(x interface{}) interface{} { return x })},
271 wantPanic: "cannot use an unfiltered option",
272 reason: "unfiltered options are functionally useless",
274 label: label + "/AmbiguousOptions",
278 cmp.Comparer(func(x, y int) bool { return true }),
279 cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
281 wantPanic: "ambiguous set of applicable options",
282 reason: "both options apply on int, leading to ambiguity",
284 label: label + "/IgnorePrecedence",
288 cmp.FilterPath(func(p cmp.Path) bool {
289 return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int
290 }, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}),
291 cmp.Comparer(func(x, y int) bool { return true }),
292 cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
295 reason: "ignore takes precedence over other options",
297 label: label + "/UnknownOption",
298 opts: []cmp.Option{struct{ cmp.Option }{}},
299 wantPanic: "unknown option",
300 reason: "use of unknown option should panic",
302 label: label + "/StructEqual",
303 x: struct{ A, B, C int }{1, 2, 3},
304 y: struct{ A, B, C int }{1, 2, 3},
306 reason: "struct comparison with all equal fields",
308 label: label + "/StructInequal",
309 x: struct{ A, B, C int }{1, 2, 3},
310 y: struct{ A, B, C int }{1, 2, 4},
312 reason: "struct comparison with inequal C field",
314 label: label + "/StructUnexported",
315 x: struct{ a, b, c int }{1, 2, 3},
316 y: struct{ a, b, c int }{1, 2, 4},
317 wantPanic: "cannot handle unexported field",
318 reason: "unexported fields result in a panic by default",
320 label: label + "/PointerStructEqual",
321 x: &struct{ A *int }{newInt(4)},
322 y: &struct{ A *int }{newInt(4)},
324 reason: "comparison of pointer to struct with equal A field",
326 label: label + "/PointerStructInequal",
327 x: &struct{ A *int }{newInt(4)},
328 y: &struct{ A *int }{newInt(5)},
330 reason: "comparison of pointer to struct with inequal A field",
332 label: label + "/PointerStructTrueComparer",
333 x: &struct{ A *int }{newInt(4)},
334 y: &struct{ A *int }{newInt(5)},
336 cmp.Comparer(func(x, y int) bool { return true }),
339 reason: "comparison of pointer to struct with inequal A field, but treated as equal with always equal comparer",
341 label: label + "/PointerStructNonNilComparer",
342 x: &struct{ A *int }{newInt(4)},
343 y: &struct{ A *int }{newInt(5)},
345 cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }),
348 reason: "comparison of pointer to struct with inequal A field, but treated as equal with comparer checking pointers for nilness",
350 label: label + "/StructNestedPointerEqual",
351 x: &struct{ R *bytes.Buffer }{},
352 y: &struct{ R *bytes.Buffer }{},
354 reason: "equal since both pointers in R field are nil",
356 label: label + "/StructNestedPointerInequal",
357 x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
358 y: &struct{ R *bytes.Buffer }{},
360 reason: "inequal since R field is inequal",
362 label: label + "/StructNestedPointerTrueComparer",
363 x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
364 y: &struct{ R *bytes.Buffer }{},
366 cmp.Comparer(func(x, y io.Reader) bool { return true }),
369 reason: "equal despite inequal R field values since the comparer always reports true",
371 label: label + "/StructNestedValueUnexportedPanic1",
372 x: &struct{ R bytes.Buffer }{},
373 y: &struct{ R bytes.Buffer }{},
374 wantPanic: "cannot handle unexported field",
375 reason: "bytes.Buffer contains unexported fields",
377 label: label + "/StructNestedValueUnexportedPanic2",
378 x: &struct{ R bytes.Buffer }{},
379 y: &struct{ R bytes.Buffer }{},
381 cmp.Comparer(func(x, y io.Reader) bool { return true }),
383 wantPanic: "cannot handle unexported field",
384 reason: "bytes.Buffer value does not implement io.Reader",
386 label: label + "/StructNestedValueEqual",
387 x: &struct{ R bytes.Buffer }{},
388 y: &struct{ R bytes.Buffer }{},
390 cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }),
391 cmp.Comparer(func(x, y io.Reader) bool { return true }),
394 reason: "bytes.Buffer pointer due to shallow copy does implement io.Reader",
396 label: label + "/RegexpUnexportedPanic",
397 x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
398 y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
399 wantPanic: "cannot handle unexported field",
400 reason: "regexp.Regexp contains unexported fields",
402 label: label + "/RegexpEqual",
403 x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
404 y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
405 opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
406 if x == nil || y == nil {
407 return x == nil && y == nil
409 return x.String() == y.String()
412 reason: "comparer for *regexp.Regexp applied with equal regexp strings",
414 label: label + "/RegexpInequal",
415 x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
416 y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
417 opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
418 if x == nil || y == nil {
419 return x == nil && y == nil
421 return x.String() == y.String()
424 reason: "comparer for *regexp.Regexp applied with inequal regexp strings",
426 label: label + "/TriplePointerEqual",
440 reason: "three layers of pointers to the same value",
442 label: label + "/TriplePointerInequal",
456 reason: "three layers of pointers to different values",
458 label: label + "/SliceWithDifferingCapacity",
459 x: []int{1, 2, 3, 4, 5}[:3],
462 reason: "elements past the slice length are not compared",
464 label: label + "/StringerEqual",
465 x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
466 y: struct{ fmt.Stringer }{regexp.MustCompile("hello")},
467 opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
469 reason: "comparer for fmt.Stringer used to compare differing types with same string",
471 label: label + "/StringerInequal",
472 x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
473 y: struct{ fmt.Stringer }{regexp.MustCompile("hello2")},
474 opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
476 reason: "comparer for fmt.Stringer used to compare differing types with different strings",
478 label: label + "/DifferingHash",
479 x: md5.Sum([]byte{'a'}),
480 y: md5.Sum([]byte{'b'}),
482 reason: "hash differs",
484 label: label + "/NilStringer",
485 x: new(fmt.Stringer),
488 reason: "by default differing types are always inequal",
490 label: label + "/TarHeaders",
491 x: makeTarHeaders('0'),
492 y: makeTarHeaders('\x00'),
494 reason: "type flag differs between the headers",
496 label: label + "/NonDeterministicComparer",
497 x: make([]int, 1000),
498 y: make([]int, 1000),
500 cmp.Comparer(func(_, _ int) bool {
501 return rand.Intn(2) == 0
504 wantPanic: "non-deterministic or non-symmetric function detected",
505 reason: "non-deterministic comparer",
507 label: label + "/NonDeterministicFilter",
508 x: make([]int, 1000),
509 y: make([]int, 1000),
511 cmp.FilterValues(func(_, _ int) bool {
512 return rand.Intn(2) == 0
515 wantPanic: "non-deterministic or non-symmetric function detected",
516 reason: "non-deterministic filter",
518 label: label + "/AssymetricComparer",
519 x: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
520 y: []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
522 cmp.Comparer(func(x, y int) bool {
526 wantPanic: "non-deterministic or non-symmetric function detected",
527 reason: "asymmetric comparer",
529 label: label + "/NonDeterministicTransformer",
530 x: make([]string, 1000),
531 y: make([]string, 1000),
533 cmp.Transformer("λ", func(x string) int {
537 wantPanic: "non-deterministic function detected",
538 reason: "non-deterministic transformer",
540 label: label + "/IrreflexiveComparison",
544 cmp.Transformer("λ", func(x int) float64 {
549 reason: "dynamic checks should not panic for non-reflexive comparisons",
551 label: label + "/StringerMapKey",
552 x: map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}},
553 y: map[*pb.Stringer]*pb.Stringer(nil),
555 reason: "stringer should be used to format the map key",
557 label: label + "/StringerBacktick",
558 x: []*pb.Stringer{{`multi\nline\nline\nline`}},
560 reason: "stringer should use backtick quoting if more readable",
562 label: label + "/AvoidPanicAssignableConverter",
563 x: struct{ I Iface2 }{},
564 y: struct{ I Iface2 }{},
566 cmp.Comparer(func(x, y Iface1) bool {
567 return x == nil && y == nil
571 reason: "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
573 label: label + "/AvoidPanicAssignableTransformer",
574 x: struct{ I Iface2 }{},
575 y: struct{ I Iface2 }{},
577 cmp.Transformer("λ", func(v Iface1) bool {
582 reason: "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
584 label: label + "/AvoidPanicAssignableFilter",
585 x: struct{ I Iface2 }{},
586 y: struct{ I Iface2 }{},
588 cmp.FilterValues(func(x, y Iface1) bool {
589 return x == nil && y == nil
593 reason: "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
595 label: label + "/DynamicMap",
596 x: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63, "name": "Sammy Sosa"}},
597 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"}},
599 reason: "dynamic map with differing types (but semantically equivalent values) should be inequal",
601 label: label + "/MapKeyPointer",
609 reason: "map keys should use shallow (rather than deep) pointer comparison",
611 label: label + "/IgnoreSliceElements",
613 {0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 7, 8, 0, 9, 0, 0},
617 {1, 2, 3, 0, 4, 5, 6, 7, 0, 8, 9, 0, 0, 0},
618 {0, 0, 1, 2, 0, 0, 0},
621 cmp.FilterPath(func(p cmp.Path) bool {
622 vx, vy := p.Last().Values()
623 if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
626 if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
633 reason: "all zero slice elements are ignored (even if missing)",
635 label: label + "/IgnoreMapEntries",
636 x: [2]map[string]int{
637 {"ignore1": 0, "ignore2": 0, "keep1": 1, "keep2": 2, "KEEP3": 3, "IGNORE3": 0},
638 {"keep1": 1, "ignore1": 0},
640 y: [2]map[string]int{
641 {"ignore1": 0, "ignore3": 0, "ignore4": 0, "keep1": 1, "keep2": 2, "KEEP3": 3},
642 {"keep1": 1, "keep2": 2, "ignore2": 0},
645 cmp.FilterPath(func(p cmp.Path) bool {
646 vx, vy := p.Last().Values()
647 if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
650 if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
657 reason: "all zero map entries are ignored (even if missing)",
659 label: label + "/PanicUnexportedNamed",
660 x: namedWithUnexported{},
661 y: namedWithUnexported{},
662 wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".namedWithUnexported",
663 reason: "panic on named struct type with unexported field",
665 label: label + "/PanicUnexportedUnnamed",
666 x: struct{ a int }{},
667 y: struct{ a int }{},
668 wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".(struct { a int })",
669 reason: "panic on unnamed struct type with unexported field",
671 label: label + "/UnaddressableStruct",
672 x: struct{ s fmt.Stringer }{new(bytes.Buffer)},
673 y: struct{ s fmt.Stringer }{nil},
675 cmp.AllowUnexported(struct{ s fmt.Stringer }{}),
676 cmp.FilterPath(func(p cmp.Path) bool {
677 if _, ok := p.Last().(cmp.StructField); !ok {
681 t := p.Index(-1).Type()
682 vx, vy := p.Index(-1).Values()
683 pvx, pvy := p.Index(-2).Values()
686 panic(fmt.Sprintf("inconsistent type: %v != %v", vx.Type(), t))
688 panic(fmt.Sprintf("inconsistent type: %v != %v", vy.Type(), t))
689 case vx.CanAddr() != pvx.CanAddr():
690 panic(fmt.Sprintf("inconsistent addressability: %v != %v", vx.CanAddr(), pvx.CanAddr()))
691 case vy.CanAddr() != pvy.CanAddr():
692 panic(fmt.Sprintf("inconsistent addressability: %v != %v", vy.CanAddr(), pvy.CanAddr()))
698 reason: "verify that exporter does not leak implementation details",
700 label: label + "/ErrorPanic",
703 wantPanic: "consider using cmpopts.EquateErrors",
704 reason: "suggest cmpopts.EquateErrors when accessing unexported fields of error types",
706 label: label + "/ErrorEqual",
709 opts: []cmp.Option{cmpopts.EquateErrors()},
711 reason: "cmpopts.EquateErrors should equate these two errors as sentinel values",
715 func transformerTests() []test {
716 type StringBytes struct {
721 const label = "Transformer"
723 transformOnce := func(name string, f interface{}) cmp.Option {
724 xform := cmp.Transformer(name, f)
725 return cmp.FilterPath(func(p cmp.Path) bool {
726 for _, ps := range p {
727 if tr, ok := ps.(cmp.Transform); ok && tr.Option() == xform {
736 label: label + "/Uints",
740 cmp.Transformer("λ", func(in uint8) uint16 { return uint16(in) }),
741 cmp.Transformer("λ", func(in uint16) uint32 { return uint32(in) }),
742 cmp.Transformer("λ", func(in uint32) uint64 { return uint64(in) }),
745 reason: "transform uint8 -> uint16 -> uint32 -> uint64",
747 label: label + "/Ambiguous",
751 cmp.Transformer("λ", func(in int) int { return in / 2 }),
752 cmp.Transformer("λ", func(in int) int { return in }),
754 wantPanic: "ambiguous set of applicable options",
755 reason: "both transformers apply on int",
757 label: label + "/Filtered",
758 x: []int{0, -5, 0, -1},
759 y: []int{1, 3, 0, -5},
762 func(x, y int) bool { return x+y >= 0 },
763 cmp.Transformer("λ", func(in int) int64 { return int64(in / 2) }),
766 func(x, y int) bool { return x+y < 0 },
767 cmp.Transformer("λ", func(in int) int64 { return int64(in) }),
771 reason: "disjoint transformers filtered based on the values",
773 label: label + "/DisjointOutput",
777 cmp.Transformer("λ", func(in int) interface{} {
785 reason: "output type differs based on input value",
787 label: label + "/JSON",
794 "city": "Los Angeles",
795 "postalCode": "10021-3100",
797 "streetAddress": "21 2nd Street"
801 "number": "212 555-4321"
804 "number": "646 555-4567"
806 "number": "123 456-7890",
811 y: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":25,
812 "address":{"streetAddress":"21 2nd Street","city":"New York",
813 "state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home",
814 "number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{
815 "type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`,
817 transformOnce("ParseJSON", func(s string) (m map[string]interface{}) {
818 if err := json.Unmarshal([]byte(s), &m); err != nil {
825 reason: "transformer used to parse JSON input",
827 label: label + "/AcyclicString",
828 x: StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")},
829 y: StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")},
831 transformOnce("SplitString", func(s string) []string { return strings.Split(s, "\n") }),
832 transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }),
835 reason: "string -> []string and []byte -> [][]byte transformer only applied once",
837 label: label + "/CyclicString",
841 cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }),
843 wantPanic: "recursive set of Transformers detected",
844 reason: "cyclic transformation from string -> []string -> string",
846 label: label + "/CyclicComplex",
850 cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }),
851 cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }),
852 cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }),
854 wantPanic: "recursive set of Transformers detected",
855 reason: "cyclic transformation from complex64 -> complex128 -> [2]float64 -> complex64",
859 func reporterTests() []test {
860 const label = "Reporter"
891 label: label + "/PanicStringer",
892 x: struct{ X fmt.Stringer }{struct{ fmt.Stringer }{nil}},
893 y: struct{ X fmt.Stringer }{bytes.NewBuffer(nil)},
895 reason: "panic from fmt.Stringer should not crash the reporter",
897 label: label + "/PanicError",
898 x: struct{ X error }{struct{ error }{nil}},
899 y: struct{ X error }{errors.New("")},
901 reason: "panic from error should not crash the reporter",
903 label: label + "/AmbiguousType",
907 reason: "reporter should display the qualified type name to disambiguate between the two values",
909 label: label + "/AmbiguousPointer",
913 cmp.Comparer(func(x, y *int) bool { return x == y }),
916 reason: "reporter should display the address to disambiguate between the two values",
918 label: label + "/AmbiguousPointerStruct",
919 x: struct{ I *int }{newInt(0)},
920 y: struct{ I *int }{newInt(0)},
922 cmp.Comparer(func(x, y *int) bool { return x == y }),
925 reason: "reporter should display the address to disambiguate between the two struct fields",
927 label: label + "/AmbiguousPointerSlice",
928 x: []*int{newInt(0)},
929 y: []*int{newInt(0)},
931 cmp.Comparer(func(x, y *int) bool { return x == y }),
934 reason: "reporter should display the address to disambiguate between the two slice elements",
936 label: label + "/AmbiguousPointerMap",
937 x: map[string]*int{"zero": newInt(0)},
938 y: map[string]*int{"zero": newInt(0)},
940 cmp.Comparer(func(x, y *int) bool { return x == y }),
943 reason: "reporter should display the address to disambiguate between the two map values",
945 label: label + "/AmbiguousStringer",
946 x: Stringer("hello"),
947 y: newStringer("hello"),
949 reason: "reporter should avoid calling String to disambiguate between the two values",
951 label: label + "/AmbiguousStringerStruct",
952 x: struct{ S fmt.Stringer }{Stringer("hello")},
953 y: struct{ S fmt.Stringer }{newStringer("hello")},
955 reason: "reporter should avoid calling String to disambiguate between the two struct fields",
957 label: label + "/AmbiguousStringerSlice",
958 x: []fmt.Stringer{Stringer("hello")},
959 y: []fmt.Stringer{newStringer("hello")},
961 reason: "reporter should avoid calling String to disambiguate between the two slice elements",
963 label: label + "/AmbiguousStringerMap",
964 x: map[string]fmt.Stringer{"zero": Stringer("hello")},
965 y: map[string]fmt.Stringer{"zero": newStringer("hello")},
967 reason: "reporter should avoid calling String to disambiguate between the two map values",
969 label: label + "/AmbiguousSliceHeader",
970 x: make([]int, 0, 5),
971 y: make([]int, 0, 1000),
973 cmp.Comparer(func(x, y []int) bool { return cap(x) == cap(y) }),
976 reason: "reporter should display the slice header to disambiguate between the two slice values",
978 label: label + "/AmbiguousStringerMapKey",
979 x: map[interface{}]string{
981 Stringer("hello"): "goodbye",
982 foo1.Bar{"fizz"}: "buzz",
984 y: map[interface{}]string{
985 newStringer("hello"): "goodbye",
986 foo2.Bar{"fizz"}: "buzz",
989 reason: "reporter should avoid calling String to disambiguate between the two map keys",
991 label: label + "/NonAmbiguousStringerMapKey",
992 x: map[interface{}]string{Stringer("hello"): "goodbye"},
993 y: map[interface{}]string{newStringer("fizz"): "buzz"},
995 reason: "reporter should call String as there is no ambiguity between the two map keys",
997 label: label + "/InvalidUTF8",
998 x: MyString("\xed\xa0\x80"),
1000 reason: "invalid UTF-8 should format as quoted string",
1002 label: label + "/UnbatchedSlice",
1003 x: MyComposite{IntsA: []int8{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
1004 y: MyComposite{IntsA: []int8{10, 11, 21, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
1006 reason: "unbatched diffing desired since few elements differ",
1008 label: label + "/BatchedSlice",
1009 x: MyComposite{IntsA: []int8{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
1010 y: MyComposite{IntsA: []int8{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
1012 reason: "batched diffing desired since many elements differ",
1014 label: label + "/BatchedWithComparer",
1015 x: MyComposite{BytesA: []byte{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
1016 y: MyComposite{BytesA: []byte{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
1019 cmp.Comparer(bytes.Equal),
1021 reason: "batched diffing desired since many elements differ",
1023 label: label + "/BatchedLong",
1024 x: MyComposite{IntsA: []int8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}},
1026 reason: "batched output desired for a single slice of primitives unique to one of the inputs",
1028 label: label + "/BatchedNamedAndUnnamed",
1030 BytesA: []byte{1, 2, 3},
1031 BytesB: []MyByte{4, 5, 6},
1032 BytesC: MyBytes{7, 8, 9},
1033 IntsA: []int8{-1, -2, -3},
1034 IntsB: []MyInt{-4, -5, -6},
1035 IntsC: MyInts{-7, -8, -9},
1036 UintsA: []uint16{1000, 2000, 3000},
1037 UintsB: []MyUint{4000, 5000, 6000},
1038 UintsC: MyUints{7000, 8000, 9000},
1039 FloatsA: []float32{1.5, 2.5, 3.5},
1040 FloatsB: []MyFloat{4.5, 5.5, 6.5},
1041 FloatsC: MyFloats{7.5, 8.5, 9.5},
1044 BytesA: []byte{3, 2, 1},
1045 BytesB: []MyByte{6, 5, 4},
1046 BytesC: MyBytes{9, 8, 7},
1047 IntsA: []int8{-3, -2, -1},
1048 IntsB: []MyInt{-6, -5, -4},
1049 IntsC: MyInts{-9, -8, -7},
1050 UintsA: []uint16{3000, 2000, 1000},
1051 UintsB: []MyUint{6000, 5000, 4000},
1052 UintsC: MyUints{9000, 8000, 7000},
1053 FloatsA: []float32{3.5, 2.5, 1.5},
1054 FloatsB: []MyFloat{6.5, 5.5, 4.5},
1055 FloatsC: MyFloats{9.5, 8.5, 7.5},
1058 reason: "batched diffing available for both named and unnamed slices",
1060 label: label + "/BinaryHexdump",
1061 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]")},
1062 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]")},
1064 reason: "binary diff in hexdump form since data is binary data",
1066 label: label + "/StringHexdump",
1067 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")},
1068 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")},
1070 reason: "binary diff desired since string looks like binary data",
1072 label: label + "/BinaryString",
1073 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}`)},
1074 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}`)},
1076 reason: "batched textual diff desired since bytes looks like textual data",
1078 label: label + "/TripleQuote",
1079 x: MyComposite{StringA: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n"},
1080 y: MyComposite{StringA: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n"},
1082 reason: "use triple-quote syntax",
1084 label: label + "/TripleQuoteSlice",
1086 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1087 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1090 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\n",
1091 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1094 reason: "use triple-quote syntax for slices of strings",
1096 label: label + "/TripleQuoteNamedTypes",
1098 StringB: MyString("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
1099 BytesC: MyBytes("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
1102 StringB: MyString("aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
1103 BytesC: MyBytes("aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
1106 reason: "use triple-quote syntax for named types",
1108 label: label + "/TripleQuoteSliceNamedTypes",
1110 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1111 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1114 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\n",
1115 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1118 reason: "use triple-quote syntax for slices of named strings",
1120 label: label + "/TripleQuoteEndlines",
1121 x: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\r\nhhh\n\riii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n\r",
1122 y: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\r\nhhh\n\riii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz",
1124 reason: "use triple-quote syntax",
1126 label: label + "/AvoidTripleQuoteAmbiguousQuotes",
1127 x: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1128 y: "aaa\nbbb\nCCC\nddd\neee\n\"\"\"\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1130 reason: "avoid triple-quote syntax due to presence of ambiguous triple quotes",
1132 label: label + "/AvoidTripleQuoteAmbiguousEllipsis",
1133 x: "aaa\nbbb\nccc\n...\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1134 y: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1136 reason: "avoid triple-quote syntax due to presence of ambiguous ellipsis",
1138 label: label + "/AvoidTripleQuoteNonPrintable",
1139 x: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1140 y: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\no\roo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1142 reason: "use triple-quote syntax",
1144 label: label + "/AvoidTripleQuoteIdenticalWhitespace",
1145 x: "aaa\nbbb\nccc\n ddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1146 y: "aaa\nbbb\nccc \nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
1148 reason: "avoid triple-quote syntax due to visual equivalence of differences",
1150 label: label + "/TripleQuoteStringer",
1152 bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")),
1153 bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n)\n\nfunc main() {\n\tfmt.Println(\"My favorite number is\", rand.Intn(10))\n}\n")),
1156 bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")),
1157 bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n\t\"math\"\n)\n\nfunc main() {\n\tfmt.Printf(\"Now you have %g problems.\\n\", math.Sqrt(7))\n}\n")),
1159 opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
1161 reason: "multi-line String output should be formatted with triple quote",
1163 label: label + "/LimitMaximumBytesDiffs",
1164 x: []byte("\xcd====\x06\x1f\xc2\xcc\xc2-S=====\x1d\xdfa\xae\x98\x9fH======ǰ\xb7=======\xef====:\\\x94\xe6J\xc7=====\xb4======\n\n\xf7\x94===========\xf2\x9c\xc0f=====4\xf6\xf1\xc3\x17\x82======n\x16`\x91D\xc6\x06=======\x1cE====.===========\xc4\x18=======\x8a\x8d\x0e====\x87\xb1\xa5\x8e\xc3=====z\x0f1\xaeU======G,=======5\xe75\xee\x82\xf4\xce====\x11r===========\xaf]=======z\x05\xb3\x91\x88%\xd2====\n1\x89=====i\xb7\x055\xe6\x81\xd2=============\x883=@̾====\x14\x05\x96%^t\x04=====\xe7Ȉ\x90\x1d============="),
1165 y: []byte("\\====|\x96\xe7SB\xa0\xab=====\xf0\xbd\xa5q\xab\x17;======\xabP\x00=======\xeb====\xa5\x14\xe6O(\xe4=====(======/c@?===========\xd9x\xed\x13=====J\xfc\x918B\x8d======a8A\xebs\x04\xae=======\aC====\x1c===========\x91\"=======uؾ====s\xec\x845\a=====;\xabS9t======\x1f\x1b=======\x80\xab/\xed+:;====\xeaI===========\xabl=======\xb9\xe9\xfdH\x93\x8e\u007f====ח\xe5=====Ig\x88m\xf5\x01V=============\xf7+4\xb0\x92E====\x9fj\xf8&\xd0h\xf9=====\xeeΨ\r\xbf============="),
1167 reason: "total bytes difference output is truncated due to excessive number of differences",
1169 label: label + "/LimitMaximumStringDiffs",
1170 x: "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n",
1171 y: "aa\nb\ncc\nd\nee\nf\ngg\nh\nii\nj\nkk\nl\nmm\nn\noo\np\nqq\nr\nss\nt\nuu\nv\nww\nx\nyy\nz\nAA\nB\nCC\nD\nEE\nF\nGG\nH\nII\nJ\nKK\nL\nMM\nN\nOO\nP\nQQ\nR\nSS\nT\nUU\nV\nWW\nX\nYY\nZ\n",
1173 reason: "total string difference output is truncated due to excessive number of differences",
1175 label: label + "/LimitMaximumSliceDiffs",
1176 x: func() (out []struct{ S string }) {
1177 for _, s := range strings.Split("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n", "\n") {
1178 out = append(out, struct{ S string }{s})
1182 y: func() (out []struct{ S string }) {
1183 for _, s := range strings.Split("aa\nb\ncc\nd\nee\nf\ngg\nh\nii\nj\nkk\nl\nmm\nn\noo\np\nqq\nr\nss\nt\nuu\nv\nww\nx\nyy\nz\nAA\nB\nCC\nD\nEE\nF\nGG\nH\nII\nJ\nKK\nL\nMM\nN\nOO\nP\nQQ\nR\nSS\nT\nUU\nV\nWW\nX\nYY\nZ\n", "\n") {
1184 out = append(out, struct{ S string }{s})
1189 reason: "total slice difference output is truncated due to excessive number of differences",
1191 label: label + "/MultilineString",
1193 StringA: strings.TrimPrefix(`
1194 Package cmp determines equality of values.
1196 This package is intended to be a more powerful and safer alternative to
1197 reflect.DeepEqual for comparing whether two values are semantically equal.
1199 The primary features of cmp are:
1201 • When the default behavior of equality does not suit the needs of the test,
1202 custom equality functions can override the equality operation.
1203 For example, an equality function may report floats as equal so long as they
1204 are within some tolerance of each other.
1206 • Types that have an Equal method may use that method to determine equality.
1207 This allows package authors to determine the equality operation for the types
1210 • If no custom equality functions are used and no Equal method is defined,
1211 equality is determined by recursively comparing the primitive kinds on both
1212 values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
1213 fields are not compared by default; they result in panics unless suppressed
1214 by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
1215 using the AllowUnexported option.
1219 StringA: strings.TrimPrefix(`
1220 Package cmp determines equality of value.
1222 This package is intended to be a more powerful and safer alternative to
1223 reflect.DeepEqual for comparing whether two values are semantically equal.
1225 The primary features of cmp are:
1227 • When the default behavior of equality does not suit the needs of the test,
1228 custom equality functions can override the equality operation.
1229 For example, an equality function may report floats as equal so long as they
1230 are within some tolerance of each other.
1232 • If no custom equality functions are used and no Equal method is defined,
1233 equality is determined by recursively comparing the primitive kinds on both
1234 values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
1235 fields are not compared by default; they result in panics unless suppressed
1236 by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
1237 using the AllowUnexported option.`, "\n"),
1240 reason: "batched per-line diff desired since string looks like multi-line textual data",
1242 label: label + "/Slices",
1244 BytesA: []byte{1, 2, 3},
1245 BytesB: []MyByte{4, 5, 6},
1246 BytesC: MyBytes{7, 8, 9},
1247 IntsA: []int8{-1, -2, -3},
1248 IntsB: []MyInt{-4, -5, -6},
1249 IntsC: MyInts{-7, -8, -9},
1250 UintsA: []uint16{1000, 2000, 3000},
1251 UintsB: []MyUint{4000, 5000, 6000},
1252 UintsC: MyUints{7000, 8000, 9000},
1253 FloatsA: []float32{1.5, 2.5, 3.5},
1254 FloatsB: []MyFloat{4.5, 5.5, 6.5},
1255 FloatsC: MyFloats{7.5, 8.5, 9.5},
1259 reason: "batched diffing for non-nil slices and nil slices",
1261 label: label + "/EmptySlices",
1272 FloatsA: []float32{},
1273 FloatsB: []MyFloat{},
1274 FloatsC: MyFloats{},
1278 reason: "batched diffing for empty slices and nil slices",
1280 label: label + "/LargeMapKey",
1281 x: map[*[]byte]int{func() *[]byte {
1282 b := make([]byte, 1<<20, 1<<20)
1285 y: map[*[]byte]int{func() *[]byte {
1286 b := make([]byte, 1<<20, 1<<20)
1289 reason: "printing map keys should have some verbosity limit imposed",
1293 func embeddedTests() []test {
1294 const label = "EmbeddedStruct"
1296 privateStruct := *new(ts.ParentStructA).PrivateStruct()
1298 createStructA := func(i int) ts.ParentStructA {
1299 s := ts.ParentStructA{}
1300 s.PrivateStruct().Public = 1 + i
1301 s.PrivateStruct().SetPrivate(2 + i)
1305 createStructB := func(i int) ts.ParentStructB {
1306 s := ts.ParentStructB{}
1307 s.PublicStruct.Public = 1 + i
1308 s.PublicStruct.SetPrivate(2 + i)
1312 createStructC := func(i int) ts.ParentStructC {
1313 s := ts.ParentStructC{}
1314 s.PrivateStruct().Public = 1 + i
1315 s.PrivateStruct().SetPrivate(2 + i)
1321 createStructD := func(i int) ts.ParentStructD {
1322 s := ts.ParentStructD{}
1323 s.PublicStruct.Public = 1 + i
1324 s.PublicStruct.SetPrivate(2 + i)
1330 createStructE := func(i int) ts.ParentStructE {
1331 s := ts.ParentStructE{}
1332 s.PrivateStruct().Public = 1 + i
1333 s.PrivateStruct().SetPrivate(2 + i)
1334 s.PublicStruct.Public = 3 + i
1335 s.PublicStruct.SetPrivate(4 + i)
1339 createStructF := func(i int) ts.ParentStructF {
1340 s := ts.ParentStructF{}
1341 s.PrivateStruct().Public = 1 + i
1342 s.PrivateStruct().SetPrivate(2 + i)
1343 s.PublicStruct.Public = 3 + i
1344 s.PublicStruct.SetPrivate(4 + i)
1350 createStructG := func(i int) *ts.ParentStructG {
1351 s := ts.NewParentStructG()
1352 s.PrivateStruct().Public = 1 + i
1353 s.PrivateStruct().SetPrivate(2 + i)
1357 createStructH := func(i int) *ts.ParentStructH {
1358 s := ts.NewParentStructH()
1359 s.PublicStruct.Public = 1 + i
1360 s.PublicStruct.SetPrivate(2 + i)
1364 createStructI := func(i int) *ts.ParentStructI {
1365 s := ts.NewParentStructI()
1366 s.PrivateStruct().Public = 1 + i
1367 s.PrivateStruct().SetPrivate(2 + i)
1368 s.PublicStruct.Public = 3 + i
1369 s.PublicStruct.SetPrivate(4 + i)
1373 createStructJ := func(i int) *ts.ParentStructJ {
1374 s := ts.NewParentStructJ()
1375 s.PrivateStruct().Public = 1 + i
1376 s.PrivateStruct().SetPrivate(2 + i)
1377 s.PublicStruct.Public = 3 + i
1378 s.PublicStruct.SetPrivate(4 + i)
1379 s.Private().Public = 5 + i
1380 s.Private().SetPrivate(6 + i)
1381 s.Public.Public = 7 + i
1382 s.Public.SetPrivate(8 + i)
1386 // TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/21122).
1387 wantPanicNotGo110 := func(s string) string {
1388 if !flags.AtLeastGo110 {
1395 label: label + "/ParentStructA/PanicUnexported1",
1396 x: ts.ParentStructA{},
1397 y: ts.ParentStructA{},
1398 wantPanic: "cannot handle unexported field",
1399 reason: "ParentStructA has an unexported field",
1401 label: label + "/ParentStructA/Ignored",
1402 x: ts.ParentStructA{},
1403 y: ts.ParentStructA{},
1405 cmpopts.IgnoreUnexported(ts.ParentStructA{}),
1408 reason: "the only field (which is unexported) of ParentStructA is ignored",
1410 label: label + "/ParentStructA/PanicUnexported2",
1411 x: createStructA(0),
1412 y: createStructA(0),
1414 cmp.AllowUnexported(ts.ParentStructA{}),
1416 wantPanic: "cannot handle unexported field",
1417 reason: "privateStruct also has unexported fields",
1419 label: label + "/ParentStructA/Equal",
1420 x: createStructA(0),
1421 y: createStructA(0),
1423 cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
1426 reason: "unexported fields of both ParentStructA and privateStruct are allowed",
1428 label: label + "/ParentStructA/Inequal",
1429 x: createStructA(0),
1430 y: createStructA(1),
1432 cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
1435 reason: "the two values differ on some fields",
1437 label: label + "/ParentStructB/PanicUnexported1",
1438 x: ts.ParentStructB{},
1439 y: ts.ParentStructB{},
1441 cmpopts.IgnoreUnexported(ts.ParentStructB{}),
1443 wantPanic: "cannot handle unexported field",
1444 reason: "PublicStruct has an unexported field",
1446 label: label + "/ParentStructB/Ignored",
1447 x: ts.ParentStructB{},
1448 y: ts.ParentStructB{},
1450 cmpopts.IgnoreUnexported(ts.ParentStructB{}),
1451 cmpopts.IgnoreUnexported(ts.PublicStruct{}),
1454 reason: "unexported fields of both ParentStructB and PublicStruct are ignored",
1456 label: label + "/ParentStructB/PanicUnexported2",
1457 x: createStructB(0),
1458 y: createStructB(0),
1460 cmp.AllowUnexported(ts.ParentStructB{}),
1462 wantPanic: "cannot handle unexported field",
1463 reason: "PublicStruct also has unexported fields",
1465 label: label + "/ParentStructB/Equal",
1466 x: createStructB(0),
1467 y: createStructB(0),
1469 cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
1472 reason: "unexported fields of both ParentStructB and PublicStruct are allowed",
1474 label: label + "/ParentStructB/Inequal",
1475 x: createStructB(0),
1476 y: createStructB(1),
1478 cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
1481 reason: "the two values differ on some fields",
1483 label: label + "/ParentStructC/PanicUnexported1",
1484 x: ts.ParentStructC{},
1485 y: ts.ParentStructC{},
1486 wantPanic: "cannot handle unexported field",
1487 reason: "ParentStructC has unexported fields",
1489 label: label + "/ParentStructC/Ignored",
1490 x: ts.ParentStructC{},
1491 y: ts.ParentStructC{},
1493 cmpopts.IgnoreUnexported(ts.ParentStructC{}),
1496 reason: "unexported fields of ParentStructC are ignored",
1498 label: label + "/ParentStructC/PanicUnexported2",
1499 x: createStructC(0),
1500 y: createStructC(0),
1502 cmp.AllowUnexported(ts.ParentStructC{}),
1504 wantPanic: "cannot handle unexported field",
1505 reason: "privateStruct also has unexported fields",
1507 label: label + "/ParentStructC/Equal",
1508 x: createStructC(0),
1509 y: createStructC(0),
1511 cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
1514 reason: "unexported fields of both ParentStructC and privateStruct are allowed",
1516 label: label + "/ParentStructC/Inequal",
1517 x: createStructC(0),
1518 y: createStructC(1),
1520 cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
1523 reason: "the two values differ on some fields",
1525 label: label + "/ParentStructD/PanicUnexported1",
1526 x: ts.ParentStructD{},
1527 y: ts.ParentStructD{},
1529 cmpopts.IgnoreUnexported(ts.ParentStructD{}),
1531 wantPanic: "cannot handle unexported field",
1532 reason: "ParentStructD has unexported fields",
1534 label: label + "/ParentStructD/Ignored",
1535 x: ts.ParentStructD{},
1536 y: ts.ParentStructD{},
1538 cmpopts.IgnoreUnexported(ts.ParentStructD{}),
1539 cmpopts.IgnoreUnexported(ts.PublicStruct{}),
1542 reason: "unexported fields of ParentStructD and PublicStruct are ignored",
1544 label: label + "/ParentStructD/PanicUnexported2",
1545 x: createStructD(0),
1546 y: createStructD(0),
1548 cmp.AllowUnexported(ts.ParentStructD{}),
1550 wantPanic: "cannot handle unexported field",
1551 reason: "PublicStruct also has unexported fields",
1553 label: label + "/ParentStructD/Equal",
1554 x: createStructD(0),
1555 y: createStructD(0),
1557 cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
1560 reason: "unexported fields of both ParentStructD and PublicStruct are allowed",
1562 label: label + "/ParentStructD/Inequal",
1563 x: createStructD(0),
1564 y: createStructD(1),
1566 cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
1569 reason: "the two values differ on some fields",
1571 label: label + "/ParentStructE/PanicUnexported1",
1572 x: ts.ParentStructE{},
1573 y: ts.ParentStructE{},
1575 cmpopts.IgnoreUnexported(ts.ParentStructE{}),
1577 wantPanic: "cannot handle unexported field",
1578 reason: "ParentStructE has unexported fields",
1580 label: label + "/ParentStructE/Ignored",
1581 x: ts.ParentStructE{},
1582 y: ts.ParentStructE{},
1584 cmpopts.IgnoreUnexported(ts.ParentStructE{}),
1585 cmpopts.IgnoreUnexported(ts.PublicStruct{}),
1588 reason: "unexported fields of ParentStructE and PublicStruct are ignored",
1590 label: label + "/ParentStructE/PanicUnexported2",
1591 x: createStructE(0),
1592 y: createStructE(0),
1594 cmp.AllowUnexported(ts.ParentStructE{}),
1596 wantPanic: "cannot handle unexported field",
1597 reason: "PublicStruct and privateStruct also has unexported fields",
1599 label: label + "/ParentStructE/PanicUnexported3",
1600 x: createStructE(0),
1601 y: createStructE(0),
1603 cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}),
1605 wantPanic: "cannot handle unexported field",
1606 reason: "privateStruct also has unexported fields",
1608 label: label + "/ParentStructE/Equal",
1609 x: createStructE(0),
1610 y: createStructE(0),
1612 cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
1615 reason: "unexported fields of both ParentStructE, PublicStruct, and privateStruct are allowed",
1617 label: label + "/ParentStructE/Inequal",
1618 x: createStructE(0),
1619 y: createStructE(1),
1621 cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
1624 reason: "the two values differ on some fields",
1626 label: label + "/ParentStructF/PanicUnexported1",
1627 x: ts.ParentStructF{},
1628 y: ts.ParentStructF{},
1630 cmpopts.IgnoreUnexported(ts.ParentStructF{}),
1632 wantPanic: "cannot handle unexported field",
1633 reason: "ParentStructF has unexported fields",
1635 label: label + "/ParentStructF/Ignored",
1636 x: ts.ParentStructF{},
1637 y: ts.ParentStructF{},
1639 cmpopts.IgnoreUnexported(ts.ParentStructF{}),
1640 cmpopts.IgnoreUnexported(ts.PublicStruct{}),
1643 reason: "unexported fields of ParentStructF and PublicStruct are ignored",
1645 label: label + "/ParentStructF/PanicUnexported2",
1646 x: createStructF(0),
1647 y: createStructF(0),
1649 cmp.AllowUnexported(ts.ParentStructF{}),
1651 wantPanic: "cannot handle unexported field",
1652 reason: "PublicStruct and privateStruct also has unexported fields",
1654 label: label + "/ParentStructF/PanicUnexported3",
1655 x: createStructF(0),
1656 y: createStructF(0),
1658 cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}),
1660 wantPanic: "cannot handle unexported field",
1661 reason: "privateStruct also has unexported fields",
1663 label: label + "/ParentStructF/Equal",
1664 x: createStructF(0),
1665 y: createStructF(0),
1667 cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
1670 reason: "unexported fields of both ParentStructF, PublicStruct, and privateStruct are allowed",
1672 label: label + "/ParentStructF/Inequal",
1673 x: createStructF(0),
1674 y: createStructF(1),
1676 cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
1679 reason: "the two values differ on some fields",
1681 label: label + "/ParentStructG/PanicUnexported1",
1682 x: ts.ParentStructG{},
1683 y: ts.ParentStructG{},
1684 wantPanic: wantPanicNotGo110("cannot handle unexported field"),
1685 wantEqual: !flags.AtLeastGo110,
1686 reason: "ParentStructG has unexported fields",
1688 label: label + "/ParentStructG/Ignored",
1689 x: ts.ParentStructG{},
1690 y: ts.ParentStructG{},
1692 cmpopts.IgnoreUnexported(ts.ParentStructG{}),
1695 reason: "unexported fields of ParentStructG are ignored",
1697 label: label + "/ParentStructG/PanicUnexported2",
1698 x: createStructG(0),
1699 y: createStructG(0),
1701 cmp.AllowUnexported(ts.ParentStructG{}),
1703 wantPanic: "cannot handle unexported field",
1704 reason: "privateStruct also has unexported fields",
1706 label: label + "/ParentStructG/Equal",
1707 x: createStructG(0),
1708 y: createStructG(0),
1710 cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
1713 reason: "unexported fields of both ParentStructG and privateStruct are allowed",
1715 label: label + "/ParentStructG/Inequal",
1716 x: createStructG(0),
1717 y: createStructG(1),
1719 cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
1722 reason: "the two values differ on some fields",
1724 label: label + "/ParentStructH/EqualNil",
1725 x: ts.ParentStructH{},
1726 y: ts.ParentStructH{},
1728 reason: "PublicStruct is not compared because the pointer is nil",
1730 label: label + "/ParentStructH/PanicUnexported1",
1731 x: createStructH(0),
1732 y: createStructH(0),
1733 wantPanic: "cannot handle unexported field",
1734 reason: "PublicStruct has unexported fields",
1736 label: label + "/ParentStructH/Ignored",
1737 x: ts.ParentStructH{},
1738 y: ts.ParentStructH{},
1740 cmpopts.IgnoreUnexported(ts.ParentStructH{}),
1743 reason: "unexported fields of ParentStructH are ignored (it has none)",
1745 label: label + "/ParentStructH/PanicUnexported2",
1746 x: createStructH(0),
1747 y: createStructH(0),
1749 cmp.AllowUnexported(ts.ParentStructH{}),
1751 wantPanic: "cannot handle unexported field",
1752 reason: "PublicStruct also has unexported fields",
1754 label: label + "/ParentStructH/Equal",
1755 x: createStructH(0),
1756 y: createStructH(0),
1758 cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
1761 reason: "unexported fields of both ParentStructH and PublicStruct are allowed",
1763 label: label + "/ParentStructH/Inequal",
1764 x: createStructH(0),
1765 y: createStructH(1),
1767 cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
1770 reason: "the two values differ on some fields",
1772 label: label + "/ParentStructI/PanicUnexported1",
1773 x: ts.ParentStructI{},
1774 y: ts.ParentStructI{},
1775 wantPanic: wantPanicNotGo110("cannot handle unexported field"),
1776 wantEqual: !flags.AtLeastGo110,
1777 reason: "ParentStructI has unexported fields",
1779 label: label + "/ParentStructI/Ignored1",
1780 x: ts.ParentStructI{},
1781 y: ts.ParentStructI{},
1783 cmpopts.IgnoreUnexported(ts.ParentStructI{}),
1786 reason: "unexported fields of ParentStructI are ignored",
1788 label: label + "/ParentStructI/PanicUnexported2",
1789 x: createStructI(0),
1790 y: createStructI(0),
1792 cmpopts.IgnoreUnexported(ts.ParentStructI{}),
1794 wantPanic: "cannot handle unexported field",
1795 reason: "PublicStruct and privateStruct also has unexported fields",
1797 label: label + "/ParentStructI/Ignored2",
1798 x: createStructI(0),
1799 y: createStructI(0),
1801 cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
1804 reason: "unexported fields of ParentStructI and PublicStruct are ignored",
1806 label: label + "/ParentStructI/PanicUnexported3",
1807 x: createStructI(0),
1808 y: createStructI(0),
1810 cmp.AllowUnexported(ts.ParentStructI{}),
1812 wantPanic: "cannot handle unexported field",
1813 reason: "PublicStruct and privateStruct also has unexported fields",
1815 label: label + "/ParentStructI/Equal",
1816 x: createStructI(0),
1817 y: createStructI(0),
1819 cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
1822 reason: "unexported fields of both ParentStructI, PublicStruct, and privateStruct are allowed",
1824 label: label + "/ParentStructI/Inequal",
1825 x: createStructI(0),
1826 y: createStructI(1),
1828 cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
1831 reason: "the two values differ on some fields",
1833 label: label + "/ParentStructJ/PanicUnexported1",
1834 x: ts.ParentStructJ{},
1835 y: ts.ParentStructJ{},
1836 wantPanic: "cannot handle unexported field",
1837 reason: "ParentStructJ has unexported fields",
1839 label: label + "/ParentStructJ/PanicUnexported2",
1840 x: ts.ParentStructJ{},
1841 y: ts.ParentStructJ{},
1843 cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
1845 wantPanic: "cannot handle unexported field",
1846 reason: "PublicStruct and privateStruct also has unexported fields",
1848 label: label + "/ParentStructJ/Ignored",
1849 x: ts.ParentStructJ{},
1850 y: ts.ParentStructJ{},
1852 cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
1855 reason: "unexported fields of ParentStructJ and PublicStruct are ignored",
1857 label: label + "/ParentStructJ/PanicUnexported3",
1858 x: createStructJ(0),
1859 y: createStructJ(0),
1861 cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
1863 wantPanic: "cannot handle unexported field",
1864 reason: "privateStruct also has unexported fields",
1866 label: label + "/ParentStructJ/Equal",
1867 x: createStructJ(0),
1868 y: createStructJ(0),
1870 cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
1873 reason: "unexported fields of both ParentStructJ, PublicStruct, and privateStruct are allowed",
1875 label: label + "/ParentStructJ/Inequal",
1876 x: createStructJ(0),
1877 y: createStructJ(1),
1879 cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
1882 reason: "the two values differ on some fields",
1886 func methodTests() []test {
1887 const label = "EqualMethod"
1889 // A common mistake that the Equal method is on a pointer receiver,
1890 // but only a non-pointer value is present in the struct.
1891 // A transform can be used to forcibly reference the value.
1892 addrTransform := cmp.FilterPath(func(p cmp.Path) bool {
1896 t := p[len(p)-1].Type()
1897 if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr {
1900 if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
1902 return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
1903 tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true)
1906 }, cmp.Transformer("Addr", func(x interface{}) interface{} {
1907 v := reflect.ValueOf(x)
1908 vp := reflect.New(v.Type())
1910 return vp.Interface()
1913 // For each of these types, there is an Equal method defined, which always
1914 // returns true, while the underlying data are fundamentally different.
1915 // Since the method should be called, these are expected to be equal.
1917 label: label + "/StructA/ValueEqual",
1918 x: ts.StructA{X: "NotEqual"},
1919 y: ts.StructA{X: "not_equal"},
1921 reason: "Equal method on StructA value called",
1923 label: label + "/StructA/PointerEqual",
1924 x: &ts.StructA{X: "NotEqual"},
1925 y: &ts.StructA{X: "not_equal"},
1927 reason: "Equal method on StructA pointer called",
1929 label: label + "/StructB/ValueInequal",
1930 x: ts.StructB{X: "NotEqual"},
1931 y: ts.StructB{X: "not_equal"},
1933 reason: "Equal method on StructB value not called",
1935 label: label + "/StructB/ValueAddrEqual",
1936 x: ts.StructB{X: "NotEqual"},
1937 y: ts.StructB{X: "not_equal"},
1938 opts: []cmp.Option{addrTransform},
1940 reason: "Equal method on StructB pointer called due to shallow copy transform",
1942 label: label + "/StructB/PointerEqual",
1943 x: &ts.StructB{X: "NotEqual"},
1944 y: &ts.StructB{X: "not_equal"},
1946 reason: "Equal method on StructB pointer called",
1948 label: label + "/StructC/ValueEqual",
1949 x: ts.StructC{X: "NotEqual"},
1950 y: ts.StructC{X: "not_equal"},
1952 reason: "Equal method on StructC value called",
1954 label: label + "/StructC/PointerEqual",
1955 x: &ts.StructC{X: "NotEqual"},
1956 y: &ts.StructC{X: "not_equal"},
1958 reason: "Equal method on StructC pointer called",
1960 label: label + "/StructD/ValueInequal",
1961 x: ts.StructD{X: "NotEqual"},
1962 y: ts.StructD{X: "not_equal"},
1964 reason: "Equal method on StructD value not called",
1966 label: label + "/StructD/ValueAddrEqual",
1967 x: ts.StructD{X: "NotEqual"},
1968 y: ts.StructD{X: "not_equal"},
1969 opts: []cmp.Option{addrTransform},
1971 reason: "Equal method on StructD pointer called due to shallow copy transform",
1973 label: label + "/StructD/PointerEqual",
1974 x: &ts.StructD{X: "NotEqual"},
1975 y: &ts.StructD{X: "not_equal"},
1977 reason: "Equal method on StructD pointer called",
1979 label: label + "/StructE/ValueInequal",
1980 x: ts.StructE{X: "NotEqual"},
1981 y: ts.StructE{X: "not_equal"},
1983 reason: "Equal method on StructE value not called",
1985 label: label + "/StructE/ValueAddrEqual",
1986 x: ts.StructE{X: "NotEqual"},
1987 y: ts.StructE{X: "not_equal"},
1988 opts: []cmp.Option{addrTransform},
1990 reason: "Equal method on StructE pointer called due to shallow copy transform",
1992 label: label + "/StructE/PointerEqual",
1993 x: &ts.StructE{X: "NotEqual"},
1994 y: &ts.StructE{X: "not_equal"},
1996 reason: "Equal method on StructE pointer called",
1998 label: label + "/StructF/ValueInequal",
1999 x: ts.StructF{X: "NotEqual"},
2000 y: ts.StructF{X: "not_equal"},
2002 reason: "Equal method on StructF value not called",
2004 label: label + "/StructF/PointerEqual",
2005 x: &ts.StructF{X: "NotEqual"},
2006 y: &ts.StructF{X: "not_equal"},
2008 reason: "Equal method on StructF pointer called",
2010 label: label + "/StructA1/ValueEqual",
2011 x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
2012 y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
2014 reason: "Equal method on StructA value called with equal X field",
2016 label: label + "/StructA1/ValueInequal",
2017 x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
2018 y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
2020 reason: "Equal method on StructA value called, but inequal X field",
2022 label: label + "/StructA1/PointerEqual",
2023 x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
2024 y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
2026 reason: "Equal method on StructA value called with equal X field",
2028 label: label + "/StructA1/PointerInequal",
2029 x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
2030 y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
2032 reason: "Equal method on StructA value called, but inequal X field",
2034 label: label + "/StructB1/ValueEqual",
2035 x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
2036 y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
2037 opts: []cmp.Option{addrTransform},
2039 reason: "Equal method on StructB pointer called due to shallow copy transform with equal X field",
2041 label: label + "/StructB1/ValueInequal",
2042 x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
2043 y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
2044 opts: []cmp.Option{addrTransform},
2046 reason: "Equal method on StructB pointer called due to shallow copy transform, but inequal X field",
2048 label: label + "/StructB1/PointerEqual",
2049 x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
2050 y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
2051 opts: []cmp.Option{addrTransform},
2053 reason: "Equal method on StructB pointer called due to shallow copy transform with equal X field",
2055 label: label + "/StructB1/PointerInequal",
2056 x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
2057 y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
2058 opts: []cmp.Option{addrTransform},
2060 reason: "Equal method on StructB pointer called due to shallow copy transform, but inequal X field",
2062 label: label + "/StructC1/ValueEqual",
2063 x: ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
2064 y: ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
2066 reason: "Equal method on StructC1 value called",
2068 label: label + "/StructC1/PointerEqual",
2069 x: &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
2070 y: &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
2072 reason: "Equal method on StructC1 pointer called",
2074 label: label + "/StructD1/ValueInequal",
2075 x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2076 y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
2078 reason: "Equal method on StructD1 value not called",
2080 label: label + "/StructD1/PointerAddrEqual",
2081 x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2082 y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
2083 opts: []cmp.Option{addrTransform},
2085 reason: "Equal method on StructD1 pointer called due to shallow copy transform",
2087 label: label + "/StructD1/PointerEqual",
2088 x: &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2089 y: &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
2091 reason: "Equal method on StructD1 pointer called",
2093 label: label + "/StructE1/ValueInequal",
2094 x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2095 y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
2097 reason: "Equal method on StructE1 value not called",
2099 label: label + "/StructE1/ValueAddrEqual",
2100 x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2101 y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
2102 opts: []cmp.Option{addrTransform},
2104 reason: "Equal method on StructE1 pointer called due to shallow copy transform",
2106 label: label + "/StructE1/PointerEqual",
2107 x: &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2108 y: &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
2110 reason: "Equal method on StructE1 pointer called",
2112 label: label + "/StructF1/ValueInequal",
2113 x: ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
2114 y: ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
2116 reason: "Equal method on StructF1 value not called",
2118 label: label + "/StructF1/PointerEqual",
2119 x: &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
2120 y: &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
2122 reason: "Equal method on StructF1 pointer called",
2124 label: label + "/StructA2/ValueEqual",
2125 x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
2126 y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
2128 reason: "Equal method on StructA pointer called with equal X field",
2130 label: label + "/StructA2/ValueInequal",
2131 x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
2132 y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
2134 reason: "Equal method on StructA pointer called, but inequal X field",
2136 label: label + "/StructA2/PointerEqual",
2137 x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
2138 y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
2140 reason: "Equal method on StructA pointer called with equal X field",
2142 label: label + "/StructA2/PointerInequal",
2143 x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
2144 y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
2146 reason: "Equal method on StructA pointer called, but inequal X field",
2148 label: label + "/StructB2/ValueEqual",
2149 x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
2150 y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
2152 reason: "Equal method on StructB pointer called with equal X field",
2154 label: label + "/StructB2/ValueInequal",
2155 x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
2156 y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
2158 reason: "Equal method on StructB pointer called, but inequal X field",
2160 label: label + "/StructB2/PointerEqual",
2161 x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
2162 y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
2164 reason: "Equal method on StructB pointer called with equal X field",
2166 label: label + "/StructB2/PointerInequal",
2167 x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
2168 y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
2170 reason: "Equal method on StructB pointer called, but inequal X field",
2172 label: label + "/StructC2/ValueEqual",
2173 x: ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
2174 y: ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
2176 reason: "Equal method called on StructC2 value due to forwarded StructC pointer",
2178 label: label + "/StructC2/PointerEqual",
2179 x: &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
2180 y: &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
2182 reason: "Equal method called on StructC2 pointer due to forwarded StructC pointer",
2184 label: label + "/StructD2/ValueEqual",
2185 x: ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2186 y: ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
2188 reason: "Equal method called on StructD2 value due to forwarded StructD pointer",
2190 label: label + "/StructD2/PointerEqual",
2191 x: &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2192 y: &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
2194 reason: "Equal method called on StructD2 pointer due to forwarded StructD pointer",
2196 label: label + "/StructE2/ValueEqual",
2197 x: ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2198 y: ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
2200 reason: "Equal method called on StructE2 value due to forwarded StructE pointer",
2202 label: label + "/StructE2/PointerEqual",
2203 x: &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2204 y: &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
2206 reason: "Equal method called on StructE2 pointer due to forwarded StructE pointer",
2208 label: label + "/StructF2/ValueEqual",
2209 x: ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
2210 y: ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
2212 reason: "Equal method called on StructF2 value due to forwarded StructF pointer",
2214 label: label + "/StructF2/PointerEqual",
2215 x: &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
2216 y: &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
2218 reason: "Equal method called on StructF2 pointer due to forwarded StructF pointer",
2220 label: label + "/StructNo/Inequal",
2221 x: ts.StructNo{X: "NotEqual"},
2222 y: ts.StructNo{X: "not_equal"},
2224 reason: "Equal method not called since StructNo is not assignable to InterfaceA",
2226 label: label + "/AssignA/Equal",
2227 x: ts.AssignA(func() int { return 0 }),
2228 y: ts.AssignA(func() int { return 1 }),
2230 reason: "Equal method called since named func is assignable to unnamed func",
2232 label: label + "/AssignB/Equal",
2233 x: ts.AssignB(struct{ A int }{0}),
2234 y: ts.AssignB(struct{ A int }{1}),
2236 reason: "Equal method called since named struct is assignable to unnamed struct",
2238 label: label + "/AssignC/Equal",
2239 x: ts.AssignC(make(chan bool)),
2240 y: ts.AssignC(make(chan bool)),
2242 reason: "Equal method called since named channel is assignable to unnamed channel",
2244 label: label + "/AssignD/Equal",
2245 x: ts.AssignD(make(chan bool)),
2246 y: ts.AssignD(make(chan bool)),
2248 reason: "Equal method called since named channel is assignable to unnamed channel",
2255 Bravos map[string]*CycleBravo
2261 Alphas map[string]*CycleAlpha
2265 func cycleTests() []test {
2266 const label = "Cycle"
2274 makeGraph := func() map[string]*CycleAlpha {
2275 v := map[string]*CycleAlpha{
2278 Bravos: map[string]*CycleBravo{
2279 "FooBravo": &CycleBravo{
2283 Alphas: map[string]*CycleAlpha{
2284 "Foo": nil, // cyclic reference
2291 Bravos: map[string]*CycleBravo{
2292 "BarBuzzBravo": &CycleBravo{
2293 Name: "BarBuzzBravo",
2296 Alphas: map[string]*CycleAlpha{
2297 "Bar": nil, // cyclic reference
2298 "Buzz": nil, // cyclic reference
2301 "BuzzBarBravo": &CycleBravo{
2302 Name: "BuzzBarBravo",
2305 Alphas: map[string]*CycleAlpha{
2306 "Bar": nil, // cyclic reference
2307 "Buzz": nil, // cyclic reference
2312 "Buzz": &CycleAlpha{
2314 Bravos: map[string]*CycleBravo{
2315 "BarBuzzBravo": nil, // cyclic reference
2316 "BuzzBarBravo": nil, // cyclic reference
2320 v["Foo"].Bravos["FooBravo"].Alphas["Foo"] = v["Foo"]
2321 v["Bar"].Bravos["BarBuzzBravo"].Alphas["Bar"] = v["Bar"]
2322 v["Bar"].Bravos["BarBuzzBravo"].Alphas["Buzz"] = v["Buzz"]
2323 v["Bar"].Bravos["BuzzBarBravo"].Alphas["Bar"] = v["Bar"]
2324 v["Bar"].Bravos["BuzzBarBravo"].Alphas["Buzz"] = v["Buzz"]
2325 v["Buzz"].Bravos["BarBuzzBravo"] = v["Bar"].Bravos["BarBuzzBravo"]
2326 v["Buzz"].Bravos["BuzzBarBravo"] = v["Bar"].Bravos["BuzzBarBravo"]
2331 type XY struct{ x, y interface{} }
2332 for _, tt := range []struct {
2338 label: "PointersEqual",
2347 reason: "equal pair of single-node pointers",
2349 label: "PointersInequal",
2353 y1, y2 := new(P), new(P)
2359 reason: "inequal pair of single-node and double-node pointers",
2361 label: "SlicesEqual",
2370 reason: "equal pair of single-node slices",
2372 label: "SlicesInequal",
2376 y1, y2 := S{nil}, S{nil}
2382 reason: "inequal pair of single-node and double node slices",
2393 reason: "equal pair of single-node maps",
2395 label: "MapsInequal",
2399 y1, y2 := M{0: nil}, M{0: nil}
2405 reason: "inequal pair of single-node and double-node maps",
2407 label: "GraphEqual",
2408 in: XY{makeGraph(), makeGraph()},
2410 reason: "graphs are equal since they have identical forms",
2412 label: "GraphInequalZeroed",
2416 y["Foo"].Bravos["FooBravo"].ID = 0
2417 y["Bar"].Bravos["BarBuzzBravo"].ID = 0
2418 y["Bar"].Bravos["BuzzBarBravo"].ID = 0
2422 reason: "graphs are inequal because the ID fields are different",
2424 label: "GraphInequalStruct",
2428 x["Buzz"].Bravos["BuzzBarBravo"] = &CycleBravo{
2429 Name: "BuzzBarBravo",
2435 reason: "graphs are inequal because they differ on a map element",
2437 tests = append(tests, test{
2438 label: label + "/" + tt.label,
2441 wantEqual: tt.wantEqual,
2448 func project1Tests() []test {
2449 const label = "Project1"
2451 ignoreUnexported := cmpopts.IgnoreUnexported(
2452 ts.EagleImmutable{},
2453 ts.DreamerImmutable{},
2456 ts.DonkeyImmutable{},
2459 ts.SummerLoveSummary{},
2462 createEagle := func() ts.Eagle {
2465 Hounds: []string{"buford", "tannen"},
2466 Desc: "some description",
2467 Dreamers: []ts.Dreamer{{}, {
2469 Animal: []interface{}{
2471 Target: "corporation",
2472 Immutable: &ts.GoatImmutable{
2474 State: (*pb.Goat_States)(newInt(5)),
2484 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2485 Immutable: &ts.SlapImmutable{
2486 ID: "immutableSlap",
2489 LoveRadius: &ts.LoveRadius{
2490 Summer: &ts.SummerLove{
2491 Summary: &ts.SummerLoveSummary{
2492 Devices: []string{"foo", "bar", "baz"},
2493 ChangeType: []pb.SummerType{1, 2, 3},
2499 Immutable: &ts.EagleImmutable{
2502 MissingCall: (*pb.Eagle_MissingCalls)(newInt(55)),
2508 label: label + "/PanicUnexported",
2509 x: ts.Eagle{Slaps: []ts.Slap{{
2510 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2512 y: ts.Eagle{Slaps: []ts.Slap{{
2513 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2515 wantPanic: "cannot handle unexported field",
2516 reason: "struct contains unexported fields",
2518 label: label + "/ProtoEqual",
2519 x: ts.Eagle{Slaps: []ts.Slap{{
2520 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2522 y: ts.Eagle{Slaps: []ts.Slap{{
2523 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2525 opts: []cmp.Option{cmp.Comparer(pb.Equal)},
2527 reason: "simulated protobuf messages contain the same values",
2529 label: label + "/ProtoInequal",
2530 x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
2531 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2533 y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
2534 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata2"}},
2536 opts: []cmp.Option{cmp.Comparer(pb.Equal)},
2538 reason: "simulated protobuf messages contain different values",
2540 label: label + "/Equal",
2543 opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
2545 reason: "equal because values are the same",
2547 label: label + "/Inequal",
2548 x: func() ts.Eagle {
2550 eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2"
2551 eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(newInt(6))
2552 eg.Slaps[0].Immutable.MildSlap = false
2555 y: func() ts.Eagle {
2557 devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices
2558 eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1]
2561 opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
2563 reason: "inequal because some values are different",
2567 type germSorter []*pb.Germ
2569 func (gs germSorter) Len() int { return len(gs) }
2570 func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() }
2571 func (gs germSorter) Swap(i, j int) { gs[i], gs[j] = gs[j], gs[i] }
2573 func project2Tests() []test {
2574 const label = "Project2"
2576 sortGerms := cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ {
2577 out := append([]*pb.Germ(nil), in...) // Make copy
2578 sort.Sort(germSorter(out))
2582 equalDish := cmp.Comparer(func(x, y *ts.Dish) bool {
2583 if x == nil || y == nil {
2584 return x == nil && y == nil
2586 px, err1 := x.Proto()
2587 py, err2 := y.Proto()
2588 if err1 != nil || err2 != nil {
2591 return pb.Equal(px, py)
2594 createBatch := func() ts.GermBatch {
2595 return ts.GermBatch{
2596 DirtyGerms: map[int32][]*pb.Germ{
2598 {Stringer: pb.Stringer{X: "germ1"}},
2601 {Stringer: pb.Stringer{X: "germ2"}},
2602 {Stringer: pb.Stringer{X: "germ3"}},
2603 {Stringer: pb.Stringer{X: "germ4"}},
2606 GermMap: map[int32]*pb.Germ{
2607 13: {Stringer: pb.Stringer{X: "germ13"}},
2608 21: {Stringer: pb.Stringer{X: "germ21"}},
2610 DishMap: map[int32]*ts.Dish{
2611 0: ts.CreateDish(nil, io.EOF),
2612 1: ts.CreateDish(nil, io.ErrUnexpectedEOF),
2613 2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{X: "dish"}}, nil),
2615 HasPreviousResult: true,
2623 label: label + "/PanicUnexported",
2626 wantPanic: "cannot handle unexported field",
2627 reason: "struct contains unexported fields",
2629 label: label + "/Equal",
2632 opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
2634 reason: "equal because identical values are compared",
2636 label: label + "/InequalOrder",
2638 y: func() ts.GermBatch {
2640 s := gb.DirtyGerms[18]
2641 s[0], s[1], s[2] = s[1], s[2], s[0]
2644 opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
2646 reason: "inequal because slice contains elements in differing order",
2648 label: label + "/EqualOrder",
2650 y: func() ts.GermBatch {
2652 s := gb.DirtyGerms[18]
2653 s[0], s[1], s[2] = s[1], s[2], s[0]
2656 opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
2658 reason: "equal because unordered slice is sorted using transformer",
2660 label: label + "/Inequal",
2661 x: func() ts.GermBatch {
2663 delete(gb.DirtyGerms, 17)
2667 y: func() ts.GermBatch {
2669 gb.DirtyGerms[18] = gb.DirtyGerms[18][:2]
2673 opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
2675 reason: "inequal because some values are different",
2679 func project3Tests() []test {
2680 const label = "Project3"
2682 allowVisibility := cmp.AllowUnexported(ts.Dirt{})
2684 ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})
2686 transformProtos := cmp.Transformer("λ", func(x pb.Dirt) *pb.Dirt {
2690 equalTable := cmp.Comparer(func(x, y ts.Table) bool {
2691 tx, ok1 := x.(*ts.MockTable)
2692 ty, ok2 := y.(*ts.MockTable)
2694 panic("table type must be MockTable")
2696 return cmp.Equal(tx.State(), ty.State())
2699 createDirt := func() (d ts.Dirt) {
2700 d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"}))
2701 d.SetTimestamp(12345)
2703 d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "proto"}}
2704 d.SetWizard(map[string]*pb.Wizard{
2705 "harry": {Stringer: pb.Stringer{X: "potter"}},
2706 "albus": {Stringer: pb.Stringer{X: "dumbledore"}},
2708 d.SetLastTime(54321)
2713 label: label + "/PanicUnexported1",
2716 wantPanic: "cannot handle unexported field",
2717 reason: "struct contains unexported fields",
2719 label: label + "/PanicUnexported2",
2722 opts: []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
2723 wantPanic: "cannot handle unexported field",
2724 reason: "struct contains references to simulated protobuf types with unexported fields",
2726 label: label + "/Equal",
2729 opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
2731 reason: "transformer used to create reference to protobuf message so it works with pb.Equal",
2733 label: label + "/Inequal",
2736 d.SetTable(ts.CreateMockTable([]string{"a", "c"}))
2737 d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "blah"}}
2743 d.SetWizard(map[string]*pb.Wizard{
2744 "harry": {Stringer: pb.Stringer{X: "otter"}},
2748 opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
2750 reason: "inequal because some values are different",
2754 func project4Tests() []test {
2755 const label = "Project4"
2757 allowVisibility := cmp.AllowUnexported(
2763 transformProtos := cmp.Transformer("λ", func(x pb.Restrictions) *pb.Restrictions {
2767 createCartel := func() ts.Cartel {
2770 p.SetExpiration(now)
2771 p.SetManufacturer("acme")
2773 var hq ts.Headquarter
2775 hq.SetLocation("moon")
2776 hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"})
2777 hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{X: "metadata"}})
2778 hq.SetPublicMessage([]byte{1, 2, 3, 4, 5})
2779 hq.SetHorseBack("abcdef")
2785 c.SetCreationTime(now)
2786 c.SetBoss("al capone")
2787 c.SetPoisons([]*ts.Poison{&p})
2793 label: label + "/PanicUnexported1",
2796 wantPanic: "cannot handle unexported field",
2797 reason: "struct contains unexported fields",
2799 label: label + "/PanicUnexported2",
2802 opts: []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)},
2803 wantPanic: "cannot handle unexported field",
2804 reason: "struct contains references to simulated protobuf types with unexported fields",
2806 label: label + "/Equal",
2809 opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
2811 reason: "transformer used to create reference to protobuf message so it works with pb.Equal",
2813 label: label + "/Inequal",
2814 x: func() ts.Cartel {
2816 var p1, p2 ts.Poison
2818 p1.SetExpiration(now)
2819 p1.SetManufacturer("acme")
2821 p2.SetManufacturer("acme2")
2822 d.SetPoisons([]*ts.Poison{&p1, &p2})
2825 y: func() ts.Cartel {
2827 d.SetSubDivisions([]string{"bravo", "charlie"})
2828 d.SetPublicMessage([]byte{1, 2, 4, 3, 5})
2831 opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
2833 reason: "inequal because some values are different",
2837 // BenchmarkBytes benchmarks the performance of performing Equal or Diff on
2838 // large slices of bytes.
2839 func BenchmarkBytes(b *testing.B) {
2840 // Create a list of PathFilters that never apply, but are evaluated.
2841 const maxFilters = 5
2842 var filters cmp.Options
2843 errorIface := reflect.TypeOf((*error)(nil)).Elem()
2844 for i := 0; i <= maxFilters; i++ {
2845 filters = append(filters, cmp.FilterPath(func(p cmp.Path) bool {
2846 return p.Last().Type().AssignableTo(errorIface) // Never true
2850 type benchSize struct {
2854 for _, ts := range []benchSize{
2860 bx := append(append(make([]byte, ts.size/2), 'x'), make([]byte, ts.size/2)...)
2861 by := append(append(make([]byte, ts.size/2), 'y'), make([]byte, ts.size/2)...)
2862 b.Run(ts.label, func(b *testing.B) {
2863 // Iteratively add more filters that never apply, but are evaluated
2864 // to measure the cost of simply evaluating each filter.
2865 for i := 0; i <= maxFilters; i++ {
2866 b.Run(fmt.Sprintf("EqualFilter%d", i), func(b *testing.B) {
2868 b.SetBytes(2 * ts.size)
2869 for j := 0; j < b.N; j++ {
2870 cmp.Equal(bx, by, filters[:i]...)
2874 for i := 0; i <= maxFilters; i++ {
2875 b.Run(fmt.Sprintf("DiffFilter%d", i), func(b *testing.B) {
2877 b.SetBytes(2 * ts.size)
2878 for j := 0; j < b.N; j++ {
2879 cmp.Diff(bx, by, filters[:i]...)