1
2
3
4
5 package cmp_test
6
7 import (
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
37 func init() {
38 flags.Deterministic = true
39 }
40
41 var update = flag.Bool("update", false, "update golden test files")
42
43 const goldenHeaderPrefix = "<<< "
44 const goldenFooterPrefix = ">>> "
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 func mustParseGolden(path string) map[string]string {
61 b, err := ioutil.ReadFile(path)
62 if err != nil {
63 panic(err)
64 }
65 s := string(b)
66
67 out := map[string]string{}
68 for len(s) > 0 {
69
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
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
84 name := header[len(goldenHeaderPrefix) : len(header)-len("\n")]
85 if _, ok := out[name]; ok {
86 panic(fmt.Sprintf("duplicate name: %q", name))
87 }
88 out[name] = s[len(header):j]
89 s = s[j+len(footer):]
90 }
91 return out
92 }
93 func mustFormatGolden(path string, in []struct{ Name, Data string }) {
94 var b []byte
95 for _, v := range in {
96 b = append(b, goldenHeaderPrefix+v.Name+"\n"...)
97 b = append(b, v.Data...)
98 b = append(b, goldenFooterPrefix+v.Name+"\n"...)
99 }
100 if err := ioutil.WriteFile(path, b, 0664); err != nil {
101 panic(err)
102 }
103 }
104
105 var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC)
106
107
108 func newInt(n int) *int { return &n }
109
110 type Stringer string
111
112 func newStringer(s string) fmt.Stringer { return (*Stringer)(&s) }
113 func (s Stringer) String() string { return string(s) }
114
115 type test struct {
116 label string
117 x, y interface{}
118 opts []cmp.Option
119 wantEqual bool
120 wantPanic string
121 reason string
122 }
123
124 func 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
196 func 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
716 func 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
860 func 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(`
1196 Package cmp determines equality of values.
1197
1198 This package is intended to be a more powerful and safer alternative to
1199 reflect.DeepEqual for comparing whether two values are semantically equal.
1200
1201 The primary features of cmp are:
1202
1203 • When the default behavior of equality does not suit the needs of the test,
1204 custom equality functions can override the equality operation.
1205 For example, an equality function may report floats as equal so long as they
1206 are within some tolerance of each other.
1207
1208 • Types that have an Equal method may use that method to determine equality.
1209 This allows package authors to determine the equality operation for the types
1210 that they define.
1211
1212 • If no custom equality functions are used and no Equal method is defined,
1213 equality is determined by recursively comparing the primitive kinds on both
1214 values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
1215 fields are not compared by default; they result in panics unless suppressed
1216 by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
1217 using the AllowUnexported option.
1218 `, "\n"),
1219 },
1220 y: MyComposite{
1221 StringA: strings.TrimPrefix(`
1222 Package cmp determines equality of value.
1223
1224 This package is intended to be a more powerful and safer alternative to
1225 reflect.DeepEqual for comparing whether two values are semantically equal.
1226
1227 The primary features of cmp are:
1228
1229 • When the default behavior of equality does not suit the needs of the test,
1230 custom equality functions can override the equality operation.
1231 For example, an equality function may report floats as equal so long as they
1232 are within some tolerance of each other.
1233
1234 • If no custom equality functions are used and no Equal method is defined,
1235 equality is determined by recursively comparing the primitive kinds on both
1236 values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
1237 fields are not compared by default; they result in panics unless suppressed
1238 by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
1239 using 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
1426 func 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
2009 func methodTests() []test {
2010 const label = "EqualMethod"
2011
2012
2013
2014
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
2037
2038
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
2375 type (
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
2388 func 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,
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,
2421 "Buzz": nil,
2422 },
2423 },
2424 "BuzzBarBravo": &CycleBravo{
2425 Name: "BuzzBarBravo",
2426 ID: 103,
2427 Mods: 0,
2428 Alphas: map[string]*CycleAlpha{
2429 "Bar": nil,
2430 "Buzz": nil,
2431 },
2432 },
2433 },
2434 },
2435 "Buzz": &CycleAlpha{
2436 Name: "Buzz",
2437 Bravos: map[string]*CycleBravo{
2438 "BarBuzzBravo": nil,
2439 "BuzzBarBravo": nil,
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
2571 func 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
2690 type germSorter []*pb.Germ
2691
2692 func (gs germSorter) Len() int { return len(gs) }
2693 func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() }
2694 func (gs germSorter) Swap(i, j int) { gs[i], gs[j] = gs[j], gs[i] }
2695
2696 func 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...)
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
2802 func 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
2877 func 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
2961
2962 func BenchmarkBytes(b *testing.B) {
2963
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)
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
2987
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
View as plain text