Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / github.com / google / go-cmp@v0.5.1 / cmp / cmpopts / util_test.go
1 // Copyright 2017, The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE.md file.
4
5 package cmpopts
6
7 import (
8         "bytes"
9         "errors"
10         "fmt"
11         "io"
12         "math"
13         "reflect"
14         "strings"
15         "sync"
16         "testing"
17         "time"
18
19         "github.com/google/go-cmp/cmp"
20         "golang.org/x/xerrors"
21 )
22
23 type (
24         MyInt    int
25         MyInts   []int
26         MyFloat  float32
27         MyString string
28         MyTime   struct{ time.Time }
29         MyStruct struct {
30                 A, B []int
31                 C, D map[time.Time]string
32         }
33
34         Foo1 struct{ Alpha, Bravo, Charlie int }
35         Foo2 struct{ *Foo1 }
36         Foo3 struct{ *Foo2 }
37         Bar1 struct{ Foo3 }
38         Bar2 struct {
39                 Bar1
40                 *Foo3
41                 Bravo float32
42         }
43         Bar3 struct {
44                 Bar1
45                 Bravo *Bar2
46                 Delta struct{ Echo Foo1 }
47                 *Foo3
48                 Alpha string
49         }
50
51         privateStruct struct{ Public, private int }
52         PublicStruct  struct{ Public, private int }
53         ParentStruct  struct {
54                 *privateStruct
55                 *PublicStruct
56                 Public  int
57                 private int
58         }
59
60         Everything struct {
61                 MyInt
62                 MyFloat
63                 MyTime
64                 MyStruct
65                 Bar3
66                 ParentStruct
67         }
68
69         EmptyInterface interface{}
70 )
71
72 func TestOptions(t *testing.T) {
73         createBar3X := func() *Bar3 {
74                 return &Bar3{
75                         Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 2}}}},
76                         Bravo: &Bar2{
77                                 Bar1:  Bar1{Foo3{&Foo2{&Foo1{Charlie: 7}}}},
78                                 Foo3:  &Foo3{&Foo2{&Foo1{Bravo: 5}}},
79                                 Bravo: 4,
80                         },
81                         Delta: struct{ Echo Foo1 }{Foo1{Charlie: 3}},
82                         Foo3:  &Foo3{&Foo2{&Foo1{Alpha: 1}}},
83                         Alpha: "alpha",
84                 }
85         }
86         createBar3Y := func() *Bar3 {
87                 return &Bar3{
88                         Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 3}}}},
89                         Bravo: &Bar2{
90                                 Bar1:  Bar1{Foo3{&Foo2{&Foo1{Charlie: 8}}}},
91                                 Foo3:  &Foo3{&Foo2{&Foo1{Bravo: 6}}},
92                                 Bravo: 5,
93                         },
94                         Delta: struct{ Echo Foo1 }{Foo1{Charlie: 4}},
95                         Foo3:  &Foo3{&Foo2{&Foo1{Alpha: 2}}},
96                         Alpha: "ALPHA",
97                 }
98         }
99
100         tests := []struct {
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
107         }{{
108                 label:     "EquateEmpty",
109                 x:         []int{},
110                 y:         []int(nil),
111                 wantEqual: false,
112                 reason:    "not equal because empty non-nil and nil slice differ",
113         }, {
114                 label:     "EquateEmpty",
115                 x:         []int{},
116                 y:         []int(nil),
117                 opts:      []cmp.Option{EquateEmpty()},
118                 wantEqual: true,
119                 reason:    "equal because EquateEmpty equates empty slices",
120         }, {
121                 label:     "SortSlices",
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},
124                 wantEqual: false,
125                 reason:    "not equal because element order differs",
126         }, {
127                 label:     "SortSlices",
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 })},
131                 wantEqual: true,
132                 reason:    "equal because SortSlices sorts the slices",
133         }, {
134                 label:     "SortSlices",
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 })},
138                 wantEqual: false,
139                 reason:    "not equal because MyInt is not the same type as int",
140         }, {
141                 label:     "SortSlices",
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 })},
145                 wantEqual: true,
146                 reason:    "equal even when sorted with duplicate elements",
147         }, {
148                 label:     "SortSlices",
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 })},
152                 wantPanic: true,
153                 reason:    "panics because SortSlices used with non-transitive less function",
154         }, {
155                 label: "SortSlices",
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
160                 })},
161                 wantEqual: false,
162                 reason:    "no panics because SortSlices used with valid less function; not equal because NaN != NaN",
163         }, {
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},
167                 opts: []cmp.Option{
168                         EquateNaNs(),
169                         SortSlices(func(x, y float64) bool {
170                                 return (!math.IsNaN(x) && math.IsNaN(y)) || x < y
171                         }),
172                 },
173                 wantEqual: true,
174                 reason:    "no panics because SortSlices used with valid less function; equal because EquateNaNs is used",
175         }, {
176                 label: "SortMaps",
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",
181                 },
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",
186                 },
187                 wantEqual: false,
188                 reason:    "not equal because timezones differ",
189         }, {
190                 label: "SortMaps",
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",
195                 },
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",
200                 },
201                 opts:      []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
202                 wantEqual: true,
203                 reason:    "equal because SortMaps flattens to a slice where Time.Equal can be used",
204         }, {
205                 label: "SortMaps",
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",
210                 },
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",
215                 },
216                 opts:      []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
217                 wantEqual: false,
218                 reason:    "not equal because MyTime is not assignable to time.Time",
219         }, {
220                 label: "SortMaps",
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 {
227                                 a *= -100
228                         }
229                         if -10 < b && b <= 0 {
230                                 b *= -100
231                         }
232                         return a < b
233                 })},
234                 wantEqual: false,
235                 reason:    "not equal because values differ even though SortMap provides valid ordering",
236         }, {
237                 label: "SortMaps",
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},
242                 opts: []cmp.Option{
243                         SortMaps(func(x, y int) bool {
244                                 if -10 < x && x <= 0 {
245                                         x *= -100
246                                 }
247                                 if -10 < y && y <= 0 {
248                                         y *= -100
249                                 }
250                                 return x < y
251                         }),
252                         cmp.Comparer(func(x, y int) bool {
253                                 if -10 < x && x <= 0 {
254                                         x *= -100
255                                 }
256                                 if -10 < y && y <= 0 {
257                                         y *= -100
258                                 }
259                                 return x == y
260                         }),
261                 },
262                 wantEqual: true,
263                 reason:    "equal because Comparer used to equate differences",
264         }, {
265                 label: "SortMaps",
266                 x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
267                 y:     map[int]string{},
268                 opts: []cmp.Option{SortMaps(func(x, y int) bool {
269                         return x < y && x >= 0 && y >= 0
270                 })},
271                 wantPanic: true,
272                 reason:    "panics because SortMaps used with non-transitive less function",
273         }, {
274                 label: "SortMaps",
275                 x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
276                 y:     map[int]string{},
277                 opts: []cmp.Option{SortMaps(func(x, y int) bool {
278                         return math.Abs(float64(x)) < math.Abs(float64(y))
279                 })},
280                 wantPanic: true,
281                 reason:    "panics because SortMaps used with partial less function",
282         }, {
283                 label: "EquateEmpty+SortSlices+SortMaps",
284                 x: MyStruct{
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",
289                         },
290                         D: map[time.Time]string{},
291                 },
292                 y: MyStruct{
293                         A: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
294                         B: []int{},
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",
298                         },
299                 },
300                 opts: []cmp.Option{
301                         EquateEmpty(),
302                         SortSlices(func(x, y int) bool { return x < y }),
303                         SortMaps(func(x, y time.Time) bool { return x.Before(y) }),
304                 },
305                 wantEqual: true,
306                 reason:    "no panics because EquateEmpty should compose with the sort options",
307         }, {
308                 label:     "EquateApprox",
309                 x:         3.09,
310                 y:         3.10,
311                 wantEqual: false,
312                 reason:    "not equal because floats do not exactly matches",
313         }, {
314                 label:     "EquateApprox",
315                 x:         3.09,
316                 y:         3.10,
317                 opts:      []cmp.Option{EquateApprox(0, 0)},
318                 wantEqual: false,
319                 reason:    "not equal because EquateApprox(0 ,0) is equivalent to using ==",
320         }, {
321                 label:     "EquateApprox",
322                 x:         3.09,
323                 y:         3.10,
324                 opts:      []cmp.Option{EquateApprox(0.003, 0.009)},
325                 wantEqual: false,
326                 reason:    "not equal because EquateApprox is too strict",
327         }, {
328                 label:     "EquateApprox",
329                 x:         3.09,
330                 y:         3.10,
331                 opts:      []cmp.Option{EquateApprox(0, 0.011)},
332                 wantEqual: true,
333                 reason:    "equal because margin is loose enough to match",
334         }, {
335                 label:     "EquateApprox",
336                 x:         3.09,
337                 y:         3.10,
338                 opts:      []cmp.Option{EquateApprox(0.004, 0)},
339                 wantEqual: true,
340                 reason:    "equal because fraction is loose enough to match",
341         }, {
342                 label:     "EquateApprox",
343                 x:         3.09,
344                 y:         3.10,
345                 opts:      []cmp.Option{EquateApprox(0.004, 0.011)},
346                 wantEqual: true,
347                 reason:    "equal because both the margin and fraction are loose enough to match",
348         }, {
349                 label:     "EquateApprox",
350                 x:         float32(3.09),
351                 y:         float64(3.10),
352                 opts:      []cmp.Option{EquateApprox(0.004, 0)},
353                 wantEqual: false,
354                 reason:    "not equal because the types differ",
355         }, {
356                 label:     "EquateApprox",
357                 x:         float32(3.09),
358                 y:         float32(3.10),
359                 opts:      []cmp.Option{EquateApprox(0.004, 0)},
360                 wantEqual: true,
361                 reason:    "equal because EquateApprox also applies on float32s",
362         }, {
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)},
367                 wantEqual: true,
368                 reason:    "equal because we fall back on == which matches Inf (EquateApprox does not apply on Inf) ",
369         }, {
370                 label:     "EquateApprox",
371                 x:         []float64{math.Inf(+1), -1e100},
372                 y:         []float64{+1e100, math.Inf(-1)},
373                 opts:      []cmp.Option{EquateApprox(0, 1)},
374                 wantEqual: false,
375                 reason:    "not equal because we fall back on == where Inf != 1e100 (EquateApprox does not apply on Inf)",
376         }, {
377                 label:     "EquateApprox",
378                 x:         float64(+1e100),
379                 y:         float64(-1e100),
380                 opts:      []cmp.Option{EquateApprox(math.Inf(+1), 0)},
381                 wantEqual: true,
382                 reason:    "equal because infinite fraction matches everything",
383         }, {
384                 label:     "EquateApprox",
385                 x:         float64(+1e100),
386                 y:         float64(-1e100),
387                 opts:      []cmp.Option{EquateApprox(0, math.Inf(+1))},
388                 wantEqual: true,
389                 reason:    "equal because infinite margin matches everything",
390         }, {
391                 label:     "EquateApprox",
392                 x:         math.Pi,
393                 y:         math.Pi,
394                 opts:      []cmp.Option{EquateApprox(0, 0)},
395                 wantEqual: true,
396                 reason:    "equal because EquateApprox(0, 0) is equivalent to ==",
397         }, {
398                 label:     "EquateApprox",
399                 x:         math.Pi,
400                 y:         math.Nextafter(math.Pi, math.Inf(+1)),
401                 opts:      []cmp.Option{EquateApprox(0, 0)},
402                 wantEqual: false,
403                 reason:    "not equal because EquateApprox(0, 0) is equivalent to ==",
404         }, {
405                 label:     "EquateNaNs",
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)},
408                 wantEqual: false,
409                 reason:    "not equal because NaN != NaN",
410         }, {
411                 label:     "EquateNaNs",
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()},
415                 wantEqual: true,
416                 reason:    "equal because EquateNaNs allows NaN == NaN",
417         }, {
418                 label:     "EquateNaNs",
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()},
422                 wantEqual: true,
423                 reason:    "equal because EquateNaNs operates on float32",
424         }, {
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},
428                 opts: []cmp.Option{
429                         EquateNaNs(),
430                         EquateApprox(0.01, 0),
431                 },
432                 wantEqual: true,
433                 reason:    "equal because EquateNaNs and EquateApprox compose together",
434         }, {
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},
438                 opts: []cmp.Option{
439                         EquateNaNs(),
440                         EquateApprox(0.01, 0),
441                 },
442                 wantEqual: false,
443                 reason:    "not equal because EquateApprox and EquateNaNs do not apply on a named type",
444         }, {
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},
448                 opts: []cmp.Option{
449                         cmp.Transformer("", func(x MyFloat) float64 { return float64(x) }),
450                         EquateNaNs(),
451                         EquateApprox(0.01, 0),
452                 },
453                 wantEqual: true,
454                 reason:    "equal because named type is transformed to float64",
455         }, {
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)},
460                 wantEqual: true,
461                 reason:    "equal because times are identical",
462         }, {
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)},
467                 wantEqual: true,
468                 reason:    "equal because time is exactly at the allowed margin",
469         }, {
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)},
474                 wantEqual: true,
475                 reason:    "equal because time is exactly at the allowed margin (negative)",
476         }, {
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)},
481                 wantEqual: false,
482                 reason:    "not equal because time is outside allowed margin",
483         }, {
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)},
488                 wantEqual: false,
489                 reason:    "not equal because time is outside allowed margin (negative)",
490         }, {
491                 label:     "EquateApproxTime",
492                 x:         time.Time{},
493                 y:         time.Time{},
494                 opts:      []cmp.Option{EquateApproxTime(3 * time.Second)},
495                 wantEqual: true,
496                 reason:    "equal because both times are zero",
497         }, {
498                 label:     "EquateApproxTime",
499                 x:         time.Time{},
500                 y:         time.Time{}.Add(1),
501                 opts:      []cmp.Option{EquateApproxTime(3 * time.Second)},
502                 wantEqual: false,
503                 reason:    "not equal because zero time is always not equal not non-zero",
504         }, {
505                 label:     "EquateApproxTime",
506                 x:         time.Time{}.Add(1),
507                 y:         time.Time{},
508                 opts:      []cmp.Option{EquateApproxTime(3 * time.Second)},
509                 wantEqual: false,
510                 reason:    "not equal because zero time is always not equal not non-zero",
511         }, {
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)},
516                 wantEqual: false,
517                 reason:    "time difference overflows time.Duration",
518         }, {
519                 label:     "EquateErrors",
520                 x:         nil,
521                 y:         nil,
522                 opts:      []cmp.Option{EquateErrors()},
523                 wantEqual: true,
524                 reason:    "nil values are equal",
525         }, {
526                 label:     "EquateErrors",
527                 x:         errors.New("EOF"),
528                 y:         io.EOF,
529                 opts:      []cmp.Option{EquateErrors()},
530                 wantEqual: false,
531                 reason:    "user-defined EOF is not exactly equal",
532         }, {
533                 label:     "EquateErrors",
534                 x:         xerrors.Errorf("wrapped: %w", io.EOF),
535                 y:         io.EOF,
536                 opts:      []cmp.Option{EquateErrors()},
537                 wantEqual: true,
538                 reason:    "wrapped io.EOF is equal according to errors.Is",
539         }, {
540                 label:     "EquateErrors",
541                 x:         xerrors.Errorf("wrapped: %w", io.EOF),
542                 y:         io.EOF,
543                 wantEqual: false,
544                 reason:    "wrapped io.EOF is not equal without EquateErrors option",
545         }, {
546                 label:     "EquateErrors",
547                 x:         io.EOF,
548                 y:         io.EOF,
549                 opts:      []cmp.Option{EquateErrors()},
550                 wantEqual: true,
551                 reason:    "sentinel errors are equal",
552         }, {
553                 label:     "EquateErrors",
554                 x:         io.EOF,
555                 y:         AnyError,
556                 opts:      []cmp.Option{EquateErrors()},
557                 wantEqual: true,
558                 reason:    "AnyError is equal to any non-nil error",
559         }, {
560                 label:     "EquateErrors",
561                 x:         io.EOF,
562                 y:         AnyError,
563                 wantEqual: false,
564                 reason:    "AnyError is not equal to any non-nil error without EquateErrors option",
565         }, {
566                 label:     "EquateErrors",
567                 x:         nil,
568                 y:         AnyError,
569                 opts:      []cmp.Option{EquateErrors()},
570                 wantEqual: false,
571                 reason:    "AnyError is not equal to nil value",
572         }, {
573                 label:     "EquateErrors",
574                 x:         nil,
575                 y:         nil,
576                 opts:      []cmp.Option{EquateErrors()},
577                 wantEqual: true,
578                 reason:    "nil values are equal",
579         }, {
580                 label:     "EquateErrors",
581                 x:         errors.New("EOF"),
582                 y:         io.EOF,
583                 opts:      []cmp.Option{EquateErrors()},
584                 wantEqual: false,
585                 reason:    "user-defined EOF is not exactly equal",
586         }, {
587                 label:     "EquateErrors",
588                 x:         xerrors.Errorf("wrapped: %w", io.EOF),
589                 y:         io.EOF,
590                 opts:      []cmp.Option{EquateErrors()},
591                 wantEqual: true,
592                 reason:    "wrapped io.EOF is equal according to errors.Is",
593         }, {
594                 label:     "EquateErrors",
595                 x:         xerrors.Errorf("wrapped: %w", io.EOF),
596                 y:         io.EOF,
597                 wantEqual: false,
598                 reason:    "wrapped io.EOF is not equal without EquateErrors option",
599         }, {
600                 label:     "EquateErrors",
601                 x:         io.EOF,
602                 y:         io.EOF,
603                 opts:      []cmp.Option{EquateErrors()},
604                 wantEqual: true,
605                 reason:    "sentinel errors are equal",
606         }, {
607                 label:     "EquateErrors",
608                 x:         io.EOF,
609                 y:         AnyError,
610                 opts:      []cmp.Option{EquateErrors()},
611                 wantEqual: true,
612                 reason:    "AnyError is equal to any non-nil error",
613         }, {
614                 label:     "EquateErrors",
615                 x:         io.EOF,
616                 y:         AnyError,
617                 wantEqual: false,
618                 reason:    "AnyError is not equal to any non-nil error without EquateErrors option",
619         }, {
620                 label:     "EquateErrors",
621                 x:         nil,
622                 y:         AnyError,
623                 opts:      []cmp.Option{EquateErrors()},
624                 wantEqual: false,
625                 reason:    "AnyError is not equal to nil value",
626         }, {
627                 label:     "EquateErrors",
628                 x:         struct{ E error }{nil},
629                 y:         struct{ E error }{nil},
630                 opts:      []cmp.Option{EquateErrors()},
631                 wantEqual: true,
632                 reason:    "nil values are equal",
633         }, {
634                 label:     "EquateErrors",
635                 x:         struct{ E error }{errors.New("EOF")},
636                 y:         struct{ E error }{io.EOF},
637                 opts:      []cmp.Option{EquateErrors()},
638                 wantEqual: false,
639                 reason:    "user-defined EOF is not exactly equal",
640         }, {
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()},
645                 wantEqual: true,
646                 reason:    "wrapped io.EOF is equal according to errors.Is",
647         }, {
648                 label:     "EquateErrors",
649                 x:         struct{ E error }{xerrors.Errorf("wrapped: %w", io.EOF)},
650                 y:         struct{ E error }{io.EOF},
651                 wantEqual: false,
652                 reason:    "wrapped io.EOF is not equal without EquateErrors option",
653         }, {
654                 label:     "EquateErrors",
655                 x:         struct{ E error }{io.EOF},
656                 y:         struct{ E error }{io.EOF},
657                 opts:      []cmp.Option{EquateErrors()},
658                 wantEqual: true,
659                 reason:    "sentinel errors are equal",
660         }, {
661                 label:     "EquateErrors",
662                 x:         struct{ E error }{io.EOF},
663                 y:         struct{ E error }{AnyError},
664                 opts:      []cmp.Option{EquateErrors()},
665                 wantEqual: true,
666                 reason:    "AnyError is equal to any non-nil error",
667         }, {
668                 label:     "EquateErrors",
669                 x:         struct{ E error }{io.EOF},
670                 y:         struct{ E error }{AnyError},
671                 wantEqual: false,
672                 reason:    "AnyError is not equal to any non-nil error without EquateErrors option",
673         }, {
674                 label:     "EquateErrors",
675                 x:         struct{ E error }{nil},
676                 y:         struct{ E error }{AnyError},
677                 opts:      []cmp.Option{EquateErrors()},
678                 wantEqual: false,
679                 reason:    "AnyError is not equal to nil value",
680         }, {
681                 label:     "IgnoreFields",
682                 x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
683                 y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
684                 wantEqual: false,
685                 reason:    "not equal because values do not match in deeply embedded field",
686         }, {
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")},
691                 wantEqual: true,
692                 reason:    "equal because IgnoreField ignores deeply embedded field: Alpha",
693         }, {
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")},
698                 wantEqual: true,
699                 reason:    "equal because IgnoreField ignores deeply embedded field: Foo1.Alpha",
700         }, {
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")},
705                 wantEqual: true,
706                 reason:    "equal because IgnoreField ignores deeply embedded field: Foo2.Alpha",
707         }, {
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")},
712                 wantEqual: true,
713                 reason:    "equal because IgnoreField ignores deeply embedded field: Foo3.Alpha",
714         }, {
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")},
719                 wantEqual: true,
720                 reason:    "equal because IgnoreField ignores deeply embedded field: Foo3.Foo2.Alpha",
721         }, {
722                 label:     "IgnoreFields",
723                 x:         createBar3X(),
724                 y:         createBar3Y(),
725                 wantEqual: false,
726                 reason:    "not equal because many deeply nested or embedded fields differ",
727         }, {
728                 label:     "IgnoreFields",
729                 x:         createBar3X(),
730                 y:         createBar3Y(),
731                 opts:      []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Foo3", "Alpha")},
732                 wantEqual: true,
733                 reason:    "equal because IgnoreFields ignores fields at the highest levels",
734         }, {
735                 label: "IgnoreFields",
736                 x:     createBar3X(),
737                 y:     createBar3Y(),
738                 opts: []cmp.Option{
739                         IgnoreFields(Bar3{},
740                                 "Bar1.Foo3.Bravo",
741                                 "Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
742                                 "Bravo.Foo3.Foo2.Foo1.Bravo",
743                                 "Bravo.Bravo",
744                                 "Delta.Echo.Charlie",
745                                 "Foo3.Foo2.Foo1.Alpha",
746                                 "Alpha",
747                         ),
748                 },
749                 wantEqual: true,
750                 reason:    "equal because IgnoreFields ignores fields using fully-qualified field",
751         }, {
752                 label: "IgnoreFields",
753                 x:     createBar3X(),
754                 y:     createBar3Y(),
755                 opts: []cmp.Option{
756                         IgnoreFields(Bar3{},
757                                 "Bar1.Foo3.Bravo",
758                                 "Bravo.Foo3.Foo2.Foo1.Bravo",
759                                 "Bravo.Bravo",
760                                 "Delta.Echo.Charlie",
761                                 "Foo3.Foo2.Foo1.Alpha",
762                                 "Alpha",
763                         ),
764                 },
765                 wantEqual: false,
766                 reason:    "not equal because one fully-qualified field is not ignored: Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
767         }, {
768                 label:     "IgnoreFields",
769                 x:         createBar3X(),
770                 y:         createBar3Y(),
771                 opts:      []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha")},
772                 wantEqual: false,
773                 reason:    "not equal because highest-level field is not ignored: Foo3",
774         }, {
775                 label: "IgnoreFields",
776                 x: ParentStruct{
777                         privateStruct: &privateStruct{private: 1},
778                         PublicStruct:  &PublicStruct{private: 2},
779                         private:       3,
780                 },
781                 y: ParentStruct{
782                         privateStruct: &privateStruct{private: 10},
783                         PublicStruct:  &PublicStruct{private: 20},
784                         private:       30,
785                 },
786                 opts:      []cmp.Option{cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{})},
787                 wantEqual: false,
788                 reason:    "not equal because unexported fields mismatch",
789         }, {
790                 label: "IgnoreFields",
791                 x: ParentStruct{
792                         privateStruct: &privateStruct{private: 1},
793                         PublicStruct:  &PublicStruct{private: 2},
794                         private:       3,
795                 },
796                 y: ParentStruct{
797                         privateStruct: &privateStruct{private: 10},
798                         PublicStruct:  &PublicStruct{private: 20},
799                         private:       30,
800                 },
801                 opts: []cmp.Option{
802                         cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{}),
803                         IgnoreFields(ParentStruct{}, "PublicStruct.private", "privateStruct.private", "private"),
804                 },
805                 wantEqual: true,
806                 reason:    "equal because mismatching unexported fields are ignored",
807         }, {
808                 label:     "IgnoreTypes",
809                 x:         []interface{}{5, "same"},
810                 y:         []interface{}{6, "same"},
811                 wantEqual: false,
812                 reason:    "not equal because 5 != 6",
813         }, {
814                 label:     "IgnoreTypes",
815                 x:         []interface{}{5, "same"},
816                 y:         []interface{}{6, "same"},
817                 opts:      []cmp.Option{IgnoreTypes(0)},
818                 wantEqual: true,
819                 reason:    "equal because ints are ignored",
820         }, {
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)},
825                 wantPanic: true,
826                 reason:    "panics because bytes.Buffer has unexported fields",
827         }, {
828                 label: "IgnoreTypes+IgnoreInterfaces",
829                 x:     []interface{}{5, "same", new(bytes.Buffer)},
830                 y:     []interface{}{6, "diff", new(bytes.Buffer)},
831                 opts: []cmp.Option{
832                         IgnoreTypes(0, ""),
833                         IgnoreInterfaces(struct{ io.Reader }{}),
834                 },
835                 wantEqual: true,
836                 reason:    "equal because bytes.Buffer is ignored by match on interface type",
837         }, {
838                 label: "IgnoreTypes+IgnoreInterfaces",
839                 x:     []interface{}{5, "same", new(bytes.Buffer)},
840                 y:     []interface{}{6, "same", new(bytes.Buffer)},
841                 opts: []cmp.Option{
842                         IgnoreTypes(0, ""),
843                         IgnoreInterfaces(struct {
844                                 io.Reader
845                                 io.Writer
846                                 fmt.Stringer
847                         }{}),
848                 },
849                 wantEqual: true,
850                 reason:    "equal because bytes.Buffer is ignored by match on multiple interface types",
851         }, {
852                 label:     "IgnoreInterfaces",
853                 x:         struct{ mu sync.Mutex }{},
854                 y:         struct{ mu sync.Mutex }{},
855                 wantPanic: true,
856                 reason:    "panics because sync.Mutex has unexported fields",
857         }, {
858                 label:     "IgnoreInterfaces",
859                 x:         struct{ mu sync.Mutex }{},
860                 y:         struct{ mu sync.Mutex }{},
861                 opts:      []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
862                 wantEqual: true,
863                 reason:    "equal because IgnoreInterfaces applies on values (with pointer receiver)",
864         }, {
865                 label:     "IgnoreInterfaces",
866                 x:         struct{ mu *sync.Mutex }{},
867                 y:         struct{ mu *sync.Mutex }{},
868                 opts:      []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
869                 wantEqual: true,
870                 reason:    "equal because IgnoreInterfaces applies on pointers",
871         }, {
872                 label:     "IgnoreUnexported",
873                 x:         ParentStruct{Public: 1, private: 2},
874                 y:         ParentStruct{Public: 1, private: -2},
875                 opts:      []cmp.Option{cmp.AllowUnexported(ParentStruct{})},
876                 wantEqual: false,
877                 reason:    "not equal because ParentStruct.private differs with AllowUnexported",
878         }, {
879                 label:     "IgnoreUnexported",
880                 x:         ParentStruct{Public: 1, private: 2},
881                 y:         ParentStruct{Public: 1, private: -2},
882                 opts:      []cmp.Option{IgnoreUnexported(ParentStruct{})},
883                 wantEqual: true,
884                 reason:    "equal because IgnoreUnexported ignored ParentStruct.private",
885         }, {
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}},
889                 opts: []cmp.Option{
890                         cmp.AllowUnexported(PublicStruct{}),
891                         IgnoreUnexported(ParentStruct{}),
892                 },
893                 wantEqual: true,
894                 reason:    "equal because ParentStruct.private is ignored",
895         }, {
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}},
899                 opts: []cmp.Option{
900                         cmp.AllowUnexported(PublicStruct{}),
901                         IgnoreUnexported(ParentStruct{}),
902                 },
903                 wantEqual: false,
904                 reason:    "not equal because ParentStruct.PublicStruct.private differs and not ignored by IgnoreUnexported(ParentStruct{})",
905         }, {
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}},
909                 opts: []cmp.Option{
910                         IgnoreUnexported(ParentStruct{}, PublicStruct{}),
911                 },
912                 wantEqual: true,
913                 reason:    "equal because both ParentStruct.PublicStruct and ParentStruct.PublicStruct.private are ignored",
914         }, {
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}},
918                 opts: []cmp.Option{
919                         cmp.AllowUnexported(privateStruct{}, PublicStruct{}, ParentStruct{}),
920                 },
921                 wantEqual: false,
922                 reason:    "not equal since ParentStruct.privateStruct differs",
923         }, {
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}},
927                 opts: []cmp.Option{
928                         cmp.AllowUnexported(privateStruct{}, PublicStruct{}),
929                         IgnoreUnexported(ParentStruct{}),
930                 },
931                 wantEqual: true,
932                 reason:    "equal because ParentStruct.privateStruct ignored by IgnoreUnexported(ParentStruct{})",
933         }, {
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}},
937                 opts: []cmp.Option{
938                         cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
939                         IgnoreUnexported(privateStruct{}),
940                 },
941                 wantEqual: true,
942                 reason:    "equal because privateStruct.private ignored by IgnoreUnexported(privateStruct{})",
943         }, {
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}},
947                 opts: []cmp.Option{
948                         cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
949                         IgnoreUnexported(privateStruct{}),
950                 },
951                 wantEqual: false,
952                 reason:    "not equal because privateStruct.Public differs and not ignored by IgnoreUnexported(privateStruct{})",
953         }, {
954                 label: "IgnoreFields+IgnoreTypes+IgnoreUnexported",
955                 x: &Everything{
956                         MyInt:   5,
957                         MyFloat: 3.3,
958                         MyTime:  MyTime{time.Now()},
959                         Bar3:    *createBar3X(),
960                         ParentStruct: ParentStruct{
961                                 Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4},
962                         },
963                 },
964                 y: &Everything{
965                         MyInt:   -5,
966                         MyFloat: 3.3,
967                         MyTime:  MyTime{time.Now()},
968                         Bar3:    *createBar3Y(),
969                         ParentStruct: ParentStruct{
970                                 Public: 1, private: -2, PublicStruct: &PublicStruct{Public: -3, private: -4},
971                         },
972                 },
973                 opts: []cmp.Option{
974                         IgnoreFields(Everything{}, "MyTime", "Bar3.Foo3"),
975                         IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha"),
976                         IgnoreTypes(MyInt(0), PublicStruct{}),
977                         IgnoreUnexported(ParentStruct{}),
978                 },
979                 wantEqual: true,
980                 reason:    "equal because all Ignore options can be composed together",
981         }, {
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},
985                 opts: []cmp.Option{
986                         IgnoreSliceElements(func(v int) bool { return v == 0 }),
987                 },
988                 wantEqual: true,
989                 reason:    "equal because zero elements are ignored",
990         }, {
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},
994                 opts: []cmp.Option{
995                         IgnoreSliceElements(func(v int) bool { return v == 0 }),
996                 },
997                 wantEqual: false,
998                 reason:    "not equal because MyInt is not assignable to int",
999         }, {
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},
1003                 opts: []cmp.Option{
1004                         IgnoreSliceElements(func(v int) bool { return v == 0 }),
1005                 },
1006                 wantEqual: true,
1007                 reason:    "equal because the element type of MyInts is assignable to int",
1008         }, {
1009                 label: "IgnoreSliceElements+EquateEmpty",
1010                 x:     []MyInt{},
1011                 y:     []MyInt{0, 0, 0, 0},
1012                 opts: []cmp.Option{
1013                         IgnoreSliceElements(func(v int) bool { return v == 0 }),
1014                         EquateEmpty(),
1015                 },
1016                 wantEqual: false,
1017                 reason:    "not equal because ignored elements does not imply empty slice",
1018         }, {
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},
1022                 opts: []cmp.Option{
1023                         IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1024                 },
1025                 wantEqual: true,
1026                 reason:    "equal because uppercase keys are ignored",
1027         }, {
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},
1031                 opts: []cmp.Option{
1032                         IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1033                 },
1034                 wantEqual: false,
1035                 reason:    "not equal because MyString is not assignable to string",
1036         }, {
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},
1040                 opts: []cmp.Option{
1041                         IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1042                 },
1043                 wantEqual: false,
1044                 reason:    "not equal because MyInt is not assignable to int",
1045         }, {
1046                 label: "IgnoreMapEntries+EquateEmpty",
1047                 x:     map[string]MyInt{"ONE": 1, "TWO": 2, "THREE": 3},
1048                 y:     nil,
1049                 opts: []cmp.Option{
1050                         IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1051                         EquateEmpty(),
1052                 },
1053                 wantEqual: false,
1054                 reason:    "not equal because ignored entries does not imply empty map",
1055         }, {
1056                 label: "AcyclicTransformer",
1057                 x:     "a\nb\nc\nd",
1058                 y:     "a\nb\nd\nd",
1059                 opts: []cmp.Option{
1060                         AcyclicTransformer("", func(s string) []string { return strings.Split(s, "\n") }),
1061                 },
1062                 wantEqual: false,
1063                 reason:    "not equal because 3rd line differs, but should not recurse infinitely",
1064         }, {
1065                 label: "AcyclicTransformer",
1066                 x:     []string{"foo", "Bar", "BAZ"},
1067                 y:     []string{"Foo", "BAR", "baz"},
1068                 opts: []cmp.Option{
1069                         AcyclicTransformer("", strings.ToUpper),
1070                 },
1071                 wantEqual: true,
1072                 reason:    "equal because of strings.ToUpper; AcyclicTransformer unnecessary, but check this still works",
1073         }, {
1074                 label: "AcyclicTransformer",
1075                 x:     "this is a sentence",
1076                 y: "this                        is a                    sentence",
1077                 opts: []cmp.Option{
1078                         AcyclicTransformer("", strings.Fields),
1079                 },
1080                 wantEqual: true,
1081                 reason:    "equal because acyclic transformer splits on any contiguous whitespace",
1082         }}
1083
1084         for _, tt := range tests {
1085                 t.Run(tt.label, func(t *testing.T) {
1086                         var gotEqual bool
1087                         var gotPanic string
1088                         func() {
1089                                 defer func() {
1090                                         if ex := recover(); ex != nil {
1091                                                 gotPanic = fmt.Sprint(ex)
1092                                         }
1093                                 }()
1094                                 gotEqual = cmp.Equal(tt.x, tt.y, tt.opts...)
1095                         }()
1096                         switch {
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)
1105                         }
1106                 })
1107         }
1108 }
1109
1110 func TestPanic(t *testing.T) {
1111         args := func(x ...interface{}) []interface{} { return x }
1112         tests := []struct {
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
1118         }{{
1119                 label:  "EquateApprox",
1120                 fnc:    EquateApprox,
1121                 args:   args(0.0, 0.0),
1122                 reason: "zero margin and fraction is equivalent to exact equality",
1123         }, {
1124                 label:     "EquateApprox",
1125                 fnc:       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",
1129         }, {
1130                 label:     "EquateApprox",
1131                 fnc:       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",
1135         }, {
1136                 label:     "EquateApprox",
1137                 fnc:       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",
1141         }, {
1142                 label:  "EquateApprox",
1143                 fnc:    EquateApprox,
1144                 args:   args(1.0, 0.0),
1145                 reason: "fraction of 1.0 or greater is valid",
1146         }, {
1147                 label:  "EquateApprox",
1148                 fnc:    EquateApprox,
1149                 args:   args(0.0, math.Inf(+1)),
1150                 reason: "margin of infinity is valid",
1151         }, {
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",
1157         }, {
1158                 label:     "SortSlices",
1159                 fnc:       SortSlices,
1160                 args:      args(strings.Compare),
1161                 wantPanic: "invalid less function",
1162                 reason:    "func(x, y string) int is wrong signature for less",
1163         }, {
1164                 label:     "SortSlices",
1165                 fnc:       SortSlices,
1166                 args:      args((func(_, _ int) bool)(nil)),
1167                 wantPanic: "invalid less function",
1168                 reason:    "nil value is not valid",
1169         }, {
1170                 label:     "SortMaps",
1171                 fnc:       SortMaps,
1172                 args:      args(strings.Compare),
1173                 wantPanic: "invalid less function",
1174                 reason:    "func(x, y string) int is wrong signature for less",
1175         }, {
1176                 label:     "SortMaps",
1177                 fnc:       SortMaps,
1178                 args:      args((func(_, _ int) bool)(nil)),
1179                 wantPanic: "invalid less function",
1180                 reason:    "nil value is not valid",
1181         }, {
1182                 label:     "IgnoreFields",
1183                 fnc:       IgnoreFields,
1184                 args:      args(Foo1{}, ""),
1185                 wantPanic: "name must not be empty",
1186                 reason:    "empty selector is invalid",
1187         }, {
1188                 label:     "IgnoreFields",
1189                 fnc:       IgnoreFields,
1190                 args:      args(Foo1{}, "."),
1191                 wantPanic: "name must not be empty",
1192                 reason:    "single dot selector is invalid",
1193         }, {
1194                 label:  "IgnoreFields",
1195                 fnc:    IgnoreFields,
1196                 args:   args(Foo1{}, ".Alpha"),
1197                 reason: "dot-prefix is okay since Foo1.Alpha reads naturally",
1198         }, {
1199                 label:     "IgnoreFields",
1200                 fnc:       IgnoreFields,
1201                 args:      args(Foo1{}, "Alpha."),
1202                 wantPanic: "name must not be empty",
1203                 reason:    "dot-suffix is invalid",
1204         }, {
1205                 label:     "IgnoreFields",
1206                 fnc:       IgnoreFields,
1207                 args:      args(Foo1{}, "Alpha "),
1208                 wantPanic: "does not exist",
1209                 reason:    "identifiers must not have spaces",
1210         }, {
1211                 label:     "IgnoreFields",
1212                 fnc:       IgnoreFields,
1213                 args:      args(Foo1{}, "Zulu"),
1214                 wantPanic: "does not exist",
1215                 reason:    "name of non-existent field is invalid",
1216         }, {
1217                 label:     "IgnoreFields",
1218                 fnc:       IgnoreFields,
1219                 args:      args(Foo1{}, "Alpha.NoExist"),
1220                 wantPanic: "must be a struct",
1221                 reason:    "cannot select into a non-struct",
1222         }, {
1223                 label:     "IgnoreFields",
1224                 fnc:       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)",
1228         }, {
1229                 label:  "IgnoreFields",
1230                 fnc:    IgnoreFields,
1231                 args:   args(struct{ privateStruct }{}, "privateStruct"),
1232                 reason: "privateStruct field permitted since it is the default name of the embedded type",
1233         }, {
1234                 label:  "IgnoreFields",
1235                 fnc:    IgnoreFields,
1236                 args:   args(struct{ privateStruct }{}, "Public"),
1237                 reason: "Public field permitted since it is a forwarded field that is exported",
1238         }, {
1239                 label:     "IgnoreFields",
1240                 fnc:       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",
1244         }, {
1245                 label:  "IgnoreTypes",
1246                 fnc:    IgnoreTypes,
1247                 reason: "empty input is valid",
1248         }, {
1249                 label:     "IgnoreTypes",
1250                 fnc:       IgnoreTypes,
1251                 args:      args(nil),
1252                 wantPanic: "cannot determine type",
1253                 reason:    "input must not be nil value",
1254         }, {
1255                 label:  "IgnoreTypes",
1256                 fnc:    IgnoreTypes,
1257                 args:   args(0, 0, 0),
1258                 reason: "duplicate inputs of the same type is valid",
1259         }, {
1260                 label:     "IgnoreInterfaces",
1261                 fnc:       IgnoreInterfaces,
1262                 args:      args(nil),
1263                 wantPanic: "input must be an anonymous struct",
1264                 reason:    "input must not be nil value",
1265         }, {
1266                 label:     "IgnoreInterfaces",
1267                 fnc:       IgnoreInterfaces,
1268                 args:      args(Foo1{}),
1269                 wantPanic: "input must be an anonymous struct",
1270                 reason:    "input must not be a named struct type",
1271         }, {
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",
1277         }, {
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",
1283         }, {
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",
1289         }, {
1290                 label: "IgnoreInterfaces",
1291                 fnc:   IgnoreInterfaces,
1292                 args: args(struct {
1293                         io.Reader
1294                         io.Writer
1295                         io.Closer
1296                         io.ReadWriteCloser
1297                 }{}),
1298                 reason: "multiple interfaces may be specified, even if they overlap",
1299         }, {
1300                 label:  "IgnoreUnexported",
1301                 fnc:    IgnoreUnexported,
1302                 reason: "empty input is valid",
1303         }, {
1304                 label:     "IgnoreUnexported",
1305                 fnc:       IgnoreUnexported,
1306                 args:      args(nil),
1307                 wantPanic: "must be a non-pointer struct",
1308                 reason:    "input must not be nil value",
1309         }, {
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)",
1315         }, {
1316                 label:  "IgnoreUnexported",
1317                 fnc:    IgnoreUnexported,
1318                 args:   args(Foo1{}, struct{ x, X int }{}),
1319                 reason: "input may be named or unnamed structs",
1320         }, {
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",
1326         }}
1327
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 {
1334                                 if arg == nil {
1335                                         tf := vf.Type()
1336                                         if i == tf.NumIn()-1 && tf.IsVariadic() {
1337                                                 vargs = append(vargs, reflect.Zero(tf.In(i).Elem()))
1338                                         } else {
1339                                                 vargs = append(vargs, reflect.Zero(tf.In(i)))
1340                                         }
1341                                 } else {
1342                                         vargs = append(vargs, reflect.ValueOf(arg))
1343                                 }
1344                         }
1345
1346                         // Call the function and capture any panics.
1347                         var gotPanic string
1348                         func() {
1349                                 defer func() {
1350                                         if ex := recover(); ex != nil {
1351                                                 if s, ok := ex.(string); ok {
1352                                                         gotPanic = s
1353                                                 } else {
1354                                                         panic(ex)
1355                                                 }
1356                                         }
1357                                 }()
1358                                 vf.Call(vargs)
1359                         }()
1360
1361                         switch {
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)
1368                         }
1369                 })
1370         }
1371 }