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