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