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