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