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