1
2
3
4
5 package cmpopts
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "io"
12 "math"
13 "net/netip"
14 "reflect"
15 "strings"
16 "sync"
17 "testing"
18 "time"
19
20 "github.com/google/go-cmp/cmp"
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
102 x, y interface{}
103 opts []cmp.Option
104 wantEqual bool
105 wantPanic bool
106 reason string
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
223 y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
224
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
240 y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
241
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: fmt.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: fmt.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: fmt.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: fmt.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 }{fmt.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 }{fmt.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: "EquateComparable",
682 x: []struct{ P netip.Addr }{
683 {netip.AddrFrom4([4]byte{1, 2, 3, 4})},
684 {netip.AddrFrom4([4]byte{1, 2, 3, 5})},
685 {netip.AddrFrom4([4]byte{1, 2, 3, 6})},
686 },
687 y: []struct{ P netip.Addr }{
688 {netip.AddrFrom4([4]byte{1, 2, 3, 4})},
689 {netip.AddrFrom4([4]byte{1, 2, 3, 5})},
690 {netip.AddrFrom4([4]byte{1, 2, 3, 6})},
691 },
692 opts: []cmp.Option{EquateComparable(netip.Addr{})},
693 wantEqual: true,
694 reason: "equal because all IP addresses are the same",
695 }, {
696 label: "EquateComparable",
697 x: []struct{ P netip.Addr }{
698 {netip.AddrFrom4([4]byte{1, 2, 3, 4})},
699 {netip.AddrFrom4([4]byte{1, 2, 3, 5})},
700 {netip.AddrFrom4([4]byte{1, 2, 3, 6})},
701 },
702 y: []struct{ P netip.Addr }{
703 {netip.AddrFrom4([4]byte{1, 2, 3, 4})},
704 {netip.AddrFrom4([4]byte{1, 2, 3, 7})},
705 {netip.AddrFrom4([4]byte{1, 2, 3, 6})},
706 },
707 opts: []cmp.Option{EquateComparable(netip.Addr{})},
708 wantEqual: false,
709 reason: "not equal because second IP address is different",
710 }, {
711 label: "IgnoreFields",
712 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
713 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
714 wantEqual: false,
715 reason: "not equal because values do not match in deeply embedded field",
716 }, {
717 label: "IgnoreFields",
718 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
719 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
720 opts: []cmp.Option{IgnoreFields(Bar1{}, "Alpha")},
721 wantEqual: true,
722 reason: "equal because IgnoreField ignores deeply embedded field: Alpha",
723 }, {
724 label: "IgnoreFields",
725 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
726 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
727 opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo1.Alpha")},
728 wantEqual: true,
729 reason: "equal because IgnoreField ignores deeply embedded field: Foo1.Alpha",
730 }, {
731 label: "IgnoreFields",
732 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
733 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
734 opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo2.Alpha")},
735 wantEqual: true,
736 reason: "equal because IgnoreField ignores deeply embedded field: Foo2.Alpha",
737 }, {
738 label: "IgnoreFields",
739 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
740 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
741 opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Alpha")},
742 wantEqual: true,
743 reason: "equal because IgnoreField ignores deeply embedded field: Foo3.Alpha",
744 }, {
745 label: "IgnoreFields",
746 x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
747 y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
748 opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Foo2.Alpha")},
749 wantEqual: true,
750 reason: "equal because IgnoreField ignores deeply embedded field: Foo3.Foo2.Alpha",
751 }, {
752 label: "IgnoreFields",
753 x: createBar3X(),
754 y: createBar3Y(),
755 wantEqual: false,
756 reason: "not equal because many deeply nested or embedded fields differ",
757 }, {
758 label: "IgnoreFields",
759 x: createBar3X(),
760 y: createBar3Y(),
761 opts: []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Foo3", "Alpha")},
762 wantEqual: true,
763 reason: "equal because IgnoreFields ignores fields at the highest levels",
764 }, {
765 label: "IgnoreFields",
766 x: createBar3X(),
767 y: createBar3Y(),
768 opts: []cmp.Option{
769 IgnoreFields(Bar3{},
770 "Bar1.Foo3.Bravo",
771 "Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
772 "Bravo.Foo3.Foo2.Foo1.Bravo",
773 "Bravo.Bravo",
774 "Delta.Echo.Charlie",
775 "Foo3.Foo2.Foo1.Alpha",
776 "Alpha",
777 ),
778 },
779 wantEqual: true,
780 reason: "equal because IgnoreFields ignores fields using fully-qualified field",
781 }, {
782 label: "IgnoreFields",
783 x: createBar3X(),
784 y: createBar3Y(),
785 opts: []cmp.Option{
786 IgnoreFields(Bar3{},
787 "Bar1.Foo3.Bravo",
788 "Bravo.Foo3.Foo2.Foo1.Bravo",
789 "Bravo.Bravo",
790 "Delta.Echo.Charlie",
791 "Foo3.Foo2.Foo1.Alpha",
792 "Alpha",
793 ),
794 },
795 wantEqual: false,
796 reason: "not equal because one fully-qualified field is not ignored: Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
797 }, {
798 label: "IgnoreFields",
799 x: createBar3X(),
800 y: createBar3Y(),
801 opts: []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha")},
802 wantEqual: false,
803 reason: "not equal because highest-level field is not ignored: Foo3",
804 }, {
805 label: "IgnoreFields",
806 x: ParentStruct{
807 privateStruct: &privateStruct{private: 1},
808 PublicStruct: &PublicStruct{private: 2},
809 private: 3,
810 },
811 y: ParentStruct{
812 privateStruct: &privateStruct{private: 10},
813 PublicStruct: &PublicStruct{private: 20},
814 private: 30,
815 },
816 opts: []cmp.Option{cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{})},
817 wantEqual: false,
818 reason: "not equal because unexported fields mismatch",
819 }, {
820 label: "IgnoreFields",
821 x: ParentStruct{
822 privateStruct: &privateStruct{private: 1},
823 PublicStruct: &PublicStruct{private: 2},
824 private: 3,
825 },
826 y: ParentStruct{
827 privateStruct: &privateStruct{private: 10},
828 PublicStruct: &PublicStruct{private: 20},
829 private: 30,
830 },
831 opts: []cmp.Option{
832 cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{}),
833 IgnoreFields(ParentStruct{}, "PublicStruct.private", "privateStruct.private", "private"),
834 },
835 wantEqual: true,
836 reason: "equal because mismatching unexported fields are ignored",
837 }, {
838 label: "IgnoreTypes",
839 x: []interface{}{5, "same"},
840 y: []interface{}{6, "same"},
841 wantEqual: false,
842 reason: "not equal because 5 != 6",
843 }, {
844 label: "IgnoreTypes",
845 x: []interface{}{5, "same"},
846 y: []interface{}{6, "same"},
847 opts: []cmp.Option{IgnoreTypes(0)},
848 wantEqual: true,
849 reason: "equal because ints are ignored",
850 }, {
851 label: "IgnoreTypes+IgnoreInterfaces",
852 x: []interface{}{5, "same", new(bytes.Buffer)},
853 y: []interface{}{6, "same", new(bytes.Buffer)},
854 opts: []cmp.Option{IgnoreTypes(0)},
855 wantPanic: true,
856 reason: "panics because bytes.Buffer has unexported fields",
857 }, {
858 label: "IgnoreTypes+IgnoreInterfaces",
859 x: []interface{}{5, "same", new(bytes.Buffer)},
860 y: []interface{}{6, "diff", new(bytes.Buffer)},
861 opts: []cmp.Option{
862 IgnoreTypes(0, ""),
863 IgnoreInterfaces(struct{ io.Reader }{}),
864 },
865 wantEqual: true,
866 reason: "equal because bytes.Buffer is ignored by match on interface type",
867 }, {
868 label: "IgnoreTypes+IgnoreInterfaces",
869 x: []interface{}{5, "same", new(bytes.Buffer)},
870 y: []interface{}{6, "same", new(bytes.Buffer)},
871 opts: []cmp.Option{
872 IgnoreTypes(0, ""),
873 IgnoreInterfaces(struct {
874 io.Reader
875 io.Writer
876 fmt.Stringer
877 }{}),
878 },
879 wantEqual: true,
880 reason: "equal because bytes.Buffer is ignored by match on multiple interface types",
881 }, {
882 label: "IgnoreInterfaces",
883 x: struct{ mu sync.Mutex }{},
884 y: struct{ mu sync.Mutex }{},
885 wantPanic: true,
886 reason: "panics because sync.Mutex has unexported fields",
887 }, {
888 label: "IgnoreInterfaces",
889 x: struct{ mu sync.Mutex }{},
890 y: struct{ mu sync.Mutex }{},
891 opts: []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
892 wantEqual: true,
893 reason: "equal because IgnoreInterfaces applies on values (with pointer receiver)",
894 }, {
895 label: "IgnoreInterfaces",
896 x: struct{ mu *sync.Mutex }{},
897 y: struct{ mu *sync.Mutex }{},
898 opts: []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
899 wantEqual: true,
900 reason: "equal because IgnoreInterfaces applies on pointers",
901 }, {
902 label: "IgnoreUnexported",
903 x: ParentStruct{Public: 1, private: 2},
904 y: ParentStruct{Public: 1, private: -2},
905 opts: []cmp.Option{cmp.AllowUnexported(ParentStruct{})},
906 wantEqual: false,
907 reason: "not equal because ParentStruct.private differs with AllowUnexported",
908 }, {
909 label: "IgnoreUnexported",
910 x: ParentStruct{Public: 1, private: 2},
911 y: ParentStruct{Public: 1, private: -2},
912 opts: []cmp.Option{IgnoreUnexported(ParentStruct{})},
913 wantEqual: true,
914 reason: "equal because IgnoreUnexported ignored ParentStruct.private",
915 }, {
916 label: "IgnoreUnexported",
917 x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
918 y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
919 opts: []cmp.Option{
920 cmp.AllowUnexported(PublicStruct{}),
921 IgnoreUnexported(ParentStruct{}),
922 },
923 wantEqual: true,
924 reason: "equal because ParentStruct.private is ignored",
925 }, {
926 label: "IgnoreUnexported",
927 x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
928 y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
929 opts: []cmp.Option{
930 cmp.AllowUnexported(PublicStruct{}),
931 IgnoreUnexported(ParentStruct{}),
932 },
933 wantEqual: false,
934 reason: "not equal because ParentStruct.PublicStruct.private differs and not ignored by IgnoreUnexported(ParentStruct{})",
935 }, {
936 label: "IgnoreUnexported",
937 x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
938 y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
939 opts: []cmp.Option{
940 IgnoreUnexported(ParentStruct{}, PublicStruct{}),
941 },
942 wantEqual: true,
943 reason: "equal because both ParentStruct.PublicStruct and ParentStruct.PublicStruct.private are ignored",
944 }, {
945 label: "IgnoreUnexported",
946 x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
947 y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
948 opts: []cmp.Option{
949 cmp.AllowUnexported(privateStruct{}, PublicStruct{}, ParentStruct{}),
950 },
951 wantEqual: false,
952 reason: "not equal since ParentStruct.privateStruct differs",
953 }, {
954 label: "IgnoreUnexported",
955 x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
956 y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
957 opts: []cmp.Option{
958 cmp.AllowUnexported(privateStruct{}, PublicStruct{}),
959 IgnoreUnexported(ParentStruct{}),
960 },
961 wantEqual: true,
962 reason: "equal because ParentStruct.privateStruct ignored by IgnoreUnexported(ParentStruct{})",
963 }, {
964 label: "IgnoreUnexported",
965 x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
966 y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: -4}},
967 opts: []cmp.Option{
968 cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
969 IgnoreUnexported(privateStruct{}),
970 },
971 wantEqual: true,
972 reason: "equal because privateStruct.private ignored by IgnoreUnexported(privateStruct{})",
973 }, {
974 label: "IgnoreUnexported",
975 x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
976 y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
977 opts: []cmp.Option{
978 cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
979 IgnoreUnexported(privateStruct{}),
980 },
981 wantEqual: false,
982 reason: "not equal because privateStruct.Public differs and not ignored by IgnoreUnexported(privateStruct{})",
983 }, {
984 label: "IgnoreFields+IgnoreTypes+IgnoreUnexported",
985 x: &Everything{
986 MyInt: 5,
987 MyFloat: 3.3,
988 MyTime: MyTime{time.Now()},
989 Bar3: *createBar3X(),
990 ParentStruct: ParentStruct{
991 Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4},
992 },
993 },
994 y: &Everything{
995 MyInt: -5,
996 MyFloat: 3.3,
997 MyTime: MyTime{time.Now()},
998 Bar3: *createBar3Y(),
999 ParentStruct: ParentStruct{
1000 Public: 1, private: -2, PublicStruct: &PublicStruct{Public: -3, private: -4},
1001 },
1002 },
1003 opts: []cmp.Option{
1004 IgnoreFields(Everything{}, "MyTime", "Bar3.Foo3"),
1005 IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha"),
1006 IgnoreTypes(MyInt(0), PublicStruct{}),
1007 IgnoreUnexported(ParentStruct{}),
1008 },
1009 wantEqual: true,
1010 reason: "equal because all Ignore options can be composed together",
1011 }, {
1012 label: "IgnoreSliceElements",
1013 x: []int{1, 0, 2, 3, 0, 4, 0, 0},
1014 y: []int{0, 0, 0, 0, 1, 2, 3, 4},
1015 opts: []cmp.Option{
1016 IgnoreSliceElements(func(v int) bool { return v == 0 }),
1017 },
1018 wantEqual: true,
1019 reason: "equal because zero elements are ignored",
1020 }, {
1021 label: "IgnoreSliceElements",
1022 x: []MyInt{1, 0, 2, 3, 0, 4, 0, 0},
1023 y: []MyInt{0, 0, 0, 0, 1, 2, 3, 4},
1024 opts: []cmp.Option{
1025 IgnoreSliceElements(func(v int) bool { return v == 0 }),
1026 },
1027 wantEqual: false,
1028 reason: "not equal because MyInt is not assignable to int",
1029 }, {
1030 label: "IgnoreSliceElements",
1031 x: MyInts{1, 0, 2, 3, 0, 4, 0, 0},
1032 y: MyInts{0, 0, 0, 0, 1, 2, 3, 4},
1033 opts: []cmp.Option{
1034 IgnoreSliceElements(func(v int) bool { return v == 0 }),
1035 },
1036 wantEqual: true,
1037 reason: "equal because the element type of MyInts is assignable to int",
1038 }, {
1039 label: "IgnoreSliceElements+EquateEmpty",
1040 x: []MyInt{},
1041 y: []MyInt{0, 0, 0, 0},
1042 opts: []cmp.Option{
1043 IgnoreSliceElements(func(v int) bool { return v == 0 }),
1044 EquateEmpty(),
1045 },
1046 wantEqual: false,
1047 reason: "not equal because ignored elements does not imply empty slice",
1048 }, {
1049 label: "IgnoreMapEntries",
1050 x: map[string]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
1051 y: map[string]int{"one": 1, "three": 3, "TEN": 10},
1052 opts: []cmp.Option{
1053 IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1054 },
1055 wantEqual: true,
1056 reason: "equal because uppercase keys are ignored",
1057 }, {
1058 label: "IgnoreMapEntries",
1059 x: map[MyString]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
1060 y: map[MyString]int{"one": 1, "three": 3, "TEN": 10},
1061 opts: []cmp.Option{
1062 IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1063 },
1064 wantEqual: false,
1065 reason: "not equal because MyString is not assignable to string",
1066 }, {
1067 label: "IgnoreMapEntries",
1068 x: map[string]MyInt{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
1069 y: map[string]MyInt{"one": 1, "three": 3, "TEN": 10},
1070 opts: []cmp.Option{
1071 IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1072 },
1073 wantEqual: false,
1074 reason: "not equal because MyInt is not assignable to int",
1075 }, {
1076 label: "IgnoreMapEntries+EquateEmpty",
1077 x: map[string]MyInt{"ONE": 1, "TWO": 2, "THREE": 3},
1078 y: nil,
1079 opts: []cmp.Option{
1080 IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
1081 EquateEmpty(),
1082 },
1083 wantEqual: false,
1084 reason: "not equal because ignored entries does not imply empty map",
1085 }, {
1086 label: "AcyclicTransformer",
1087 x: "a\nb\nc\nd",
1088 y: "a\nb\nd\nd",
1089 opts: []cmp.Option{
1090 AcyclicTransformer("", func(s string) []string { return strings.Split(s, "\n") }),
1091 },
1092 wantEqual: false,
1093 reason: "not equal because 3rd line differs, but should not recurse infinitely",
1094 }, {
1095 label: "AcyclicTransformer",
1096 x: []string{"foo", "Bar", "BAZ"},
1097 y: []string{"Foo", "BAR", "baz"},
1098 opts: []cmp.Option{
1099 AcyclicTransformer("", strings.ToUpper),
1100 },
1101 wantEqual: true,
1102 reason: "equal because of strings.ToUpper; AcyclicTransformer unnecessary, but check this still works",
1103 }, {
1104 label: "AcyclicTransformer",
1105 x: "this is a sentence",
1106 y: "this is a sentence",
1107 opts: []cmp.Option{
1108 AcyclicTransformer("", strings.Fields),
1109 },
1110 wantEqual: true,
1111 reason: "equal because acyclic transformer splits on any contiguous whitespace",
1112 }}
1113
1114 for _, tt := range tests {
1115 t.Run(tt.label, func(t *testing.T) {
1116 var gotEqual bool
1117 var gotPanic string
1118 func() {
1119 defer func() {
1120 if ex := recover(); ex != nil {
1121 gotPanic = fmt.Sprint(ex)
1122 }
1123 }()
1124 gotEqual = cmp.Equal(tt.x, tt.y, tt.opts...)
1125 }()
1126 switch {
1127 case tt.reason == "":
1128 t.Errorf("reason must be provided")
1129 case gotPanic == "" && tt.wantPanic:
1130 t.Errorf("expected Equal panic\nreason: %s", tt.reason)
1131 case gotPanic != "" && !tt.wantPanic:
1132 t.Errorf("unexpected Equal panic: got %v\nreason: %v", gotPanic, tt.reason)
1133 case gotEqual != tt.wantEqual:
1134 t.Errorf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
1135 }
1136 })
1137 }
1138 }
1139
1140 func TestPanic(t *testing.T) {
1141 args := func(x ...interface{}) []interface{} { return x }
1142 tests := []struct {
1143 label string
1144 fnc interface{}
1145 args []interface{}
1146 wantPanic string
1147 reason string
1148 }{{
1149 label: "EquateApprox",
1150 fnc: EquateApprox,
1151 args: args(0.0, 0.0),
1152 reason: "zero margin and fraction is equivalent to exact equality",
1153 }, {
1154 label: "EquateApprox",
1155 fnc: EquateApprox,
1156 args: args(-0.1, 0.0),
1157 wantPanic: "margin or fraction must be a non-negative number",
1158 reason: "negative inputs are invalid",
1159 }, {
1160 label: "EquateApprox",
1161 fnc: EquateApprox,
1162 args: args(0.0, -0.1),
1163 wantPanic: "margin or fraction must be a non-negative number",
1164 reason: "negative inputs are invalid",
1165 }, {
1166 label: "EquateApprox",
1167 fnc: EquateApprox,
1168 args: args(math.NaN(), 0.0),
1169 wantPanic: "margin or fraction must be a non-negative number",
1170 reason: "NaN inputs are invalid",
1171 }, {
1172 label: "EquateApprox",
1173 fnc: EquateApprox,
1174 args: args(1.0, 0.0),
1175 reason: "fraction of 1.0 or greater is valid",
1176 }, {
1177 label: "EquateApprox",
1178 fnc: EquateApprox,
1179 args: args(0.0, math.Inf(+1)),
1180 reason: "margin of infinity is valid",
1181 }, {
1182 label: "EquateApproxTime",
1183 fnc: EquateApproxTime,
1184 args: args(time.Duration(-1)),
1185 wantPanic: "margin must be a non-negative number",
1186 reason: "negative duration is invalid",
1187 }, {
1188 label: "SortSlices",
1189 fnc: SortSlices,
1190 args: args(strings.Compare),
1191 wantPanic: "invalid less function",
1192 reason: "func(x, y string) int is wrong signature for less",
1193 }, {
1194 label: "SortSlices",
1195 fnc: SortSlices,
1196 args: args((func(_, _ int) bool)(nil)),
1197 wantPanic: "invalid less function",
1198 reason: "nil value is not valid",
1199 }, {
1200 label: "SortMaps",
1201 fnc: SortMaps,
1202 args: args(strings.Compare),
1203 wantPanic: "invalid less function",
1204 reason: "func(x, y string) int is wrong signature for less",
1205 }, {
1206 label: "SortMaps",
1207 fnc: SortMaps,
1208 args: args((func(_, _ int) bool)(nil)),
1209 wantPanic: "invalid less function",
1210 reason: "nil value is not valid",
1211 }, {
1212 label: "IgnoreFields",
1213 fnc: IgnoreFields,
1214 args: args(Foo1{}, ""),
1215 wantPanic: "name must not be empty",
1216 reason: "empty selector is invalid",
1217 }, {
1218 label: "IgnoreFields",
1219 fnc: IgnoreFields,
1220 args: args(Foo1{}, "."),
1221 wantPanic: "name must not be empty",
1222 reason: "single dot selector is invalid",
1223 }, {
1224 label: "IgnoreFields",
1225 fnc: IgnoreFields,
1226 args: args(Foo1{}, ".Alpha"),
1227 reason: "dot-prefix is okay since Foo1.Alpha reads naturally",
1228 }, {
1229 label: "IgnoreFields",
1230 fnc: IgnoreFields,
1231 args: args(Foo1{}, "Alpha."),
1232 wantPanic: "name must not be empty",
1233 reason: "dot-suffix is invalid",
1234 }, {
1235 label: "IgnoreFields",
1236 fnc: IgnoreFields,
1237 args: args(Foo1{}, "Alpha "),
1238 wantPanic: "does not exist",
1239 reason: "identifiers must not have spaces",
1240 }, {
1241 label: "IgnoreFields",
1242 fnc: IgnoreFields,
1243 args: args(Foo1{}, "Zulu"),
1244 wantPanic: "does not exist",
1245 reason: "name of non-existent field is invalid",
1246 }, {
1247 label: "IgnoreFields",
1248 fnc: IgnoreFields,
1249 args: args(Foo1{}, "Alpha.NoExist"),
1250 wantPanic: "must be a struct",
1251 reason: "cannot select into a non-struct",
1252 }, {
1253 label: "IgnoreFields",
1254 fnc: IgnoreFields,
1255 args: args(&Foo1{}, "Alpha"),
1256 wantPanic: "must be a non-pointer struct",
1257 reason: "the type must be a struct (not pointer to a struct)",
1258 }, {
1259 label: "IgnoreFields",
1260 fnc: IgnoreFields,
1261 args: args(struct{ privateStruct }{}, "privateStruct"),
1262 reason: "privateStruct field permitted since it is the default name of the embedded type",
1263 }, {
1264 label: "IgnoreFields",
1265 fnc: IgnoreFields,
1266 args: args(struct{ privateStruct }{}, "Public"),
1267 reason: "Public field permitted since it is a forwarded field that is exported",
1268 }, {
1269 label: "IgnoreFields",
1270 fnc: IgnoreFields,
1271 args: args(struct{ privateStruct }{}, "private"),
1272 wantPanic: "does not exist",
1273 reason: "private field not permitted since it is a forwarded field that is unexported",
1274 }, {
1275 label: "IgnoreTypes",
1276 fnc: IgnoreTypes,
1277 reason: "empty input is valid",
1278 }, {
1279 label: "IgnoreTypes",
1280 fnc: IgnoreTypes,
1281 args: args(nil),
1282 wantPanic: "cannot determine type",
1283 reason: "input must not be nil value",
1284 }, {
1285 label: "IgnoreTypes",
1286 fnc: IgnoreTypes,
1287 args: args(0, 0, 0),
1288 reason: "duplicate inputs of the same type is valid",
1289 }, {
1290 label: "IgnoreInterfaces",
1291 fnc: IgnoreInterfaces,
1292 args: args(nil),
1293 wantPanic: "input must be an anonymous struct",
1294 reason: "input must not be nil value",
1295 }, {
1296 label: "IgnoreInterfaces",
1297 fnc: IgnoreInterfaces,
1298 args: args(Foo1{}),
1299 wantPanic: "input must be an anonymous struct",
1300 reason: "input must not be a named struct type",
1301 }, {
1302 label: "IgnoreInterfaces",
1303 fnc: IgnoreInterfaces,
1304 args: args(struct{ _ io.Reader }{}),
1305 wantPanic: "struct cannot have named fields",
1306 reason: "input must not have named fields",
1307 }, {
1308 label: "IgnoreInterfaces",
1309 fnc: IgnoreInterfaces,
1310 args: args(struct{ Foo1 }{}),
1311 wantPanic: "embedded field must be an interface type",
1312 reason: "field types must be interfaces",
1313 }, {
1314 label: "IgnoreInterfaces",
1315 fnc: IgnoreInterfaces,
1316 args: args(struct{ EmptyInterface }{}),
1317 wantPanic: "cannot ignore empty interface",
1318 reason: "field types must not be the empty interface",
1319 }, {
1320 label: "IgnoreInterfaces",
1321 fnc: IgnoreInterfaces,
1322 args: args(struct {
1323 io.Reader
1324 io.Writer
1325 io.Closer
1326 io.ReadWriteCloser
1327 }{}),
1328 reason: "multiple interfaces may be specified, even if they overlap",
1329 }, {
1330 label: "IgnoreUnexported",
1331 fnc: IgnoreUnexported,
1332 reason: "empty input is valid",
1333 }, {
1334 label: "IgnoreUnexported",
1335 fnc: IgnoreUnexported,
1336 args: args(nil),
1337 wantPanic: "must be a non-pointer struct",
1338 reason: "input must not be nil value",
1339 }, {
1340 label: "IgnoreUnexported",
1341 fnc: IgnoreUnexported,
1342 args: args(&Foo1{}),
1343 wantPanic: "must be a non-pointer struct",
1344 reason: "input must be a struct type (not a pointer to a struct)",
1345 }, {
1346 label: "IgnoreUnexported",
1347 fnc: IgnoreUnexported,
1348 args: args(Foo1{}, struct{ x, X int }{}),
1349 reason: "input may be named or unnamed structs",
1350 }, {
1351 label: "AcyclicTransformer",
1352 fnc: AcyclicTransformer,
1353 args: args("", "not a func"),
1354 wantPanic: "invalid transformer function",
1355 reason: "AcyclicTransformer has same input requirements as Transformer",
1356 }}
1357
1358 for _, tt := range tests {
1359 t.Run(tt.label, func(t *testing.T) {
1360
1361 vf := reflect.ValueOf(tt.fnc)
1362 var vargs []reflect.Value
1363 for i, arg := range tt.args {
1364 if arg == nil {
1365 tf := vf.Type()
1366 if i == tf.NumIn()-1 && tf.IsVariadic() {
1367 vargs = append(vargs, reflect.Zero(tf.In(i).Elem()))
1368 } else {
1369 vargs = append(vargs, reflect.Zero(tf.In(i)))
1370 }
1371 } else {
1372 vargs = append(vargs, reflect.ValueOf(arg))
1373 }
1374 }
1375
1376
1377 var gotPanic string
1378 func() {
1379 defer func() {
1380 if ex := recover(); ex != nil {
1381 if s, ok := ex.(string); ok {
1382 gotPanic = s
1383 } else {
1384 panic(ex)
1385 }
1386 }
1387 }()
1388 vf.Call(vargs)
1389 }()
1390
1391 switch {
1392 case tt.reason == "":
1393 t.Errorf("reason must be provided")
1394 case tt.wantPanic == "" && gotPanic != "":
1395 t.Errorf("unexpected panic message: %s\nreason: %s", gotPanic, tt.reason)
1396 case tt.wantPanic != "" && !strings.Contains(gotPanic, tt.wantPanic):
1397 t.Errorf("panic message:\ngot: %s\nwant: %s\nreason: %s", gotPanic, tt.wantPanic, tt.reason)
1398 }
1399 })
1400 }
1401 }
1402
View as plain text