github.com/integration-system/go-cmp@v0.0.0-20190131081942-ac5582987a2f/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.md file.
     4  
     5  package cmpopts
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"math"
    12  	"reflect"
    13  	"strings"
    14  	"sync"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/integration-system/go-cmp/cmp"
    19  )
    20  
    21  type (
    22  	MyInt    int
    23  	MyFloat  float32
    24  	MyTime   struct{ time.Time }
    25  	MyStruct struct {
    26  		A, B []int
    27  		C, D map[time.Time]string
    28  	}
    29  
    30  	Foo1 struct{ Alpha, Bravo, Charlie int }
    31  	Foo2 struct{ *Foo1 }
    32  	Foo3 struct{ *Foo2 }
    33  	Bar1 struct{ Foo3 }
    34  	Bar2 struct {
    35  		Bar1
    36  		*Foo3
    37  		Bravo float32
    38  	}
    39  	Bar3 struct {
    40  		Bar1
    41  		Bravo *Bar2
    42  		Delta struct{ Echo Foo1 }
    43  		*Foo3
    44  		Alpha string
    45  	}
    46  
    47  	privateStruct struct{ Public, private int }
    48  	PublicStruct  struct{ Public, private int }
    49  	ParentStruct  struct {
    50  		*privateStruct
    51  		*PublicStruct
    52  		Public  int
    53  		private int
    54  	}
    55  
    56  	Everything struct {
    57  		MyInt
    58  		MyFloat
    59  		MyTime
    60  		MyStruct
    61  		Bar3
    62  		ParentStruct
    63  	}
    64  
    65  	EmptyInterface interface{}
    66  )
    67  
    68  func TestOptions(t *testing.T) {
    69  	createBar3X := func() *Bar3 {
    70  		return &Bar3{
    71  			Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 2}}}},
    72  			Bravo: &Bar2{
    73  				Bar1:  Bar1{Foo3{&Foo2{&Foo1{Charlie: 7}}}},
    74  				Foo3:  &Foo3{&Foo2{&Foo1{Bravo: 5}}},
    75  				Bravo: 4,
    76  			},
    77  			Delta: struct{ Echo Foo1 }{Foo1{Charlie: 3}},
    78  			Foo3:  &Foo3{&Foo2{&Foo1{Alpha: 1}}},
    79  			Alpha: "alpha",
    80  		}
    81  	}
    82  	createBar3Y := func() *Bar3 {
    83  		return &Bar3{
    84  			Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 3}}}},
    85  			Bravo: &Bar2{
    86  				Bar1:  Bar1{Foo3{&Foo2{&Foo1{Charlie: 8}}}},
    87  				Foo3:  &Foo3{&Foo2{&Foo1{Bravo: 6}}},
    88  				Bravo: 5,
    89  			},
    90  			Delta: struct{ Echo Foo1 }{Foo1{Charlie: 4}},
    91  			Foo3:  &Foo3{&Foo2{&Foo1{Alpha: 2}}},
    92  			Alpha: "ALPHA",
    93  		}
    94  	}
    95  
    96  	tests := []struct {
    97  		label     string       // Test name
    98  		x, y      interface{}  // Input values to compare
    99  		opts      []cmp.Option // Input options
   100  		wantEqual bool         // Whether the inputs are equal
   101  		wantPanic bool         // Whether Equal should panic
   102  		reason    string       // The reason for the expected outcome
   103  	}{{
   104  		label:     "EquateEmpty",
   105  		x:         []int{},
   106  		y:         []int(nil),
   107  		wantEqual: false,
   108  		reason:    "not equal because empty non-nil and nil slice differ",
   109  	}, {
   110  		label:     "EquateEmpty",
   111  		x:         []int{},
   112  		y:         []int(nil),
   113  		opts:      []cmp.Option{EquateEmpty()},
   114  		wantEqual: true,
   115  		reason:    "equal because EquateEmpty equates empty slices",
   116  	}, {
   117  		label:     "SortSlices",
   118  		x:         []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
   119  		y:         []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
   120  		wantEqual: false,
   121  		reason:    "not equal because element order differs",
   122  	}, {
   123  		label:     "SortSlices",
   124  		x:         []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
   125  		y:         []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
   126  		opts:      []cmp.Option{SortSlices(func(x, y int) bool { return x < y })},
   127  		wantEqual: true,
   128  		reason:    "equal because SortSlices sorts the slices",
   129  	}, {
   130  		label:     "SortSlices",
   131  		x:         []MyInt{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
   132  		y:         []MyInt{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
   133  		opts:      []cmp.Option{SortSlices(func(x, y int) bool { return x < y })},
   134  		wantEqual: false,
   135  		reason:    "not equal because MyInt is not the same type as int",
   136  	}, {
   137  		label:     "SortSlices",
   138  		x:         []float64{0, 1, 1, 2, 2, 2},
   139  		y:         []float64{2, 0, 2, 1, 2, 1},
   140  		opts:      []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })},
   141  		wantEqual: true,
   142  		reason:    "equal even when sorted with duplicate elements",
   143  	}, {
   144  		label:     "SortSlices",
   145  		x:         []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4},
   146  		y:         []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2},
   147  		opts:      []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })},
   148  		wantPanic: true,
   149  		reason:    "panics because SortSlices used with non-transitive less function",
   150  	}, {
   151  		label: "SortSlices",
   152  		x:     []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4},
   153  		y:     []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2},
   154  		opts: []cmp.Option{SortSlices(func(x, y float64) bool {
   155  			return (!math.IsNaN(x) && math.IsNaN(y)) || x < y
   156  		})},
   157  		wantEqual: false,
   158  		reason:    "no panics because SortSlices used with valid less function; not equal because NaN != NaN",
   159  	}, {
   160  		label: "SortSlices+EquateNaNs",
   161  		x:     []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, math.NaN(), 3, 4, 4, 4, 4},
   162  		y:     []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, math.NaN(), 2},
   163  		opts: []cmp.Option{
   164  			EquateNaNs(),
   165  			SortSlices(func(x, y float64) bool {
   166  				return (!math.IsNaN(x) && math.IsNaN(y)) || x < y
   167  			}),
   168  		},
   169  		wantEqual: true,
   170  		reason:    "no panics because SortSlices used with valid less function; equal because EquateNaNs is used",
   171  	}, {
   172  		label: "SortMaps",
   173  		x: map[time.Time]string{
   174  			time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
   175  			time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
   176  			time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday",
   177  		},
   178  		y: map[time.Time]string{
   179  			time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
   180  			time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
   181  			time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday",
   182  		},
   183  		wantEqual: false,
   184  		reason:    "not equal because timezones differ",
   185  	}, {
   186  		label: "SortMaps",
   187  		x: map[time.Time]string{
   188  			time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
   189  			time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
   190  			time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday",
   191  		},
   192  		y: map[time.Time]string{
   193  			time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
   194  			time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
   195  			time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday",
   196  		},
   197  		opts:      []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
   198  		wantEqual: true,
   199  		reason:    "equal because SortMaps flattens to a slice where Time.Equal can be used",
   200  	}, {
   201  		label: "SortMaps",
   202  		x: map[MyTime]string{
   203  			{time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}: "0th birthday",
   204  			{time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)}: "1st birthday",
   205  			{time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC)}: "2nd birthday",
   206  		},
   207  		y: map[MyTime]string{
   208  			{time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "0th birthday",
   209  			{time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "1st birthday",
   210  			{time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "2nd birthday",
   211  		},
   212  		opts:      []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
   213  		wantEqual: false,
   214  		reason:    "not equal because MyTime is not assignable to time.Time",
   215  	}, {
   216  		label: "SortMaps",
   217  		x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
   218  		// => {0, 1, 2, 3, -1, -2, -3},
   219  		y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
   220  		// => {0, 1, 2, 3, 100, 200, 300},
   221  		opts: []cmp.Option{SortMaps(func(a, b int) bool {
   222  			if -10 < a && a <= 0 {
   223  				a *= -100
   224  			}
   225  			if -10 < b && b <= 0 {
   226  				b *= -100
   227  			}
   228  			return a < b
   229  		})},
   230  		wantEqual: false,
   231  		reason:    "not equal because values differ even though SortMap provides valid ordering",
   232  	}, {
   233  		label: "SortMaps",
   234  		x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
   235  		// => {0, 1, 2, 3, -1, -2, -3},
   236  		y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
   237  		// => {0, 1, 2, 3, 100, 200, 300},
   238  		opts: []cmp.Option{
   239  			SortMaps(func(x, y int) bool {
   240  				if -10 < x && x <= 0 {
   241  					x *= -100
   242  				}
   243  				if -10 < y && y <= 0 {
   244  					y *= -100
   245  				}
   246  				return x < y
   247  			}),
   248  			cmp.Comparer(func(x, y int) bool {
   249  				if -10 < x && x <= 0 {
   250  					x *= -100
   251  				}
   252  				if -10 < y && y <= 0 {
   253  					y *= -100
   254  				}
   255  				return x == y
   256  			}),
   257  		},
   258  		wantEqual: true,
   259  		reason:    "equal because Comparer used to equate differences",
   260  	}, {
   261  		label: "SortMaps",
   262  		x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
   263  		y:     map[int]string{},
   264  		opts: []cmp.Option{SortMaps(func(x, y int) bool {
   265  			return x < y && x >= 0 && y >= 0
   266  		})},
   267  		wantPanic: true,
   268  		reason:    "panics because SortMaps used with non-transitive less function",
   269  	}, {
   270  		label: "SortMaps",
   271  		x:     map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
   272  		y:     map[int]string{},
   273  		opts: []cmp.Option{SortMaps(func(x, y int) bool {
   274  			return math.Abs(float64(x)) < math.Abs(float64(y))
   275  		})},
   276  		wantPanic: true,
   277  		reason:    "panics because SortMaps used with partial less function",
   278  	}, {
   279  		label: "EquateEmpty+SortSlices+SortMaps",
   280  		x: MyStruct{
   281  			A: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
   282  			C: map[time.Time]string{
   283  				time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
   284  				time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
   285  			},
   286  			D: map[time.Time]string{},
   287  		},
   288  		y: MyStruct{
   289  			A: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
   290  			B: []int{},
   291  			C: map[time.Time]string{
   292  				time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
   293  				time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
   294  			},
   295  		},
   296  		opts: []cmp.Option{
   297  			EquateEmpty(),
   298  			SortSlices(func(x, y int) bool { return x < y }),
   299  			SortMaps(func(x, y time.Time) bool { return x.Before(y) }),
   300  		},
   301  		wantEqual: true,
   302  		reason:    "no panics because EquateEmpty should compose with the sort options",
   303  	}, {
   304  		label:     "EquateApprox",
   305  		x:         3.09,
   306  		y:         3.10,
   307  		wantEqual: false,
   308  		reason:    "not equal because floats do not exactly matches",
   309  	}, {
   310  		label:     "EquateApprox",
   311  		x:         3.09,
   312  		y:         3.10,
   313  		opts:      []cmp.Option{EquateApprox(0, 0)},
   314  		wantEqual: false,
   315  		reason:    "not equal because EquateApprox(0 ,0) is equivalent to using ==",
   316  	}, {
   317  		label:     "EquateApprox",
   318  		x:         3.09,
   319  		y:         3.10,
   320  		opts:      []cmp.Option{EquateApprox(0.003, 0.009)},
   321  		wantEqual: false,
   322  		reason:    "not equal because EquateApprox is too strict",
   323  	}, {
   324  		label:     "EquateApprox",
   325  		x:         3.09,
   326  		y:         3.10,
   327  		opts:      []cmp.Option{EquateApprox(0, 0.011)},
   328  		wantEqual: true,
   329  		reason:    "equal because margin is loose enough to match",
   330  	}, {
   331  		label:     "EquateApprox",
   332  		x:         3.09,
   333  		y:         3.10,
   334  		opts:      []cmp.Option{EquateApprox(0.004, 0)},
   335  		wantEqual: true,
   336  		reason:    "equal because fraction is loose enough to match",
   337  	}, {
   338  		label:     "EquateApprox",
   339  		x:         3.09,
   340  		y:         3.10,
   341  		opts:      []cmp.Option{EquateApprox(0.004, 0.011)},
   342  		wantEqual: true,
   343  		reason:    "equal because both the margin and fraction are loose enough to match",
   344  	}, {
   345  		label:     "EquateApprox",
   346  		x:         float32(3.09),
   347  		y:         float64(3.10),
   348  		opts:      []cmp.Option{EquateApprox(0.004, 0)},
   349  		wantEqual: false,
   350  		reason:    "not equal because the types differ",
   351  	}, {
   352  		label:     "EquateApprox",
   353  		x:         float32(3.09),
   354  		y:         float32(3.10),
   355  		opts:      []cmp.Option{EquateApprox(0.004, 0)},
   356  		wantEqual: true,
   357  		reason:    "equal because EquateApprox also applies on float32s",
   358  	}, {
   359  		label:     "EquateApprox",
   360  		x:         []float64{math.Inf(+1), math.Inf(-1)},
   361  		y:         []float64{math.Inf(+1), math.Inf(-1)},
   362  		opts:      []cmp.Option{EquateApprox(0, 1)},
   363  		wantEqual: true,
   364  		reason:    "equal because we fall back on == which matches Inf (EquateApprox does not apply on Inf) ",
   365  	}, {
   366  		label:     "EquateApprox",
   367  		x:         []float64{math.Inf(+1), -1e100},
   368  		y:         []float64{+1e100, math.Inf(-1)},
   369  		opts:      []cmp.Option{EquateApprox(0, 1)},
   370  		wantEqual: false,
   371  		reason:    "not equal because we fall back on == where Inf != 1e100 (EquateApprox does not apply on Inf)",
   372  	}, {
   373  		label:     "EquateApprox",
   374  		x:         float64(+1e100),
   375  		y:         float64(-1e100),
   376  		opts:      []cmp.Option{EquateApprox(math.Inf(+1), 0)},
   377  		wantEqual: true,
   378  		reason:    "equal because infinite fraction matches everything",
   379  	}, {
   380  		label:     "EquateApprox",
   381  		x:         float64(+1e100),
   382  		y:         float64(-1e100),
   383  		opts:      []cmp.Option{EquateApprox(0, math.Inf(+1))},
   384  		wantEqual: true,
   385  		reason:    "equal because infinite margin matches everything",
   386  	}, {
   387  		label:     "EquateApprox",
   388  		x:         math.Pi,
   389  		y:         math.Pi,
   390  		opts:      []cmp.Option{EquateApprox(0, 0)},
   391  		wantEqual: true,
   392  		reason:    "equal because EquateApprox(0, 0) is equivalent to ==",
   393  	}, {
   394  		label:     "EquateApprox",
   395  		x:         math.Pi,
   396  		y:         math.Nextafter(math.Pi, math.Inf(+1)),
   397  		opts:      []cmp.Option{EquateApprox(0, 0)},
   398  		wantEqual: false,
   399  		reason:    "not equal because EquateApprox(0, 0) is equivalent to ==",
   400  	}, {
   401  		label:     "EquateNaNs",
   402  		x:         []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
   403  		y:         []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
   404  		wantEqual: false,
   405  		reason:    "not equal because NaN != NaN",
   406  	}, {
   407  		label:     "EquateNaNs",
   408  		x:         []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
   409  		y:         []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
   410  		opts:      []cmp.Option{EquateNaNs()},
   411  		wantEqual: true,
   412  		reason:    "equal because EquateNaNs allows NaN == NaN",
   413  	}, {
   414  		label:     "EquateNaNs",
   415  		x:         []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0},
   416  		y:         []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0},
   417  		opts:      []cmp.Option{EquateNaNs()},
   418  		wantEqual: true,
   419  		reason:    "equal because EquateNaNs operates on float32",
   420  	}, {
   421  		label: "EquateApprox+EquateNaNs",
   422  		x:     []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.01, 5001},
   423  		y:     []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.02, 5002},
   424  		opts: []cmp.Option{
   425  			EquateNaNs(),
   426  			EquateApprox(0.01, 0),
   427  		},
   428  		wantEqual: true,
   429  		reason:    "equal because EquateNaNs and EquateApprox compose together",
   430  	}, {
   431  		label: "EquateApprox+EquateNaNs",
   432  		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},
   433  		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},
   434  		opts: []cmp.Option{
   435  			EquateNaNs(),
   436  			EquateApprox(0.01, 0),
   437  		},
   438  		wantEqual: false,
   439  		reason:    "not equal because EquateApprox and EquateNaNs do not apply on a named type",
   440  	}, {
   441  		label: "EquateApprox+EquateNaNs+Transform",
   442  		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},
   443  		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},
   444  		opts: []cmp.Option{
   445  			cmp.Transformer("", func(x MyFloat) float64 { return float64(x) }),
   446  			EquateNaNs(),
   447  			EquateApprox(0.01, 0),
   448  		},
   449  		wantEqual: true,
   450  		reason:    "equal because named type is transformed to float64",
   451  	}, {
   452  		label:     "IgnoreFields",
   453  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   454  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   455  		wantEqual: false,
   456  		reason:    "not equal because values do not match in deeply embedded field",
   457  	}, {
   458  		label:     "IgnoreFields",
   459  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   460  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   461  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Alpha")},
   462  		wantEqual: true,
   463  		reason:    "equal because IgnoreField ignores deeply embedded field: Alpha",
   464  	}, {
   465  		label:     "IgnoreFields",
   466  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   467  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   468  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo1.Alpha")},
   469  		wantEqual: true,
   470  		reason:    "equal because IgnoreField ignores deeply embedded field: Foo1.Alpha",
   471  	}, {
   472  		label:     "IgnoreFields",
   473  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   474  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   475  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo2.Alpha")},
   476  		wantEqual: true,
   477  		reason:    "equal because IgnoreField ignores deeply embedded field: Foo2.Alpha",
   478  	}, {
   479  		label:     "IgnoreFields",
   480  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   481  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   482  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Alpha")},
   483  		wantEqual: true,
   484  		reason:    "equal because IgnoreField ignores deeply embedded field: Foo3.Alpha",
   485  	}, {
   486  		label:     "IgnoreFields",
   487  		x:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
   488  		y:         Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
   489  		opts:      []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Foo2.Alpha")},
   490  		wantEqual: true,
   491  		reason:    "equal because IgnoreField ignores deeply embedded field: Foo3.Foo2.Alpha",
   492  	}, {
   493  		label:     "IgnoreFields",
   494  		x:         createBar3X(),
   495  		y:         createBar3Y(),
   496  		wantEqual: false,
   497  		reason:    "not equal because many deeply nested or embedded fields differ",
   498  	}, {
   499  		label:     "IgnoreFields",
   500  		x:         createBar3X(),
   501  		y:         createBar3Y(),
   502  		opts:      []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Foo3", "Alpha")},
   503  		wantEqual: true,
   504  		reason:    "equal because IgnoreFields ignores fields at the highest levels",
   505  	}, {
   506  		label: "IgnoreFields",
   507  		x:     createBar3X(),
   508  		y:     createBar3Y(),
   509  		opts: []cmp.Option{
   510  			IgnoreFields(Bar3{},
   511  				"Bar1.Foo3.Bravo",
   512  				"Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
   513  				"Bravo.Foo3.Foo2.Foo1.Bravo",
   514  				"Bravo.Bravo",
   515  				"Delta.Echo.Charlie",
   516  				"Foo3.Foo2.Foo1.Alpha",
   517  				"Alpha",
   518  			),
   519  		},
   520  		wantEqual: true,
   521  		reason:    "equal because IgnoreFields ignores fields using fully-qualified field",
   522  	}, {
   523  		label: "IgnoreFields",
   524  		x:     createBar3X(),
   525  		y:     createBar3Y(),
   526  		opts: []cmp.Option{
   527  			IgnoreFields(Bar3{},
   528  				"Bar1.Foo3.Bravo",
   529  				"Bravo.Foo3.Foo2.Foo1.Bravo",
   530  				"Bravo.Bravo",
   531  				"Delta.Echo.Charlie",
   532  				"Foo3.Foo2.Foo1.Alpha",
   533  				"Alpha",
   534  			),
   535  		},
   536  		wantEqual: false,
   537  		reason:    "not equal because one fully-qualified field is not ignored: Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
   538  	}, {
   539  		label:     "IgnoreFields",
   540  		x:         createBar3X(),
   541  		y:         createBar3Y(),
   542  		opts:      []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha")},
   543  		wantEqual: false,
   544  		reason:    "not equal because highest-level field is not ignored: Foo3",
   545  	}, {
   546  		label:     "IgnoreTypes",
   547  		x:         []interface{}{5, "same"},
   548  		y:         []interface{}{6, "same"},
   549  		wantEqual: false,
   550  		reason:    "not equal because 5 != 6",
   551  	}, {
   552  		label:     "IgnoreTypes",
   553  		x:         []interface{}{5, "same"},
   554  		y:         []interface{}{6, "same"},
   555  		opts:      []cmp.Option{IgnoreTypes(0)},
   556  		wantEqual: true,
   557  		reason:    "equal because ints are ignored",
   558  	}, {
   559  		label:     "IgnoreTypes+IgnoreInterfaces",
   560  		x:         []interface{}{5, "same", new(bytes.Buffer)},
   561  		y:         []interface{}{6, "same", new(bytes.Buffer)},
   562  		opts:      []cmp.Option{IgnoreTypes(0)},
   563  		wantPanic: true,
   564  		reason:    "panics because bytes.Buffer has unexported fields",
   565  	}, {
   566  		label: "IgnoreTypes+IgnoreInterfaces",
   567  		x:     []interface{}{5, "same", new(bytes.Buffer)},
   568  		y:     []interface{}{6, "diff", new(bytes.Buffer)},
   569  		opts: []cmp.Option{
   570  			IgnoreTypes(0, ""),
   571  			IgnoreInterfaces(struct{ io.Reader }{}),
   572  		},
   573  		wantEqual: true,
   574  		reason:    "equal because bytes.Buffer is ignored by match on interface type",
   575  	}, {
   576  		label: "IgnoreTypes+IgnoreInterfaces",
   577  		x:     []interface{}{5, "same", new(bytes.Buffer)},
   578  		y:     []interface{}{6, "same", new(bytes.Buffer)},
   579  		opts: []cmp.Option{
   580  			IgnoreTypes(0, ""),
   581  			IgnoreInterfaces(struct {
   582  				io.Reader
   583  				io.Writer
   584  				fmt.Stringer
   585  			}{}),
   586  		},
   587  		wantEqual: true,
   588  		reason:    "equal because bytes.Buffer is ignored by match on multiple interface types",
   589  	}, {
   590  		label:     "IgnoreInterfaces",
   591  		x:         struct{ mu sync.Mutex }{},
   592  		y:         struct{ mu sync.Mutex }{},
   593  		wantPanic: true,
   594  		reason:    "panics because sync.Mutex has unexported fields",
   595  	}, {
   596  		label:     "IgnoreInterfaces",
   597  		x:         struct{ mu sync.Mutex }{},
   598  		y:         struct{ mu sync.Mutex }{},
   599  		opts:      []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
   600  		wantEqual: true,
   601  		reason:    "equal because IgnoreInterfaces applies on values (with pointer receiver)",
   602  	}, {
   603  		label:     "IgnoreInterfaces",
   604  		x:         struct{ mu *sync.Mutex }{},
   605  		y:         struct{ mu *sync.Mutex }{},
   606  		opts:      []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
   607  		wantEqual: true,
   608  		reason:    "equal because IgnoreInterfaces applies on pointers",
   609  	}, {
   610  		label:     "IgnoreUnexported",
   611  		x:         ParentStruct{Public: 1, private: 2},
   612  		y:         ParentStruct{Public: 1, private: -2},
   613  		opts:      []cmp.Option{cmp.AllowUnexported(ParentStruct{})},
   614  		wantEqual: false,
   615  		reason:    "not equal because ParentStruct.private differs with AllowUnexported",
   616  	}, {
   617  		label:     "IgnoreUnexported",
   618  		x:         ParentStruct{Public: 1, private: 2},
   619  		y:         ParentStruct{Public: 1, private: -2},
   620  		opts:      []cmp.Option{IgnoreUnexported(ParentStruct{})},
   621  		wantEqual: true,
   622  		reason:    "equal because IgnoreUnexported ignored ParentStruct.private",
   623  	}, {
   624  		label: "IgnoreUnexported",
   625  		x:     ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
   626  		y:     ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
   627  		opts: []cmp.Option{
   628  			cmp.AllowUnexported(PublicStruct{}),
   629  			IgnoreUnexported(ParentStruct{}),
   630  		},
   631  		wantEqual: true,
   632  		reason:    "equal because ParentStruct.private is ignored",
   633  	}, {
   634  		label: "IgnoreUnexported",
   635  		x:     ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
   636  		y:     ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
   637  		opts: []cmp.Option{
   638  			cmp.AllowUnexported(PublicStruct{}),
   639  			IgnoreUnexported(ParentStruct{}),
   640  		},
   641  		wantEqual: false,
   642  		reason:    "not equal because ParentStruct.PublicStruct.private differs and not ignored by IgnoreUnexported(ParentStruct{})",
   643  	}, {
   644  		label: "IgnoreUnexported",
   645  		x:     ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
   646  		y:     ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
   647  		opts: []cmp.Option{
   648  			IgnoreUnexported(ParentStruct{}, PublicStruct{}),
   649  		},
   650  		wantEqual: true,
   651  		reason:    "equal because both ParentStruct.PublicStruct and ParentStruct.PublicStruct.private are ignored",
   652  	}, {
   653  		label: "IgnoreUnexported",
   654  		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
   655  		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
   656  		opts: []cmp.Option{
   657  			cmp.AllowUnexported(privateStruct{}, PublicStruct{}, ParentStruct{}),
   658  		},
   659  		wantEqual: false,
   660  		reason:    "not equal since ParentStruct.privateStruct differs",
   661  	}, {
   662  		label: "IgnoreUnexported",
   663  		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
   664  		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
   665  		opts: []cmp.Option{
   666  			cmp.AllowUnexported(privateStruct{}, PublicStruct{}),
   667  			IgnoreUnexported(ParentStruct{}),
   668  		},
   669  		wantEqual: true,
   670  		reason:    "equal because ParentStruct.privateStruct ignored by IgnoreUnexported(ParentStruct{})",
   671  	}, {
   672  		label: "IgnoreUnexported",
   673  		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
   674  		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: -4}},
   675  		opts: []cmp.Option{
   676  			cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
   677  			IgnoreUnexported(privateStruct{}),
   678  		},
   679  		wantEqual: true,
   680  		reason:    "equal because privateStruct.private ignored by IgnoreUnexported(privateStruct{})",
   681  	}, {
   682  		label: "IgnoreUnexported",
   683  		x:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
   684  		y:     ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
   685  		opts: []cmp.Option{
   686  			cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
   687  			IgnoreUnexported(privateStruct{}),
   688  		},
   689  		wantEqual: false,
   690  		reason:    "not equal because privateStruct.Public differs and not ignored by IgnoreUnexported(privateStruct{})",
   691  	}, {
   692  		label: "IgnoreFields+IgnoreTypes+IgnoreUnexported",
   693  		x: &Everything{
   694  			MyInt:   5,
   695  			MyFloat: 3.3,
   696  			MyTime:  MyTime{time.Now()},
   697  			Bar3:    *createBar3X(),
   698  			ParentStruct: ParentStruct{
   699  				Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4},
   700  			},
   701  		},
   702  		y: &Everything{
   703  			MyInt:   -5,
   704  			MyFloat: 3.3,
   705  			MyTime:  MyTime{time.Now()},
   706  			Bar3:    *createBar3Y(),
   707  			ParentStruct: ParentStruct{
   708  				Public: 1, private: -2, PublicStruct: &PublicStruct{Public: -3, private: -4},
   709  			},
   710  		},
   711  		opts: []cmp.Option{
   712  			IgnoreFields(Everything{}, "MyTime", "Bar3.Foo3"),
   713  			IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha"),
   714  			IgnoreTypes(MyInt(0), PublicStruct{}),
   715  			IgnoreUnexported(ParentStruct{}),
   716  		},
   717  		wantEqual: true,
   718  		reason:    "equal because all Ignore options can be composed together",
   719  	}, {
   720  		label: "AcyclicTransformer",
   721  		x:     "a\nb\nc\nd",
   722  		y:     "a\nb\nd\nd",
   723  		opts: []cmp.Option{
   724  			AcyclicTransformer("", func(s string) []string { return strings.Split(s, "\n") }),
   725  		},
   726  		wantEqual: false,
   727  		reason:    "not equal because 3rd line differs, but should not recurse infinitely",
   728  	}, {
   729  		label: "AcyclicTransformer",
   730  		x:     []string{"foo", "Bar", "BAZ"},
   731  		y:     []string{"Foo", "BAR", "baz"},
   732  		opts: []cmp.Option{
   733  			AcyclicTransformer("", func(s string) string { return strings.ToUpper(s) }),
   734  		},
   735  		wantEqual: true,
   736  		reason:    "equal because of strings.ToUpper; AcyclicTransformer unnecessary, but check this still works",
   737  	}, {
   738  		label: "AcyclicTransformer",
   739  		x:     "this is a sentence",
   740  		y: "this   			is a 			sentence",
   741  		opts: []cmp.Option{
   742  			AcyclicTransformer("", func(s string) []string { return strings.Fields(s) }),
   743  		},
   744  		wantEqual: true,
   745  		reason:    "equal because acyclic transformer splits on any contiguous whitespace",
   746  	}}
   747  
   748  	for _, tt := range tests {
   749  		t.Run(tt.label, func(t *testing.T) {
   750  			var gotEqual bool
   751  			var gotPanic string
   752  			func() {
   753  				defer func() {
   754  					if ex := recover(); ex != nil {
   755  						gotPanic = fmt.Sprint(ex)
   756  					}
   757  				}()
   758  				gotEqual = cmp.Equal(tt.x, tt.y, tt.opts...)
   759  			}()
   760  			switch {
   761  			case tt.reason == "":
   762  				t.Errorf("reason must be provided")
   763  			case gotPanic == "" && tt.wantPanic:
   764  				t.Errorf("expected Equal panic\nreason: %s", tt.reason)
   765  			case gotPanic != "" && !tt.wantPanic:
   766  				t.Errorf("unexpected Equal panic: got %v\nreason: %v", gotPanic, tt.reason)
   767  			case gotEqual != tt.wantEqual:
   768  				t.Errorf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
   769  			}
   770  		})
   771  	}
   772  }
   773  
   774  func TestPanic(t *testing.T) {
   775  	args := func(x ...interface{}) []interface{} { return x }
   776  	tests := []struct {
   777  		label     string        // Test name
   778  		fnc       interface{}   // Option function to call
   779  		args      []interface{} // Arguments to pass in
   780  		wantPanic string        // Expected panic message
   781  		reason    string        // The reason for the expected outcome
   782  	}{{
   783  		label:  "EquateApprox",
   784  		fnc:    EquateApprox,
   785  		args:   args(0.0, 0.0),
   786  		reason: "zero margin and fraction is equivalent to exact equality",
   787  	}, {
   788  		label:     "EquateApprox",
   789  		fnc:       EquateApprox,
   790  		args:      args(-0.1, 0.0),
   791  		wantPanic: "margin or fraction must be a non-negative number",
   792  		reason:    "negative inputs are invalid",
   793  	}, {
   794  		label:     "EquateApprox",
   795  		fnc:       EquateApprox,
   796  		args:      args(0.0, -0.1),
   797  		wantPanic: "margin or fraction must be a non-negative number",
   798  		reason:    "negative inputs are invalid",
   799  	}, {
   800  		label:     "EquateApprox",
   801  		fnc:       EquateApprox,
   802  		args:      args(math.NaN(), 0.0),
   803  		wantPanic: "margin or fraction must be a non-negative number",
   804  		reason:    "NaN inputs are invalid",
   805  	}, {
   806  		label:  "EquateApprox",
   807  		fnc:    EquateApprox,
   808  		args:   args(1.0, 0.0),
   809  		reason: "fraction of 1.0 or greater is valid",
   810  	}, {
   811  		label:  "EquateApprox",
   812  		fnc:    EquateApprox,
   813  		args:   args(0.0, math.Inf(+1)),
   814  		reason: "margin of infinity is valid",
   815  	}, {
   816  		label:     "SortSlices",
   817  		fnc:       SortSlices,
   818  		args:      args(strings.Compare),
   819  		wantPanic: "invalid less function",
   820  		reason:    "func(x, y string) int is wrong signature for less",
   821  	}, {
   822  		label:     "SortSlices",
   823  		fnc:       SortSlices,
   824  		args:      args((func(_, _ int) bool)(nil)),
   825  		wantPanic: "invalid less function",
   826  		reason:    "nil value is not valid",
   827  	}, {
   828  		label:     "SortMaps",
   829  		fnc:       SortMaps,
   830  		args:      args(strings.Compare),
   831  		wantPanic: "invalid less function",
   832  		reason:    "func(x, y string) int is wrong signature for less",
   833  	}, {
   834  		label:     "SortMaps",
   835  		fnc:       SortMaps,
   836  		args:      args((func(_, _ int) bool)(nil)),
   837  		wantPanic: "invalid less function",
   838  		reason:    "nil value is not valid",
   839  	}, {
   840  		label:     "IgnoreFields",
   841  		fnc:       IgnoreFields,
   842  		args:      args(Foo1{}, ""),
   843  		wantPanic: "name must not be empty",
   844  		reason:    "empty selector is invalid",
   845  	}, {
   846  		label:     "IgnoreFields",
   847  		fnc:       IgnoreFields,
   848  		args:      args(Foo1{}, "."),
   849  		wantPanic: "name must not be empty",
   850  		reason:    "single dot selector is invalid",
   851  	}, {
   852  		label:  "IgnoreFields",
   853  		fnc:    IgnoreFields,
   854  		args:   args(Foo1{}, ".Alpha"),
   855  		reason: "dot-prefix is okay since Foo1.Alpha reads naturally",
   856  	}, {
   857  		label:     "IgnoreFields",
   858  		fnc:       IgnoreFields,
   859  		args:      args(Foo1{}, "Alpha."),
   860  		wantPanic: "name must not be empty",
   861  		reason:    "dot-suffix is invalid",
   862  	}, {
   863  		label:     "IgnoreFields",
   864  		fnc:       IgnoreFields,
   865  		args:      args(Foo1{}, "Alpha "),
   866  		wantPanic: "does not exist",
   867  		reason:    "identifiers must not have spaces",
   868  	}, {
   869  		label:     "IgnoreFields",
   870  		fnc:       IgnoreFields,
   871  		args:      args(Foo1{}, "Zulu"),
   872  		wantPanic: "does not exist",
   873  		reason:    "name of non-existent field is invalid",
   874  	}, {
   875  		label:     "IgnoreFields",
   876  		fnc:       IgnoreFields,
   877  		args:      args(Foo1{}, "Alpha.NoExist"),
   878  		wantPanic: "must be a struct",
   879  		reason:    "cannot select into a non-struct",
   880  	}, {
   881  		label:     "IgnoreFields",
   882  		fnc:       IgnoreFields,
   883  		args:      args(&Foo1{}, "Alpha"),
   884  		wantPanic: "must be a struct",
   885  		reason:    "the type must be a struct (not pointer to a struct)",
   886  	}, {
   887  		label:     "IgnoreFields",
   888  		fnc:       IgnoreFields,
   889  		args:      args(Foo1{}, "unexported"),
   890  		wantPanic: "name must be exported",
   891  		reason:    "unexported fields must not be specified",
   892  	}, {
   893  		label:  "IgnoreTypes",
   894  		fnc:    IgnoreTypes,
   895  		reason: "empty input is valid",
   896  	}, {
   897  		label:     "IgnoreTypes",
   898  		fnc:       IgnoreTypes,
   899  		args:      args(nil),
   900  		wantPanic: "cannot determine type",
   901  		reason:    "input must not be nil value",
   902  	}, {
   903  		label:  "IgnoreTypes",
   904  		fnc:    IgnoreTypes,
   905  		args:   args(0, 0, 0),
   906  		reason: "duplicate inputs of the same type is valid",
   907  	}, {
   908  		label:     "IgnoreInterfaces",
   909  		fnc:       IgnoreInterfaces,
   910  		args:      args(nil),
   911  		wantPanic: "input must be an anonymous struct",
   912  		reason:    "input must not be nil value",
   913  	}, {
   914  		label:     "IgnoreInterfaces",
   915  		fnc:       IgnoreInterfaces,
   916  		args:      args(Foo1{}),
   917  		wantPanic: "input must be an anonymous struct",
   918  		reason:    "input must not be a named struct type",
   919  	}, {
   920  		label:     "IgnoreInterfaces",
   921  		fnc:       IgnoreInterfaces,
   922  		args:      args(struct{ _ io.Reader }{}),
   923  		wantPanic: "struct cannot have named fields",
   924  		reason:    "input must not have named fields",
   925  	}, {
   926  		label:     "IgnoreInterfaces",
   927  		fnc:       IgnoreInterfaces,
   928  		args:      args(struct{ Foo1 }{}),
   929  		wantPanic: "embedded field must be an interface type",
   930  		reason:    "field types must be interfaces",
   931  	}, {
   932  		label:     "IgnoreInterfaces",
   933  		fnc:       IgnoreInterfaces,
   934  		args:      args(struct{ EmptyInterface }{}),
   935  		wantPanic: "cannot ignore empty interface",
   936  		reason:    "field types must not be the empty interface",
   937  	}, {
   938  		label: "IgnoreInterfaces",
   939  		fnc:   IgnoreInterfaces,
   940  		args: args(struct {
   941  			io.Reader
   942  			io.Writer
   943  			io.Closer
   944  			io.ReadWriteCloser
   945  		}{}),
   946  		reason: "multiple interfaces may be specified, even if they overlap",
   947  	}, {
   948  		label:  "IgnoreUnexported",
   949  		fnc:    IgnoreUnexported,
   950  		reason: "empty input is valid",
   951  	}, {
   952  		label:     "IgnoreUnexported",
   953  		fnc:       IgnoreUnexported,
   954  		args:      args(nil),
   955  		wantPanic: "invalid struct type",
   956  		reason:    "input must not be nil value",
   957  	}, {
   958  		label:     "IgnoreUnexported",
   959  		fnc:       IgnoreUnexported,
   960  		args:      args(&Foo1{}),
   961  		wantPanic: "invalid struct type",
   962  		reason:    "input must be a struct type (not a pointer to a struct)",
   963  	}, {
   964  		label:  "IgnoreUnexported",
   965  		fnc:    IgnoreUnexported,
   966  		args:   args(Foo1{}, struct{ x, X int }{}),
   967  		reason: "input may be named or unnamed structs",
   968  	}, {
   969  		label:     "AcyclicTransformer",
   970  		fnc:       AcyclicTransformer,
   971  		args:      args("", "not a func"),
   972  		wantPanic: "invalid transformer function",
   973  		reason:    "AcyclicTransformer has same input requirements as Transformer",
   974  	}}
   975  
   976  	for _, tt := range tests {
   977  		t.Run(tt.label, func(t *testing.T) {
   978  			// Prepare function arguments.
   979  			vf := reflect.ValueOf(tt.fnc)
   980  			var vargs []reflect.Value
   981  			for i, arg := range tt.args {
   982  				if arg == nil {
   983  					tf := vf.Type()
   984  					if i == tf.NumIn()-1 && tf.IsVariadic() {
   985  						vargs = append(vargs, reflect.Zero(tf.In(i).Elem()))
   986  					} else {
   987  						vargs = append(vargs, reflect.Zero(tf.In(i)))
   988  					}
   989  				} else {
   990  					vargs = append(vargs, reflect.ValueOf(arg))
   991  				}
   992  			}
   993  
   994  			// Call the function and capture any panics.
   995  			var gotPanic string
   996  			func() {
   997  				defer func() {
   998  					if ex := recover(); ex != nil {
   999  						if s, ok := ex.(string); ok {
  1000  							gotPanic = s
  1001  						} else {
  1002  							panic(ex)
  1003  						}
  1004  					}
  1005  				}()
  1006  				vf.Call(vargs)
  1007  			}()
  1008  
  1009  			switch {
  1010  			case tt.reason == "":
  1011  				t.Errorf("reason must be provided")
  1012  			case tt.wantPanic == "" && gotPanic != "":
  1013  				t.Errorf("unexpected panic message: %s\nreason: %s", gotPanic, tt.reason)
  1014  			case tt.wantPanic != "" && !strings.Contains(gotPanic, tt.wantPanic):
  1015  				t.Errorf("panic message:\ngot:  %s\nwant: %s\nreason: %s", gotPanic, tt.wantPanic, tt.reason)
  1016  			}
  1017  		})
  1018  	}
  1019  }