Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / github.com / google / go-cmp@v0.5.1 / cmp / compare_test.go
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.md file.
4
5 package cmp_test
6
7 import (
8         "bytes"
9         "crypto/md5"
10         "encoding/json"
11         "errors"
12         "flag"
13         "fmt"
14         "io"
15         "io/ioutil"
16         "math"
17         "math/rand"
18         "reflect"
19         "regexp"
20         "sort"
21         "strconv"
22         "strings"
23         "sync"
24         "testing"
25         "time"
26
27         "github.com/google/go-cmp/cmp"
28         "github.com/google/go-cmp/cmp/cmpopts"
29         "github.com/google/go-cmp/cmp/internal/flags"
30
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"
35 )
36
37 func init() {
38         flags.Deterministic = true
39 }
40
41 var update = flag.Bool("update", false, "update golden test files")
42
43 const goldenHeaderPrefix = "<<< "
44 const goldenFooterPrefix = ">>> "
45
46 /// mustParseGolden parses a file as a set of key-value pairs.
47 //
48 // The syntax is simple and looks something like:
49 //
50 //      <<< Key1
51 //      value1a
52 //      value1b
53 //      >>> Key1
54 //      <<< Key2
55 //      value2
56 //      >>> Key2
57 //
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)
62         if err != nil {
63                 panic(err)
64         }
65         s := string(b)
66
67         out := map[string]string{}
68         for len(s) > 0 {
69                 // Identify the next header.
70                 i := strings.Index(s, "\n") + len("\n")
71                 header := s[:i]
72                 if !strings.HasPrefix(header, goldenHeaderPrefix) {
73                         panic(fmt.Sprintf("invalid header: %q", header))
74                 }
75
76                 // Locate the next footer.
77                 footer := goldenFooterPrefix + header[len(goldenHeaderPrefix):]
78                 j := strings.Index(s, footer)
79                 if j < 0 {
80                         panic(fmt.Sprintf("missing footer: %q", footer))
81                 }
82
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))
87                 }
88                 out[name] = s[len(header):j]
89                 s = s[j+len(footer):]
90         }
91         return out
92 }
93 func mustFormatGolden(path string, in []struct{ Name, Data string }) {
94         var b []byte
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"...)
99         }
100         if err := ioutil.WriteFile(path, b, 0664); err != nil {
101                 panic(err)
102         }
103 }
104
105 var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC)
106
107 func newInt(n int) *int { return &n }
108
109 type Stringer string
110
111 func newStringer(s string) fmt.Stringer { return (*Stringer)(&s) }
112 func (s Stringer) String() string       { return string(s) }
113
114 type test struct {
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
121 }
122
123 func TestDiff(t *testing.T) {
124         var tests []test
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()...)
135
136         const goldenFile = "testdata/diffs"
137         gotDiffs := []struct{ Name, Data string }{}
138         wantDiffs := mustParseGolden(goldenFile)
139         for _, tt := range tests {
140                 tt := tt
141                 t.Run(tt.label, func(t *testing.T) {
142                         if !*update {
143                                 t.Parallel()
144                         }
145                         var gotDiff, gotPanic string
146                         func() {
147                                 defer func() {
148                                         if ex := recover(); ex != nil {
149                                                 if s, ok := ex.(string); ok {
150                                                         gotPanic = s
151                                                 } else {
152                                                         panic(ex)
153                                                 }
154                                         }
155                                 }()
156                                 gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...)
157                         }()
158
159                         switch {
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 == "":
165                                 if gotPanic != "" {
166                                         t.Fatalf("unexpected panic message: %s\nreason: %v", gotPanic, tt.reason)
167                                 }
168                                 if *update {
169                                         if gotDiff != "" {
170                                                 gotDiffs = append(gotDiffs, struct{ Name, Data string }{t.Name(), gotDiff})
171                                         }
172                                 } else {
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)
176                                         }
177                                 }
178                                 gotEqual := gotDiff == ""
179                                 if gotEqual != tt.wantEqual {
180                                         t.Fatalf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
181                                 }
182                         default:
183                                 if !strings.Contains(gotPanic, tt.wantPanic) {
184                                         t.Fatalf("panic message:\ngot:  %s\nwant: %s\nreason: %v", gotPanic, tt.wantPanic, tt.reason)
185                                 }
186                         }
187                 })
188         }
189
190         if *update {
191                 mustFormatGolden(goldenFile, gotDiffs)
192         }
193 }
194
195 func comparerTests() []test {
196         const label = "Comparer"
197
198         type Iface1 interface {
199                 Method()
200         }
201         type Iface2 interface {
202                 Method()
203         }
204
205         type tarHeader struct {
206                 Name       string
207                 Mode       int64
208                 Uid        int
209                 Gid        int
210                 Size       int64
211                 ModTime    time.Time
212                 Typeflag   byte
213                 Linkname   string
214                 Uname      string
215                 Gname      string
216                 Devmajor   int64
217                 Devminor   int64
218                 AccessTime time.Time
219                 ChangeTime time.Time
220                 Xattrs     map[string]string
221         }
222
223         type namedWithUnexported struct {
224                 unexported string
225         }
226
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",
234                                 Typeflag: tf,
235                         })
236                 }
237                 return hs
238         }
239
240         return []test{{
241                 label:     label + "/Nil",
242                 x:         nil,
243                 y:         nil,
244                 wantEqual: true,
245                 reason:    "nils are equal",
246         }, {
247                 label:     label + "/Integer",
248                 x:         1,
249                 y:         1,
250                 wantEqual: true,
251                 reason:    "identical integers are equal",
252         }, {
253                 label:     label + "/UnfilteredIgnore",
254                 x:         1,
255                 y:         1,
256                 opts:      []cmp.Option{cmp.Ignore()},
257                 wantPanic: "cannot use an unfiltered option",
258                 reason:    "unfiltered options are functionally useless",
259         }, {
260                 label:     label + "/UnfilteredCompare",
261                 x:         1,
262                 y:         1,
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",
266         }, {
267                 label:     label + "/UnfilteredTransform",
268                 x:         1,
269                 y:         1,
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",
273         }, {
274                 label: label + "/AmbiguousOptions",
275                 x:     1,
276                 y:     1,
277                 opts: []cmp.Option{
278                         cmp.Comparer(func(x, y int) bool { return true }),
279                         cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
280                 },
281                 wantPanic: "ambiguous set of applicable options",
282                 reason:    "both options apply on int, leading to ambiguity",
283         }, {
284                 label: label + "/IgnorePrecedence",
285                 x:     1,
286                 y:     1,
287                 opts: []cmp.Option{
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) }),
293                 },
294                 wantEqual: true,
295                 reason:    "ignore takes precedence over other options",
296         }, {
297                 label:     label + "/UnknownOption",
298                 opts:      []cmp.Option{struct{ cmp.Option }{}},
299                 wantPanic: "unknown option",
300                 reason:    "use of unknown option should panic",
301         }, {
302                 label:     label + "/StructEqual",
303                 x:         struct{ A, B, C int }{1, 2, 3},
304                 y:         struct{ A, B, C int }{1, 2, 3},
305                 wantEqual: true,
306                 reason:    "struct comparison with all equal fields",
307         }, {
308                 label:     label + "/StructInequal",
309                 x:         struct{ A, B, C int }{1, 2, 3},
310                 y:         struct{ A, B, C int }{1, 2, 4},
311                 wantEqual: false,
312                 reason:    "struct comparison with inequal C field",
313         }, {
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",
319         }, {
320                 label:     label + "/PointerStructEqual",
321                 x:         &struct{ A *int }{newInt(4)},
322                 y:         &struct{ A *int }{newInt(4)},
323                 wantEqual: true,
324                 reason:    "comparison of pointer to struct with equal A field",
325         }, {
326                 label:     label + "/PointerStructInequal",
327                 x:         &struct{ A *int }{newInt(4)},
328                 y:         &struct{ A *int }{newInt(5)},
329                 wantEqual: false,
330                 reason:    "comparison of pointer to struct with inequal A field",
331         }, {
332                 label: label + "/PointerStructTrueComparer",
333                 x:     &struct{ A *int }{newInt(4)},
334                 y:     &struct{ A *int }{newInt(5)},
335                 opts: []cmp.Option{
336                         cmp.Comparer(func(x, y int) bool { return true }),
337                 },
338                 wantEqual: true,
339                 reason:    "comparison of pointer to struct with inequal A field, but treated as equal with always equal comparer",
340         }, {
341                 label: label + "/PointerStructNonNilComparer",
342                 x:     &struct{ A *int }{newInt(4)},
343                 y:     &struct{ A *int }{newInt(5)},
344                 opts: []cmp.Option{
345                         cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }),
346                 },
347                 wantEqual: true,
348                 reason:    "comparison of pointer to struct with inequal A field, but treated as equal with comparer checking pointers for nilness",
349         }, {
350                 label:     label + "/StructNestedPointerEqual",
351                 x:         &struct{ R *bytes.Buffer }{},
352                 y:         &struct{ R *bytes.Buffer }{},
353                 wantEqual: true,
354                 reason:    "equal since both pointers in R field are nil",
355         }, {
356                 label:     label + "/StructNestedPointerInequal",
357                 x:         &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
358                 y:         &struct{ R *bytes.Buffer }{},
359                 wantEqual: false,
360                 reason:    "inequal since R field is inequal",
361         }, {
362                 label: label + "/StructNestedPointerTrueComparer",
363                 x:     &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
364                 y:     &struct{ R *bytes.Buffer }{},
365                 opts: []cmp.Option{
366                         cmp.Comparer(func(x, y io.Reader) bool { return true }),
367                 },
368                 wantEqual: true,
369                 reason:    "equal despite inequal R field values since the comparer always reports true",
370         }, {
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",
376         }, {
377                 label: label + "/StructNestedValueUnexportedPanic2",
378                 x:     &struct{ R bytes.Buffer }{},
379                 y:     &struct{ R bytes.Buffer }{},
380                 opts: []cmp.Option{
381                         cmp.Comparer(func(x, y io.Reader) bool { return true }),
382                 },
383                 wantPanic: "cannot handle unexported field",
384                 reason:    "bytes.Buffer value does not implement io.Reader",
385         }, {
386                 label: label + "/StructNestedValueEqual",
387                 x:     &struct{ R bytes.Buffer }{},
388                 y:     &struct{ R bytes.Buffer }{},
389                 opts: []cmp.Option{
390                         cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }),
391                         cmp.Comparer(func(x, y io.Reader) bool { return true }),
392                 },
393                 wantEqual: true,
394                 reason:    "bytes.Buffer pointer due to shallow copy does implement io.Reader",
395         }, {
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",
401         }, {
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
408                         }
409                         return x.String() == y.String()
410                 })},
411                 wantEqual: true,
412                 reason:    "comparer for *regexp.Regexp applied with equal regexp strings",
413         }, {
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
420                         }
421                         return x.String() == y.String()
422                 })},
423                 wantEqual: false,
424                 reason:    "comparer for *regexp.Regexp applied with inequal regexp strings",
425         }, {
426                 label: label + "/TriplePointerEqual",
427                 x: func() ***int {
428                         a := 0
429                         b := &a
430                         c := &b
431                         return &c
432                 }(),
433                 y: func() ***int {
434                         a := 0
435                         b := &a
436                         c := &b
437                         return &c
438                 }(),
439                 wantEqual: true,
440                 reason:    "three layers of pointers to the same value",
441         }, {
442                 label: label + "/TriplePointerInequal",
443                 x: func() ***int {
444                         a := 0
445                         b := &a
446                         c := &b
447                         return &c
448                 }(),
449                 y: func() ***int {
450                         a := 1
451                         b := &a
452                         c := &b
453                         return &c
454                 }(),
455                 wantEqual: false,
456                 reason:    "three layers of pointers to different values",
457         }, {
458                 label:     label + "/SliceWithDifferingCapacity",
459                 x:         []int{1, 2, 3, 4, 5}[:3],
460                 y:         []int{1, 2, 3},
461                 wantEqual: true,
462                 reason:    "elements past the slice length are not compared",
463         }, {
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() })},
468                 wantEqual: true,
469                 reason:    "comparer for fmt.Stringer used to compare differing types with same string",
470         }, {
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() })},
475                 wantEqual: false,
476                 reason:    "comparer for fmt.Stringer used to compare differing types with different strings",
477         }, {
478                 label:     label + "/DifferingHash",
479                 x:         md5.Sum([]byte{'a'}),
480                 y:         md5.Sum([]byte{'b'}),
481                 wantEqual: false,
482                 reason:    "hash differs",
483         }, {
484                 label:     label + "/NilStringer",
485                 x:         new(fmt.Stringer),
486                 y:         nil,
487                 wantEqual: false,
488                 reason:    "by default differing types are always inequal",
489         }, {
490                 label:     label + "/TarHeaders",
491                 x:         makeTarHeaders('0'),
492                 y:         makeTarHeaders('\x00'),
493                 wantEqual: false,
494                 reason:    "type flag differs between the headers",
495         }, {
496                 label: label + "/NonDeterministicComparer",
497                 x:     make([]int, 1000),
498                 y:     make([]int, 1000),
499                 opts: []cmp.Option{
500                         cmp.Comparer(func(_, _ int) bool {
501                                 return rand.Intn(2) == 0
502                         }),
503                 },
504                 wantPanic: "non-deterministic or non-symmetric function detected",
505                 reason:    "non-deterministic comparer",
506         }, {
507                 label: label + "/NonDeterministicFilter",
508                 x:     make([]int, 1000),
509                 y:     make([]int, 1000),
510                 opts: []cmp.Option{
511                         cmp.FilterValues(func(_, _ int) bool {
512                                 return rand.Intn(2) == 0
513                         }, cmp.Ignore()),
514                 },
515                 wantPanic: "non-deterministic or non-symmetric function detected",
516                 reason:    "non-deterministic filter",
517         }, {
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},
521                 opts: []cmp.Option{
522                         cmp.Comparer(func(x, y int) bool {
523                                 return x < y
524                         }),
525                 },
526                 wantPanic: "non-deterministic or non-symmetric function detected",
527                 reason:    "asymmetric comparer",
528         }, {
529                 label: label + "/NonDeterministicTransformer",
530                 x:     make([]string, 1000),
531                 y:     make([]string, 1000),
532                 opts: []cmp.Option{
533                         cmp.Transformer("λ", func(x string) int {
534                                 return rand.Int()
535                         }),
536                 },
537                 wantPanic: "non-deterministic function detected",
538                 reason:    "non-deterministic transformer",
539         }, {
540                 label: label + "/IrreflexiveComparison",
541                 x:     make([]int, 10),
542                 y:     make([]int, 10),
543                 opts: []cmp.Option{
544                         cmp.Transformer("λ", func(x int) float64 {
545                                 return math.NaN()
546                         }),
547                 },
548                 wantEqual: false,
549                 reason:    "dynamic checks should not panic for non-reflexive comparisons",
550         }, {
551                 label:     label + "/StringerMapKey",
552                 x:         map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}},
553                 y:         map[*pb.Stringer]*pb.Stringer(nil),
554                 wantEqual: false,
555                 reason:    "stringer should be used to format the map key",
556         }, {
557                 label:     label + "/StringerBacktick",
558                 x:         []*pb.Stringer{{`multi\nline\nline\nline`}},
559                 wantEqual: false,
560                 reason:    "stringer should use backtick quoting if more readable",
561         }, {
562                 label: label + "/AvoidPanicAssignableConverter",
563                 x:     struct{ I Iface2 }{},
564                 y:     struct{ I Iface2 }{},
565                 opts: []cmp.Option{
566                         cmp.Comparer(func(x, y Iface1) bool {
567                                 return x == nil && y == nil
568                         }),
569                 },
570                 wantEqual: true,
571                 reason:    "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
572         }, {
573                 label: label + "/AvoidPanicAssignableTransformer",
574                 x:     struct{ I Iface2 }{},
575                 y:     struct{ I Iface2 }{},
576                 opts: []cmp.Option{
577                         cmp.Transformer("λ", func(v Iface1) bool {
578                                 return v == nil
579                         }),
580                 },
581                 wantEqual: true,
582                 reason:    "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
583         }, {
584                 label: label + "/AvoidPanicAssignableFilter",
585                 x:     struct{ I Iface2 }{},
586                 y:     struct{ I Iface2 }{},
587                 opts: []cmp.Option{
588                         cmp.FilterValues(func(x, y Iface1) bool {
589                                 return x == nil && y == nil
590                         }, cmp.Ignore()),
591                 },
592                 wantEqual: true,
593                 reason:    "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
594         }, {
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"}},
598                 wantEqual: false,
599                 reason:    "dynamic map with differing types (but semantically equivalent values) should be inequal",
600         }, {
601                 label: label + "/MapKeyPointer",
602                 x: map[*int]string{
603                         new(int): "hello",
604                 },
605                 y: map[*int]string{
606                         new(int): "world",
607                 },
608                 wantEqual: false,
609                 reason:    "map keys should use shallow (rather than deep) pointer comparison",
610         }, {
611                 label: label + "/IgnoreSliceElements",
612                 x: [2][]int{
613                         {0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 7, 8, 0, 9, 0, 0},
614                         {0, 1, 0, 0, 0, 20},
615                 },
616                 y: [2][]int{
617                         {1, 2, 3, 0, 4, 5, 6, 7, 0, 8, 9, 0, 0, 0},
618                         {0, 0, 1, 2, 0, 0, 0},
619                 },
620                 opts: []cmp.Option{
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 {
624                                         return true
625                                 }
626                                 if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
627                                         return true
628                                 }
629                                 return false
630                         }, cmp.Ignore()),
631                 },
632                 wantEqual: false,
633                 reason:    "all zero slice elements are ignored (even if missing)",
634         }, {
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},
639                 },
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},
643                 },
644                 opts: []cmp.Option{
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 {
648                                         return true
649                                 }
650                                 if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
651                                         return true
652                                 }
653                                 return false
654                         }, cmp.Ignore()),
655                 },
656                 wantEqual: false,
657                 reason:    "all zero map entries are ignored (even if missing)",
658         }, {
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",
664         }, {
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",
670         }, {
671                 label: label + "/UnaddressableStruct",
672                 x:     struct{ s fmt.Stringer }{new(bytes.Buffer)},
673                 y:     struct{ s fmt.Stringer }{nil},
674                 opts: []cmp.Option{
675                         cmp.AllowUnexported(struct{ s fmt.Stringer }{}),
676                         cmp.FilterPath(func(p cmp.Path) bool {
677                                 if _, ok := p.Last().(cmp.StructField); !ok {
678                                         return false
679                                 }
680
681                                 t := p.Index(-1).Type()
682                                 vx, vy := p.Index(-1).Values()
683                                 pvx, pvy := p.Index(-2).Values()
684                                 switch {
685                                 case vx.Type() != t:
686                                         panic(fmt.Sprintf("inconsistent type: %v != %v", vx.Type(), t))
687                                 case vy.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()))
693                                 }
694                                 return true
695                         }, cmp.Ignore()),
696                 },
697                 wantEqual: true,
698                 reason:    "verify that exporter does not leak implementation details",
699         }}
700 }
701
702 func transformerTests() []test {
703         type StringBytes struct {
704                 String string
705                 Bytes  []byte
706         }
707
708         const label = "Transformer"
709
710         transformOnce := func(name string, f interface{}) cmp.Option {
711                 xform := cmp.Transformer(name, f)
712                 return cmp.FilterPath(func(p cmp.Path) bool {
713                         for _, ps := range p {
714                                 if tr, ok := ps.(cmp.Transform); ok && tr.Option() == xform {
715                                         return false
716                                 }
717                         }
718                         return true
719                 }, xform)
720         }
721
722         return []test{{
723                 label: label + "/Uints",
724                 x:     uint8(0),
725                 y:     uint8(1),
726                 opts: []cmp.Option{
727                         cmp.Transformer("λ", func(in uint8) uint16 { return uint16(in) }),
728                         cmp.Transformer("λ", func(in uint16) uint32 { return uint32(in) }),
729                         cmp.Transformer("λ", func(in uint32) uint64 { return uint64(in) }),
730                 },
731                 wantEqual: false,
732                 reason:    "transform uint8 -> uint16 -> uint32 -> uint64",
733         }, {
734                 label: label + "/Ambiguous",
735                 x:     0,
736                 y:     1,
737                 opts: []cmp.Option{
738                         cmp.Transformer("λ", func(in int) int { return in / 2 }),
739                         cmp.Transformer("λ", func(in int) int { return in }),
740                 },
741                 wantPanic: "ambiguous set of applicable options",
742                 reason:    "both transformers apply on int",
743         }, {
744                 label: label + "/Filtered",
745                 x:     []int{0, -5, 0, -1},
746                 y:     []int{1, 3, 0, -5},
747                 opts: []cmp.Option{
748                         cmp.FilterValues(
749                                 func(x, y int) bool { return x+y >= 0 },
750                                 cmp.Transformer("λ", func(in int) int64 { return int64(in / 2) }),
751                         ),
752                         cmp.FilterValues(
753                                 func(x, y int) bool { return x+y < 0 },
754                                 cmp.Transformer("λ", func(in int) int64 { return int64(in) }),
755                         ),
756                 },
757                 wantEqual: false,
758                 reason:    "disjoint transformers filtered based on the values",
759         }, {
760                 label: label + "/DisjointOutput",
761                 x:     0,
762                 y:     1,
763                 opts: []cmp.Option{
764                         cmp.Transformer("λ", func(in int) interface{} {
765                                 if in == 0 {
766                                         return "zero"
767                                 }
768                                 return float64(in)
769                         }),
770                 },
771                 wantEqual: false,
772                 reason:    "output type differs based on input value",
773         }, {
774                 label: label + "/JSON",
775                 x: `{
776                   "firstName": "John",
777                   "lastName": "Smith",
778                   "age": 25,
779                   "isAlive": true,
780                   "address": {
781                     "city": "Los Angeles",
782                     "postalCode": "10021-3100",
783                     "state": "CA",
784                     "streetAddress": "21 2nd Street"
785                   },
786                   "phoneNumbers": [{
787                     "type": "home",
788                     "number": "212 555-4321"
789                   },{
790                     "type": "office",
791                     "number": "646 555-4567"
792                   },{
793                     "number": "123 456-7890",
794                     "type": "mobile"
795                   }],
796                   "children": []
797                 }`,
798                 y: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":25,
799                         "address":{"streetAddress":"21 2nd Street","city":"New York",
800                         "state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home",
801                         "number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{
802                         "type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`,
803                 opts: []cmp.Option{
804                         transformOnce("ParseJSON", func(s string) (m map[string]interface{}) {
805                                 if err := json.Unmarshal([]byte(s), &m); err != nil {
806                                         panic(err)
807                                 }
808                                 return m
809                         }),
810                 },
811                 wantEqual: false,
812                 reason:    "transformer used to parse JSON input",
813         }, {
814                 label: label + "/AcyclicString",
815                 x:     StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")},
816                 y:     StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")},
817                 opts: []cmp.Option{
818                         transformOnce("SplitString", func(s string) []string { return strings.Split(s, "\n") }),
819                         transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }),
820                 },
821                 wantEqual: false,
822                 reason:    "string -> []string and []byte -> [][]byte transformer only applied once",
823         }, {
824                 label: label + "/CyclicString",
825                 x:     "a\nb\nc\n",
826                 y:     "a\nb\nc\n",
827                 opts: []cmp.Option{
828                         cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }),
829                 },
830                 wantPanic: "recursive set of Transformers detected",
831                 reason:    "cyclic transformation from string -> []string -> string",
832         }, {
833                 label: label + "/CyclicComplex",
834                 x:     complex64(0),
835                 y:     complex64(0),
836                 opts: []cmp.Option{
837                         cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }),
838                         cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }),
839                         cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }),
840                 },
841                 wantPanic: "recursive set of Transformers detected",
842                 reason:    "cyclic transformation from complex64 -> complex128 -> [2]float64 -> complex64",
843         }}
844 }
845
846 func reporterTests() []test {
847         const label = "Reporter"
848
849         type (
850                 MyString    string
851                 MyByte      byte
852                 MyBytes     []byte
853                 MyInt       int8
854                 MyInts      []int8
855                 MyUint      int16
856                 MyUints     []int16
857                 MyFloat     float32
858                 MyFloats    []float32
859                 MyComposite struct {
860                         StringA string
861                         StringB MyString
862                         BytesA  []byte
863                         BytesB  []MyByte
864                         BytesC  MyBytes
865                         IntsA   []int8
866                         IntsB   []MyInt
867                         IntsC   MyInts
868                         UintsA  []uint16
869                         UintsB  []MyUint
870                         UintsC  MyUints
871                         FloatsA []float32
872                         FloatsB []MyFloat
873                         FloatsC MyFloats
874                 }
875         )
876
877         return []test{{
878                 label:     label + "/PanicStringer",
879                 x:         struct{ X fmt.Stringer }{struct{ fmt.Stringer }{nil}},
880                 y:         struct{ X fmt.Stringer }{bytes.NewBuffer(nil)},
881                 wantEqual: false,
882                 reason:    "panic from fmt.Stringer should not crash the reporter",
883         }, {
884                 label:     label + "/PanicError",
885                 x:         struct{ X error }{struct{ error }{nil}},
886                 y:         struct{ X error }{errors.New("")},
887                 wantEqual: false,
888                 reason:    "panic from error should not crash the reporter",
889         }, {
890                 label:     label + "/AmbiguousType",
891                 x:         foo1.Bar{},
892                 y:         foo2.Bar{},
893                 wantEqual: false,
894                 reason:    "reporter should display the qualified type name to disambiguate between the two values",
895         }, {
896                 label: label + "/AmbiguousPointer",
897                 x:     newInt(0),
898                 y:     newInt(0),
899                 opts: []cmp.Option{
900                         cmp.Comparer(func(x, y *int) bool { return x == y }),
901                 },
902                 wantEqual: false,
903                 reason:    "reporter should display the address to disambiguate between the two values",
904         }, {
905                 label: label + "/AmbiguousPointerStruct",
906                 x:     struct{ I *int }{newInt(0)},
907                 y:     struct{ I *int }{newInt(0)},
908                 opts: []cmp.Option{
909                         cmp.Comparer(func(x, y *int) bool { return x == y }),
910                 },
911                 wantEqual: false,
912                 reason:    "reporter should display the address to disambiguate between the two struct fields",
913         }, {
914                 label: label + "/AmbiguousPointerSlice",
915                 x:     []*int{newInt(0)},
916                 y:     []*int{newInt(0)},
917                 opts: []cmp.Option{
918                         cmp.Comparer(func(x, y *int) bool { return x == y }),
919                 },
920                 wantEqual: false,
921                 reason:    "reporter should display the address to disambiguate between the two slice elements",
922         }, {
923                 label: label + "/AmbiguousPointerMap",
924                 x:     map[string]*int{"zero": newInt(0)},
925                 y:     map[string]*int{"zero": newInt(0)},
926                 opts: []cmp.Option{
927                         cmp.Comparer(func(x, y *int) bool { return x == y }),
928                 },
929                 wantEqual: false,
930                 reason:    "reporter should display the address to disambiguate between the two map values",
931         }, {
932                 label:     label + "/AmbiguousStringer",
933                 x:         Stringer("hello"),
934                 y:         newStringer("hello"),
935                 wantEqual: false,
936                 reason:    "reporter should avoid calling String to disambiguate between the two values",
937         }, {
938                 label:     label + "/AmbiguousStringerStruct",
939                 x:         struct{ S fmt.Stringer }{Stringer("hello")},
940                 y:         struct{ S fmt.Stringer }{newStringer("hello")},
941                 wantEqual: false,
942                 reason:    "reporter should avoid calling String to disambiguate between the two struct fields",
943         }, {
944                 label:     label + "/AmbiguousStringerSlice",
945                 x:         []fmt.Stringer{Stringer("hello")},
946                 y:         []fmt.Stringer{newStringer("hello")},
947                 wantEqual: false,
948                 reason:    "reporter should avoid calling String to disambiguate between the two slice elements",
949         }, {
950                 label:     label + "/AmbiguousStringerMap",
951                 x:         map[string]fmt.Stringer{"zero": Stringer("hello")},
952                 y:         map[string]fmt.Stringer{"zero": newStringer("hello")},
953                 wantEqual: false,
954                 reason:    "reporter should avoid calling String to disambiguate between the two map values",
955         }, {
956                 label: label + "/AmbiguousSliceHeader",
957                 x:     make([]int, 0, 5),
958                 y:     make([]int, 0, 1000),
959                 opts: []cmp.Option{
960                         cmp.Comparer(func(x, y []int) bool { return cap(x) == cap(y) }),
961                 },
962                 wantEqual: false,
963                 reason:    "reporter should display the slice header to disambiguate between the two slice values",
964         }, {
965                 label: label + "/AmbiguousStringerMapKey",
966                 x: map[interface{}]string{
967                         nil:               "nil",
968                         Stringer("hello"): "goodbye",
969                         foo1.Bar{"fizz"}:  "buzz",
970                 },
971                 y: map[interface{}]string{
972                         newStringer("hello"): "goodbye",
973                         foo2.Bar{"fizz"}:     "buzz",
974                 },
975                 wantEqual: false,
976                 reason:    "reporter should avoid calling String to disambiguate between the two map keys",
977         }, {
978                 label:     label + "/NonAmbiguousStringerMapKey",
979                 x:         map[interface{}]string{Stringer("hello"): "goodbye"},
980                 y:         map[interface{}]string{newStringer("fizz"): "buzz"},
981                 wantEqual: false,
982                 reason:    "reporter should call String as there is no ambiguity between the two map keys",
983         }, {
984                 label:     label + "/InvalidUTF8",
985                 x:         MyString("\xed\xa0\x80"),
986                 wantEqual: false,
987                 reason:    "invalid UTF-8 should format as quoted string",
988         }, {
989                 label:     label + "/UnbatchedSlice",
990                 x:         MyComposite{IntsA: []int8{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
991                 y:         MyComposite{IntsA: []int8{10, 11, 21, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
992                 wantEqual: false,
993                 reason:    "unbatched diffing desired since few elements differ",
994         }, {
995                 label:     label + "/BatchedSlice",
996                 x:         MyComposite{IntsA: []int8{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
997                 y:         MyComposite{IntsA: []int8{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
998                 wantEqual: false,
999                 reason:    "batched diffing desired since many elements differ",
1000         }, {
1001                 label:     label + "/BatchedWithComparer",
1002                 x:         MyComposite{BytesA: []byte{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
1003                 y:         MyComposite{BytesA: []byte{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
1004                 wantEqual: false,
1005                 opts: []cmp.Option{
1006                         cmp.Comparer(bytes.Equal),
1007                 },
1008                 reason: "batched diffing desired since many elements differ",
1009         }, {
1010                 label:     label + "/BatchedLong",
1011                 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}},
1012                 wantEqual: false,
1013                 reason:    "batched output desired for a single slice of primitives unique to one of the inputs",
1014         }, {
1015                 label: label + "/BatchedNamedAndUnnamed",
1016                 x: MyComposite{
1017                         BytesA:  []byte{1, 2, 3},
1018                         BytesB:  []MyByte{4, 5, 6},
1019                         BytesC:  MyBytes{7, 8, 9},
1020                         IntsA:   []int8{-1, -2, -3},
1021                         IntsB:   []MyInt{-4, -5, -6},
1022                         IntsC:   MyInts{-7, -8, -9},
1023                         UintsA:  []uint16{1000, 2000, 3000},
1024                         UintsB:  []MyUint{4000, 5000, 6000},
1025                         UintsC:  MyUints{7000, 8000, 9000},
1026                         FloatsA: []float32{1.5, 2.5, 3.5},
1027                         FloatsB: []MyFloat{4.5, 5.5, 6.5},
1028                         FloatsC: MyFloats{7.5, 8.5, 9.5},
1029                 },
1030                 y: MyComposite{
1031                         BytesA:  []byte{3, 2, 1},
1032                         BytesB:  []MyByte{6, 5, 4},
1033                         BytesC:  MyBytes{9, 8, 7},
1034                         IntsA:   []int8{-3, -2, -1},
1035                         IntsB:   []MyInt{-6, -5, -4},
1036                         IntsC:   MyInts{-9, -8, -7},
1037                         UintsA:  []uint16{3000, 2000, 1000},
1038                         UintsB:  []MyUint{6000, 5000, 4000},
1039                         UintsC:  MyUints{9000, 8000, 7000},
1040                         FloatsA: []float32{3.5, 2.5, 1.5},
1041                         FloatsB: []MyFloat{6.5, 5.5, 4.5},
1042                         FloatsC: MyFloats{9.5, 8.5, 7.5},
1043                 },
1044                 wantEqual: false,
1045                 reason:    "batched diffing available for both named and unnamed slices",
1046         }, {
1047                 label:     label + "/BinaryHexdump",
1048                 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]")},
1049                 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]")},
1050                 wantEqual: false,
1051                 reason:    "binary diff in hexdump form since data is binary data",
1052         }, {
1053                 label:     label + "/StringHexdump",
1054                 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")},
1055                 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")},
1056                 wantEqual: false,
1057                 reason:    "binary diff desired since string looks like binary data",
1058         }, {
1059                 label:     label + "/BinaryString",
1060                 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}`)},
1061                 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}`)},
1062                 wantEqual: false,
1063                 reason:    "batched textual diff desired since bytes looks like textual data",
1064         }, {
1065                 label:     label + "/TripleQuote",
1066                 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"},
1067                 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"},
1068                 wantEqual: false,
1069                 reason:    "use triple-quote syntax",
1070         }, {
1071                 label: label + "/TripleQuoteSlice",
1072                 x: []string{
1073                         "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",
1074                         "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",
1075                 },
1076                 y: []string{
1077                         "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",
1078                         "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",
1079                 },
1080                 wantEqual: false,
1081                 reason:    "use triple-quote syntax for slices of strings",
1082         }, {
1083                 label: label + "/TripleQuoteNamedTypes",
1084                 x: MyComposite{
1085                         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"),
1086                         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"),
1087                 },
1088                 y: MyComposite{
1089                         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"),
1090                         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"),
1091                 },
1092                 wantEqual: false,
1093                 reason:    "use triple-quote syntax for named types",
1094         }, {
1095                 label: label + "/TripleQuoteSliceNamedTypes",
1096                 x: []MyString{
1097                         "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",
1098                         "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",
1099                 },
1100                 y: []MyString{
1101                         "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",
1102                         "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",
1103                 },
1104                 wantEqual: false,
1105                 reason:    "use triple-quote syntax for slices of named strings",
1106         }, {
1107                 label:     label + "/TripleQuoteEndlines",
1108                 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",
1109                 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",
1110                 wantEqual: false,
1111                 reason:    "use triple-quote syntax",
1112         }, {
1113                 label:     label + "/AvoidTripleQuoteAmbiguousQuotes",
1114                 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",
1115                 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",
1116                 wantEqual: false,
1117                 reason:    "avoid triple-quote syntax due to presence of ambiguous triple quotes",
1118         }, {
1119                 label:     label + "/AvoidTripleQuoteAmbiguousEllipsis",
1120                 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",
1121                 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",
1122                 wantEqual: false,
1123                 reason:    "avoid triple-quote syntax due to presence of ambiguous ellipsis",
1124         }, {
1125                 label:     label + "/AvoidTripleQuoteNonPrintable",
1126                 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",
1127                 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",
1128                 wantEqual: false,
1129                 reason:    "use triple-quote syntax",
1130         }, {
1131                 label:     label + "/AvoidTripleQuoteIdenticalWhitespace",
1132                 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",
1133                 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",
1134                 wantEqual: false,
1135                 reason:    "avoid triple-quote syntax due to visual equivalence of differences",
1136         }, {
1137                 label: label + "/TripleQuoteStringer",
1138                 x: []fmt.Stringer{
1139                         bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")),
1140                         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")),
1141                 },
1142                 y: []fmt.Stringer{
1143                         bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")),
1144                         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")),
1145                 },
1146                 opts:      []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
1147                 wantEqual: false,
1148                 reason:    "multi-line String output should be formatted with triple quote",
1149         }, {
1150                 label:     label + "/LimitMaximumBytesDiffs",
1151                 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============="),
1152                 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============="),
1153                 wantEqual: false,
1154                 reason:    "total bytes difference output is truncated due to excessive number of differences",
1155         }, {
1156                 label:     label + "/LimitMaximumStringDiffs",
1157                 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",
1158                 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",
1159                 wantEqual: false,
1160                 reason:    "total string difference output is truncated due to excessive number of differences",
1161         }, {
1162                 label: label + "/LimitMaximumSliceDiffs",
1163                 x: func() (out []struct{ S string }) {
1164                         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") {
1165                                 out = append(out, struct{ S string }{s})
1166                         }
1167                         return out
1168                 }(),
1169                 y: func() (out []struct{ S string }) {
1170                         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") {
1171                                 out = append(out, struct{ S string }{s})
1172                         }
1173                         return out
1174                 }(),
1175                 wantEqual: false,
1176                 reason:    "total slice difference output is truncated due to excessive number of differences",
1177         }, {
1178                 label: label + "/MultilineString",
1179                 x: MyComposite{
1180                         StringA: strings.TrimPrefix(`
1181 Package cmp determines equality of values.
1182
1183 This package is intended to be a more powerful and safer alternative to
1184 reflect.DeepEqual for comparing whether two values are semantically equal.
1185
1186 The primary features of cmp are:
1187
1188 • When the default behavior of equality does not suit the needs of the test,
1189 custom equality functions can override the equality operation.
1190 For example, an equality function may report floats as equal so long as they
1191 are within some tolerance of each other.
1192
1193 • Types that have an Equal method may use that method to determine equality.
1194 This allows package authors to determine the equality operation for the types
1195 that they define.
1196
1197 • If no custom equality functions are used and no Equal method is defined,
1198 equality is determined by recursively comparing the primitive kinds on both
1199 values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
1200 fields are not compared by default; they result in panics unless suppressed
1201 by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
1202 using the AllowUnexported option.
1203 `, "\n"),
1204                 },
1205                 y: MyComposite{
1206                         StringA: strings.TrimPrefix(`
1207 Package cmp determines equality of value.
1208
1209 This package is intended to be a more powerful and safer alternative to
1210 reflect.DeepEqual for comparing whether two values are semantically equal.
1211
1212 The primary features of cmp are:
1213
1214 • When the default behavior of equality does not suit the needs of the test,
1215 custom equality functions can override the equality operation.
1216 For example, an equality function may report floats as equal so long as they
1217 are within some tolerance of each other.
1218
1219 • If no custom equality functions are used and no Equal method is defined,
1220 equality is determined by recursively comparing the primitive kinds on both
1221 values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
1222 fields are not compared by default; they result in panics unless suppressed
1223 by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
1224 using the AllowUnexported option.`, "\n"),
1225                 },
1226                 wantEqual: false,
1227                 reason:    "batched per-line diff desired since string looks like multi-line textual data",
1228         }, {
1229                 label: label + "/Slices",
1230                 x: MyComposite{
1231                         BytesA:  []byte{1, 2, 3},
1232                         BytesB:  []MyByte{4, 5, 6},
1233                         BytesC:  MyBytes{7, 8, 9},
1234                         IntsA:   []int8{-1, -2, -3},
1235                         IntsB:   []MyInt{-4, -5, -6},
1236                         IntsC:   MyInts{-7, -8, -9},
1237                         UintsA:  []uint16{1000, 2000, 3000},
1238                         UintsB:  []MyUint{4000, 5000, 6000},
1239                         UintsC:  MyUints{7000, 8000, 9000},
1240                         FloatsA: []float32{1.5, 2.5, 3.5},
1241                         FloatsB: []MyFloat{4.5, 5.5, 6.5},
1242                         FloatsC: MyFloats{7.5, 8.5, 9.5},
1243                 },
1244                 y:         MyComposite{},
1245                 wantEqual: false,
1246                 reason:    "batched diffing for non-nil slices and nil slices",
1247         }, {
1248                 label: label + "/EmptySlices",
1249                 x: MyComposite{
1250                         BytesA:  []byte{},
1251                         BytesB:  []MyByte{},
1252                         BytesC:  MyBytes{},
1253                         IntsA:   []int8{},
1254                         IntsB:   []MyInt{},
1255                         IntsC:   MyInts{},
1256                         UintsA:  []uint16{},
1257                         UintsB:  []MyUint{},
1258                         UintsC:  MyUints{},
1259                         FloatsA: []float32{},
1260                         FloatsB: []MyFloat{},
1261                         FloatsC: MyFloats{},
1262                 },
1263                 y:         MyComposite{},
1264                 wantEqual: false,
1265                 reason:    "batched diffing for empty slices and nil slices",
1266         }}
1267 }
1268
1269 func embeddedTests() []test {
1270         const label = "EmbeddedStruct"
1271
1272         privateStruct := *new(ts.ParentStructA).PrivateStruct()
1273
1274         createStructA := func(i int) ts.ParentStructA {
1275                 s := ts.ParentStructA{}
1276                 s.PrivateStruct().Public = 1 + i
1277                 s.PrivateStruct().SetPrivate(2 + i)
1278                 return s
1279         }
1280
1281         createStructB := func(i int) ts.ParentStructB {
1282                 s := ts.ParentStructB{}
1283                 s.PublicStruct.Public = 1 + i
1284                 s.PublicStruct.SetPrivate(2 + i)
1285                 return s
1286         }
1287
1288         createStructC := func(i int) ts.ParentStructC {
1289                 s := ts.ParentStructC{}
1290                 s.PrivateStruct().Public = 1 + i
1291                 s.PrivateStruct().SetPrivate(2 + i)
1292                 s.Public = 3 + i
1293                 s.SetPrivate(4 + i)
1294                 return s
1295         }
1296
1297         createStructD := func(i int) ts.ParentStructD {
1298                 s := ts.ParentStructD{}
1299                 s.PublicStruct.Public = 1 + i
1300                 s.PublicStruct.SetPrivate(2 + i)
1301                 s.Public = 3 + i
1302                 s.SetPrivate(4 + i)
1303                 return s
1304         }
1305
1306         createStructE := func(i int) ts.ParentStructE {
1307                 s := ts.ParentStructE{}
1308                 s.PrivateStruct().Public = 1 + i
1309                 s.PrivateStruct().SetPrivate(2 + i)
1310                 s.PublicStruct.Public = 3 + i
1311                 s.PublicStruct.SetPrivate(4 + i)
1312                 return s
1313         }
1314
1315         createStructF := func(i int) ts.ParentStructF {
1316                 s := ts.ParentStructF{}
1317                 s.PrivateStruct().Public = 1 + i
1318                 s.PrivateStruct().SetPrivate(2 + i)
1319                 s.PublicStruct.Public = 3 + i
1320                 s.PublicStruct.SetPrivate(4 + i)
1321                 s.Public = 5 + i
1322                 s.SetPrivate(6 + i)
1323                 return s
1324         }
1325
1326         createStructG := func(i int) *ts.ParentStructG {
1327                 s := ts.NewParentStructG()
1328                 s.PrivateStruct().Public = 1 + i
1329                 s.PrivateStruct().SetPrivate(2 + i)
1330                 return s
1331         }
1332
1333         createStructH := func(i int) *ts.ParentStructH {
1334                 s := ts.NewParentStructH()
1335                 s.PublicStruct.Public = 1 + i
1336                 s.PublicStruct.SetPrivate(2 + i)
1337                 return s
1338         }
1339
1340         createStructI := func(i int) *ts.ParentStructI {
1341                 s := ts.NewParentStructI()
1342                 s.PrivateStruct().Public = 1 + i
1343                 s.PrivateStruct().SetPrivate(2 + i)
1344                 s.PublicStruct.Public = 3 + i
1345                 s.PublicStruct.SetPrivate(4 + i)
1346                 return s
1347         }
1348
1349         createStructJ := func(i int) *ts.ParentStructJ {
1350                 s := ts.NewParentStructJ()
1351                 s.PrivateStruct().Public = 1 + i
1352                 s.PrivateStruct().SetPrivate(2 + i)
1353                 s.PublicStruct.Public = 3 + i
1354                 s.PublicStruct.SetPrivate(4 + i)
1355                 s.Private().Public = 5 + i
1356                 s.Private().SetPrivate(6 + i)
1357                 s.Public.Public = 7 + i
1358                 s.Public.SetPrivate(8 + i)
1359                 return s
1360         }
1361
1362         // TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/21122).
1363         wantPanicNotGo110 := func(s string) string {
1364                 if !flags.AtLeastGo110 {
1365                         return ""
1366                 }
1367                 return s
1368         }
1369
1370         return []test{{
1371                 label:     label + "/ParentStructA/PanicUnexported1",
1372                 x:         ts.ParentStructA{},
1373                 y:         ts.ParentStructA{},
1374                 wantPanic: "cannot handle unexported field",
1375                 reason:    "ParentStructA has an unexported field",
1376         }, {
1377                 label: label + "/ParentStructA/Ignored",
1378                 x:     ts.ParentStructA{},
1379                 y:     ts.ParentStructA{},
1380                 opts: []cmp.Option{
1381                         cmpopts.IgnoreUnexported(ts.ParentStructA{}),
1382                 },
1383                 wantEqual: true,
1384                 reason:    "the only field (which is unexported) of ParentStructA is ignored",
1385         }, {
1386                 label: label + "/ParentStructA/PanicUnexported2",
1387                 x:     createStructA(0),
1388                 y:     createStructA(0),
1389                 opts: []cmp.Option{
1390                         cmp.AllowUnexported(ts.ParentStructA{}),
1391                 },
1392                 wantPanic: "cannot handle unexported field",
1393                 reason:    "privateStruct also has unexported fields",
1394         }, {
1395                 label: label + "/ParentStructA/Equal",
1396                 x:     createStructA(0),
1397                 y:     createStructA(0),
1398                 opts: []cmp.Option{
1399                         cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
1400                 },
1401                 wantEqual: true,
1402                 reason:    "unexported fields of both ParentStructA and privateStruct are allowed",
1403         }, {
1404                 label: label + "/ParentStructA/Inequal",
1405                 x:     createStructA(0),
1406                 y:     createStructA(1),
1407                 opts: []cmp.Option{
1408                         cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
1409                 },
1410                 wantEqual: false,
1411                 reason:    "the two values differ on some fields",
1412         }, {
1413                 label: label + "/ParentStructB/PanicUnexported1",
1414                 x:     ts.ParentStructB{},
1415                 y:     ts.ParentStructB{},
1416                 opts: []cmp.Option{
1417                         cmpopts.IgnoreUnexported(ts.ParentStructB{}),
1418                 },
1419                 wantPanic: "cannot handle unexported field",
1420                 reason:    "PublicStruct has an unexported field",
1421         }, {
1422                 label: label + "/ParentStructB/Ignored",
1423                 x:     ts.ParentStructB{},
1424                 y:     ts.ParentStructB{},
1425                 opts: []cmp.Option{
1426                         cmpopts.IgnoreUnexported(ts.ParentStructB{}),
1427                         cmpopts.IgnoreUnexported(ts.PublicStruct{}),
1428                 },
1429                 wantEqual: true,
1430                 reason:    "unexported fields of both ParentStructB and PublicStruct are ignored",
1431         }, {
1432                 label: label + "/ParentStructB/PanicUnexported2",
1433                 x:     createStructB(0),
1434                 y:     createStructB(0),
1435                 opts: []cmp.Option{
1436                         cmp.AllowUnexported(ts.ParentStructB{}),
1437                 },
1438                 wantPanic: "cannot handle unexported field",
1439                 reason:    "PublicStruct also has unexported fields",
1440         }, {
1441                 label: label + "/ParentStructB/Equal",
1442                 x:     createStructB(0),
1443                 y:     createStructB(0),
1444                 opts: []cmp.Option{
1445                         cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
1446                 },
1447                 wantEqual: true,
1448                 reason:    "unexported fields of both ParentStructB and PublicStruct are allowed",
1449         }, {
1450                 label: label + "/ParentStructB/Inequal",
1451                 x:     createStructB(0),
1452                 y:     createStructB(1),
1453                 opts: []cmp.Option{
1454                         cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
1455                 },
1456                 wantEqual: false,
1457                 reason:    "the two values differ on some fields",
1458         }, {
1459                 label:     label + "/ParentStructC/PanicUnexported1",
1460                 x:         ts.ParentStructC{},
1461                 y:         ts.ParentStructC{},
1462                 wantPanic: "cannot handle unexported field",
1463                 reason:    "ParentStructC has unexported fields",
1464         }, {
1465                 label: label + "/ParentStructC/Ignored",
1466                 x:     ts.ParentStructC{},
1467                 y:     ts.ParentStructC{},
1468                 opts: []cmp.Option{
1469                         cmpopts.IgnoreUnexported(ts.ParentStructC{}),
1470                 },
1471                 wantEqual: true,
1472                 reason:    "unexported fields of ParentStructC are ignored",
1473         }, {
1474                 label: label + "/ParentStructC/PanicUnexported2",
1475                 x:     createStructC(0),
1476                 y:     createStructC(0),
1477                 opts: []cmp.Option{
1478                         cmp.AllowUnexported(ts.ParentStructC{}),
1479                 },
1480                 wantPanic: "cannot handle unexported field",
1481                 reason:    "privateStruct also has unexported fields",
1482         }, {
1483                 label: label + "/ParentStructC/Equal",
1484                 x:     createStructC(0),
1485                 y:     createStructC(0),
1486                 opts: []cmp.Option{
1487                         cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
1488                 },
1489                 wantEqual: true,
1490                 reason:    "unexported fields of both ParentStructC and privateStruct are allowed",
1491         }, {
1492                 label: label + "/ParentStructC/Inequal",
1493                 x:     createStructC(0),
1494                 y:     createStructC(1),
1495                 opts: []cmp.Option{
1496                         cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
1497                 },
1498                 wantEqual: false,
1499                 reason:    "the two values differ on some fields",
1500         }, {
1501                 label: label + "/ParentStructD/PanicUnexported1",
1502                 x:     ts.ParentStructD{},
1503                 y:     ts.ParentStructD{},
1504                 opts: []cmp.Option{
1505                         cmpopts.IgnoreUnexported(ts.ParentStructD{}),
1506                 },
1507                 wantPanic: "cannot handle unexported field",
1508                 reason:    "ParentStructD has unexported fields",
1509         }, {
1510                 label: label + "/ParentStructD/Ignored",
1511                 x:     ts.ParentStructD{},
1512                 y:     ts.ParentStructD{},
1513                 opts: []cmp.Option{
1514                         cmpopts.IgnoreUnexported(ts.ParentStructD{}),
1515                         cmpopts.IgnoreUnexported(ts.PublicStruct{}),
1516                 },
1517                 wantEqual: true,
1518                 reason:    "unexported fields of ParentStructD and PublicStruct are ignored",
1519         }, {
1520                 label: label + "/ParentStructD/PanicUnexported2",
1521                 x:     createStructD(0),
1522                 y:     createStructD(0),
1523                 opts: []cmp.Option{
1524                         cmp.AllowUnexported(ts.ParentStructD{}),
1525                 },
1526                 wantPanic: "cannot handle unexported field",
1527                 reason:    "PublicStruct also has unexported fields",
1528         }, {
1529                 label: label + "/ParentStructD/Equal",
1530                 x:     createStructD(0),
1531                 y:     createStructD(0),
1532                 opts: []cmp.Option{
1533                         cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
1534                 },
1535                 wantEqual: true,
1536                 reason:    "unexported fields of both ParentStructD and PublicStruct are allowed",
1537         }, {
1538                 label: label + "/ParentStructD/Inequal",
1539                 x:     createStructD(0),
1540                 y:     createStructD(1),
1541                 opts: []cmp.Option{
1542                         cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
1543                 },
1544                 wantEqual: false,
1545                 reason:    "the two values differ on some fields",
1546         }, {
1547                 label: label + "/ParentStructE/PanicUnexported1",
1548                 x:     ts.ParentStructE{},
1549                 y:     ts.ParentStructE{},
1550                 opts: []cmp.Option{
1551                         cmpopts.IgnoreUnexported(ts.ParentStructE{}),
1552                 },
1553                 wantPanic: "cannot handle unexported field",
1554                 reason:    "ParentStructE has unexported fields",
1555         }, {
1556                 label: label + "/ParentStructE/Ignored",
1557                 x:     ts.ParentStructE{},
1558                 y:     ts.ParentStructE{},
1559                 opts: []cmp.Option{
1560                         cmpopts.IgnoreUnexported(ts.ParentStructE{}),
1561                         cmpopts.IgnoreUnexported(ts.PublicStruct{}),
1562                 },
1563                 wantEqual: true,
1564                 reason:    "unexported fields of ParentStructE and PublicStruct are ignored",
1565         }, {
1566                 label: label + "/ParentStructE/PanicUnexported2",
1567                 x:     createStructE(0),
1568                 y:     createStructE(0),
1569                 opts: []cmp.Option{
1570                         cmp.AllowUnexported(ts.ParentStructE{}),
1571                 },
1572                 wantPanic: "cannot handle unexported field",
1573                 reason:    "PublicStruct and privateStruct also has unexported fields",
1574         }, {
1575                 label: label + "/ParentStructE/PanicUnexported3",
1576                 x:     createStructE(0),
1577                 y:     createStructE(0),
1578                 opts: []cmp.Option{
1579                         cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}),
1580                 },
1581                 wantPanic: "cannot handle unexported field",
1582                 reason:    "privateStruct also has unexported fields",
1583         }, {
1584                 label: label + "/ParentStructE/Equal",
1585                 x:     createStructE(0),
1586                 y:     createStructE(0),
1587                 opts: []cmp.Option{
1588                         cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
1589                 },
1590                 wantEqual: true,
1591                 reason:    "unexported fields of both ParentStructE, PublicStruct, and privateStruct are allowed",
1592         }, {
1593                 label: label + "/ParentStructE/Inequal",
1594                 x:     createStructE(0),
1595                 y:     createStructE(1),
1596                 opts: []cmp.Option{
1597                         cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
1598                 },
1599                 wantEqual: false,
1600                 reason:    "the two values differ on some fields",
1601         }, {
1602                 label: label + "/ParentStructF/PanicUnexported1",
1603                 x:     ts.ParentStructF{},
1604                 y:     ts.ParentStructF{},
1605                 opts: []cmp.Option{
1606                         cmpopts.IgnoreUnexported(ts.ParentStructF{}),
1607                 },
1608                 wantPanic: "cannot handle unexported field",
1609                 reason:    "ParentStructF has unexported fields",
1610         }, {
1611                 label: label + "/ParentStructF/Ignored",
1612                 x:     ts.ParentStructF{},
1613                 y:     ts.ParentStructF{},
1614                 opts: []cmp.Option{
1615                         cmpopts.IgnoreUnexported(ts.ParentStructF{}),
1616                         cmpopts.IgnoreUnexported(ts.PublicStruct{}),
1617                 },
1618                 wantEqual: true,
1619                 reason:    "unexported fields of ParentStructF and PublicStruct are ignored",
1620         }, {
1621                 label: label + "/ParentStructF/PanicUnexported2",
1622                 x:     createStructF(0),
1623                 y:     createStructF(0),
1624                 opts: []cmp.Option{
1625                         cmp.AllowUnexported(ts.ParentStructF{}),
1626                 },
1627                 wantPanic: "cannot handle unexported field",
1628                 reason:    "PublicStruct and privateStruct also has unexported fields",
1629         }, {
1630                 label: label + "/ParentStructF/PanicUnexported3",
1631                 x:     createStructF(0),
1632                 y:     createStructF(0),
1633                 opts: []cmp.Option{
1634                         cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}),
1635                 },
1636                 wantPanic: "cannot handle unexported field",
1637                 reason:    "privateStruct also has unexported fields",
1638         }, {
1639                 label: label + "/ParentStructF/Equal",
1640                 x:     createStructF(0),
1641                 y:     createStructF(0),
1642                 opts: []cmp.Option{
1643                         cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
1644                 },
1645                 wantEqual: true,
1646                 reason:    "unexported fields of both ParentStructF, PublicStruct, and privateStruct are allowed",
1647         }, {
1648                 label: label + "/ParentStructF/Inequal",
1649                 x:     createStructF(0),
1650                 y:     createStructF(1),
1651                 opts: []cmp.Option{
1652                         cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
1653                 },
1654                 wantEqual: false,
1655                 reason:    "the two values differ on some fields",
1656         }, {
1657                 label:     label + "/ParentStructG/PanicUnexported1",
1658                 x:         ts.ParentStructG{},
1659                 y:         ts.ParentStructG{},
1660                 wantPanic: wantPanicNotGo110("cannot handle unexported field"),
1661                 wantEqual: !flags.AtLeastGo110,
1662                 reason:    "ParentStructG has unexported fields",
1663         }, {
1664                 label: label + "/ParentStructG/Ignored",
1665                 x:     ts.ParentStructG{},
1666                 y:     ts.ParentStructG{},
1667                 opts: []cmp.Option{
1668                         cmpopts.IgnoreUnexported(ts.ParentStructG{}),
1669                 },
1670                 wantEqual: true,
1671                 reason:    "unexported fields of ParentStructG are ignored",
1672         }, {
1673                 label: label + "/ParentStructG/PanicUnexported2",
1674                 x:     createStructG(0),
1675                 y:     createStructG(0),
1676                 opts: []cmp.Option{
1677                         cmp.AllowUnexported(ts.ParentStructG{}),
1678                 },
1679                 wantPanic: "cannot handle unexported field",
1680                 reason:    "privateStruct also has unexported fields",
1681         }, {
1682                 label: label + "/ParentStructG/Equal",
1683                 x:     createStructG(0),
1684                 y:     createStructG(0),
1685                 opts: []cmp.Option{
1686                         cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
1687                 },
1688                 wantEqual: true,
1689                 reason:    "unexported fields of both ParentStructG and privateStruct are allowed",
1690         }, {
1691                 label: label + "/ParentStructG/Inequal",
1692                 x:     createStructG(0),
1693                 y:     createStructG(1),
1694                 opts: []cmp.Option{
1695                         cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
1696                 },
1697                 wantEqual: false,
1698                 reason:    "the two values differ on some fields",
1699         }, {
1700                 label:     label + "/ParentStructH/EqualNil",
1701                 x:         ts.ParentStructH{},
1702                 y:         ts.ParentStructH{},
1703                 wantEqual: true,
1704                 reason:    "PublicStruct is not compared because the pointer is nil",
1705         }, {
1706                 label:     label + "/ParentStructH/PanicUnexported1",
1707                 x:         createStructH(0),
1708                 y:         createStructH(0),
1709                 wantPanic: "cannot handle unexported field",
1710                 reason:    "PublicStruct has unexported fields",
1711         }, {
1712                 label: label + "/ParentStructH/Ignored",
1713                 x:     ts.ParentStructH{},
1714                 y:     ts.ParentStructH{},
1715                 opts: []cmp.Option{
1716                         cmpopts.IgnoreUnexported(ts.ParentStructH{}),
1717                 },
1718                 wantEqual: true,
1719                 reason:    "unexported fields of ParentStructH are ignored (it has none)",
1720         }, {
1721                 label: label + "/ParentStructH/PanicUnexported2",
1722                 x:     createStructH(0),
1723                 y:     createStructH(0),
1724                 opts: []cmp.Option{
1725                         cmp.AllowUnexported(ts.ParentStructH{}),
1726                 },
1727                 wantPanic: "cannot handle unexported field",
1728                 reason:    "PublicStruct also has unexported fields",
1729         }, {
1730                 label: label + "/ParentStructH/Equal",
1731                 x:     createStructH(0),
1732                 y:     createStructH(0),
1733                 opts: []cmp.Option{
1734                         cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
1735                 },
1736                 wantEqual: true,
1737                 reason:    "unexported fields of both ParentStructH and PublicStruct are allowed",
1738         }, {
1739                 label: label + "/ParentStructH/Inequal",
1740                 x:     createStructH(0),
1741                 y:     createStructH(1),
1742                 opts: []cmp.Option{
1743                         cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
1744                 },
1745                 wantEqual: false,
1746                 reason:    "the two values differ on some fields",
1747         }, {
1748                 label:     label + "/ParentStructI/PanicUnexported1",
1749                 x:         ts.ParentStructI{},
1750                 y:         ts.ParentStructI{},
1751                 wantPanic: wantPanicNotGo110("cannot handle unexported field"),
1752                 wantEqual: !flags.AtLeastGo110,
1753                 reason:    "ParentStructI has unexported fields",
1754         }, {
1755                 label: label + "/ParentStructI/Ignored1",
1756                 x:     ts.ParentStructI{},
1757                 y:     ts.ParentStructI{},
1758                 opts: []cmp.Option{
1759                         cmpopts.IgnoreUnexported(ts.ParentStructI{}),
1760                 },
1761                 wantEqual: true,
1762                 reason:    "unexported fields of ParentStructI are ignored",
1763         }, {
1764                 label: label + "/ParentStructI/PanicUnexported2",
1765                 x:     createStructI(0),
1766                 y:     createStructI(0),
1767                 opts: []cmp.Option{
1768                         cmpopts.IgnoreUnexported(ts.ParentStructI{}),
1769                 },
1770                 wantPanic: "cannot handle unexported field",
1771                 reason:    "PublicStruct and privateStruct also has unexported fields",
1772         }, {
1773                 label: label + "/ParentStructI/Ignored2",
1774                 x:     createStructI(0),
1775                 y:     createStructI(0),
1776                 opts: []cmp.Option{
1777                         cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
1778                 },
1779                 wantEqual: true,
1780                 reason:    "unexported fields of ParentStructI and PublicStruct are ignored",
1781         }, {
1782                 label: label + "/ParentStructI/PanicUnexported3",
1783                 x:     createStructI(0),
1784                 y:     createStructI(0),
1785                 opts: []cmp.Option{
1786                         cmp.AllowUnexported(ts.ParentStructI{}),
1787                 },
1788                 wantPanic: "cannot handle unexported field",
1789                 reason:    "PublicStruct and privateStruct also has unexported fields",
1790         }, {
1791                 label: label + "/ParentStructI/Equal",
1792                 x:     createStructI(0),
1793                 y:     createStructI(0),
1794                 opts: []cmp.Option{
1795                         cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
1796                 },
1797                 wantEqual: true,
1798                 reason:    "unexported fields of both ParentStructI, PublicStruct, and privateStruct are allowed",
1799         }, {
1800                 label: label + "/ParentStructI/Inequal",
1801                 x:     createStructI(0),
1802                 y:     createStructI(1),
1803                 opts: []cmp.Option{
1804                         cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
1805                 },
1806                 wantEqual: false,
1807                 reason:    "the two values differ on some fields",
1808         }, {
1809                 label:     label + "/ParentStructJ/PanicUnexported1",
1810                 x:         ts.ParentStructJ{},
1811                 y:         ts.ParentStructJ{},
1812                 wantPanic: "cannot handle unexported field",
1813                 reason:    "ParentStructJ has unexported fields",
1814         }, {
1815                 label: label + "/ParentStructJ/PanicUnexported2",
1816                 x:     ts.ParentStructJ{},
1817                 y:     ts.ParentStructJ{},
1818                 opts: []cmp.Option{
1819                         cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
1820                 },
1821                 wantPanic: "cannot handle unexported field",
1822                 reason:    "PublicStruct and privateStruct also has unexported fields",
1823         }, {
1824                 label: label + "/ParentStructJ/Ignored",
1825                 x:     ts.ParentStructJ{},
1826                 y:     ts.ParentStructJ{},
1827                 opts: []cmp.Option{
1828                         cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
1829                 },
1830                 wantEqual: true,
1831                 reason:    "unexported fields of ParentStructJ and PublicStruct are ignored",
1832         }, {
1833                 label: label + "/ParentStructJ/PanicUnexported3",
1834                 x:     createStructJ(0),
1835                 y:     createStructJ(0),
1836                 opts: []cmp.Option{
1837                         cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
1838                 },
1839                 wantPanic: "cannot handle unexported field",
1840                 reason:    "privateStruct also has unexported fields",
1841         }, {
1842                 label: label + "/ParentStructJ/Equal",
1843                 x:     createStructJ(0),
1844                 y:     createStructJ(0),
1845                 opts: []cmp.Option{
1846                         cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
1847                 },
1848                 wantEqual: true,
1849                 reason:    "unexported fields of both ParentStructJ, PublicStruct, and privateStruct are allowed",
1850         }, {
1851                 label: label + "/ParentStructJ/Inequal",
1852                 x:     createStructJ(0),
1853                 y:     createStructJ(1),
1854                 opts: []cmp.Option{
1855                         cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
1856                 },
1857                 wantEqual: false,
1858                 reason:    "the two values differ on some fields",
1859         }}
1860 }
1861
1862 func methodTests() []test {
1863         const label = "EqualMethod"
1864
1865         // A common mistake that the Equal method is on a pointer receiver,
1866         // but only a non-pointer value is present in the struct.
1867         // A transform can be used to forcibly reference the value.
1868         addrTransform := cmp.FilterPath(func(p cmp.Path) bool {
1869                 if len(p) == 0 {
1870                         return false
1871                 }
1872                 t := p[len(p)-1].Type()
1873                 if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr {
1874                         return false
1875                 }
1876                 if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
1877                         tf := m.Func.Type()
1878                         return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
1879                                 tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true)
1880                 }
1881                 return false
1882         }, cmp.Transformer("Addr", func(x interface{}) interface{} {
1883                 v := reflect.ValueOf(x)
1884                 vp := reflect.New(v.Type())
1885                 vp.Elem().Set(v)
1886                 return vp.Interface()
1887         }))
1888
1889         // For each of these types, there is an Equal method defined, which always
1890         // returns true, while the underlying data are fundamentally different.
1891         // Since the method should be called, these are expected to be equal.
1892         return []test{{
1893                 label:     label + "/StructA/ValueEqual",
1894                 x:         ts.StructA{X: "NotEqual"},
1895                 y:         ts.StructA{X: "not_equal"},
1896                 wantEqual: true,
1897                 reason:    "Equal method on StructA value called",
1898         }, {
1899                 label:     label + "/StructA/PointerEqual",
1900                 x:         &ts.StructA{X: "NotEqual"},
1901                 y:         &ts.StructA{X: "not_equal"},
1902                 wantEqual: true,
1903                 reason:    "Equal method on StructA pointer called",
1904         }, {
1905                 label:     label + "/StructB/ValueInequal",
1906                 x:         ts.StructB{X: "NotEqual"},
1907                 y:         ts.StructB{X: "not_equal"},
1908                 wantEqual: false,
1909                 reason:    "Equal method on StructB value not called",
1910         }, {
1911                 label:     label + "/StructB/ValueAddrEqual",
1912                 x:         ts.StructB{X: "NotEqual"},
1913                 y:         ts.StructB{X: "not_equal"},
1914                 opts:      []cmp.Option{addrTransform},
1915                 wantEqual: true,
1916                 reason:    "Equal method on StructB pointer called due to shallow copy transform",
1917         }, {
1918                 label:     label + "/StructB/PointerEqual",
1919                 x:         &ts.StructB{X: "NotEqual"},
1920                 y:         &ts.StructB{X: "not_equal"},
1921                 wantEqual: true,
1922                 reason:    "Equal method on StructB pointer called",
1923         }, {
1924                 label:     label + "/StructC/ValueEqual",
1925                 x:         ts.StructC{X: "NotEqual"},
1926                 y:         ts.StructC{X: "not_equal"},
1927                 wantEqual: true,
1928                 reason:    "Equal method on StructC value called",
1929         }, {
1930                 label:     label + "/StructC/PointerEqual",
1931                 x:         &ts.StructC{X: "NotEqual"},
1932                 y:         &ts.StructC{X: "not_equal"},
1933                 wantEqual: true,
1934                 reason:    "Equal method on StructC pointer called",
1935         }, {
1936                 label:     label + "/StructD/ValueInequal",
1937                 x:         ts.StructD{X: "NotEqual"},
1938                 y:         ts.StructD{X: "not_equal"},
1939                 wantEqual: false,
1940                 reason:    "Equal method on StructD value not called",
1941         }, {
1942                 label:     label + "/StructD/ValueAddrEqual",
1943                 x:         ts.StructD{X: "NotEqual"},
1944                 y:         ts.StructD{X: "not_equal"},
1945                 opts:      []cmp.Option{addrTransform},
1946                 wantEqual: true,
1947                 reason:    "Equal method on StructD pointer called due to shallow copy transform",
1948         }, {
1949                 label:     label + "/StructD/PointerEqual",
1950                 x:         &ts.StructD{X: "NotEqual"},
1951                 y:         &ts.StructD{X: "not_equal"},
1952                 wantEqual: true,
1953                 reason:    "Equal method on StructD pointer called",
1954         }, {
1955                 label:     label + "/StructE/ValueInequal",
1956                 x:         ts.StructE{X: "NotEqual"},
1957                 y:         ts.StructE{X: "not_equal"},
1958                 wantEqual: false,
1959                 reason:    "Equal method on StructE value not called",
1960         }, {
1961                 label:     label + "/StructE/ValueAddrEqual",
1962                 x:         ts.StructE{X: "NotEqual"},
1963                 y:         ts.StructE{X: "not_equal"},
1964                 opts:      []cmp.Option{addrTransform},
1965                 wantEqual: true,
1966                 reason:    "Equal method on StructE pointer called due to shallow copy transform",
1967         }, {
1968                 label:     label + "/StructE/PointerEqual",
1969                 x:         &ts.StructE{X: "NotEqual"},
1970                 y:         &ts.StructE{X: "not_equal"},
1971                 wantEqual: true,
1972                 reason:    "Equal method on StructE pointer called",
1973         }, {
1974                 label:     label + "/StructF/ValueInequal",
1975                 x:         ts.StructF{X: "NotEqual"},
1976                 y:         ts.StructF{X: "not_equal"},
1977                 wantEqual: false,
1978                 reason:    "Equal method on StructF value not called",
1979         }, {
1980                 label:     label + "/StructF/PointerEqual",
1981                 x:         &ts.StructF{X: "NotEqual"},
1982                 y:         &ts.StructF{X: "not_equal"},
1983                 wantEqual: true,
1984                 reason:    "Equal method on StructF pointer called",
1985         }, {
1986                 label:     label + "/StructA1/ValueEqual",
1987                 x:         ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
1988                 y:         ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
1989                 wantEqual: true,
1990                 reason:    "Equal method on StructA value called with equal X field",
1991         }, {
1992                 label:     label + "/StructA1/ValueInequal",
1993                 x:         ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
1994                 y:         ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
1995                 wantEqual: false,
1996                 reason:    "Equal method on StructA value called, but inequal X field",
1997         }, {
1998                 label:     label + "/StructA1/PointerEqual",
1999                 x:         &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
2000                 y:         &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
2001                 wantEqual: true,
2002                 reason:    "Equal method on StructA value called with equal X field",
2003         }, {
2004                 label:     label + "/StructA1/PointerInequal",
2005                 x:         &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
2006                 y:         &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
2007                 wantEqual: false,
2008                 reason:    "Equal method on StructA value called, but inequal X field",
2009         }, {
2010                 label:     label + "/StructB1/ValueEqual",
2011                 x:         ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
2012                 y:         ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
2013                 opts:      []cmp.Option{addrTransform},
2014                 wantEqual: true,
2015                 reason:    "Equal method on StructB pointer called due to shallow copy transform with equal X field",
2016         }, {
2017                 label:     label + "/StructB1/ValueInequal",
2018                 x:         ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
2019                 y:         ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
2020                 opts:      []cmp.Option{addrTransform},
2021                 wantEqual: false,
2022                 reason:    "Equal method on StructB pointer called due to shallow copy transform, but inequal X field",
2023         }, {
2024                 label:     label + "/StructB1/PointerEqual",
2025                 x:         &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
2026                 y:         &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
2027                 opts:      []cmp.Option{addrTransform},
2028                 wantEqual: true,
2029                 reason:    "Equal method on StructB pointer called due to shallow copy transform with equal X field",
2030         }, {
2031                 label:     label + "/StructB1/PointerInequal",
2032                 x:         &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
2033                 y:         &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
2034                 opts:      []cmp.Option{addrTransform},
2035                 wantEqual: false,
2036                 reason:    "Equal method on StructB pointer called due to shallow copy transform, but inequal X field",
2037         }, {
2038                 label:     label + "/StructC1/ValueEqual",
2039                 x:         ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
2040                 y:         ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
2041                 wantEqual: true,
2042                 reason:    "Equal method on StructC1 value called",
2043         }, {
2044                 label:     label + "/StructC1/PointerEqual",
2045                 x:         &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
2046                 y:         &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
2047                 wantEqual: true,
2048                 reason:    "Equal method on StructC1 pointer called",
2049         }, {
2050                 label:     label + "/StructD1/ValueInequal",
2051                 x:         ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2052                 y:         ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
2053                 wantEqual: false,
2054                 reason:    "Equal method on StructD1 value not called",
2055         }, {
2056                 label:     label + "/StructD1/PointerAddrEqual",
2057                 x:         ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2058                 y:         ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
2059                 opts:      []cmp.Option{addrTransform},
2060                 wantEqual: true,
2061                 reason:    "Equal method on StructD1 pointer called due to shallow copy transform",
2062         }, {
2063                 label:     label + "/StructD1/PointerEqual",
2064                 x:         &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2065                 y:         &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
2066                 wantEqual: true,
2067                 reason:    "Equal method on StructD1 pointer called",
2068         }, {
2069                 label:     label + "/StructE1/ValueInequal",
2070                 x:         ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2071                 y:         ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
2072                 wantEqual: false,
2073                 reason:    "Equal method on StructE1 value not called",
2074         }, {
2075                 label:     label + "/StructE1/ValueAddrEqual",
2076                 x:         ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2077                 y:         ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
2078                 opts:      []cmp.Option{addrTransform},
2079                 wantEqual: true,
2080                 reason:    "Equal method on StructE1 pointer called due to shallow copy transform",
2081         }, {
2082                 label:     label + "/StructE1/PointerEqual",
2083                 x:         &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2084                 y:         &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
2085                 wantEqual: true,
2086                 reason:    "Equal method on StructE1 pointer called",
2087         }, {
2088                 label:     label + "/StructF1/ValueInequal",
2089                 x:         ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
2090                 y:         ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
2091                 wantEqual: false,
2092                 reason:    "Equal method on StructF1 value not called",
2093         }, {
2094                 label:     label + "/StructF1/PointerEqual",
2095                 x:         &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
2096                 y:         &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
2097                 wantEqual: true,
2098                 reason:    "Equal method on StructF1 pointer called",
2099         }, {
2100                 label:     label + "/StructA2/ValueEqual",
2101                 x:         ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
2102                 y:         ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
2103                 wantEqual: true,
2104                 reason:    "Equal method on StructA pointer called with equal X field",
2105         }, {
2106                 label:     label + "/StructA2/ValueInequal",
2107                 x:         ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
2108                 y:         ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
2109                 wantEqual: false,
2110                 reason:    "Equal method on StructA pointer called, but inequal X field",
2111         }, {
2112                 label:     label + "/StructA2/PointerEqual",
2113                 x:         &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
2114                 y:         &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
2115                 wantEqual: true,
2116                 reason:    "Equal method on StructA pointer called with equal X field",
2117         }, {
2118                 label:     label + "/StructA2/PointerInequal",
2119                 x:         &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
2120                 y:         &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
2121                 wantEqual: false,
2122                 reason:    "Equal method on StructA pointer called, but inequal X field",
2123         }, {
2124                 label:     label + "/StructB2/ValueEqual",
2125                 x:         ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
2126                 y:         ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
2127                 wantEqual: true,
2128                 reason:    "Equal method on StructB pointer called with equal X field",
2129         }, {
2130                 label:     label + "/StructB2/ValueInequal",
2131                 x:         ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
2132                 y:         ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
2133                 wantEqual: false,
2134                 reason:    "Equal method on StructB pointer called, but inequal X field",
2135         }, {
2136                 label:     label + "/StructB2/PointerEqual",
2137                 x:         &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
2138                 y:         &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
2139                 wantEqual: true,
2140                 reason:    "Equal method on StructB pointer called with equal X field",
2141         }, {
2142                 label:     label + "/StructB2/PointerInequal",
2143                 x:         &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
2144                 y:         &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
2145                 wantEqual: false,
2146                 reason:    "Equal method on StructB pointer called, but inequal X field",
2147         }, {
2148                 label:     label + "/StructC2/ValueEqual",
2149                 x:         ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
2150                 y:         ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
2151                 wantEqual: true,
2152                 reason:    "Equal method called on StructC2 value due to forwarded StructC pointer",
2153         }, {
2154                 label:     label + "/StructC2/PointerEqual",
2155                 x:         &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
2156                 y:         &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
2157                 wantEqual: true,
2158                 reason:    "Equal method called on StructC2 pointer due to forwarded StructC pointer",
2159         }, {
2160                 label:     label + "/StructD2/ValueEqual",
2161                 x:         ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2162                 y:         ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
2163                 wantEqual: true,
2164                 reason:    "Equal method called on StructD2 value due to forwarded StructD pointer",
2165         }, {
2166                 label:     label + "/StructD2/PointerEqual",
2167                 x:         &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
2168                 y:         &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
2169                 wantEqual: true,
2170                 reason:    "Equal method called on StructD2 pointer due to forwarded StructD pointer",
2171         }, {
2172                 label:     label + "/StructE2/ValueEqual",
2173                 x:         ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2174                 y:         ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
2175                 wantEqual: true,
2176                 reason:    "Equal method called on StructE2 value due to forwarded StructE pointer",
2177         }, {
2178                 label:     label + "/StructE2/PointerEqual",
2179                 x:         &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
2180                 y:         &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
2181                 wantEqual: true,
2182                 reason:    "Equal method called on StructE2 pointer due to forwarded StructE pointer",
2183         }, {
2184                 label:     label + "/StructF2/ValueEqual",
2185                 x:         ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
2186                 y:         ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
2187                 wantEqual: true,
2188                 reason:    "Equal method called on StructF2 value due to forwarded StructF pointer",
2189         }, {
2190                 label:     label + "/StructF2/PointerEqual",
2191                 x:         &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
2192                 y:         &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
2193                 wantEqual: true,
2194                 reason:    "Equal method called on StructF2 pointer due to forwarded StructF pointer",
2195         }, {
2196                 label:     label + "/StructNo/Inequal",
2197                 x:         ts.StructNo{X: "NotEqual"},
2198                 y:         ts.StructNo{X: "not_equal"},
2199                 wantEqual: false,
2200                 reason:    "Equal method not called since StructNo is not assignable to InterfaceA",
2201         }, {
2202                 label:     label + "/AssignA/Equal",
2203                 x:         ts.AssignA(func() int { return 0 }),
2204                 y:         ts.AssignA(func() int { return 1 }),
2205                 wantEqual: true,
2206                 reason:    "Equal method called since named func is assignable to unnamed func",
2207         }, {
2208                 label:     label + "/AssignB/Equal",
2209                 x:         ts.AssignB(struct{ A int }{0}),
2210                 y:         ts.AssignB(struct{ A int }{1}),
2211                 wantEqual: true,
2212                 reason:    "Equal method called since named struct is assignable to unnamed struct",
2213         }, {
2214                 label:     label + "/AssignC/Equal",
2215                 x:         ts.AssignC(make(chan bool)),
2216                 y:         ts.AssignC(make(chan bool)),
2217                 wantEqual: true,
2218                 reason:    "Equal method called since named channel is assignable to unnamed channel",
2219         }, {
2220                 label:     label + "/AssignD/Equal",
2221                 x:         ts.AssignD(make(chan bool)),
2222                 y:         ts.AssignD(make(chan bool)),
2223                 wantEqual: true,
2224                 reason:    "Equal method called since named channel is assignable to unnamed channel",
2225         }}
2226 }
2227
2228 type (
2229         CycleAlpha struct {
2230                 Name   string
2231                 Bravos map[string]*CycleBravo
2232         }
2233         CycleBravo struct {
2234                 ID     int
2235                 Name   string
2236                 Mods   int
2237                 Alphas map[string]*CycleAlpha
2238         }
2239 )
2240
2241 func cycleTests() []test {
2242         const label = "Cycle"
2243
2244         type (
2245                 P *P
2246                 S []S
2247                 M map[int]M
2248         )
2249
2250         makeGraph := func() map[string]*CycleAlpha {
2251                 v := map[string]*CycleAlpha{
2252                         "Foo": &CycleAlpha{
2253                                 Name: "Foo",
2254                                 Bravos: map[string]*CycleBravo{
2255                                         "FooBravo": &CycleBravo{
2256                                                 Name: "FooBravo",
2257                                                 ID:   101,
2258                                                 Mods: 100,
2259                                                 Alphas: map[string]*CycleAlpha{
2260                                                         "Foo": nil, // cyclic reference
2261                                                 },
2262                                         },
2263                                 },
2264                         },
2265                         "Bar": &CycleAlpha{
2266                                 Name: "Bar",
2267                                 Bravos: map[string]*CycleBravo{
2268                                         "BarBuzzBravo": &CycleBravo{
2269                                                 Name: "BarBuzzBravo",
2270                                                 ID:   102,
2271                                                 Mods: 2,
2272                                                 Alphas: map[string]*CycleAlpha{
2273                                                         "Bar":  nil, // cyclic reference
2274                                                         "Buzz": nil, // cyclic reference
2275                                                 },
2276                                         },
2277                                         "BuzzBarBravo": &CycleBravo{
2278                                                 Name: "BuzzBarBravo",
2279                                                 ID:   103,
2280                                                 Mods: 0,
2281                                                 Alphas: map[string]*CycleAlpha{
2282                                                         "Bar":  nil, // cyclic reference
2283                                                         "Buzz": nil, // cyclic reference
2284                                                 },
2285                                         },
2286                                 },
2287                         },
2288                         "Buzz": &CycleAlpha{
2289                                 Name: "Buzz",
2290                                 Bravos: map[string]*CycleBravo{
2291                                         "BarBuzzBravo": nil, // cyclic reference
2292                                         "BuzzBarBravo": nil, // cyclic reference
2293                                 },
2294                         },
2295                 }
2296                 v["Foo"].Bravos["FooBravo"].Alphas["Foo"] = v["Foo"]
2297                 v["Bar"].Bravos["BarBuzzBravo"].Alphas["Bar"] = v["Bar"]
2298                 v["Bar"].Bravos["BarBuzzBravo"].Alphas["Buzz"] = v["Buzz"]
2299                 v["Bar"].Bravos["BuzzBarBravo"].Alphas["Bar"] = v["Bar"]
2300                 v["Bar"].Bravos["BuzzBarBravo"].Alphas["Buzz"] = v["Buzz"]
2301                 v["Buzz"].Bravos["BarBuzzBravo"] = v["Bar"].Bravos["BarBuzzBravo"]
2302                 v["Buzz"].Bravos["BuzzBarBravo"] = v["Bar"].Bravos["BuzzBarBravo"]
2303                 return v
2304         }
2305
2306         var tests []test
2307         type XY struct{ x, y interface{} }
2308         for _, tt := range []struct {
2309                 label     string
2310                 in        XY
2311                 wantEqual bool
2312                 reason    string
2313         }{{
2314                 label: "PointersEqual",
2315                 in: func() XY {
2316                         x := new(P)
2317                         *x = x
2318                         y := new(P)
2319                         *y = y
2320                         return XY{x, y}
2321                 }(),
2322                 wantEqual: true,
2323                 reason:    "equal pair of single-node pointers",
2324         }, {
2325                 label: "PointersInequal",
2326                 in: func() XY {
2327                         x := new(P)
2328                         *x = x
2329                         y1, y2 := new(P), new(P)
2330                         *y1 = y2
2331                         *y2 = y1
2332                         return XY{x, y1}
2333                 }(),
2334                 wantEqual: false,
2335                 reason:    "inequal pair of single-node and double-node pointers",
2336         }, {
2337                 label: "SlicesEqual",
2338                 in: func() XY {
2339                         x := S{nil}
2340                         x[0] = x
2341                         y := S{nil}
2342                         y[0] = y
2343                         return XY{x, y}
2344                 }(),
2345                 wantEqual: true,
2346                 reason:    "equal pair of single-node slices",
2347         }, {
2348                 label: "SlicesInequal",
2349                 in: func() XY {
2350                         x := S{nil}
2351                         x[0] = x
2352                         y1, y2 := S{nil}, S{nil}
2353                         y1[0] = y2
2354                         y2[0] = y1
2355                         return XY{x, y1}
2356                 }(),
2357                 wantEqual: false,
2358                 reason:    "inequal pair of single-node and double node slices",
2359         }, {
2360                 label: "MapsEqual",
2361                 in: func() XY {
2362                         x := M{0: nil}
2363                         x[0] = x
2364                         y := M{0: nil}
2365                         y[0] = y
2366                         return XY{x, y}
2367                 }(),
2368                 wantEqual: true,
2369                 reason:    "equal pair of single-node maps",
2370         }, {
2371                 label: "MapsInequal",
2372                 in: func() XY {
2373                         x := M{0: nil}
2374                         x[0] = x
2375                         y1, y2 := M{0: nil}, M{0: nil}
2376                         y1[0] = y2
2377                         y2[0] = y1
2378                         return XY{x, y1}
2379                 }(),
2380                 wantEqual: false,
2381                 reason:    "inequal pair of single-node and double-node maps",
2382         }, {
2383                 label:     "GraphEqual",
2384                 in:        XY{makeGraph(), makeGraph()},
2385                 wantEqual: true,
2386                 reason:    "graphs are equal since they have identical forms",
2387         }, {
2388                 label: "GraphInequalZeroed",
2389                 in: func() XY {
2390                         x := makeGraph()
2391                         y := makeGraph()
2392                         y["Foo"].Bravos["FooBravo"].ID = 0
2393                         y["Bar"].Bravos["BarBuzzBravo"].ID = 0
2394                         y["Bar"].Bravos["BuzzBarBravo"].ID = 0
2395                         return XY{x, y}
2396                 }(),
2397                 wantEqual: false,
2398                 reason:    "graphs are inequal because the ID fields are different",
2399         }, {
2400                 label: "GraphInequalStruct",
2401                 in: func() XY {
2402                         x := makeGraph()
2403                         y := makeGraph()
2404                         x["Buzz"].Bravos["BuzzBarBravo"] = &CycleBravo{
2405                                 Name: "BuzzBarBravo",
2406                                 ID:   103,
2407                         }
2408                         return XY{x, y}
2409                 }(),
2410                 wantEqual: false,
2411                 reason:    "graphs are inequal because they differ on a map element",
2412         }} {
2413                 tests = append(tests, test{
2414                         label:     label + "/" + tt.label,
2415                         x:         tt.in.x,
2416                         y:         tt.in.y,
2417                         wantEqual: tt.wantEqual,
2418                         reason:    tt.reason,
2419                 })
2420         }
2421         return tests
2422 }
2423
2424 func project1Tests() []test {
2425         const label = "Project1"
2426
2427         ignoreUnexported := cmpopts.IgnoreUnexported(
2428                 ts.EagleImmutable{},
2429                 ts.DreamerImmutable{},
2430                 ts.SlapImmutable{},
2431                 ts.GoatImmutable{},
2432                 ts.DonkeyImmutable{},
2433                 ts.LoveRadius{},
2434                 ts.SummerLove{},
2435                 ts.SummerLoveSummary{},
2436         )
2437
2438         createEagle := func() ts.Eagle {
2439                 return ts.Eagle{
2440                         Name:   "eagle",
2441                         Hounds: []string{"buford", "tannen"},
2442                         Desc:   "some description",
2443                         Dreamers: []ts.Dreamer{{}, {
2444                                 Name: "dreamer2",
2445                                 Animal: []interface{}{
2446                                         ts.Goat{
2447                                                 Target: "corporation",
2448                                                 Immutable: &ts.GoatImmutable{
2449                                                         ID:      "southbay",
2450                                                         State:   (*pb.Goat_States)(newInt(5)),
2451                                                         Started: now,
2452                                                 },
2453                                         },
2454                                         ts.Donkey{},
2455                                 },
2456                                 Amoeba: 53,
2457                         }},
2458                         Slaps: []ts.Slap{{
2459                                 Name: "slapID",
2460                                 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2461                                 Immutable: &ts.SlapImmutable{
2462                                         ID:       "immutableSlap",
2463                                         MildSlap: true,
2464                                         Started:  now,
2465                                         LoveRadius: &ts.LoveRadius{
2466                                                 Summer: &ts.SummerLove{
2467                                                         Summary: &ts.SummerLoveSummary{
2468                                                                 Devices:    []string{"foo", "bar", "baz"},
2469                                                                 ChangeType: []pb.SummerType{1, 2, 3},
2470                                                         },
2471                                                 },
2472                                         },
2473                                 },
2474                         }},
2475                         Immutable: &ts.EagleImmutable{
2476                                 ID:          "eagleID",
2477                                 Birthday:    now,
2478                                 MissingCall: (*pb.Eagle_MissingCalls)(newInt(55)),
2479                         },
2480                 }
2481         }
2482
2483         return []test{{
2484                 label: label + "/PanicUnexported",
2485                 x: ts.Eagle{Slaps: []ts.Slap{{
2486                         Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2487                 }}},
2488                 y: ts.Eagle{Slaps: []ts.Slap{{
2489                         Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2490                 }}},
2491                 wantPanic: "cannot handle unexported field",
2492                 reason:    "struct contains unexported fields",
2493         }, {
2494                 label: label + "/ProtoEqual",
2495                 x: ts.Eagle{Slaps: []ts.Slap{{
2496                         Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2497                 }}},
2498                 y: ts.Eagle{Slaps: []ts.Slap{{
2499                         Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2500                 }}},
2501                 opts:      []cmp.Option{cmp.Comparer(pb.Equal)},
2502                 wantEqual: true,
2503                 reason:    "simulated protobuf messages contain the same values",
2504         }, {
2505                 label: label + "/ProtoInequal",
2506                 x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
2507                         Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
2508                 }}},
2509                 y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
2510                         Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata2"}},
2511                 }}},
2512                 opts:      []cmp.Option{cmp.Comparer(pb.Equal)},
2513                 wantEqual: false,
2514                 reason:    "simulated protobuf messages contain different values",
2515         }, {
2516                 label:     label + "/Equal",
2517                 x:         createEagle(),
2518                 y:         createEagle(),
2519                 opts:      []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
2520                 wantEqual: true,
2521                 reason:    "equal because values are the same",
2522         }, {
2523                 label: label + "/Inequal",
2524                 x: func() ts.Eagle {
2525                         eg := createEagle()
2526                         eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2"
2527                         eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(newInt(6))
2528                         eg.Slaps[0].Immutable.MildSlap = false
2529                         return eg
2530                 }(),
2531                 y: func() ts.Eagle {
2532                         eg := createEagle()
2533                         devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices
2534                         eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1]
2535                         return eg
2536                 }(),
2537                 opts:      []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
2538                 wantEqual: false,
2539                 reason:    "inequal because some values are different",
2540         }}
2541 }
2542
2543 type germSorter []*pb.Germ
2544
2545 func (gs germSorter) Len() int           { return len(gs) }
2546 func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() }
2547 func (gs germSorter) Swap(i, j int)      { gs[i], gs[j] = gs[j], gs[i] }
2548
2549 func project2Tests() []test {
2550         const label = "Project2"
2551
2552         sortGerms := cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ {
2553                 out := append([]*pb.Germ(nil), in...) // Make copy
2554                 sort.Sort(germSorter(out))
2555                 return out
2556         })
2557
2558         equalDish := cmp.Comparer(func(x, y *ts.Dish) bool {
2559                 if x == nil || y == nil {
2560                         return x == nil && y == nil
2561                 }
2562                 px, err1 := x.Proto()
2563                 py, err2 := y.Proto()
2564                 if err1 != nil || err2 != nil {
2565                         return err1 == err2
2566                 }
2567                 return pb.Equal(px, py)
2568         })
2569
2570         createBatch := func() ts.GermBatch {
2571                 return ts.GermBatch{
2572                         DirtyGerms: map[int32][]*pb.Germ{
2573                                 17: {
2574                                         {Stringer: pb.Stringer{X: "germ1"}},
2575                                 },
2576                                 18: {
2577                                         {Stringer: pb.Stringer{X: "germ2"}},
2578                                         {Stringer: pb.Stringer{X: "germ3"}},
2579                                         {Stringer: pb.Stringer{X: "germ4"}},
2580                                 },
2581                         },
2582                         GermMap: map[int32]*pb.Germ{
2583                                 13: {Stringer: pb.Stringer{X: "germ13"}},
2584                                 21: {Stringer: pb.Stringer{X: "germ21"}},
2585                         },
2586                         DishMap: map[int32]*ts.Dish{
2587                                 0: ts.CreateDish(nil, io.EOF),
2588                                 1: ts.CreateDish(nil, io.ErrUnexpectedEOF),
2589                                 2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{X: "dish"}}, nil),
2590                         },
2591                         HasPreviousResult: true,
2592                         DirtyID:           10,
2593                         GermStrain:        421,
2594                         InfectedAt:        now,
2595                 }
2596         }
2597
2598         return []test{{
2599                 label:     label + "/PanicUnexported",
2600                 x:         createBatch(),
2601                 y:         createBatch(),
2602                 wantPanic: "cannot handle unexported field",
2603                 reason:    "struct contains unexported fields",
2604         }, {
2605                 label:     label + "/Equal",
2606                 x:         createBatch(),
2607                 y:         createBatch(),
2608                 opts:      []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
2609                 wantEqual: true,
2610                 reason:    "equal because identical values are compared",
2611         }, {
2612                 label: label + "/InequalOrder",
2613                 x:     createBatch(),
2614                 y: func() ts.GermBatch {
2615                         gb := createBatch()
2616                         s := gb.DirtyGerms[18]
2617                         s[0], s[1], s[2] = s[1], s[2], s[0]
2618                         return gb
2619                 }(),
2620                 opts:      []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
2621                 wantEqual: false,
2622                 reason:    "inequal because slice contains elements in differing order",
2623         }, {
2624                 label: label + "/EqualOrder",
2625                 x:     createBatch(),
2626                 y: func() ts.GermBatch {
2627                         gb := createBatch()
2628                         s := gb.DirtyGerms[18]
2629                         s[0], s[1], s[2] = s[1], s[2], s[0]
2630                         return gb
2631                 }(),
2632                 opts:      []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
2633                 wantEqual: true,
2634                 reason:    "equal because unordered slice is sorted using transformer",
2635         }, {
2636                 label: label + "/Inequal",
2637                 x: func() ts.GermBatch {
2638                         gb := createBatch()
2639                         delete(gb.DirtyGerms, 17)
2640                         gb.DishMap[1] = nil
2641                         return gb
2642                 }(),
2643                 y: func() ts.GermBatch {
2644                         gb := createBatch()
2645                         gb.DirtyGerms[18] = gb.DirtyGerms[18][:2]
2646                         gb.GermStrain = 22
2647                         return gb
2648                 }(),
2649                 opts:      []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
2650                 wantEqual: false,
2651                 reason:    "inequal because some values are different",
2652         }}
2653 }
2654
2655 func project3Tests() []test {
2656         const label = "Project3"
2657
2658         allowVisibility := cmp.AllowUnexported(ts.Dirt{})
2659
2660         ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})
2661
2662         transformProtos := cmp.Transformer("λ", func(x pb.Dirt) *pb.Dirt {
2663                 return &x
2664         })
2665
2666         equalTable := cmp.Comparer(func(x, y ts.Table) bool {
2667                 tx, ok1 := x.(*ts.MockTable)
2668                 ty, ok2 := y.(*ts.MockTable)
2669                 if !ok1 || !ok2 {
2670                         panic("table type must be MockTable")
2671                 }
2672                 return cmp.Equal(tx.State(), ty.State())
2673         })
2674
2675         createDirt := func() (d ts.Dirt) {
2676                 d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"}))
2677                 d.SetTimestamp(12345)
2678                 d.Discord = 554
2679                 d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "proto"}}
2680                 d.SetWizard(map[string]*pb.Wizard{
2681                         "harry": {Stringer: pb.Stringer{X: "potter"}},
2682                         "albus": {Stringer: pb.Stringer{X: "dumbledore"}},
2683                 })
2684                 d.SetLastTime(54321)
2685                 return d
2686         }
2687
2688         return []test{{
2689                 label:     label + "/PanicUnexported1",
2690                 x:         createDirt(),
2691                 y:         createDirt(),
2692                 wantPanic: "cannot handle unexported field",
2693                 reason:    "struct contains unexported fields",
2694         }, {
2695                 label:     label + "/PanicUnexported2",
2696                 x:         createDirt(),
2697                 y:         createDirt(),
2698                 opts:      []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
2699                 wantPanic: "cannot handle unexported field",
2700                 reason:    "struct contains references to simulated protobuf types with unexported fields",
2701         }, {
2702                 label:     label + "/Equal",
2703                 x:         createDirt(),
2704                 y:         createDirt(),
2705                 opts:      []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
2706                 wantEqual: true,
2707                 reason:    "transformer used to create reference to protobuf message so it works with pb.Equal",
2708         }, {
2709                 label: label + "/Inequal",
2710                 x: func() ts.Dirt {
2711                         d := createDirt()
2712                         d.SetTable(ts.CreateMockTable([]string{"a", "c"}))
2713                         d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "blah"}}
2714                         return d
2715                 }(),
2716                 y: func() ts.Dirt {
2717                         d := createDirt()
2718                         d.Discord = 500
2719                         d.SetWizard(map[string]*pb.Wizard{
2720                                 "harry": {Stringer: pb.Stringer{X: "otter"}},
2721                         })
2722                         return d
2723                 }(),
2724                 opts:      []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
2725                 wantEqual: false,
2726                 reason:    "inequal because some values are different",
2727         }}
2728 }
2729
2730 func project4Tests() []test {
2731         const label = "Project4"
2732
2733         allowVisibility := cmp.AllowUnexported(
2734                 ts.Cartel{},
2735                 ts.Headquarter{},
2736                 ts.Poison{},
2737         )
2738
2739         transformProtos := cmp.Transformer("λ", func(x pb.Restrictions) *pb.Restrictions {
2740                 return &x
2741         })
2742
2743         createCartel := func() ts.Cartel {
2744                 var p ts.Poison
2745                 p.SetPoisonType(5)
2746                 p.SetExpiration(now)
2747                 p.SetManufacturer("acme")
2748
2749                 var hq ts.Headquarter
2750                 hq.SetID(5)
2751                 hq.SetLocation("moon")
2752                 hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"})
2753                 hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{X: "metadata"}})
2754                 hq.SetPublicMessage([]byte{1, 2, 3, 4, 5})
2755                 hq.SetHorseBack("abcdef")
2756                 hq.SetStatus(44)
2757
2758                 var c ts.Cartel
2759                 c.Headquarter = hq
2760                 c.SetSource("mars")
2761                 c.SetCreationTime(now)
2762                 c.SetBoss("al capone")
2763                 c.SetPoisons([]*ts.Poison{&p})
2764
2765                 return c
2766         }
2767
2768         return []test{{
2769                 label:     label + "/PanicUnexported1",
2770                 x:         createCartel(),
2771                 y:         createCartel(),
2772                 wantPanic: "cannot handle unexported field",
2773                 reason:    "struct contains unexported fields",
2774         }, {
2775                 label:     label + "/PanicUnexported2",
2776                 x:         createCartel(),
2777                 y:         createCartel(),
2778                 opts:      []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)},
2779                 wantPanic: "cannot handle unexported field",
2780                 reason:    "struct contains references to simulated protobuf types with unexported fields",
2781         }, {
2782                 label:     label + "/Equal",
2783                 x:         createCartel(),
2784                 y:         createCartel(),
2785                 opts:      []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
2786                 wantEqual: true,
2787                 reason:    "transformer used to create reference to protobuf message so it works with pb.Equal",
2788         }, {
2789                 label: label + "/Inequal",
2790                 x: func() ts.Cartel {
2791                         d := createCartel()
2792                         var p1, p2 ts.Poison
2793                         p1.SetPoisonType(1)
2794                         p1.SetExpiration(now)
2795                         p1.SetManufacturer("acme")
2796                         p2.SetPoisonType(2)
2797                         p2.SetManufacturer("acme2")
2798                         d.SetPoisons([]*ts.Poison{&p1, &p2})
2799                         return d
2800                 }(),
2801                 y: func() ts.Cartel {
2802                         d := createCartel()
2803                         d.SetSubDivisions([]string{"bravo", "charlie"})
2804                         d.SetPublicMessage([]byte{1, 2, 4, 3, 5})
2805                         return d
2806                 }(),
2807                 opts:      []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
2808                 wantEqual: false,
2809                 reason:    "inequal because some values are different",
2810         }}
2811 }
2812
2813 // BenchmarkBytes benchmarks the performance of performing Equal or Diff on
2814 // large slices of bytes.
2815 func BenchmarkBytes(b *testing.B) {
2816         // Create a list of PathFilters that never apply, but are evaluated.
2817         const maxFilters = 5
2818         var filters cmp.Options
2819         errorIface := reflect.TypeOf((*error)(nil)).Elem()
2820         for i := 0; i <= maxFilters; i++ {
2821                 filters = append(filters, cmp.FilterPath(func(p cmp.Path) bool {
2822                         return p.Last().Type().AssignableTo(errorIface) // Never true
2823                 }, cmp.Ignore()))
2824         }
2825
2826         type benchSize struct {
2827                 label string
2828                 size  int64
2829         }
2830         for _, ts := range []benchSize{
2831                 {"4KiB", 1 << 12},
2832                 {"64KiB", 1 << 16},
2833                 {"1MiB", 1 << 20},
2834                 {"16MiB", 1 << 24},
2835         } {
2836                 bx := append(append(make([]byte, ts.size/2), 'x'), make([]byte, ts.size/2)...)
2837                 by := append(append(make([]byte, ts.size/2), 'y'), make([]byte, ts.size/2)...)
2838                 b.Run(ts.label, func(b *testing.B) {
2839                         // Iteratively add more filters that never apply, but are evaluated
2840                         // to measure the cost of simply evaluating each filter.
2841                         for i := 0; i <= maxFilters; i++ {
2842                                 b.Run(fmt.Sprintf("EqualFilter%d", i), func(b *testing.B) {
2843                                         b.ReportAllocs()
2844                                         b.SetBytes(2 * ts.size)
2845                                         for j := 0; j < b.N; j++ {
2846                                                 cmp.Equal(bx, by, filters[:i]...)
2847                                         }
2848                                 })
2849                         }
2850                         for i := 0; i <= maxFilters; i++ {
2851                                 b.Run(fmt.Sprintf("DiffFilter%d", i), func(b *testing.B) {
2852                                         b.ReportAllocs()
2853                                         b.SetBytes(2 * ts.size)
2854                                         for j := 0; j < b.N; j++ {
2855                                                 cmp.Diff(bx, by, filters[:i]...)
2856                                         }
2857                                 })
2858                         }
2859                 })
2860         }
2861 }