1 // Copyright 2017, The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE.md file.
19 "github.com/google/go-cmp/cmp"
20 "golang.org/x/xerrors"
28 MyTime struct{ time.Time }
31 C, D map[time.Time]string
34 Foo1 struct{ Alpha, Bravo, Charlie int }
46 Delta struct{ Echo Foo1 }
51 privateStruct struct{ Public, private int }
52 PublicStruct struct{ Public, private int }
69 EmptyInterface interface{}
72 func TestOptions(t *testing.T) {
73 createBar3X := func() *Bar3 {
75 Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 2}}}},
77 Bar1: Bar1{Foo3{&Foo2{&Foo1{Charlie: 7}}}},
78 Foo3: &Foo3{&Foo2{&Foo1{Bravo: 5}}},
81 Delta: struct{ Echo Foo1 }{Foo1{Charlie: 3}},
82 Foo3: &Foo3{&Foo2{&Foo1{Alpha: 1}}},
86 createBar3Y := func() *Bar3 {
88 Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 3}}}},
90 Bar1: Bar1{Foo3{&Foo2{&Foo1{Charlie: 8}}}},
91 Foo3: &Foo3{&Foo2{&Foo1{Bravo: 6}}},
94 Delta: struct{ Echo Foo1 }{Foo1{Charlie: 4}},
95 Foo3: &Foo3{&Foo2{&Foo1{Alpha: 2}}},
101 label string // Test name
102 x, y interface{} // Input values to compare
103 opts []cmp.Option // Input options
104 wantEqual bool // Whether the inputs are equal
105 wantPanic bool // Whether Equal should panic
106 reason string // The reason for the expected outcome
108 label: "EquateEmpty",
112 reason: "not equal because empty non-nil and nil slice differ",
114 label: "EquateEmpty",
117 opts: []cmp.Option{EquateEmpty()},
119 reason: "equal because EquateEmpty equates empty slices",
122 x: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
123 y: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
125 reason: "not equal because element order differs",
128 x: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
129 y: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
130 opts: []cmp.Option{SortSlices(func(x, y int) bool { return x < y })},
132 reason: "equal because SortSlices sorts the slices",
135 x: []MyInt{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
136 y: []MyInt{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
137 opts: []cmp.Option{SortSlices(func(x, y int) bool { return x < y })},
139 reason: "not equal because MyInt is not the same type as int",
142 x: []float64{0, 1, 1, 2, 2, 2},
143 y: []float64{2, 0, 2, 1, 2, 1},
144 opts: []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })},
146 reason: "equal even when sorted with duplicate elements",
149 x: []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4},
150 y: []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2},
151 opts: []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })},
153 reason: "panics because SortSlices used with non-transitive less function",
156 x: []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4},
157 y: []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2},
158 opts: []cmp.Option{SortSlices(func(x, y float64) bool {
159 return (!math.IsNaN(x) && math.IsNaN(y)) || x < y
162 reason: "no panics because SortSlices used with valid less function; not equal because NaN != NaN",
164 label: "SortSlices+EquateNaNs",
165 x: []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, math.NaN(), 3, 4, 4, 4, 4},
166 y: []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, math.NaN(), 2},
169 SortSlices(func(x, y float64) bool {
170 return (!math.IsNaN(x) && math.IsNaN(y)) || x < y
174 reason: "no panics because SortSlices used with valid less function; equal because EquateNaNs is used",
177 x: map[time.Time]string{
178 time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
179 time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
180 time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday",
182 y: map[time.Time]string{
183 time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
184 time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
185 time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday",
188 reason: "not equal because timezones differ",
191 x: map[time.Time]string{
192 time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
193 time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
194 time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday",
196 y: map[time.Time]string{
197 time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
198 time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
199 time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday",
201 opts: []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
203 reason: "equal because SortMaps flattens to a slice where Time.Equal can be used",
206 x: map[MyTime]string{
207 {time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}: "0th birthday",
208 {time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)}: "1st birthday",
209 {time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC)}: "2nd birthday",
211 y: map[MyTime]string{
212 {time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "0th birthday",
213 {time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "1st birthday",
214 {time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "2nd birthday",
216 opts: []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
218 reason: "not equal because MyTime is not assignable to time.Time",
221 x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
222 // => {0, 1, 2, 3, -1, -2, -3},
223 y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
224 // => {0, 1, 2, 3, 100, 200, 300},
225 opts: []cmp.Option{SortMaps(func(a, b int) bool {
226 if -10 < a && a <= 0 {
229 if -10 < b && b <= 0 {
235 reason: "not equal because values differ even though SortMap provides valid ordering",
238 x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
239 // => {0, 1, 2, 3, -1, -2, -3},
240 y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
241 // => {0, 1, 2, 3, 100, 200, 300},
243 SortMaps(func(x, y int) bool {
244 if -10 < x && x <= 0 {
247 if -10 < y && y <= 0 {
252 cmp.Comparer(func(x, y int) bool {
253 if -10 < x && x <= 0 {
256 if -10 < y && y <= 0 {
263 reason: "equal because Comparer used to equate differences",
266 x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
268 opts: []cmp.Option{SortMaps(func(x, y int) bool {
269 return x < y && x >= 0 && y >= 0
272 reason: "panics because SortMaps used with non-transitive less function",
275 x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
277 opts: []cmp.Option{SortMaps(func(x, y int) bool {
278 return math.Abs(float64(x)) < math.Abs(float64(y))
281 reason: "panics because SortMaps used with partial less function",
283 label: "EquateEmpty+SortSlices+SortMaps",
285 A: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
286 C: map[time.Time]string{
287 time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
288 time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
290 D: map[time.Time]string{},
293 A: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
295 C: map[time.Time]string{
296 time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
297 time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
302 SortSlices(func(x, y int) bool { return x < y }),
303 SortMaps(func(x, y time.Time) bool { return x.Before(y) }),
306 reason: "no panics because EquateEmpty should compose with the sort options",
308 label: "EquateApprox",
312 reason: "not equal because floats do not exactly matches",
314 label: "EquateApprox",
317 opts: []cmp.Option{EquateApprox(0, 0)},
319 reason: "not equal because EquateApprox(0 ,0) is equivalent to using ==",
321 label: "EquateApprox",
324 opts: []cmp.Option{EquateApprox(0.003, 0.009)},
326 reason: "not equal because EquateApprox is too strict",
328 label: "EquateApprox",
331 opts: []cmp.Option{EquateApprox(0, 0.011)},
333 reason: "equal because margin is loose enough to match",
335 label: "EquateApprox",
338 opts: []cmp.Option{EquateApprox(0.004, 0)},
340 reason: "equal because fraction is loose enough to match",
342 label: "EquateApprox",
345 opts: []cmp.Option{EquateApprox(0.004, 0.011)},
347 reason: "equal because both the margin and fraction are loose enough to match",
349 label: "EquateApprox",
352 opts: []cmp.Option{EquateApprox(0.004, 0)},
354 reason: "not equal because the types differ",
356 label: "EquateApprox",
359 opts: []cmp.Option{EquateApprox(0.004, 0)},
361 reason: "equal because EquateApprox also applies on float32s",
363 label: "EquateApprox",
364 x: []float64{math.Inf(+1), math.Inf(-1)},
365 y: []float64{math.Inf(+1), math.Inf(-1)},
366 opts: []cmp.Option{EquateApprox(0, 1)},
368 reason: "equal because we fall back on == which matches Inf (EquateApprox does not apply on Inf) ",
370 label: "EquateApprox",
371 x: []float64{math.Inf(+1), -1e100},
372 y: []float64{+1e100, math.Inf(-1)},
373 opts: []cmp.Option{EquateApprox(0, 1)},
375 reason: "not equal because we fall back on == where Inf != 1e100 (EquateApprox does not apply on Inf)",
377 label: "EquateApprox",
380 opts: []cmp.Option{EquateApprox(math.Inf(+1), 0)},
382 reason: "equal because infinite fraction matches everything",
384 label: "EquateApprox",
387 opts: []cmp.Option{EquateApprox(0, math.Inf(+1))},
389 reason: "equal because infinite margin matches everything",
391 label: "EquateApprox",
394 opts: []cmp.Option{EquateApprox(0, 0)},
396 reason: "equal because EquateApprox(0, 0) is equivalent to ==",
398 label: "EquateApprox",
400 y: math.Nextafter(math.Pi, math.Inf(+1)),
401 opts: []cmp.Option{EquateApprox(0, 0)},
403 reason: "not equal because EquateApprox(0, 0) is equivalent to ==",
406 x: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
407 y: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
409 reason: "not equal because NaN != NaN",
412 x: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
413 y: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
414 opts: []cmp.Option{EquateNaNs()},
416 reason: "equal because EquateNaNs allows NaN == NaN",
419 x: []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0},
420 y: []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0},
421 opts: []cmp.Option{EquateNaNs()},
423 reason: "equal because EquateNaNs operates on float32",
425 label: "EquateApprox+EquateNaNs",
426 x: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.01, 5001},
427 y: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.02, 5002},
430 EquateApprox(0.01, 0),
433 reason: "equal because EquateNaNs and EquateApprox compose together",
435 label: "EquateApprox+EquateNaNs",
436 x: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.01, 5001},
437 y: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.02, 5002},
440 EquateApprox(0.01, 0),
443 reason: "not equal because EquateApprox and EquateNaNs do not apply on a named type",
445 label: "EquateApprox+EquateNaNs+Transform",
446 x: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.01, 5001},
447 y: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.02, 5002},
449 cmp.Transformer("", func(x MyFloat) float64 { return float64(x) }),
451 EquateApprox(0.01, 0),
454 reason: "equal because named type is transformed to float64",
456 label: "EquateApproxTime",
457 x: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
458 y: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
459 opts: []cmp.Option{EquateApproxTime(0)},
461 reason: "equal because times are identical",
463 label: "EquateApproxTime",
464 x: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
465 y: time.Date(2009, 11, 10, 23, 0, 3, 0, time.UTC),
466 opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
468 reason: "equal because time is exactly at the allowed margin",
470 label: "EquateApproxTime",
471 x: time.Date(2009, 11, 10, 23, 0, 3, 0, time.UTC),
472 y: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
473 opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
475 reason: "equal because time is exactly at the allowed margin (negative)",
477 label: "EquateApproxTime",
478 x: time.Date(2009, 11, 10, 23, 0, 3, 0, time.UTC),
479 y: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
480 opts: []cmp.Option{EquateApproxTime(3*time.Second - 1)},
482 reason: "not equal because time is outside allowed margin",
484 label: "EquateApproxTime",
485 x: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
486 y: time.Date(2009, 11, 10, 23, 0, 3, 0, time.UTC),
487 opts: []cmp.Option{EquateApproxTime(3*time.Second - 1)},
489 reason: "not equal because time is outside allowed margin (negative)",
491 label: "EquateApproxTime",
494 opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
496 reason: "equal because both times are zero",
498 label: "EquateApproxTime",
500 y: time.Time{}.Add(1),
501 opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
503 reason: "not equal because zero time is always not equal not non-zero",
505 label: "EquateApproxTime",
506 x: time.Time{}.Add(1),
508 opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
510 reason: "not equal because zero time is always not equal not non-zero",
512 label: "EquateApproxTime",
513 x: time.Date(2409, 11, 10, 23, 0, 0, 0, time.UTC),
514 y: time.Date(2000, 11, 10, 23, 0, 3, 0, time.UTC),
515 opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
517 reason: "time difference overflows time.Duration",
519 label: "EquateErrors",
522 opts: []cmp.Option{EquateErrors()},
524 reason: "nil values are equal",
526 label: "EquateErrors",
527 x: errors.New("EOF"),
529 opts: []cmp.Option{EquateErrors()},
531 reason: "user-defined EOF is not exactly equal",
533 label: "EquateErrors",
534 x: xerrors.Errorf("wrapped: %w", io.EOF),
536 opts: []cmp.Option{EquateErrors()},
538 reason: "wrapped io.EOF is equal according to errors.Is",
540 label: "EquateErrors",
541 x: xerrors.Errorf("wrapped: %w", io.EOF),
544 reason: "wrapped io.EOF is not equal without EquateErrors option",
546 label: "EquateErrors",
549 opts: []cmp.Option{EquateErrors()},
551 reason: "sentinel errors are equal",
553 label: "EquateErrors",
556 opts: []cmp.Option{EquateErrors()},
558 reason: "AnyError is equal to any non-nil error",
560 label: "EquateErrors",
564 reason: "AnyError is not equal to any non-nil error without EquateErrors option",
566 label: "EquateErrors",
569 opts: []cmp.Option{EquateErrors()},
571 reason: "AnyError is not equal to nil value",
573 label: "EquateErrors",
576 opts: []cmp.Option{EquateErrors()},
578 reason: "nil values are equal",
580 label: "EquateErrors",
581 x: errors.New("EOF"),
583 opts: []cmp.Option{EquateErrors()},
585 reason: "user-defined EOF is not exactly equal",
587 label: "EquateErrors",
588 x: xerrors.Errorf("wrapped: %w", io.EOF),
590 opts: []cmp.Option{EquateErrors()},
592 reason: "wrapped io.EOF is equal according to errors.Is",
594 label: "EquateErrors",
595 x: xerrors.Errorf("wrapped: %w", io.EOF),
598 reason: "wrapped io.EOF is not equal without EquateErrors option",
600 label: "EquateErrors",
603 opts: []cmp.Option{EquateErrors()},
605 reason: "sentinel errors are equal",
607 label: "EquateErrors",
610 opts: []cmp.Option{EquateErrors()},
612 reason: "AnyError is equal to any non-nil error",
614 label: "EquateErrors",
618 reason: "AnyError is not equal to any non-nil error without EquateErrors option",
620 label: "EquateErrors",
623 opts: []cmp.Option{EquateErrors()},
625 reason: "AnyError is not equal to nil value",
627 label: "EquateErrors",
628 x: struct{ E error }{nil},
629 y: struct{ E error }{nil},
630 opts: []cmp.Option{EquateErrors()},
632 reason: "nil values are equal",
634 label: "EquateErrors",
635 x: struct{ E error }{errors.New("EOF")},
636 y: struct{ E error }{io.EOF},
637 opts: []cmp.Option{EquateErrors()},
639 reason: "user-defined EOF is not exactly equal",
641 label: "EquateErrors",
642 x: struct{ E error }{xerrors.Errorf("wrapped: %w", io.EOF)},
643 y: struct{ E error }{io.EOF},
644 opts: []cmp.Option{EquateErrors()},
646 reason: "wrapped io.EOF is equal according to errors.Is",
648 label: "EquateErrors",
649 x: struct{ E error }{xerrors.Errorf("wrapped: %w", io.EOF)},
650 y: struct{ E error }{io.EOF},
652 reason: "wrapped io.EOF is not equal without EquateErrors option",
654 label: "EquateErrors",
655 x: struct{ E error }{io.EOF},
656 y: struct{ E error }{io.EOF},
657 opts: []cmp.Option{EquateErrors()},
659 reason: "sentinel errors are equal",
661 label: "EquateErrors",
662 x: struct{ E error }{io.EOF},
663 y: struct{ E error }{AnyError},
664 opts: []cmp.Option{EquateErrors()},
666 reason: "AnyError is equal to any non-nil error",
668 label: "EquateErrors",
669 x: struct{ E error }{io.EOF},
670 y: struct{ E error }{AnyError},
672 reason: "AnyError is not equal to any non-nil error without EquateErrors option",
674 label: "EquateErrors",
675 x: struct{ E error }{nil},
676 y: struct{ E error }{AnyError},
677 opts: []cmp.Option{EquateErrors()},
679 reason: "AnyError is not equal to nil value",
681 label: "IgnoreFields",
682 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
683 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
685 reason: "not equal because values do not match in deeply embedded field",
687 label: "IgnoreFields",
688 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
689 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
690 opts: []cmp.Option{IgnoreFields(Bar1{}, "Alpha")},
692 reason: "equal because IgnoreField ignores deeply embedded field: Alpha",
694 label: "IgnoreFields",
695 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
696 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
697 opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo1.Alpha")},
699 reason: "equal because IgnoreField ignores deeply embedded field: Foo1.Alpha",
701 label: "IgnoreFields",
702 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
703 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
704 opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo2.Alpha")},
706 reason: "equal because IgnoreField ignores deeply embedded field: Foo2.Alpha",
708 label: "IgnoreFields",
709 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
710 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
711 opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Alpha")},
713 reason: "equal because IgnoreField ignores deeply embedded field: Foo3.Alpha",
715 label: "IgnoreFields",
716 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
717 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
718 opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Foo2.Alpha")},
720 reason: "equal because IgnoreField ignores deeply embedded field: Foo3.Foo2.Alpha",
722 label: "IgnoreFields",
726 reason: "not equal because many deeply nested or embedded fields differ",
728 label: "IgnoreFields",
731 opts: []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Foo3", "Alpha")},
733 reason: "equal because IgnoreFields ignores fields at the highest levels",
735 label: "IgnoreFields",
741 "Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
742 "Bravo.Foo3.Foo2.Foo1.Bravo",
744 "Delta.Echo.Charlie",
745 "Foo3.Foo2.Foo1.Alpha",
750 reason: "equal because IgnoreFields ignores fields using fully-qualified field",
752 label: "IgnoreFields",
758 "Bravo.Foo3.Foo2.Foo1.Bravo",
760 "Delta.Echo.Charlie",
761 "Foo3.Foo2.Foo1.Alpha",
766 reason: "not equal because one fully-qualified field is not ignored: Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
768 label: "IgnoreFields",
771 opts: []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha")},
773 reason: "not equal because highest-level field is not ignored: Foo3",
775 label: "IgnoreFields",
777 privateStruct: &privateStruct{private: 1},
778 PublicStruct: &PublicStruct{private: 2},
782 privateStruct: &privateStruct{private: 10},
783 PublicStruct: &PublicStruct{private: 20},
786 opts: []cmp.Option{cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{})},
788 reason: "not equal because unexported fields mismatch",
790 label: "IgnoreFields",
792 privateStruct: &privateStruct{private: 1},
793 PublicStruct: &PublicStruct{private: 2},
797 privateStruct: &privateStruct{private: 10},
798 PublicStruct: &PublicStruct{private: 20},
802 cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{}),
803 IgnoreFields(ParentStruct{}, "PublicStruct.private", "privateStruct.private", "private"),
806 reason: "equal because mismatching unexported fields are ignored",
808 label: "IgnoreTypes",
809 x: []interface{}{5, "same"},
810 y: []interface{}{6, "same"},
812 reason: "not equal because 5 != 6",
814 label: "IgnoreTypes",
815 x: []interface{}{5, "same"},
816 y: []interface{}{6, "same"},
817 opts: []cmp.Option{IgnoreTypes(0)},
819 reason: "equal because ints are ignored",
821 label: "IgnoreTypes+IgnoreInterfaces",
822 x: []interface{}{5, "same", new(bytes.Buffer)},
823 y: []interface{}{6, "same", new(bytes.Buffer)},
824 opts: []cmp.Option{IgnoreTypes(0)},
826 reason: "panics because bytes.Buffer has unexported fields",
828 label: "IgnoreTypes+IgnoreInterfaces",
829 x: []interface{}{5, "same", new(bytes.Buffer)},
830 y: []interface{}{6, "diff", new(bytes.Buffer)},
833 IgnoreInterfaces(struct{ io.Reader }{}),
836 reason: "equal because bytes.Buffer is ignored by match on interface type",
838 label: "IgnoreTypes+IgnoreInterfaces",
839 x: []interface{}{5, "same", new(bytes.Buffer)},
840 y: []interface{}{6, "same", new(bytes.Buffer)},
843 IgnoreInterfaces(struct {
850 reason: "equal because bytes.Buffer is ignored by match on multiple interface types",
852 label: "IgnoreInterfaces",
853 x: struct{ mu sync.Mutex }{},
854 y: struct{ mu sync.Mutex }{},
856 reason: "panics because sync.Mutex has unexported fields",
858 label: "IgnoreInterfaces",
859 x: struct{ mu sync.Mutex }{},
860 y: struct{ mu sync.Mutex }{},
861 opts: []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
863 reason: "equal because IgnoreInterfaces applies on values (with pointer receiver)",
865 label: "IgnoreInterfaces",
866 x: struct{ mu *sync.Mutex }{},
867 y: struct{ mu *sync.Mutex }{},
868 opts: []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
870 reason: "equal because IgnoreInterfaces applies on pointers",
872 label: "IgnoreUnexported",
873 x: ParentStruct{Public: 1, private: 2},
874 y: ParentStruct{Public: 1, private: -2},
875 opts: []cmp.Option{cmp.AllowUnexported(ParentStruct{})},
877 reason: "not equal because ParentStruct.private differs with AllowUnexported",
879 label: "IgnoreUnexported",
880 x: ParentStruct{Public: 1, private: 2},
881 y: ParentStruct{Public: 1, private: -2},
882 opts: []cmp.Option{IgnoreUnexported(ParentStruct{})},
884 reason: "equal because IgnoreUnexported ignored ParentStruct.private",
886 label: "IgnoreUnexported",
887 x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
888 y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
890 cmp.AllowUnexported(PublicStruct{}),
891 IgnoreUnexported(ParentStruct{}),
894 reason: "equal because ParentStruct.private is ignored",
896 label: "IgnoreUnexported",
897 x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
898 y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
900 cmp.AllowUnexported(PublicStruct{}),
901 IgnoreUnexported(ParentStruct{}),
904 reason: "not equal because ParentStruct.PublicStruct.private differs and not ignored by IgnoreUnexported(ParentStruct{})",
906 label: "IgnoreUnexported",
907 x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
908 y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
910 IgnoreUnexported(ParentStruct{}, PublicStruct{}),
913 reason: "equal because both ParentStruct.PublicStruct and ParentStruct.PublicStruct.private are ignored",
915 label: "IgnoreUnexported",
916 x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
917 y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
919 cmp.AllowUnexported(privateStruct{}, PublicStruct{}, ParentStruct{}),
922 reason: "not equal since ParentStruct.privateStruct differs",
924 label: "IgnoreUnexported",
925 x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
926 y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
928 cmp.AllowUnexported(privateStruct{}, PublicStruct{}),
929 IgnoreUnexported(ParentStruct{}),
932 reason: "equal because ParentStruct.privateStruct ignored by IgnoreUnexported(ParentStruct{})",
934 label: "IgnoreUnexported",
935 x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
936 y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: -4}},
938 cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
939 IgnoreUnexported(privateStruct{}),
942 reason: "equal because privateStruct.private ignored by IgnoreUnexported(privateStruct{})",
944 label: "IgnoreUnexported",
945 x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
946 y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
948 cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
949 IgnoreUnexported(privateStruct{}),
952 reason: "not equal because privateStruct.Public differs and not ignored by IgnoreUnexported(privateStruct{})",
954 label: "IgnoreFields+IgnoreTypes+IgnoreUnexported",
958 MyTime: MyTime{time.Now()},
959 Bar3: *createBar3X(),
960 ParentStruct: ParentStruct{
961 Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4},
967 MyTime: MyTime{time.Now()},
968 Bar3: *createBar3Y(),
969 ParentStruct: ParentStruct{
970 Public: 1, private: -2, PublicStruct: &PublicStruct{Public: -3, private: -4},
974 IgnoreFields(Everything{}, "MyTime", "Bar3.Foo3"),
975 IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha"),
976 IgnoreTypes(MyInt(0), PublicStruct{}),
977 IgnoreUnexported(ParentStruct{}),
980 reason: "equal because all Ignore options can be composed together",
982 label: "IgnoreSliceElements",
983 x: []int{1, 0, 2, 3, 0, 4, 0, 0},
984 y: []int{0, 0, 0, 0, 1, 2, 3, 4},
986 IgnoreSliceElements(func(v int) bool { return v == 0 }),
989 reason: "equal because zero elements are ignored",
991 label: "IgnoreSliceElements",
992 x: []MyInt{1, 0, 2, 3, 0, 4, 0, 0},
993 y: []MyInt{0, 0, 0, 0, 1, 2, 3, 4},
995 IgnoreSliceElements(func(v int) bool { return v == 0 }),
998 reason: "not equal because MyInt is not assignable to int",
1000 label: "IgnoreSliceElements",
1001 x: MyInts{1, 0, 2, 3, 0, 4, 0, 0},
1002 y: MyInts{0, 0, 0, 0, 1, 2, 3, 4},
1004 IgnoreSliceElements(func(v int) bool { return v == 0 }),
1007 reason: "equal because the element type of MyInts is assignable to int",
1009 label: "IgnoreSliceElements+EquateEmpty",
1011 y: []MyInt{0, 0, 0, 0},
1013 IgnoreSliceElements(func(v int) bool { return v == 0 }),
1017 reason: "not equal because ignored elements does not imply empty slice",
1019 label: "IgnoreMapEntries",
1020 x: map[string]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
1021 y: map[string]int{"one": 1, "three": 3, "TEN": 10},
1023 IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1026 reason: "equal because uppercase keys are ignored",
1028 label: "IgnoreMapEntries",
1029 x: map[MyString]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
1030 y: map[MyString]int{"one": 1, "three": 3, "TEN": 10},
1032 IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1035 reason: "not equal because MyString is not assignable to string",
1037 label: "IgnoreMapEntries",
1038 x: map[string]MyInt{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
1039 y: map[string]MyInt{"one": 1, "three": 3, "TEN": 10},
1041 IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1044 reason: "not equal because MyInt is not assignable to int",
1046 label: "IgnoreMapEntries+EquateEmpty",
1047 x: map[string]MyInt{"ONE": 1, "TWO": 2, "THREE": 3},
1050 IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1054 reason: "not equal because ignored entries does not imply empty map",
1056 label: "AcyclicTransformer",
1060 AcyclicTransformer("", func(s string) []string { return strings.Split(s, "\n") }),
1063 reason: "not equal because 3rd line differs, but should not recurse infinitely",
1065 label: "AcyclicTransformer",
1066 x: []string{"foo", "Bar", "BAZ"},
1067 y: []string{"Foo", "BAR", "baz"},
1069 AcyclicTransformer("", strings.ToUpper),
1072 reason: "equal because of strings.ToUpper; AcyclicTransformer unnecessary, but check this still works",
1074 label: "AcyclicTransformer",
1075 x: "this is a sentence",
1076 y: "this is a sentence",
1078 AcyclicTransformer("", strings.Fields),
1081 reason: "equal because acyclic transformer splits on any contiguous whitespace",
1084 for _, tt := range tests {
1085 t.Run(tt.label, func(t *testing.T) {
1090 if ex := recover(); ex != nil {
1091 gotPanic = fmt.Sprint(ex)
1094 gotEqual = cmp.Equal(tt.x, tt.y, tt.opts...)
1097 case tt.reason == "":
1098 t.Errorf("reason must be provided")
1099 case gotPanic == "" && tt.wantPanic:
1100 t.Errorf("expected Equal panic\nreason: %s", tt.reason)
1101 case gotPanic != "" && !tt.wantPanic:
1102 t.Errorf("unexpected Equal panic: got %v\nreason: %v", gotPanic, tt.reason)
1103 case gotEqual != tt.wantEqual:
1104 t.Errorf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
1110 func TestPanic(t *testing.T) {
1111 args := func(x ...interface{}) []interface{} { return x }
1113 label string // Test name
1114 fnc interface{} // Option function to call
1115 args []interface{} // Arguments to pass in
1116 wantPanic string // Expected panic message
1117 reason string // The reason for the expected outcome
1119 label: "EquateApprox",
1121 args: args(0.0, 0.0),
1122 reason: "zero margin and fraction is equivalent to exact equality",
1124 label: "EquateApprox",
1126 args: args(-0.1, 0.0),
1127 wantPanic: "margin or fraction must be a non-negative number",
1128 reason: "negative inputs are invalid",
1130 label: "EquateApprox",
1132 args: args(0.0, -0.1),
1133 wantPanic: "margin or fraction must be a non-negative number",
1134 reason: "negative inputs are invalid",
1136 label: "EquateApprox",
1138 args: args(math.NaN(), 0.0),
1139 wantPanic: "margin or fraction must be a non-negative number",
1140 reason: "NaN inputs are invalid",
1142 label: "EquateApprox",
1144 args: args(1.0, 0.0),
1145 reason: "fraction of 1.0 or greater is valid",
1147 label: "EquateApprox",
1149 args: args(0.0, math.Inf(+1)),
1150 reason: "margin of infinity is valid",
1152 label: "EquateApproxTime",
1153 fnc: EquateApproxTime,
1154 args: args(time.Duration(-1)),
1155 wantPanic: "margin must be a non-negative number",
1156 reason: "negative duration is invalid",
1158 label: "SortSlices",
1160 args: args(strings.Compare),
1161 wantPanic: "invalid less function",
1162 reason: "func(x, y string) int is wrong signature for less",
1164 label: "SortSlices",
1166 args: args((func(_, _ int) bool)(nil)),
1167 wantPanic: "invalid less function",
1168 reason: "nil value is not valid",
1172 args: args(strings.Compare),
1173 wantPanic: "invalid less function",
1174 reason: "func(x, y string) int is wrong signature for less",
1178 args: args((func(_, _ int) bool)(nil)),
1179 wantPanic: "invalid less function",
1180 reason: "nil value is not valid",
1182 label: "IgnoreFields",
1184 args: args(Foo1{}, ""),
1185 wantPanic: "name must not be empty",
1186 reason: "empty selector is invalid",
1188 label: "IgnoreFields",
1190 args: args(Foo1{}, "."),
1191 wantPanic: "name must not be empty",
1192 reason: "single dot selector is invalid",
1194 label: "IgnoreFields",
1196 args: args(Foo1{}, ".Alpha"),
1197 reason: "dot-prefix is okay since Foo1.Alpha reads naturally",
1199 label: "IgnoreFields",
1201 args: args(Foo1{}, "Alpha."),
1202 wantPanic: "name must not be empty",
1203 reason: "dot-suffix is invalid",
1205 label: "IgnoreFields",
1207 args: args(Foo1{}, "Alpha "),
1208 wantPanic: "does not exist",
1209 reason: "identifiers must not have spaces",
1211 label: "IgnoreFields",
1213 args: args(Foo1{}, "Zulu"),
1214 wantPanic: "does not exist",
1215 reason: "name of non-existent field is invalid",
1217 label: "IgnoreFields",
1219 args: args(Foo1{}, "Alpha.NoExist"),
1220 wantPanic: "must be a struct",
1221 reason: "cannot select into a non-struct",
1223 label: "IgnoreFields",
1225 args: args(&Foo1{}, "Alpha"),
1226 wantPanic: "must be a non-pointer struct",
1227 reason: "the type must be a struct (not pointer to a struct)",
1229 label: "IgnoreFields",
1231 args: args(struct{ privateStruct }{}, "privateStruct"),
1232 reason: "privateStruct field permitted since it is the default name of the embedded type",
1234 label: "IgnoreFields",
1236 args: args(struct{ privateStruct }{}, "Public"),
1237 reason: "Public field permitted since it is a forwarded field that is exported",
1239 label: "IgnoreFields",
1241 args: args(struct{ privateStruct }{}, "private"),
1242 wantPanic: "does not exist",
1243 reason: "private field not permitted since it is a forwarded field that is unexported",
1245 label: "IgnoreTypes",
1247 reason: "empty input is valid",
1249 label: "IgnoreTypes",
1252 wantPanic: "cannot determine type",
1253 reason: "input must not be nil value",
1255 label: "IgnoreTypes",
1257 args: args(0, 0, 0),
1258 reason: "duplicate inputs of the same type is valid",
1260 label: "IgnoreInterfaces",
1261 fnc: IgnoreInterfaces,
1263 wantPanic: "input must be an anonymous struct",
1264 reason: "input must not be nil value",
1266 label: "IgnoreInterfaces",
1267 fnc: IgnoreInterfaces,
1269 wantPanic: "input must be an anonymous struct",
1270 reason: "input must not be a named struct type",
1272 label: "IgnoreInterfaces",
1273 fnc: IgnoreInterfaces,
1274 args: args(struct{ _ io.Reader }{}),
1275 wantPanic: "struct cannot have named fields",
1276 reason: "input must not have named fields",
1278 label: "IgnoreInterfaces",
1279 fnc: IgnoreInterfaces,
1280 args: args(struct{ Foo1 }{}),
1281 wantPanic: "embedded field must be an interface type",
1282 reason: "field types must be interfaces",
1284 label: "IgnoreInterfaces",
1285 fnc: IgnoreInterfaces,
1286 args: args(struct{ EmptyInterface }{}),
1287 wantPanic: "cannot ignore empty interface",
1288 reason: "field types must not be the empty interface",
1290 label: "IgnoreInterfaces",
1291 fnc: IgnoreInterfaces,
1298 reason: "multiple interfaces may be specified, even if they overlap",
1300 label: "IgnoreUnexported",
1301 fnc: IgnoreUnexported,
1302 reason: "empty input is valid",
1304 label: "IgnoreUnexported",
1305 fnc: IgnoreUnexported,
1307 wantPanic: "must be a non-pointer struct",
1308 reason: "input must not be nil value",
1310 label: "IgnoreUnexported",
1311 fnc: IgnoreUnexported,
1312 args: args(&Foo1{}),
1313 wantPanic: "must be a non-pointer struct",
1314 reason: "input must be a struct type (not a pointer to a struct)",
1316 label: "IgnoreUnexported",
1317 fnc: IgnoreUnexported,
1318 args: args(Foo1{}, struct{ x, X int }{}),
1319 reason: "input may be named or unnamed structs",
1321 label: "AcyclicTransformer",
1322 fnc: AcyclicTransformer,
1323 args: args("", "not a func"),
1324 wantPanic: "invalid transformer function",
1325 reason: "AcyclicTransformer has same input requirements as Transformer",
1328 for _, tt := range tests {
1329 t.Run(tt.label, func(t *testing.T) {
1330 // Prepare function arguments.
1331 vf := reflect.ValueOf(tt.fnc)
1332 var vargs []reflect.Value
1333 for i, arg := range tt.args {
1336 if i == tf.NumIn()-1 && tf.IsVariadic() {
1337 vargs = append(vargs, reflect.Zero(tf.In(i).Elem()))
1339 vargs = append(vargs, reflect.Zero(tf.In(i)))
1342 vargs = append(vargs, reflect.ValueOf(arg))
1346 // Call the function and capture any panics.
1350 if ex := recover(); ex != nil {
1351 if s, ok := ex.(string); ok {
1362 case tt.reason == "":
1363 t.Errorf("reason must be provided")
1364 case tt.wantPanic == "" && gotPanic != "":
1365 t.Errorf("unexpected panic message: %s\nreason: %s", gotPanic, tt.reason)
1366 case tt.wantPanic != "" && !strings.Contains(gotPanic, tt.wantPanic):
1367 t.Errorf("panic message:\ngot: %s\nwant: %s\nreason: %s", gotPanic, tt.wantPanic, tt.reason)