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 }