github.com/google/go-cmp@v0.6.0/cmp/cmpopts/util_test.go (about)

     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  
     5  package cmpopts
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"math"
    13  	"net/netip"
    14  	"reflect"
    15  	"strings"
    16  	"sync"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/google/go-cmp/cmp"
    21  )
    22  
    23  type (
    24  	MyInt    int
    25  	MyInts   []int
    26  	MyFloat  float32
    27  	MyString string
    28  	MyTime   struct{ time.Time }
    29  	MyStruct struct {
    30  		A, B []int
    31  		C, D map[time.Time]string
    32  	}
    33  
    34  	Foo1 struct{ Alpha, Bravo, Charlie int }
    35  	Foo2 struct{ *Foo1 }
    36  	Foo3 struct{ *Foo2 }
    37  	Bar1 struct{ Foo3 }
    38  	Bar2 struct {
    39  		Bar1
    40  		*Foo3
    41  		Bravo float32
    42  	}
    43  	Bar3 struct {
    44  		Bar1
    45  		Bravo *Bar2
    46  		Delta struct{ Echo Foo1 }
    47  		*Foo3
    48  		Alpha string
    49  	}
    50  
    51  	privateStruct struct{ Public, private int }
    52  	PublicStruct  struct{ Public, private int }
    53  	ParentStruct  struct {
    54  		*privateStruct
    55  		*PublicStruct
    56  		Public  int
    57  		private int
    58  	}
    59  
    60  	Everything struct {
    61  		MyInt
    62  		MyFloat
    63  		MyTime
    64  		MyStruct
    65  		Bar3
    66  		ParentStruct
    67  	}
    68  
    69  	EmptyInterface interface{}
    70  )
    71  
    72  func TestOptions(t *testing.T) {
    73  	createBar3X := func() *Bar3 {
    74  		return &Bar3{
    75  			Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 2}}}},
    76  			Bravo: &Bar2{
    77  				Bar1:  Bar1{Foo3{&Foo2{&Foo1{Charlie: 7}}}},
    78  				Foo3:  &Foo3{&Foo2{&Foo1{Bravo: 5}}},
    79  				Bravo: 4,
    80  			},
    81  			Delta: struct{ Echo Foo1 }{Foo1{Charlie: 3}},
    82  			Foo3:  &Foo3{&Foo2{&Foo1{Alpha: 1}}},
    83  			Alpha: "alpha",
    84  		}
    85  	}
    86  	createBar3Y := func() *Bar3 {
    87  		return &Bar3{
    88  			Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 3}}}},
    89  			Bravo: &Bar2{
    90  				Bar1:  Bar1{Foo3{&Foo2{&Foo1{Charlie: 8}}}},
    91  				Foo3:  &Foo3{&Foo2{&Foo1{Bravo: 6}}},
    92  				Bravo: 5,
    93  			},
    94  			Delta: struct{ Echo Foo1 }{Foo1{Charlie: 4}},
    95  			Foo3:  &Foo3{&Foo2{&Foo1{Alpha: 2}}},
    96  			Alpha: "ALPHA",
    97  		}
    98  	}
    99  
   100  	tests := []struct {
   101  		label     string       // 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:         fmt.Errorf("wrapped: %w", io.EOF),
   535  		y:         io.EOF,
   536  		opts:      []cmp.Option{EquateErrors()},
   537  		wantEqual: true,
   538  		reason:    "wrapped io.EOF is equal according to errors.Is",
   539  	}, {
   540  		label:     "EquateErrors",
   541  		x:         fmt.Errorf("wrapped: %w", io.EOF),
   542  		y:         io.EOF,
   543  		wantEqual: false,
   544  		reason:    "wrapped io.EOF is not equal without EquateErrors option",
   545  	}, {
   546  		label:     "EquateErrors",
   547  		x:         io.EOF,
   548  		y:         io.EOF,
   549  		opts:      []cmp.Option{EquateErrors()},
   550  		wantEqual: true,
   551  		reason:    "sentinel errors are equal",
   552  	}, {
   553  		label:     "EquateErrors",
   554  		x:         io.EOF,
   555  		y:         AnyError,
   556  		opts:      []cmp.Option{EquateErrors()},
   557  		wantEqual: true,
   558  		reason:    "AnyError is equal to any non-nil error",
   559  	}, {
   560  		label:     "EquateErrors",
   561  		x:         io.EOF,
   562  		y:         AnyError,
   563  		wantEqual: false,
   564  		reason:    "AnyError is not equal to any non-nil error without EquateErrors option",
   565  	}, {
   566  		label:     "EquateErrors",
   567  		x:         nil,
   568  		y:         AnyError,
   569  		opts:      []cmp.Option{EquateErrors()},
   570  		wantEqual: false,
   571  		reason:    "AnyError is not equal to nil value",
   572  	}, {
   573  		label:     "EquateErrors",
   574  		x:         nil,
   575  		y:         nil,
   576  		opts:      []cmp.Option{EquateErrors()},
   577  		wantEqual: true,
   578  		reason:    "nil values are equal",
   579  	}, {
   580  		label:     "EquateErrors",
   581  		x:         errors.New("EOF"),
   582  		y:         io.EOF,
   583  		opts:      []cmp.Option{EquateErrors()},
   584  		wantEqual: false,
   585  		reason:    "user-defined EOF is not exactly equal",
   586  	}, {
   587  		label:     "EquateErrors",
   588  		x:         fmt.Errorf("wrapped: %w", io.EOF),
   589  		y:         io.EOF,
   590  		opts:      []cmp.Option{EquateErrors()},
   591  		wantEqual: true,
   592  		reason:    "wrapped io.EOF is equal according to errors.Is",
   593  	}, {
   594  		label:     "EquateErrors",
   595  		x:         fmt.Errorf("wrapped: %w", io.EOF),
   596  		y:         io.EOF,
   597  		wantEqual: false,
   598  		reason:    "wrapped io.EOF is not equal without EquateErrors option",
   599  	}, {
   600  		label:     "EquateErrors",
   601  		x:         io.EOF,
   602  		y:         io.EOF,
   603  		opts:      []cmp.Option{EquateErrors()},
   604  		wantEqual: true,
   605  		reason:    "sentinel errors are equal",
   606  	}, {
   607  		label:     "EquateErrors",
   608  		x:         io.EOF,
   609  		y:         AnyError,
   610  		opts:      []cmp.Option{EquateErrors()},
   611  		wantEqual: true,
   612  		reason:    "AnyError is equal to any non-nil error",
   613  	}, {
   614  		label:     "EquateErrors",
   615  		x:         io.EOF,
   616  		y:         AnyError,
   617  		wantEqual: false,
   618  		reason:    "AnyError is not equal to any non-nil error without EquateErrors option",
   619  	}, {
   620  		label:     "EquateErrors",
   621  		x:         nil,
   622  		y:         AnyError,
   623  		opts:      []cmp.Option{EquateErrors()},
   624  		wantEqual: false,
   625  		reason:    "AnyError is not equal to nil value",
   626  	}, {
   627  		label:     "EquateErrors",
   628  		x:         struct{ E error }{nil},
   629  		y:         struct{ E error }{nil},
   630  		opts:      []cmp.Option{EquateErrors()},
   631  		wantEqual: true,
   632  		reason:    "nil values are equal",
   633  	}, {
   634  		label:     "EquateErrors",
   635  		x:         struct{ E error }{errors.New("EOF")},
   636  		y:         struct{ E error }{io.EOF},
   637  		opts:      []cmp.Option{EquateErrors()},
   638  		wantEqual: false,
   639  		reason:    "user-defined EOF is not exactly equal",
   640  	}, {
   641  		label:     "EquateErrors",
   642  		x:         struct{ E error }{fmt.Errorf("wrapped: %w", io.EOF)},
   643  		y:         struct{ E error }{io.EOF},
   644  		opts:      []cmp.Option{EquateErrors()},
   645  		wantEqual: true,
   646  		reason:    "wrapped io.EOF is equal according to errors.Is",
   647  	}, {
   648  		label:     "EquateErrors",
   649  		x:         struct{ E error }{fmt.Errorf("wrapped: %w", io.EOF)},
   650  		y:         struct{ E error }{io.EOF},
   651  		wantEqual: false,
   652  		reason:    "wrapped io.EOF is not equal without EquateErrors option",
   653  	}, {
   654  		label:     "EquateErrors",
   655  		x:         struct{ E error }{io.EOF},
   656  		y:         struct{ E error }{io.EOF},
   657  		opts:      []cmp.Option{EquateErrors()},
   658  		wantEqual: true,
   659  		reason:    "sentinel errors are equal",
   660  	}, {
   661  		label:     "EquateErrors",
   662  		x:         struct{ E error }{io.EOF},
   663  		y:         struct{ E error }{AnyError},
   664  		opts:      []cmp.Option{EquateErrors()},
   665  		wantEqual: true,
   666  		reason:    "AnyError is equal to any non-nil error",
   667  	}, {
   668  		label:     "EquateErrors",
   669  		x:         struct{ E error }{io.EOF},
   670  		y:         struct{ E error }{AnyError},
   671  		wantEqual: false,
   672  		reason:    "AnyError is not equal to any non-nil error without EquateErrors option",
   673  	}, {
   674  		label:     "EquateErrors",
   675  		x:         struct{ E error }{nil},
   676  		y:         struct{ E error }{AnyError},
   677  		opts:      []cmp.Option{EquateErrors()},
   678  		wantEqual: false,
   679  		reason:    "AnyError is not equal to nil value",
   680  	}, {
   681  		label: "EquateComparable",
   682  		x: []struct{ P netip.Addr }{
   683  			{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
   684  			{netip.AddrFrom4([4]byte{1, 2, 3, 5})},
   685  			{netip.AddrFrom4([4]byte{1, 2, 3, 6})},
   686  		},
   687  		y: []struct{ P netip.Addr }{
   688  			{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
   689  			{netip.AddrFrom4([4]byte{1, 2, 3, 5})},
   690  			{netip.AddrFrom4([4]byte{1, 2, 3, 6})},
   691  		},
   692  		opts:      []cmp.Option{EquateComparable(netip.Addr{})},
   693  		wantEqual: true,
   694  		reason:    "equal because all IP addresses are the same",
   695  	}, {
   696  		label: "EquateComparable",
   697  		x: []struct{ P netip.Addr }{
   698  			{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
   699  			{netip.AddrFrom4([4]byte{1, 2, 3, 5})},
   700  			{netip.AddrFrom4([4]byte{1, 2, 3, 6})},
   701  		},
   702  		y: []struct{ P netip.Addr }{
   703  			{netip.AddrFrom4([4]byte{1, 2, 3, 4})},
   704  			{netip.AddrFrom4([4]byte{1, 2, 3, 7})},
   705  			{netip.AddrFrom4([4]byte{1, 2, 3, 6})},
   706  		},
   707  		opts:      []cmp.Option{EquateComparable(netip.Addr{})},
   708  		wantEqual: false,
   709  		reason:    "not equal because second IP address is different",
   710  	}, {
   711  		label:     "IgnoreFields",
   712  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   713  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   714  		wantEqual: false,
   715  		reason:    "not equal because values do not match in deeply embedded field",
   716  	}, {
   717  		label:     "IgnoreFields",
   718  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   719  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   720  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Alpha")},
   721  		wantEqual: true,
   722  		reason:    "equal because IgnoreField ignores deeply embedded field: Alpha",
   723  	}, {
   724  		label:     "IgnoreFields",
   725  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   726  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   727  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo1.Alpha")},
   728  		wantEqual: true,
   729  		reason:    "equal because IgnoreField ignores deeply embedded field: Foo1.Alpha",
   730  	}, {
   731  		label:     "IgnoreFields",
   732  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   733  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   734  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo2.Alpha")},
   735  		wantEqual: true,
   736  		reason:    "equal because IgnoreField ignores deeply embedded field: Foo2.Alpha",
   737  	}, {
   738  		label:     "IgnoreFields",
   739  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   740  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   741  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Alpha")},
   742  		wantEqual: true,
   743  		reason:    "equal because IgnoreField ignores deeply embedded field: Foo3.Alpha",
   744  	}, {
   745  		label:     "IgnoreFields",
   746  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   747  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   748  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Foo2.Alpha")},
   749  		wantEqual: true,
   750  		reason:    "equal because IgnoreField ignores deeply embedded field: Foo3.Foo2.Alpha",
   751  	}, {
   752  		label:     "IgnoreFields",
   753  		x:         createBar3X(),
   754  		y:         createBar3Y(),
   755  		wantEqual: false,
   756  		reason:    "not equal because many deeply nested or embedded fields differ",
   757  	}, {
   758  		label:     "IgnoreFields",
   759  		x:         createBar3X(),
   760  		y:         createBar3Y(),
   761  		opts:      []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Foo3", "Alpha")},
   762  		wantEqual: true,
   763  		reason:    "equal because IgnoreFields ignores fields at the highest levels",
   764  	}, {
   765  		label: "IgnoreFields",
   766  		x:     createBar3X(),
   767  		y:     createBar3Y(),
   768  		opts: []cmp.Option{
   769  			IgnoreFields(Bar3{},
   770  				"Bar1.Foo3.Bravo",
   771  				"Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
   772  				"Bravo.Foo3.Foo2.Foo1.Bravo",
   773  				"Bravo.Bravo",
   774  				"Delta.Echo.Charlie",
   775  				"Foo3.Foo2.Foo1.Alpha",
   776  				"Alpha",
   777  			),
   778  		},
   779  		wantEqual: true,
   780  		reason:    "equal because IgnoreFields ignores fields using fully-qualified field",
   781  	}, {
   782  		label: "IgnoreFields",
   783  		x:     createBar3X(),
   784  		y:     createBar3Y(),
   785  		opts: []cmp.Option{
   786  			IgnoreFields(Bar3{},
   787  				"Bar1.Foo3.Bravo",
   788  				"Bravo.Foo3.Foo2.Foo1.Bravo",
   789  				"Bravo.Bravo",
   790  				"Delta.Echo.Charlie",
   791  				"Foo3.Foo2.Foo1.Alpha",
   792  				"Alpha",
   793  			),
   794  		},
   795  		wantEqual: false,
   796  		reason:    "not equal because one fully-qualified field is not ignored: Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
   797  	}, {
   798  		label:     "IgnoreFields",
   799  		x:         createBar3X(),
   800  		y:         createBar3Y(),
   801  		opts:      []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha")},
   802  		wantEqual: false,
   803  		reason:    "not equal because highest-level field is not ignored: Foo3",
   804  	}, {
   805  		label: "IgnoreFields",
   806  		x: ParentStruct{
   807  			privateStruct: &privateStruct{private: 1},
   808  			PublicStruct:  &PublicStruct{private: 2},
   809  			private:       3,
   810  		},
   811  		y: ParentStruct{
   812  			privateStruct: &privateStruct{private: 10},
   813  			PublicStruct:  &PublicStruct{private: 20},
   814  			private:       30,
   815  		},
   816  		opts:      []cmp.Option{cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{})},
   817  		wantEqual: false,
   818  		reason:    "not equal because unexported fields mismatch",
   819  	}, {
   820  		label: "IgnoreFields",
   821  		x: ParentStruct{
   822  			privateStruct: &privateStruct{private: 1},
   823  			PublicStruct:  &PublicStruct{private: 2},
   824  			private:       3,
   825  		},
   826  		y: ParentStruct{
   827  			privateStruct: &privateStruct{private: 10},
   828  			PublicStruct:  &PublicStruct{private: 20},
   829  			private:       30,
   830  		},
   831  		opts: []cmp.Option{
   832  			cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{}),
   833  			IgnoreFields(ParentStruct{}, "PublicStruct.private", "privateStruct.private", "private"),
   834  		},
   835  		wantEqual: true,
   836  		reason:    "equal because mismatching unexported fields are ignored",
   837  	}, {
   838  		label:     "IgnoreTypes",
   839  		x:         []interface{}{5, "same"},
   840  		y:         []interface{}{6, "same"},
   841  		wantEqual: false,
   842  		reason:    "not equal because 5 != 6",
   843  	}, {
   844  		label:     "IgnoreTypes",
   845  		x:         []interface{}{5, "same"},
   846  		y:         []interface{}{6, "same"},
   847  		opts:      []cmp.Option{IgnoreTypes(0)},
   848  		wantEqual: true,
   849  		reason:    "equal because ints are ignored",
   850  	}, {
   851  		label:     "IgnoreTypes+IgnoreInterfaces",
   852  		x:         []interface{}{5, "same", new(bytes.Buffer)},
   853  		y:         []interface{}{6, "same", new(bytes.Buffer)},
   854  		opts:      []cmp.Option{IgnoreTypes(0)},
   855  		wantPanic: true,
   856  		reason:    "panics because bytes.Buffer has unexported fields",
   857  	}, {
   858  		label: "IgnoreTypes+IgnoreInterfaces",
   859  		x:     []interface{}{5, "same", new(bytes.Buffer)},
   860  		y:     []interface{}{6, "diff", new(bytes.Buffer)},
   861  		opts: []cmp.Option{
   862  			IgnoreTypes(0, ""),
   863  			IgnoreInterfaces(struct{ io.Reader }{}),
   864  		},
   865  		wantEqual: true,
   866  		reason:    "equal because bytes.Buffer is ignored by match on interface type",
   867  	}, {
   868  		label: "IgnoreTypes+IgnoreInterfaces",
   869  		x:     []interface{}{5, "same", new(bytes.Buffer)},
   870  		y:     []interface{}{6, "same", new(bytes.Buffer)},
   871  		opts: []cmp.Option{
   872  			IgnoreTypes(0, ""),
   873  			IgnoreInterfaces(struct {
   874  				io.Reader
   875  				io.Writer
   876  				fmt.Stringer
   877  			}{}),
   878  		},
   879  		wantEqual: true,
   880  		reason:    "equal because bytes.Buffer is ignored by match on multiple interface types",
   881  	}, {
   882  		label:     "IgnoreInterfaces",
   883  		x:         struct{ mu sync.Mutex }{},
   884  		y:         struct{ mu sync.Mutex }{},
   885  		wantPanic: true,
   886  		reason:    "panics because sync.Mutex has unexported fields",
   887  	}, {
   888  		label:     "IgnoreInterfaces",
   889  		x:         struct{ mu sync.Mutex }{},
   890  		y:         struct{ mu sync.Mutex }{},
   891  		opts:      []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
   892  		wantEqual: true,
   893  		reason:    "equal because IgnoreInterfaces applies on values (with pointer receiver)",
   894  	}, {
   895  		label:     "IgnoreInterfaces",
   896  		x:         struct{ mu *sync.Mutex }{},
   897  		y:         struct{ mu *sync.Mutex }{},
   898  		opts:      []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
   899  		wantEqual: true,
   900  		reason:    "equal because IgnoreInterfaces applies on pointers",
   901  	}, {
   902  		label:     "IgnoreUnexported",
   903  		x:         ParentStruct{Public: 1, private: 2},
   904  		y:         ParentStruct{Public: 1, private: -2},
   905  		opts:      []cmp.Option{cmp.AllowUnexported(ParentStruct{})},
   906  		wantEqual: false,
   907  		reason:    "not equal because ParentStruct.private differs with AllowUnexported",
   908  	}, {
   909  		label:     "IgnoreUnexported",
   910  		x:         ParentStruct{Public: 1, private: 2},
   911  		y:         ParentStruct{Public: 1, private: -2},
   912  		opts:      []cmp.Option{IgnoreUnexported(ParentStruct{})},
   913  		wantEqual: true,
   914  		reason:    "equal because IgnoreUnexported ignored ParentStruct.private",
   915  	}, {
   916  		label: "IgnoreUnexported",
   917  		x:     ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
   918  		y:     ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
   919  		opts: []cmp.Option{
   920  			cmp.AllowUnexported(PublicStruct{}),
   921  			IgnoreUnexported(ParentStruct{}),
   922  		},
   923  		wantEqual: true,
   924  		reason:    "equal because ParentStruct.private is ignored",
   925  	}, {
   926  		label: "IgnoreUnexported",
   927  		x:     ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
   928  		y:     ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
   929  		opts: []cmp.Option{
   930  			cmp.AllowUnexported(PublicStruct{}),
   931  			IgnoreUnexported(ParentStruct{}),
   932  		},
   933  		wantEqual: false,
   934  		reason:    "not equal because ParentStruct.PublicStruct.private differs and not ignored by IgnoreUnexported(ParentStruct{})",
   935  	}, {
   936  		label: "IgnoreUnexported",
   937  		x:     ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
   938  		y:     ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
   939  		opts: []cmp.Option{
   940  			IgnoreUnexported(ParentStruct{}, PublicStruct{}),
   941  		},
   942  		wantEqual: true,
   943  		reason:    "equal because both ParentStruct.PublicStruct and ParentStruct.PublicStruct.private are ignored",
   944  	}, {
   945  		label: "IgnoreUnexported",
   946  		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
   947  		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
   948  		opts: []cmp.Option{
   949  			cmp.AllowUnexported(privateStruct{}, PublicStruct{}, ParentStruct{}),
   950  		},
   951  		wantEqual: false,
   952  		reason:    "not equal since ParentStruct.privateStruct differs",
   953  	}, {
   954  		label: "IgnoreUnexported",
   955  		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
   956  		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
   957  		opts: []cmp.Option{
   958  			cmp.AllowUnexported(privateStruct{}, PublicStruct{}),
   959  			IgnoreUnexported(ParentStruct{}),
   960  		},
   961  		wantEqual: true,
   962  		reason:    "equal because ParentStruct.privateStruct ignored by IgnoreUnexported(ParentStruct{})",
   963  	}, {
   964  		label: "IgnoreUnexported",
   965  		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
   966  		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: -4}},
   967  		opts: []cmp.Option{
   968  			cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
   969  			IgnoreUnexported(privateStruct{}),
   970  		},
   971  		wantEqual: true,
   972  		reason:    "equal because privateStruct.private ignored by IgnoreUnexported(privateStruct{})",
   973  	}, {
   974  		label: "IgnoreUnexported",
   975  		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
   976  		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
   977  		opts: []cmp.Option{
   978  			cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
   979  			IgnoreUnexported(privateStruct{}),
   980  		},
   981  		wantEqual: false,
   982  		reason:    "not equal because privateStruct.Public differs and not ignored by IgnoreUnexported(privateStruct{})",
   983  	}, {
   984  		label: "IgnoreFields+IgnoreTypes+IgnoreUnexported",
   985  		x: &Everything{
   986  			MyInt:   5,
   987  			MyFloat: 3.3,
   988  			MyTime:  MyTime{time.Now()},
   989  			Bar3:    *createBar3X(),
   990  			ParentStruct: ParentStruct{
   991  				Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4},
   992  			},
   993  		},
   994  		y: &Everything{
   995  			MyInt:   -5,
   996  			MyFloat: 3.3,
   997  			MyTime:  MyTime{time.Now()},
   998  			Bar3:    *createBar3Y(),
   999  			ParentStruct: ParentStruct{
  1000  				Public: 1, private: -2, PublicStruct: &PublicStruct{Public: -3, private: -4},
  1001  			},
  1002  		},
  1003  		opts: []cmp.Option{
  1004  			IgnoreFields(Everything{}, "MyTime", "Bar3.Foo3"),
  1005  			IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha"),
  1006  			IgnoreTypes(MyInt(0), PublicStruct{}),
  1007  			IgnoreUnexported(ParentStruct{}),
  1008  		},
  1009  		wantEqual: true,
  1010  		reason:    "equal because all Ignore options can be composed together",
  1011  	}, {
  1012  		label: "IgnoreSliceElements",
  1013  		x:     []int{1, 0, 2, 3, 0, 4, 0, 0},
  1014  		y:     []int{0, 0, 0, 0, 1, 2, 3, 4},
  1015  		opts: []cmp.Option{
  1016  			IgnoreSliceElements(func(v int) bool { return v == 0 }),
  1017  		},
  1018  		wantEqual: true,
  1019  		reason:    "equal because zero elements are ignored",
  1020  	}, {
  1021  		label: "IgnoreSliceElements",
  1022  		x:     []MyInt{1, 0, 2, 3, 0, 4, 0, 0},
  1023  		y:     []MyInt{0, 0, 0, 0, 1, 2, 3, 4},
  1024  		opts: []cmp.Option{
  1025  			IgnoreSliceElements(func(v int) bool { return v == 0 }),
  1026  		},
  1027  		wantEqual: false,
  1028  		reason:    "not equal because MyInt is not assignable to int",
  1029  	}, {
  1030  		label: "IgnoreSliceElements",
  1031  		x:     MyInts{1, 0, 2, 3, 0, 4, 0, 0},
  1032  		y:     MyInts{0, 0, 0, 0, 1, 2, 3, 4},
  1033  		opts: []cmp.Option{
  1034  			IgnoreSliceElements(func(v int) bool { return v == 0 }),
  1035  		},
  1036  		wantEqual: true,
  1037  		reason:    "equal because the element type of MyInts is assignable to int",
  1038  	}, {
  1039  		label: "IgnoreSliceElements+EquateEmpty",
  1040  		x:     []MyInt{},
  1041  		y:     []MyInt{0, 0, 0, 0},
  1042  		opts: []cmp.Option{
  1043  			IgnoreSliceElements(func(v int) bool { return v == 0 }),
  1044  			EquateEmpty(),
  1045  		},
  1046  		wantEqual: false,
  1047  		reason:    "not equal because ignored elements does not imply empty slice",
  1048  	}, {
  1049  		label: "IgnoreMapEntries",
  1050  		x:     map[string]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
  1051  		y:     map[string]int{"one": 1, "three": 3, "TEN": 10},
  1052  		opts: []cmp.Option{
  1053  			IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
  1054  		},
  1055  		wantEqual: true,
  1056  		reason:    "equal because uppercase keys are ignored",
  1057  	}, {
  1058  		label: "IgnoreMapEntries",
  1059  		x:     map[MyString]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
  1060  		y:     map[MyString]int{"one": 1, "three": 3, "TEN": 10},
  1061  		opts: []cmp.Option{
  1062  			IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
  1063  		},
  1064  		wantEqual: false,
  1065  		reason:    "not equal because MyString is not assignable to string",
  1066  	}, {
  1067  		label: "IgnoreMapEntries",
  1068  		x:     map[string]MyInt{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
  1069  		y:     map[string]MyInt{"one": 1, "three": 3, "TEN": 10},
  1070  		opts: []cmp.Option{
  1071  			IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
  1072  		},
  1073  		wantEqual: false,
  1074  		reason:    "not equal because MyInt is not assignable to int",
  1075  	}, {
  1076  		label: "IgnoreMapEntries+EquateEmpty",
  1077  		x:     map[string]MyInt{"ONE": 1, "TWO": 2, "THREE": 3},
  1078  		y:     nil,
  1079  		opts: []cmp.Option{
  1080  			IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
  1081  			EquateEmpty(),
  1082  		},
  1083  		wantEqual: false,
  1084  		reason:    "not equal because ignored entries does not imply empty map",
  1085  	}, {
  1086  		label: "AcyclicTransformer",
  1087  		x:     "a\nb\nc\nd",
  1088  		y:     "a\nb\nd\nd",
  1089  		opts: []cmp.Option{
  1090  			AcyclicTransformer("", func(s string) []string { return strings.Split(s, "\n") }),
  1091  		},
  1092  		wantEqual: false,
  1093  		reason:    "not equal because 3rd line differs, but should not recurse infinitely",
  1094  	}, {
  1095  		label: "AcyclicTransformer",
  1096  		x:     []string{"foo", "Bar", "BAZ"},
  1097  		y:     []string{"Foo", "BAR", "baz"},
  1098  		opts: []cmp.Option{
  1099  			AcyclicTransformer("", strings.ToUpper),
  1100  		},
  1101  		wantEqual: true,
  1102  		reason:    "equal because of strings.ToUpper; AcyclicTransformer unnecessary, but check this still works",
  1103  	}, {
  1104  		label: "AcyclicTransformer",
  1105  		x:     "this is a sentence",
  1106  		y:     "this   			is a 			sentence",
  1107  		opts: []cmp.Option{
  1108  			AcyclicTransformer("", strings.Fields),
  1109  		},
  1110  		wantEqual: true,
  1111  		reason:    "equal because acyclic transformer splits on any contiguous whitespace",
  1112  	}}
  1113  
  1114  	for _, tt := range tests {
  1115  		t.Run(tt.label, func(t *testing.T) {
  1116  			var gotEqual bool
  1117  			var gotPanic string
  1118  			func() {
  1119  				defer func() {
  1120  					if ex := recover(); ex != nil {
  1121  						gotPanic = fmt.Sprint(ex)
  1122  					}
  1123  				}()
  1124  				gotEqual = cmp.Equal(tt.x, tt.y, tt.opts...)
  1125  			}()
  1126  			switch {
  1127  			case tt.reason == "":
  1128  				t.Errorf("reason must be provided")
  1129  			case gotPanic == "" && tt.wantPanic:
  1130  				t.Errorf("expected Equal panic\nreason: %s", tt.reason)
  1131  			case gotPanic != "" && !tt.wantPanic:
  1132  				t.Errorf("unexpected Equal panic: got %v\nreason: %v", gotPanic, tt.reason)
  1133  			case gotEqual != tt.wantEqual:
  1134  				t.Errorf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
  1135  			}
  1136  		})
  1137  	}
  1138  }
  1139  
  1140  func TestPanic(t *testing.T) {
  1141  	args := func(x ...interface{}) []interface{} { return x }
  1142  	tests := []struct {
  1143  		label     string        // Test name
  1144  		fnc       interface{}   // Option function to call
  1145  		args      []interface{} // Arguments to pass in
  1146  		wantPanic string        // Expected panic message
  1147  		reason    string        // The reason for the expected outcome
  1148  	}{{
  1149  		label:  "EquateApprox",
  1150  		fnc:    EquateApprox,
  1151  		args:   args(0.0, 0.0),
  1152  		reason: "zero margin and fraction is equivalent to exact equality",
  1153  	}, {
  1154  		label:     "EquateApprox",
  1155  		fnc:       EquateApprox,
  1156  		args:      args(-0.1, 0.0),
  1157  		wantPanic: "margin or fraction must be a non-negative number",
  1158  		reason:    "negative inputs are invalid",
  1159  	}, {
  1160  		label:     "EquateApprox",
  1161  		fnc:       EquateApprox,
  1162  		args:      args(0.0, -0.1),
  1163  		wantPanic: "margin or fraction must be a non-negative number",
  1164  		reason:    "negative inputs are invalid",
  1165  	}, {
  1166  		label:     "EquateApprox",
  1167  		fnc:       EquateApprox,
  1168  		args:      args(math.NaN(), 0.0),
  1169  		wantPanic: "margin or fraction must be a non-negative number",
  1170  		reason:    "NaN inputs are invalid",
  1171  	}, {
  1172  		label:  "EquateApprox",
  1173  		fnc:    EquateApprox,
  1174  		args:   args(1.0, 0.0),
  1175  		reason: "fraction of 1.0 or greater is valid",
  1176  	}, {
  1177  		label:  "EquateApprox",
  1178  		fnc:    EquateApprox,
  1179  		args:   args(0.0, math.Inf(+1)),
  1180  		reason: "margin of infinity is valid",
  1181  	}, {
  1182  		label:     "EquateApproxTime",
  1183  		fnc:       EquateApproxTime,
  1184  		args:      args(time.Duration(-1)),
  1185  		wantPanic: "margin must be a non-negative number",
  1186  		reason:    "negative duration is invalid",
  1187  	}, {
  1188  		label:     "SortSlices",
  1189  		fnc:       SortSlices,
  1190  		args:      args(strings.Compare),
  1191  		wantPanic: "invalid less function",
  1192  		reason:    "func(x, y string) int is wrong signature for less",
  1193  	}, {
  1194  		label:     "SortSlices",
  1195  		fnc:       SortSlices,
  1196  		args:      args((func(_, _ int) bool)(nil)),
  1197  		wantPanic: "invalid less function",
  1198  		reason:    "nil value is not valid",
  1199  	}, {
  1200  		label:     "SortMaps",
  1201  		fnc:       SortMaps,
  1202  		args:      args(strings.Compare),
  1203  		wantPanic: "invalid less function",
  1204  		reason:    "func(x, y string) int is wrong signature for less",
  1205  	}, {
  1206  		label:     "SortMaps",
  1207  		fnc:       SortMaps,
  1208  		args:      args((func(_, _ int) bool)(nil)),
  1209  		wantPanic: "invalid less function",
  1210  		reason:    "nil value is not valid",
  1211  	}, {
  1212  		label:     "IgnoreFields",
  1213  		fnc:       IgnoreFields,
  1214  		args:      args(Foo1{}, ""),
  1215  		wantPanic: "name must not be empty",
  1216  		reason:    "empty selector is invalid",
  1217  	}, {
  1218  		label:     "IgnoreFields",
  1219  		fnc:       IgnoreFields,
  1220  		args:      args(Foo1{}, "."),
  1221  		wantPanic: "name must not be empty",
  1222  		reason:    "single dot selector is invalid",
  1223  	}, {
  1224  		label:  "IgnoreFields",
  1225  		fnc:    IgnoreFields,
  1226  		args:   args(Foo1{}, ".Alpha"),
  1227  		reason: "dot-prefix is okay since Foo1.Alpha reads naturally",
  1228  	}, {
  1229  		label:     "IgnoreFields",
  1230  		fnc:       IgnoreFields,
  1231  		args:      args(Foo1{}, "Alpha."),
  1232  		wantPanic: "name must not be empty",
  1233  		reason:    "dot-suffix is invalid",
  1234  	}, {
  1235  		label:     "IgnoreFields",
  1236  		fnc:       IgnoreFields,
  1237  		args:      args(Foo1{}, "Alpha "),
  1238  		wantPanic: "does not exist",
  1239  		reason:    "identifiers must not have spaces",
  1240  	}, {
  1241  		label:     "IgnoreFields",
  1242  		fnc:       IgnoreFields,
  1243  		args:      args(Foo1{}, "Zulu"),
  1244  		wantPanic: "does not exist",
  1245  		reason:    "name of non-existent field is invalid",
  1246  	}, {
  1247  		label:     "IgnoreFields",
  1248  		fnc:       IgnoreFields,
  1249  		args:      args(Foo1{}, "Alpha.NoExist"),
  1250  		wantPanic: "must be a struct",
  1251  		reason:    "cannot select into a non-struct",
  1252  	}, {
  1253  		label:     "IgnoreFields",
  1254  		fnc:       IgnoreFields,
  1255  		args:      args(&Foo1{}, "Alpha"),
  1256  		wantPanic: "must be a non-pointer struct",
  1257  		reason:    "the type must be a struct (not pointer to a struct)",
  1258  	}, {
  1259  		label:  "IgnoreFields",
  1260  		fnc:    IgnoreFields,
  1261  		args:   args(struct{ privateStruct }{}, "privateStruct"),
  1262  		reason: "privateStruct field permitted since it is the default name of the embedded type",
  1263  	}, {
  1264  		label:  "IgnoreFields",
  1265  		fnc:    IgnoreFields,
  1266  		args:   args(struct{ privateStruct }{}, "Public"),
  1267  		reason: "Public field permitted since it is a forwarded field that is exported",
  1268  	}, {
  1269  		label:     "IgnoreFields",
  1270  		fnc:       IgnoreFields,
  1271  		args:      args(struct{ privateStruct }{}, "private"),
  1272  		wantPanic: "does not exist",
  1273  		reason:    "private field not permitted since it is a forwarded field that is unexported",
  1274  	}, {
  1275  		label:  "IgnoreTypes",
  1276  		fnc:    IgnoreTypes,
  1277  		reason: "empty input is valid",
  1278  	}, {
  1279  		label:     "IgnoreTypes",
  1280  		fnc:       IgnoreTypes,
  1281  		args:      args(nil),
  1282  		wantPanic: "cannot determine type",
  1283  		reason:    "input must not be nil value",
  1284  	}, {
  1285  		label:  "IgnoreTypes",
  1286  		fnc:    IgnoreTypes,
  1287  		args:   args(0, 0, 0),
  1288  		reason: "duplicate inputs of the same type is valid",
  1289  	}, {
  1290  		label:     "IgnoreInterfaces",
  1291  		fnc:       IgnoreInterfaces,
  1292  		args:      args(nil),
  1293  		wantPanic: "input must be an anonymous struct",
  1294  		reason:    "input must not be nil value",
  1295  	}, {
  1296  		label:     "IgnoreInterfaces",
  1297  		fnc:       IgnoreInterfaces,
  1298  		args:      args(Foo1{}),
  1299  		wantPanic: "input must be an anonymous struct",
  1300  		reason:    "input must not be a named struct type",
  1301  	}, {
  1302  		label:     "IgnoreInterfaces",
  1303  		fnc:       IgnoreInterfaces,
  1304  		args:      args(struct{ _ io.Reader }{}),
  1305  		wantPanic: "struct cannot have named fields",
  1306  		reason:    "input must not have named fields",
  1307  	}, {
  1308  		label:     "IgnoreInterfaces",
  1309  		fnc:       IgnoreInterfaces,
  1310  		args:      args(struct{ Foo1 }{}),
  1311  		wantPanic: "embedded field must be an interface type",
  1312  		reason:    "field types must be interfaces",
  1313  	}, {
  1314  		label:     "IgnoreInterfaces",
  1315  		fnc:       IgnoreInterfaces,
  1316  		args:      args(struct{ EmptyInterface }{}),
  1317  		wantPanic: "cannot ignore empty interface",
  1318  		reason:    "field types must not be the empty interface",
  1319  	}, {
  1320  		label: "IgnoreInterfaces",
  1321  		fnc:   IgnoreInterfaces,
  1322  		args: args(struct {
  1323  			io.Reader
  1324  			io.Writer
  1325  			io.Closer
  1326  			io.ReadWriteCloser
  1327  		}{}),
  1328  		reason: "multiple interfaces may be specified, even if they overlap",
  1329  	}, {
  1330  		label:  "IgnoreUnexported",
  1331  		fnc:    IgnoreUnexported,
  1332  		reason: "empty input is valid",
  1333  	}, {
  1334  		label:     "IgnoreUnexported",
  1335  		fnc:       IgnoreUnexported,
  1336  		args:      args(nil),
  1337  		wantPanic: "must be a non-pointer struct",
  1338  		reason:    "input must not be nil value",
  1339  	}, {
  1340  		label:     "IgnoreUnexported",
  1341  		fnc:       IgnoreUnexported,
  1342  		args:      args(&Foo1{}),
  1343  		wantPanic: "must be a non-pointer struct",
  1344  		reason:    "input must be a struct type (not a pointer to a struct)",
  1345  	}, {
  1346  		label:  "IgnoreUnexported",
  1347  		fnc:    IgnoreUnexported,
  1348  		args:   args(Foo1{}, struct{ x, X int }{}),
  1349  		reason: "input may be named or unnamed structs",
  1350  	}, {
  1351  		label:     "AcyclicTransformer",
  1352  		fnc:       AcyclicTransformer,
  1353  		args:      args("", "not a func"),
  1354  		wantPanic: "invalid transformer function",
  1355  		reason:    "AcyclicTransformer has same input requirements as Transformer",
  1356  	}}
  1357  
  1358  	for _, tt := range tests {
  1359  		t.Run(tt.label, func(t *testing.T) {
  1360  			// Prepare function arguments.
  1361  			vf := reflect.ValueOf(tt.fnc)
  1362  			var vargs []reflect.Value
  1363  			for i, arg := range tt.args {
  1364  				if arg == nil {
  1365  					tf := vf.Type()
  1366  					if i == tf.NumIn()-1 && tf.IsVariadic() {
  1367  						vargs = append(vargs, reflect.Zero(tf.In(i).Elem()))
  1368  					} else {
  1369  						vargs = append(vargs, reflect.Zero(tf.In(i)))
  1370  					}
  1371  				} else {
  1372  					vargs = append(vargs, reflect.ValueOf(arg))
  1373  				}
  1374  			}
  1375  
  1376  			// Call the function and capture any panics.
  1377  			var gotPanic string
  1378  			func() {
  1379  				defer func() {
  1380  					if ex := recover(); ex != nil {
  1381  						if s, ok := ex.(string); ok {
  1382  							gotPanic = s
  1383  						} else {
  1384  							panic(ex)
  1385  						}
  1386  					}
  1387  				}()
  1388  				vf.Call(vargs)
  1389  			}()
  1390  
  1391  			switch {
  1392  			case tt.reason == "":
  1393  				t.Errorf("reason must be provided")
  1394  			case tt.wantPanic == "" && gotPanic != "":
  1395  				t.Errorf("unexpected panic message: %s\nreason: %s", gotPanic, tt.reason)
  1396  			case tt.wantPanic != "" && !strings.Contains(gotPanic, tt.wantPanic):
  1397  				t.Errorf("panic message:\ngot:  %s\nwant: %s\nreason: %s", gotPanic, tt.wantPanic, tt.reason)
  1398  			}
  1399  		})
  1400  	}
  1401  }