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 cmp_test 6 7import ( 8 "bytes" 9 "crypto/sha256" 10 "encoding/json" 11 "errors" 12 "flag" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "math" 17 "math/rand" 18 "reflect" 19 "regexp" 20 "sort" 21 "strconv" 22 "strings" 23 "sync" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 "github.com/google/go-cmp/cmp/internal/flags" 30 31 pb "github.com/google/go-cmp/cmp/internal/testprotos" 32 ts "github.com/google/go-cmp/cmp/internal/teststructs" 33 foo1 "github.com/google/go-cmp/cmp/internal/teststructs/foo1" 34 foo2 "github.com/google/go-cmp/cmp/internal/teststructs/foo2" 35) 36 37func init() { 38 flags.Deterministic = true 39} 40 41var update = flag.Bool("update", false, "update golden test files") 42 43const goldenHeaderPrefix = "<<< " 44const goldenFooterPrefix = ">>> " 45 46// mustParseGolden parses a file as a set of key-value pairs. 47// 48// The syntax is simple and looks something like: 49// 50// <<< Key1 51// value1a 52// value1b 53// >>> Key1 54// <<< Key2 55// value2 56// >>> Key2 57// 58// It is the user's responsibility to choose a sufficiently unique key name 59// such that it never appears in the body of the value itself. 60func mustParseGolden(path string) map[string]string { 61 b, err := ioutil.ReadFile(path) 62 if err != nil { 63 panic(err) 64 } 65 s := string(b) 66 67 out := map[string]string{} 68 for len(s) > 0 { 69 // Identify the next header. 70 i := strings.Index(s, "\n") + len("\n") 71 header := s[:i] 72 if !strings.HasPrefix(header, goldenHeaderPrefix) { 73 panic(fmt.Sprintf("invalid header: %q", header)) 74 } 75 76 // Locate the next footer. 77 footer := goldenFooterPrefix + header[len(goldenHeaderPrefix):] 78 j := strings.Index(s, footer) 79 if j < 0 { 80 panic(fmt.Sprintf("missing footer: %q", footer)) 81 } 82 83 // Store the name and data. 84 name := header[len(goldenHeaderPrefix) : len(header)-len("\n")] 85 if _, ok := out[name]; ok { 86 panic(fmt.Sprintf("duplicate name: %q", name)) 87 } 88 out[name] = s[len(header):j] 89 s = s[j+len(footer):] 90 } 91 return out 92} 93func mustFormatGolden(path string, in []struct{ Name, Data string }) { 94 var b []byte 95 for _, v := range in { 96 b = append(b, goldenHeaderPrefix+v.Name+"\n"...) 97 b = append(b, v.Data...) 98 b = append(b, goldenFooterPrefix+v.Name+"\n"...) 99 } 100 if err := ioutil.WriteFile(path, b, 0664); err != nil { 101 panic(err) 102 } 103} 104 105var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC) 106 107// TODO(≥go1.18): Define a generic function that boxes a value on the heap. 108func newInt(n int) *int { return &n } 109 110type Stringer string 111 112func newStringer(s string) fmt.Stringer { return (*Stringer)(&s) } 113func (s Stringer) String() string { return string(s) } 114 115type test struct { 116 label string // Test name 117 x, y interface{} // Input values to compare 118 opts []cmp.Option // Input options 119 wantEqual bool // Whether any difference is expected 120 wantPanic string // Sub-string of an expected panic message 121 reason string // The reason for the expected outcome 122} 123 124func TestDiff(t *testing.T) { 125 var tests []test 126 tests = append(tests, comparerTests()...) 127 tests = append(tests, transformerTests()...) 128 tests = append(tests, reporterTests()...) 129 tests = append(tests, embeddedTests()...) 130 tests = append(tests, methodTests()...) 131 tests = append(tests, cycleTests()...) 132 tests = append(tests, project1Tests()...) 133 tests = append(tests, project2Tests()...) 134 tests = append(tests, project3Tests()...) 135 tests = append(tests, project4Tests()...) 136 137 const goldenFile = "testdata/diffs" 138 gotDiffs := []struct{ Name, Data string }{} 139 wantDiffs := mustParseGolden(goldenFile) 140 for _, tt := range tests { 141 tt := tt 142 t.Run(tt.label, func(t *testing.T) { 143 if !*update { 144 t.Parallel() 145 } 146 var gotDiff, gotPanic string 147 func() { 148 defer func() { 149 if ex := recover(); ex != nil { 150 if s, ok := ex.(string); ok { 151 gotPanic = s 152 } else { 153 panic(ex) 154 } 155 } 156 }() 157 gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...) 158 }() 159 160 switch { 161 case strings.Contains(t.Name(), "#"): 162 panic("unique test name must be provided") 163 case tt.reason == "": 164 panic("reason must be provided") 165 case tt.wantPanic == "": 166 if gotPanic != "" { 167 t.Fatalf("unexpected panic message: %s\nreason: %v", gotPanic, tt.reason) 168 } 169 if *update { 170 if gotDiff != "" { 171 gotDiffs = append(gotDiffs, struct{ Name, Data string }{t.Name(), gotDiff}) 172 } 173 } else { 174 wantDiff := wantDiffs[t.Name()] 175 if diff := cmp.Diff(wantDiff, gotDiff); diff != "" { 176 t.Fatalf("Diff:\ngot:\n%s\nwant:\n%s\ndiff: (-want +got)\n%s\nreason: %v", gotDiff, wantDiff, diff, tt.reason) 177 } 178 } 179 gotEqual := gotDiff == "" 180 if gotEqual != tt.wantEqual { 181 t.Fatalf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason) 182 } 183 default: 184 if !strings.Contains(gotPanic, tt.wantPanic) { 185 t.Fatalf("panic message:\ngot: %s\nwant: %s\nreason: %v", gotPanic, tt.wantPanic, tt.reason) 186 } 187 } 188 }) 189 } 190 191 if *update { 192 mustFormatGolden(goldenFile, gotDiffs) 193 } 194} 195 196func comparerTests() []test { 197 const label = "Comparer" 198 199 type Iface1 interface { 200 Method() 201 } 202 type Iface2 interface { 203 Method() 204 } 205 206 type tarHeader struct { 207 Name string 208 Mode int64 209 Uid int 210 Gid int 211 Size int64 212 ModTime time.Time 213 Typeflag byte 214 Linkname string 215 Uname string 216 Gname string 217 Devmajor int64 218 Devminor int64 219 AccessTime time.Time 220 ChangeTime time.Time 221 Xattrs map[string]string 222 } 223 224 type namedWithUnexported struct { 225 unexported string 226 } 227 228 makeTarHeaders := func(tf byte) (hs []tarHeader) { 229 for i := 0; i < 5; i++ { 230 hs = append(hs, tarHeader{ 231 Name: fmt.Sprintf("some/dummy/test/file%d", i), 232 Mode: 0664, Uid: i * 1000, Gid: i * 1000, Size: 1 << uint(i), 233 ModTime: now.Add(time.Duration(i) * time.Hour), 234 Uname: "user", Gname: "group", 235 Typeflag: tf, 236 }) 237 } 238 return hs 239 } 240 241 return []test{{ 242 label: label + "/Nil", 243 x: nil, 244 y: nil, 245 wantEqual: true, 246 reason: "nils are equal", 247 }, { 248 label: label + "/Integer", 249 x: 1, 250 y: 1, 251 wantEqual: true, 252 reason: "identical integers are equal", 253 }, { 254 label: label + "/UnfilteredIgnore", 255 x: 1, 256 y: 1, 257 opts: []cmp.Option{cmp.Ignore()}, 258 wantPanic: "cannot use an unfiltered option", 259 reason: "unfiltered options are functionally useless", 260 }, { 261 label: label + "/UnfilteredCompare", 262 x: 1, 263 y: 1, 264 opts: []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })}, 265 wantPanic: "cannot use an unfiltered option", 266 reason: "unfiltered options are functionally useless", 267 }, { 268 label: label + "/UnfilteredTransform", 269 x: 1, 270 y: 1, 271 opts: []cmp.Option{cmp.Transformer("λ", func(x interface{}) interface{} { return x })}, 272 wantPanic: "cannot use an unfiltered option", 273 reason: "unfiltered options are functionally useless", 274 }, { 275 label: label + "/AmbiguousOptions", 276 x: 1, 277 y: 1, 278 opts: []cmp.Option{ 279 cmp.Comparer(func(x, y int) bool { return true }), 280 cmp.Transformer("λ", func(x int) float64 { return float64(x) }), 281 }, 282 wantPanic: "ambiguous set of applicable options", 283 reason: "both options apply on int, leading to ambiguity", 284 }, { 285 label: label + "/IgnorePrecedence", 286 x: 1, 287 y: 1, 288 opts: []cmp.Option{ 289 cmp.FilterPath(func(p cmp.Path) bool { 290 return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int 291 }, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}), 292 cmp.Comparer(func(x, y int) bool { return true }), 293 cmp.Transformer("λ", func(x int) float64 { return float64(x) }), 294 }, 295 wantEqual: true, 296 reason: "ignore takes precedence over other options", 297 }, { 298 label: label + "/UnknownOption", 299 opts: []cmp.Option{struct{ cmp.Option }{}}, 300 wantPanic: "unknown option", 301 reason: "use of unknown option should panic", 302 }, { 303 label: label + "/StructEqual", 304 x: struct{ A, B, C int }{1, 2, 3}, 305 y: struct{ A, B, C int }{1, 2, 3}, 306 wantEqual: true, 307 reason: "struct comparison with all equal fields", 308 }, { 309 label: label + "/StructInequal", 310 x: struct{ A, B, C int }{1, 2, 3}, 311 y: struct{ A, B, C int }{1, 2, 4}, 312 wantEqual: false, 313 reason: "struct comparison with inequal C field", 314 }, { 315 label: label + "/StructUnexported", 316 x: struct{ a, b, c int }{1, 2, 3}, 317 y: struct{ a, b, c int }{1, 2, 4}, 318 wantPanic: "cannot handle unexported field", 319 reason: "unexported fields result in a panic by default", 320 }, { 321 label: label + "/PointerStructEqual", 322 x: &struct{ A *int }{newInt(4)}, 323 y: &struct{ A *int }{newInt(4)}, 324 wantEqual: true, 325 reason: "comparison of pointer to struct with equal A field", 326 }, { 327 label: label + "/PointerStructInequal", 328 x: &struct{ A *int }{newInt(4)}, 329 y: &struct{ A *int }{newInt(5)}, 330 wantEqual: false, 331 reason: "comparison of pointer to struct with inequal A field", 332 }, { 333 label: label + "/PointerStructTrueComparer", 334 x: &struct{ A *int }{newInt(4)}, 335 y: &struct{ A *int }{newInt(5)}, 336 opts: []cmp.Option{ 337 cmp.Comparer(func(x, y int) bool { return true }), 338 }, 339 wantEqual: true, 340 reason: "comparison of pointer to struct with inequal A field, but treated as equal with always equal comparer", 341 }, { 342 label: label + "/PointerStructNonNilComparer", 343 x: &struct{ A *int }{newInt(4)}, 344 y: &struct{ A *int }{newInt(5)}, 345 opts: []cmp.Option{ 346 cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }), 347 }, 348 wantEqual: true, 349 reason: "comparison of pointer to struct with inequal A field, but treated as equal with comparer checking pointers for nilness", 350 }, { 351 label: label + "/StructNestedPointerEqual", 352 x: &struct{ R *bytes.Buffer }{}, 353 y: &struct{ R *bytes.Buffer }{}, 354 wantEqual: true, 355 reason: "equal since both pointers in R field are nil", 356 }, { 357 label: label + "/StructNestedPointerInequal", 358 x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)}, 359 y: &struct{ R *bytes.Buffer }{}, 360 wantEqual: false, 361 reason: "inequal since R field is inequal", 362 }, { 363 label: label + "/StructNestedPointerTrueComparer", 364 x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)}, 365 y: &struct{ R *bytes.Buffer }{}, 366 opts: []cmp.Option{ 367 cmp.Comparer(func(x, y io.Reader) bool { return true }), 368 }, 369 wantEqual: true, 370 reason: "equal despite inequal R field values since the comparer always reports true", 371 }, { 372 label: label + "/StructNestedValueUnexportedPanic1", 373 x: &struct{ R bytes.Buffer }{}, 374 y: &struct{ R bytes.Buffer }{}, 375 wantPanic: "cannot handle unexported field", 376 reason: "bytes.Buffer contains unexported fields", 377 }, { 378 label: label + "/StructNestedValueUnexportedPanic2", 379 x: &struct{ R bytes.Buffer }{}, 380 y: &struct{ R bytes.Buffer }{}, 381 opts: []cmp.Option{ 382 cmp.Comparer(func(x, y io.Reader) bool { return true }), 383 }, 384 wantPanic: "cannot handle unexported field", 385 reason: "bytes.Buffer value does not implement io.Reader", 386 }, { 387 label: label + "/StructNestedValueEqual", 388 x: &struct{ R bytes.Buffer }{}, 389 y: &struct{ R bytes.Buffer }{}, 390 opts: []cmp.Option{ 391 cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }), 392 cmp.Comparer(func(x, y io.Reader) bool { return true }), 393 }, 394 wantEqual: true, 395 reason: "bytes.Buffer pointer due to shallow copy does implement io.Reader", 396 }, { 397 label: label + "/RegexpUnexportedPanic", 398 x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, 399 y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, 400 wantPanic: "cannot handle unexported field", 401 reason: "regexp.Regexp contains unexported fields", 402 }, { 403 label: label + "/RegexpEqual", 404 x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, 405 y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, 406 opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool { 407 if x == nil || y == nil { 408 return x == nil && y == nil 409 } 410 return x.String() == y.String() 411 })}, 412 wantEqual: true, 413 reason: "comparer for *regexp.Regexp applied with equal regexp strings", 414 }, { 415 label: label + "/RegexpInequal", 416 x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, 417 y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")}, 418 opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool { 419 if x == nil || y == nil { 420 return x == nil && y == nil 421 } 422 return x.String() == y.String() 423 })}, 424 wantEqual: false, 425 reason: "comparer for *regexp.Regexp applied with inequal regexp strings", 426 }, { 427 label: label + "/TriplePointerEqual", 428 x: func() ***int { 429 a := 0 430 b := &a 431 c := &b 432 return &c 433 }(), 434 y: func() ***int { 435 a := 0 436 b := &a 437 c := &b 438 return &c 439 }(), 440 wantEqual: true, 441 reason: "three layers of pointers to the same value", 442 }, { 443 label: label + "/TriplePointerInequal", 444 x: func() ***int { 445 a := 0 446 b := &a 447 c := &b 448 return &c 449 }(), 450 y: func() ***int { 451 a := 1 452 b := &a 453 c := &b 454 return &c 455 }(), 456 wantEqual: false, 457 reason: "three layers of pointers to different values", 458 }, { 459 label: label + "/SliceWithDifferingCapacity", 460 x: []int{1, 2, 3, 4, 5}[:3], 461 y: []int{1, 2, 3}, 462 wantEqual: true, 463 reason: "elements past the slice length are not compared", 464 }, { 465 label: label + "/StringerEqual", 466 x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")}, 467 y: struct{ fmt.Stringer }{regexp.MustCompile("hello")}, 468 opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })}, 469 wantEqual: true, 470 reason: "comparer for fmt.Stringer used to compare differing types with same string", 471 }, { 472 label: label + "/StringerInequal", 473 x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")}, 474 y: struct{ fmt.Stringer }{regexp.MustCompile("hello2")}, 475 opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })}, 476 wantEqual: false, 477 reason: "comparer for fmt.Stringer used to compare differing types with different strings", 478 }, { 479 label: label + "/DifferingHash", 480 x: sha256.Sum256([]byte{'a'}), 481 y: sha256.Sum256([]byte{'b'}), 482 wantEqual: false, 483 reason: "hash differs", 484 }, { 485 label: label + "/NilStringer", 486 x: new(fmt.Stringer), 487 y: nil, 488 wantEqual: false, 489 reason: "by default differing types are always inequal", 490 }, { 491 label: label + "/TarHeaders", 492 x: makeTarHeaders('0'), 493 y: makeTarHeaders('\x00'), 494 wantEqual: false, 495 reason: "type flag differs between the headers", 496 }, { 497 label: label + "/NonDeterministicComparer", 498 x: make([]int, 1000), 499 y: make([]int, 1000), 500 opts: []cmp.Option{ 501 cmp.Comparer(func(_, _ int) bool { 502 return rand.Intn(2) == 0 503 }), 504 }, 505 wantPanic: "non-deterministic or non-symmetric function detected", 506 reason: "non-deterministic comparer", 507 }, { 508 label: label + "/NonDeterministicFilter", 509 x: make([]int, 1000), 510 y: make([]int, 1000), 511 opts: []cmp.Option{ 512 cmp.FilterValues(func(_, _ int) bool { 513 return rand.Intn(2) == 0 514 }, cmp.Ignore()), 515 }, 516 wantPanic: "non-deterministic or non-symmetric function detected", 517 reason: "non-deterministic filter", 518 }, { 519 label: label + "/AsymmetricComparer", 520 x: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 521 y: []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, 522 opts: []cmp.Option{ 523 cmp.Comparer(func(x, y int) bool { 524 return x < y 525 }), 526 }, 527 wantPanic: "non-deterministic or non-symmetric function detected", 528 reason: "asymmetric comparer", 529 }, { 530 label: label + "/NonDeterministicTransformer", 531 x: make([]string, 1000), 532 y: make([]string, 1000), 533 opts: []cmp.Option{ 534 cmp.Transformer("λ", func(x string) int { 535 return rand.Int() 536 }), 537 }, 538 wantPanic: "non-deterministic function detected", 539 reason: "non-deterministic transformer", 540 }, { 541 label: label + "/IrreflexiveComparison", 542 x: make([]int, 10), 543 y: make([]int, 10), 544 opts: []cmp.Option{ 545 cmp.Transformer("λ", func(x int) float64 { 546 return math.NaN() 547 }), 548 }, 549 wantEqual: false, 550 reason: "dynamic checks should not panic for non-reflexive comparisons", 551 }, { 552 label: label + "/StringerMapKey", 553 x: map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}}, 554 y: map[*pb.Stringer]*pb.Stringer(nil), 555 wantEqual: false, 556 reason: "stringer should be used to format the map key", 557 }, { 558 label: label + "/StringerBacktick", 559 x: []*pb.Stringer{{`multi\nline\nline\nline`}}, 560 wantEqual: false, 561 reason: "stringer should use backtick quoting if more readable", 562 }, { 563 label: label + "/AvoidPanicAssignableConverter", 564 x: struct{ I Iface2 }{}, 565 y: struct{ I Iface2 }{}, 566 opts: []cmp.Option{ 567 cmp.Comparer(func(x, y Iface1) bool { 568 return x == nil && y == nil 569 }), 570 }, 571 wantEqual: true, 572 reason: "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143", 573 }, { 574 label: label + "/AvoidPanicAssignableTransformer", 575 x: struct{ I Iface2 }{}, 576 y: struct{ I Iface2 }{}, 577 opts: []cmp.Option{ 578 cmp.Transformer("λ", func(v Iface1) bool { 579 return v == nil 580 }), 581 }, 582 wantEqual: true, 583 reason: "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143", 584 }, { 585 label: label + "/AvoidPanicAssignableFilter", 586 x: struct{ I Iface2 }{}, 587 y: struct{ I Iface2 }{}, 588 opts: []cmp.Option{ 589 cmp.FilterValues(func(x, y Iface1) bool { 590 return x == nil && y == nil 591 }, cmp.Ignore()), 592 }, 593 wantEqual: true, 594 reason: "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143", 595 }, { 596 label: label + "/DynamicMap", 597 x: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63, "name": "Sammy Sosa"}}, 598 y: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65.0, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63.0, "name": "Sammy Sosa"}}, 599 wantEqual: false, 600 reason: "dynamic map with differing types (but semantically equivalent values) should be inequal", 601 }, { 602 label: label + "/MapKeyPointer", 603 x: map[*int]string{ 604 new(int): "hello", 605 }, 606 y: map[*int]string{ 607 new(int): "world", 608 }, 609 wantEqual: false, 610 reason: "map keys should use shallow (rather than deep) pointer comparison", 611 }, { 612 label: label + "/IgnoreSliceElements", 613 x: [2][]int{ 614 {0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 7, 8, 0, 9, 0, 0}, 615 {0, 1, 0, 0, 0, 20}, 616 }, 617 y: [2][]int{ 618 {1, 2, 3, 0, 4, 5, 6, 7, 0, 8, 9, 0, 0, 0}, 619 {0, 0, 1, 2, 0, 0, 0}, 620 }, 621 opts: []cmp.Option{ 622 cmp.FilterPath(func(p cmp.Path) bool { 623 vx, vy := p.Last().Values() 624 if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 { 625 return true 626 } 627 if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 { 628 return true 629 } 630 return false 631 }, cmp.Ignore()), 632 }, 633 wantEqual: false, 634 reason: "all zero slice elements are ignored (even if missing)", 635 }, { 636 label: label + "/IgnoreMapEntries", 637 x: [2]map[string]int{ 638 {"ignore1": 0, "ignore2": 0, "keep1": 1, "keep2": 2, "KEEP3": 3, "IGNORE3": 0}, 639 {"keep1": 1, "ignore1": 0}, 640 }, 641 y: [2]map[string]int{ 642 {"ignore1": 0, "ignore3": 0, "ignore4": 0, "keep1": 1, "keep2": 2, "KEEP3": 3}, 643 {"keep1": 1, "keep2": 2, "ignore2": 0}, 644 }, 645 opts: []cmp.Option{ 646 cmp.FilterPath(func(p cmp.Path) bool { 647 vx, vy := p.Last().Values() 648 if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 { 649 return true 650 } 651 if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 { 652 return true 653 } 654 return false 655 }, cmp.Ignore()), 656 }, 657 wantEqual: false, 658 reason: "all zero map entries are ignored (even if missing)", 659 }, { 660 label: label + "/PanicUnexportedNamed", 661 x: namedWithUnexported{unexported: "x"}, 662 y: namedWithUnexported{unexported: "y"}, 663 wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".namedWithUnexported", 664 reason: "panic on named struct type with unexported field", 665 }, { 666 label: label + "/PanicUnexportedUnnamed", 667 x: struct{ a int }{}, 668 y: struct{ a int }{}, 669 wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".(struct { a int })", 670 reason: "panic on unnamed struct type with unexported field", 671 }, { 672 label: label + "/UnaddressableStruct", 673 x: struct{ s fmt.Stringer }{new(bytes.Buffer)}, 674 y: struct{ s fmt.Stringer }{nil}, 675 opts: []cmp.Option{ 676 cmp.AllowUnexported(struct{ s fmt.Stringer }{}), 677 cmp.FilterPath(func(p cmp.Path) bool { 678 if _, ok := p.Last().(cmp.StructField); !ok { 679 return false 680 } 681 682 t := p.Index(-1).Type() 683 vx, vy := p.Index(-1).Values() 684 pvx, pvy := p.Index(-2).Values() 685 switch { 686 case vx.Type() != t: 687 panic(fmt.Sprintf("inconsistent type: %v != %v", vx.Type(), t)) 688 case vy.Type() != t: 689 panic(fmt.Sprintf("inconsistent type: %v != %v", vy.Type(), t)) 690 case vx.CanAddr() != pvx.CanAddr(): 691 panic(fmt.Sprintf("inconsistent addressability: %v != %v", vx.CanAddr(), pvx.CanAddr())) 692 case vy.CanAddr() != pvy.CanAddr(): 693 panic(fmt.Sprintf("inconsistent addressability: %v != %v", vy.CanAddr(), pvy.CanAddr())) 694 } 695 return true 696 }, cmp.Ignore()), 697 }, 698 wantEqual: true, 699 reason: "verify that exporter does not leak implementation details", 700 }, { 701 label: label + "/ErrorPanic", 702 x: io.EOF, 703 y: io.EOF, 704 wantPanic: "consider using cmpopts.EquateErrors", 705 reason: "suggest cmpopts.EquateErrors when accessing unexported fields of error types", 706 }, { 707 label: label + "/ErrorEqual", 708 x: io.EOF, 709 y: io.EOF, 710 opts: []cmp.Option{cmpopts.EquateErrors()}, 711 wantEqual: true, 712 reason: "cmpopts.EquateErrors should equate these two errors as sentinel values", 713 }} 714} 715 716func transformerTests() []test { 717 type StringBytes struct { 718 String string 719 Bytes []byte 720 } 721 722 const label = "Transformer" 723 724 transformOnce := func(name string, f interface{}) cmp.Option { 725 xform := cmp.Transformer(name, f) 726 return cmp.FilterPath(func(p cmp.Path) bool { 727 for _, ps := range p { 728 if tr, ok := ps.(cmp.Transform); ok && tr.Option() == xform { 729 return false 730 } 731 } 732 return true 733 }, xform) 734 } 735 736 return []test{{ 737 label: label + "/Uints", 738 x: uint8(0), 739 y: uint8(1), 740 opts: []cmp.Option{ 741 cmp.Transformer("λ", func(in uint8) uint16 { return uint16(in) }), 742 cmp.Transformer("λ", func(in uint16) uint32 { return uint32(in) }), 743 cmp.Transformer("λ", func(in uint32) uint64 { return uint64(in) }), 744 }, 745 wantEqual: false, 746 reason: "transform uint8 -> uint16 -> uint32 -> uint64", 747 }, { 748 label: label + "/Ambiguous", 749 x: 0, 750 y: 1, 751 opts: []cmp.Option{ 752 cmp.Transformer("λ", func(in int) int { return in / 2 }), 753 cmp.Transformer("λ", func(in int) int { return in }), 754 }, 755 wantPanic: "ambiguous set of applicable options", 756 reason: "both transformers apply on int", 757 }, { 758 label: label + "/Filtered", 759 x: []int{0, -5, 0, -1}, 760 y: []int{1, 3, 0, -5}, 761 opts: []cmp.Option{ 762 cmp.FilterValues( 763 func(x, y int) bool { return x+y >= 0 }, 764 cmp.Transformer("λ", func(in int) int64 { return int64(in / 2) }), 765 ), 766 cmp.FilterValues( 767 func(x, y int) bool { return x+y < 0 }, 768 cmp.Transformer("λ", func(in int) int64 { return int64(in) }), 769 ), 770 }, 771 wantEqual: false, 772 reason: "disjoint transformers filtered based on the values", 773 }, { 774 label: label + "/DisjointOutput", 775 x: 0, 776 y: 1, 777 opts: []cmp.Option{ 778 cmp.Transformer("λ", func(in int) interface{} { 779 if in == 0 { 780 return "zero" 781 } 782 return float64(in) 783 }), 784 }, 785 wantEqual: false, 786 reason: "output type differs based on input value", 787 }, { 788 label: label + "/JSON", 789 x: `{ 790 "firstName": "John", 791 "lastName": "Smith", 792 "age": 25, 793 "isAlive": true, 794 "address": { 795 "city": "Los Angeles", 796 "postalCode": "10021-3100", 797 "state": "CA", 798 "streetAddress": "21 2nd Street" 799 }, 800 "phoneNumbers": [{ 801 "type": "home", 802 "number": "212 555-4321" 803 },{ 804 "type": "office", 805 "number": "646 555-4567" 806 },{ 807 "number": "123 456-7890", 808 "type": "mobile" 809 }], 810 "children": [] 811 }`, 812 y: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":25, 813 "address":{"streetAddress":"21 2nd Street","city":"New York", 814 "state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home", 815 "number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{ 816 "type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`, 817 opts: []cmp.Option{ 818 transformOnce("ParseJSON", func(s string) (m map[string]interface{}) { 819 if err := json.Unmarshal([]byte(s), &m); err != nil { 820 panic(err) 821 } 822 return m 823 }), 824 }, 825 wantEqual: false, 826 reason: "transformer used to parse JSON input", 827 }, { 828 label: label + "/AcyclicString", 829 x: StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")}, 830 y: StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")}, 831 opts: []cmp.Option{ 832 transformOnce("SplitString", func(s string) []string { return strings.Split(s, "\n") }), 833 transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }), 834 }, 835 wantEqual: false, 836 reason: "string -> []string and []byte -> [][]byte transformer only applied once", 837 }, { 838 label: label + "/CyclicString", 839 x: "a\nb\nc\n", 840 y: "a\nb\nc\n", 841 opts: []cmp.Option{ 842 cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }), 843 }, 844 wantPanic: "recursive set of Transformers detected", 845 reason: "cyclic transformation from string -> []string -> string", 846 }, { 847 label: label + "/CyclicComplex", 848 x: complex64(0), 849 y: complex64(0), 850 opts: []cmp.Option{ 851 cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }), 852 cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }), 853 cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }), 854 }, 855 wantPanic: "recursive set of Transformers detected", 856 reason: "cyclic transformation from complex64 -> complex128 -> [2]float64 -> complex64", 857 }} 858} 859 860func reporterTests() []test { 861 const label = "Reporter" 862 863 type ( 864 MyString string 865 MyByte byte 866 MyBytes []byte 867 MyInt int8 868 MyInts []int8 869 MyUint int16 870 MyUints []int16 871 MyFloat float32 872 MyFloats []float32 873 MyComposite struct { 874 StringA string 875 StringB MyString 876 BytesA []byte 877 BytesB []MyByte 878 BytesC MyBytes 879 IntsA []int8 880 IntsB []MyInt 881 IntsC MyInts 882 UintsA []uint16 883 UintsB []MyUint 884 UintsC MyUints 885 FloatsA []float32 886 FloatsB []MyFloat 887 FloatsC MyFloats 888 } 889 PointerString *string 890 ) 891 892 return []test{{ 893 label: label + "/PanicStringer", 894 x: struct{ X fmt.Stringer }{struct{ fmt.Stringer }{nil}}, 895 y: struct{ X fmt.Stringer }{bytes.NewBuffer(nil)}, 896 wantEqual: false, 897 reason: "panic from fmt.Stringer should not crash the reporter", 898 }, { 899 label: label + "/PanicError", 900 x: struct{ X error }{struct{ error }{nil}}, 901 y: struct{ X error }{errors.New("")}, 902 wantEqual: false, 903 reason: "panic from error should not crash the reporter", 904 }, { 905 label: label + "/AmbiguousType", 906 x: foo1.Bar{}, 907 y: foo2.Bar{}, 908 wantEqual: false, 909 reason: "reporter should display the qualified type name to disambiguate between the two values", 910 }, { 911 label: label + "/AmbiguousPointer", 912 x: newInt(0), 913 y: newInt(0), 914 opts: []cmp.Option{ 915 cmp.Comparer(func(x, y *int) bool { return x == y }), 916 }, 917 wantEqual: false, 918 reason: "reporter should display the address to disambiguate between the two values", 919 }, { 920 label: label + "/AmbiguousPointerStruct", 921 x: struct{ I *int }{newInt(0)}, 922 y: struct{ I *int }{newInt(0)}, 923 opts: []cmp.Option{ 924 cmp.Comparer(func(x, y *int) bool { return x == y }), 925 }, 926 wantEqual: false, 927 reason: "reporter should display the address to disambiguate between the two struct fields", 928 }, { 929 label: label + "/AmbiguousPointerSlice", 930 x: []*int{newInt(0)}, 931 y: []*int{newInt(0)}, 932 opts: []cmp.Option{ 933 cmp.Comparer(func(x, y *int) bool { return x == y }), 934 }, 935 wantEqual: false, 936 reason: "reporter should display the address to disambiguate between the two slice elements", 937 }, { 938 label: label + "/AmbiguousPointerMap", 939 x: map[string]*int{"zero": newInt(0)}, 940 y: map[string]*int{"zero": newInt(0)}, 941 opts: []cmp.Option{ 942 cmp.Comparer(func(x, y *int) bool { return x == y }), 943 }, 944 wantEqual: false, 945 reason: "reporter should display the address to disambiguate between the two map values", 946 }, { 947 label: label + "/AmbiguousStringer", 948 x: Stringer("hello"), 949 y: newStringer("hello"), 950 wantEqual: false, 951 reason: "reporter should avoid calling String to disambiguate between the two values", 952 }, { 953 label: label + "/AmbiguousStringerStruct", 954 x: struct{ S fmt.Stringer }{Stringer("hello")}, 955 y: struct{ S fmt.Stringer }{newStringer("hello")}, 956 wantEqual: false, 957 reason: "reporter should avoid calling String to disambiguate between the two struct fields", 958 }, { 959 label: label + "/AmbiguousStringerSlice", 960 x: []fmt.Stringer{Stringer("hello")}, 961 y: []fmt.Stringer{newStringer("hello")}, 962 wantEqual: false, 963 reason: "reporter should avoid calling String to disambiguate between the two slice elements", 964 }, { 965 label: label + "/AmbiguousStringerMap", 966 x: map[string]fmt.Stringer{"zero": Stringer("hello")}, 967 y: map[string]fmt.Stringer{"zero": newStringer("hello")}, 968 wantEqual: false, 969 reason: "reporter should avoid calling String to disambiguate between the two map values", 970 }, { 971 label: label + "/AmbiguousSliceHeader", 972 x: make([]int, 0, 5), 973 y: make([]int, 0, 1000), 974 opts: []cmp.Option{ 975 cmp.Comparer(func(x, y []int) bool { return cap(x) == cap(y) }), 976 }, 977 wantEqual: false, 978 reason: "reporter should display the slice header to disambiguate between the two slice values", 979 }, { 980 label: label + "/AmbiguousStringerMapKey", 981 x: map[interface{}]string{ 982 nil: "nil", 983 Stringer("hello"): "goodbye", 984 foo1.Bar{"fizz"}: "buzz", 985 }, 986 y: map[interface{}]string{ 987 newStringer("hello"): "goodbye", 988 foo2.Bar{"fizz"}: "buzz", 989 }, 990 wantEqual: false, 991 reason: "reporter should avoid calling String to disambiguate between the two map keys", 992 }, { 993 label: label + "/NonAmbiguousStringerMapKey", 994 x: map[interface{}]string{Stringer("hello"): "goodbye"}, 995 y: map[interface{}]string{newStringer("fizz"): "buzz"}, 996 wantEqual: false, 997 reason: "reporter should call String as there is no ambiguity between the two map keys", 998 }, { 999 label: label + "/InvalidUTF8", 1000 x: MyString("\xed\xa0\x80"), 1001 wantEqual: false, 1002 reason: "invalid UTF-8 should format as quoted string", 1003 }, { 1004 label: label + "/UnbatchedSlice", 1005 x: MyComposite{IntsA: []int8{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}}, 1006 y: MyComposite{IntsA: []int8{10, 11, 21, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}}, 1007 wantEqual: false, 1008 reason: "unbatched diffing desired since few elements differ", 1009 }, { 1010 label: label + "/BatchedSlice", 1011 x: MyComposite{IntsA: []int8{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}}, 1012 y: MyComposite{IntsA: []int8{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}}, 1013 wantEqual: false, 1014 reason: "batched diffing desired since many elements differ", 1015 }, { 1016 label: label + "/BatchedWithComparer", 1017 x: MyComposite{BytesA: []byte{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}}, 1018 y: MyComposite{BytesA: []byte{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}}, 1019 wantEqual: false, 1020 opts: []cmp.Option{ 1021 cmp.Comparer(bytes.Equal), 1022 }, 1023 reason: "batched diffing desired since many elements differ", 1024 }, { 1025 label: label + "/BatchedLong", 1026 x: MyComposite{IntsA: []int8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}}, 1027 wantEqual: false, 1028 reason: "batched output desired for a single slice of primitives unique to one of the inputs", 1029 }, { 1030 label: label + "/BatchedNamedAndUnnamed", 1031 x: MyComposite{ 1032 BytesA: []byte{1, 2, 3}, 1033 BytesB: []MyByte{4, 5, 6}, 1034 BytesC: MyBytes{7, 8, 9}, 1035 IntsA: []int8{-1, -2, -3}, 1036 IntsB: []MyInt{-4, -5, -6}, 1037 IntsC: MyInts{-7, -8, -9}, 1038 UintsA: []uint16{1000, 2000, 3000}, 1039 UintsB: []MyUint{4000, 5000, 6000}, 1040 UintsC: MyUints{7000, 8000, 9000}, 1041 FloatsA: []float32{1.5, 2.5, 3.5}, 1042 FloatsB: []MyFloat{4.5, 5.5, 6.5}, 1043 FloatsC: MyFloats{7.5, 8.5, 9.5}, 1044 }, 1045 y: MyComposite{ 1046 BytesA: []byte{3, 2, 1}, 1047 BytesB: []MyByte{6, 5, 4}, 1048 BytesC: MyBytes{9, 8, 7}, 1049 IntsA: []int8{-3, -2, -1}, 1050 IntsB: []MyInt{-6, -5, -4}, 1051 IntsC: MyInts{-9, -8, -7}, 1052 UintsA: []uint16{3000, 2000, 1000}, 1053 UintsB: []MyUint{6000, 5000, 4000}, 1054 UintsC: MyUints{9000, 8000, 7000}, 1055 FloatsA: []float32{3.5, 2.5, 1.5}, 1056 FloatsB: []MyFloat{6.5, 5.5, 4.5}, 1057 FloatsC: MyFloats{9.5, 8.5, 7.5}, 1058 }, 1059 wantEqual: false, 1060 reason: "batched diffing available for both named and unnamed slices", 1061 }, { 1062 label: label + "/BinaryHexdump", 1063 x: MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeX\x95A\xfd$fX\x8byT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1U~{\xf6\xb3~\x1dWi \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")}, 1064 y: MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1u-[]]\xf6\xb3haha~\x1dWI \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")}, 1065 wantEqual: false, 1066 reason: "binary diff in hexdump form since data is binary data", 1067 }, { 1068 label: label + "/StringHexdump", 1069 x: MyComposite{StringB: MyString("readme.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000046\x0000000000000\x00011173\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")}, 1070 y: MyComposite{StringB: MyString("gopher.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000043\x0000000000000\x00011217\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")}, 1071 wantEqual: false, 1072 reason: "binary diff desired since string looks like binary data", 1073 }, { 1074 label: label + "/BinaryString", 1075 x: MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"314 54th Avenue","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`)}, 1076 y: MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`)}, 1077 wantEqual: false, 1078 reason: "batched textual diff desired since bytes looks like textual data", 1079 }, { 1080 label: label + "/TripleQuote", 1081 x: MyComposite{StringA: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n"}, 1082 y: MyComposite{StringA: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n"}, 1083 wantEqual: false, 1084 reason: "use triple-quote syntax", 1085 }, { 1086 label: label + "/TripleQuoteSlice", 1087 x: []string{ 1088 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1089 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1090 }, 1091 y: []string{ 1092 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\n", 1093 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1094 }, 1095 wantEqual: false, 1096 reason: "use triple-quote syntax for slices of strings", 1097 }, { 1098 label: label + "/TripleQuoteNamedTypes", 1099 x: MyComposite{ 1100 StringB: MyString("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"), 1101 BytesC: MyBytes("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"), 1102 }, 1103 y: MyComposite{ 1104 StringB: MyString("aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"), 1105 BytesC: MyBytes("aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"), 1106 }, 1107 wantEqual: false, 1108 reason: "use triple-quote syntax for named types", 1109 }, { 1110 label: label + "/TripleQuoteSliceNamedTypes", 1111 x: []MyString{ 1112 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1113 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1114 }, 1115 y: []MyString{ 1116 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\n", 1117 "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1118 }, 1119 wantEqual: false, 1120 reason: "use triple-quote syntax for slices of named strings", 1121 }, { 1122 label: label + "/TripleQuoteEndlines", 1123 x: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\r\nhhh\n\riii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n\r", 1124 y: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\r\nhhh\n\riii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz", 1125 wantEqual: false, 1126 reason: "use triple-quote syntax", 1127 }, { 1128 label: label + "/AvoidTripleQuoteAmbiguousQuotes", 1129 x: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1130 y: "aaa\nbbb\nCCC\nddd\neee\n\"\"\"\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1131 wantEqual: false, 1132 reason: "avoid triple-quote syntax due to presence of ambiguous triple quotes", 1133 }, { 1134 label: label + "/AvoidTripleQuoteAmbiguousEllipsis", 1135 x: "aaa\nbbb\nccc\n...\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1136 y: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1137 wantEqual: false, 1138 reason: "avoid triple-quote syntax due to presence of ambiguous ellipsis", 1139 }, { 1140 label: label + "/AvoidTripleQuoteNonPrintable", 1141 x: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1142 y: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\no\roo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1143 wantEqual: false, 1144 reason: "use triple-quote syntax", 1145 }, { 1146 label: label + "/AvoidTripleQuoteIdenticalWhitespace", 1147 x: "aaa\nbbb\nccc\n ddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1148 y: "aaa\nbbb\nccc \nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n", 1149 wantEqual: false, 1150 reason: "avoid triple-quote syntax due to visual equivalence of differences", 1151 }, { 1152 label: label + "/TripleQuoteStringer", 1153 x: []fmt.Stringer{ 1154 bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")), 1155 bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n)\n\nfunc main() {\n\tfmt.Println(\"My favorite number is\", rand.Intn(10))\n}\n")), 1156 }, 1157 y: []fmt.Stringer{ 1158 bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")), 1159 bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n\t\"math\"\n)\n\nfunc main() {\n\tfmt.Printf(\"Now you have %g problems.\\n\", math.Sqrt(7))\n}\n")), 1160 }, 1161 opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })}, 1162 wantEqual: false, 1163 reason: "multi-line String output should be formatted with triple quote", 1164 }, { 1165 label: label + "/LimitMaximumBytesDiffs", 1166 x: []byte("\xcd====\x06\x1f\xc2\xcc\xc2-S=====\x1d\xdfa\xae\x98\x9fH======ǰ\xb7=======\xef====:\\\x94\xe6J\xc7=====\xb4======\n\n\xf7\x94===========\xf2\x9c\xc0f=====4\xf6\xf1\xc3\x17\x82======n\x16`\x91D\xc6\x06=======\x1cE====.===========\xc4\x18=======\x8a\x8d\x0e====\x87\xb1\xa5\x8e\xc3=====z\x0f1\xaeU======G,=======5\xe75\xee\x82\xf4\xce====\x11r===========\xaf]=======z\x05\xb3\x91\x88%\xd2====\n1\x89=====i\xb7\x055\xe6\x81\xd2=============\x883=@̾====\x14\x05\x96%^t\x04=====\xe7Ȉ\x90\x1d============="), 1167 y: []byte("\\====|\x96\xe7SB\xa0\xab=====\xf0\xbd\xa5q\xab\x17;======\xabP\x00=======\xeb====\xa5\x14\xe6O(\xe4=====(======/c@?===========\xd9x\xed\x13=====J\xfc\x918B\x8d======a8A\xebs\x04\xae=======\aC====\x1c===========\x91\"=======uؾ====s\xec\x845\a=====;\xabS9t======\x1f\x1b=======\x80\xab/\xed+:;====\xeaI===========\xabl=======\xb9\xe9\xfdH\x93\x8e\u007f====ח\xe5=====Ig\x88m\xf5\x01V=============\xf7+4\xb0\x92E====\x9fj\xf8&\xd0h\xf9=====\xeeΨ\r\xbf============="), 1168 wantEqual: false, 1169 reason: "total bytes difference output is truncated due to excessive number of differences", 1170 }, { 1171 label: label + "/LimitMaximumStringDiffs", 1172 x: "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n", 1173 y: "aa\nb\ncc\nd\nee\nf\ngg\nh\nii\nj\nkk\nl\nmm\nn\noo\np\nqq\nr\nss\nt\nuu\nv\nww\nx\nyy\nz\nAA\nB\nCC\nD\nEE\nF\nGG\nH\nII\nJ\nKK\nL\nMM\nN\nOO\nP\nQQ\nR\nSS\nT\nUU\nV\nWW\nX\nYY\nZ\n", 1174 wantEqual: false, 1175 reason: "total string difference output is truncated due to excessive number of differences", 1176 }, { 1177 label: label + "/LimitMaximumSliceDiffs", 1178 x: func() (out []struct{ S string }) { 1179 for _, s := range strings.Split("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n", "\n") { 1180 out = append(out, struct{ S string }{s}) 1181 } 1182 return out 1183 }(), 1184 y: func() (out []struct{ S string }) { 1185 for _, s := range strings.Split("aa\nb\ncc\nd\nee\nf\ngg\nh\nii\nj\nkk\nl\nmm\nn\noo\np\nqq\nr\nss\nt\nuu\nv\nww\nx\nyy\nz\nAA\nB\nCC\nD\nEE\nF\nGG\nH\nII\nJ\nKK\nL\nMM\nN\nOO\nP\nQQ\nR\nSS\nT\nUU\nV\nWW\nX\nYY\nZ\n", "\n") { 1186 out = append(out, struct{ S string }{s}) 1187 } 1188 return out 1189 }(), 1190 wantEqual: false, 1191 reason: "total slice difference output is truncated due to excessive number of differences", 1192 }, { 1193 label: label + "/MultilineString", 1194 x: MyComposite{ 1195 StringA: strings.TrimPrefix(` 1196Package cmp determines equality of values. 1197 1198This package is intended to be a more powerful and safer alternative to 1199reflect.DeepEqual for comparing whether two values are semantically equal. 1200 1201The primary features of cmp are: 1202 1203• When the default behavior of equality does not suit the needs of the test, 1204custom equality functions can override the equality operation. 1205For example, an equality function may report floats as equal so long as they 1206are within some tolerance of each other. 1207 1208• Types that have an Equal method may use that method to determine equality. 1209This allows package authors to determine the equality operation for the types 1210that they define. 1211 1212• If no custom equality functions are used and no Equal method is defined, 1213equality is determined by recursively comparing the primitive kinds on both 1214values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported 1215fields are not compared by default; they result in panics unless suppressed 1216by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared 1217using the AllowUnexported option. 1218`, "\n"), 1219 }, 1220 y: MyComposite{ 1221 StringA: strings.TrimPrefix(` 1222Package cmp determines equality of value. 1223 1224This package is intended to be a more powerful and safer alternative to 1225reflect.DeepEqual for comparing whether two values are semantically equal. 1226 1227The primary features of cmp are: 1228 1229• When the default behavior of equality does not suit the needs of the test, 1230custom equality functions can override the equality operation. 1231For example, an equality function may report floats as equal so long as they 1232are within some tolerance of each other. 1233 1234• If no custom equality functions are used and no Equal method is defined, 1235equality is determined by recursively comparing the primitive kinds on both 1236values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported 1237fields are not compared by default; they result in panics unless suppressed 1238by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared 1239using the AllowUnexported option.`, "\n"), 1240 }, 1241 wantEqual: false, 1242 reason: "batched per-line diff desired since string looks like multi-line textual data", 1243 }, { 1244 label: label + "/Slices", 1245 x: MyComposite{ 1246 BytesA: []byte{1, 2, 3}, 1247 BytesB: []MyByte{4, 5, 6}, 1248 BytesC: MyBytes{7, 8, 9}, 1249 IntsA: []int8{-1, -2, -3}, 1250 IntsB: []MyInt{-4, -5, -6}, 1251 IntsC: MyInts{-7, -8, -9}, 1252 UintsA: []uint16{1000, 2000, 3000}, 1253 UintsB: []MyUint{4000, 5000, 6000}, 1254 UintsC: MyUints{7000, 8000, 9000}, 1255 FloatsA: []float32{1.5, 2.5, 3.5}, 1256 FloatsB: []MyFloat{4.5, 5.5, 6.5}, 1257 FloatsC: MyFloats{7.5, 8.5, 9.5}, 1258 }, 1259 y: MyComposite{}, 1260 wantEqual: false, 1261 reason: "batched diffing for non-nil slices and nil slices", 1262 }, { 1263 label: label + "/EmptySlices", 1264 x: MyComposite{ 1265 BytesA: []byte{}, 1266 BytesB: []MyByte{}, 1267 BytesC: MyBytes{}, 1268 IntsA: []int8{}, 1269 IntsB: []MyInt{}, 1270 IntsC: MyInts{}, 1271 UintsA: []uint16{}, 1272 UintsB: []MyUint{}, 1273 UintsC: MyUints{}, 1274 FloatsA: []float32{}, 1275 FloatsB: []MyFloat{}, 1276 FloatsC: MyFloats{}, 1277 }, 1278 y: MyComposite{}, 1279 wantEqual: false, 1280 reason: "batched diffing for empty slices and nil slices", 1281 }, { 1282 label: label + "/LargeMapKey", 1283 x: map[*[]byte]int{func() *[]byte { 1284 b := make([]byte, 1<<20) 1285 return &b 1286 }(): 0}, 1287 y: map[*[]byte]int{func() *[]byte { 1288 b := make([]byte, 1<<20) 1289 return &b 1290 }(): 0}, 1291 reason: "printing map keys should have some verbosity limit imposed", 1292 }, { 1293 label: label + "/LargeStringInInterface", 1294 x: struct{ X interface{} }{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis."}, 1295 1296 y: struct{ X interface{} }{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,"}, 1297 reason: "strings within an interface should benefit from specialized diffing", 1298 }, { 1299 label: label + "/LargeBytesInInterface", 1300 x: struct{ X interface{} }{[]byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis.")}, 1301 y: struct{ X interface{} }{[]byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,")}, 1302 reason: "bytes slice within an interface should benefit from specialized diffing", 1303 }, { 1304 label: label + "/LargeStandaloneString", 1305 x: struct{ X interface{} }{[1]string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis."}}, 1306 y: struct{ X interface{} }{[1]string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,"}}, 1307 reason: "printing a large standalone string that is different should print enough context to see the difference", 1308 }, { 1309 label: label + "/SurroundingEqualElements", 1310 x: "org-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=aa,#=_value _value=2 11\torg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=bb,#=_value _value=2 21\torg-4747474747474747,bucket-4242424242424242:m,tag1=b,tag2=cc,#=_value _value=1 21\torg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=dd,#=_value _value=3 31\torg-4747474747474747,bucket-4242424242424242:m,tag1=c,#=_value _value=4 41\t", 1311 y: "org-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=aa _value=2 11\torg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=bb _value=2 21\torg-4747474747474747,bucket-4242424242424242:m,tag1=b,tag2=cc _value=1 21\torg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=dd _value=3 31\torg-4747474747474747,bucket-4242424242424242:m,tag1=c _value=4 41\t", 1312 reason: "leading/trailing equal spans should not appear in diff lines", 1313 }, { 1314 label: label + "/MostlyTextString", 1315 x: "org-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=aa,\xff=_value _value=2 11\norg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=bb,\xff=_value _value=2 21\norg-4747474747474747,bucket-4242424242424242:m,tag1=b,tag2=cc,\xff=_value _value=1 21\norg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=dd,\xff=_value _value=3 31\norg-4747474747474747,bucket-4242424242424242:m,tag1=c,\xff=_value _value=4 41\n", 1316 y: "org-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=aa _value=2 11\norg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=bb _value=2 21\norg-4747474747474747,bucket-4242424242424242:m,tag1=b,tag2=cc _value=1 21\norg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=dd _value=3 31\norg-4747474747474747,bucket-4242424242424242:m,tag1=c _value=4 41\n", 1317 reason: "the presence of a few invalid UTF-8 characters should not prevent printing this as text", 1318 }, { 1319 label: label + "/AllLinesDiffer", 1320 x: "d5c14bdf6bac81c27afc5429500ed750\n25483503b557c606dad4f144d27ae10b\n90bdbcdbb6ea7156068e3dcfb7459244\n978f480a6e3cced51e297fbff9a506b7\n", 1321 y: "Xd5c14bdf6bac81c27afc5429500ed750\nX25483503b557c606dad4f144d27ae10b\nX90bdbcdbb6ea7156068e3dcfb7459244\nX978f480a6e3cced51e297fbff9a506b7\n", 1322 reason: "all lines are different, so diffing based on lines is pointless", 1323 }, { 1324 label: label + "/StringifiedBytes", 1325 x: struct{ X []byte }{[]byte("hello, world!")}, 1326 y: struct{ X []byte }{}, 1327 reason: "[]byte should be printed as text since it is printable text", 1328 }, { 1329 label: label + "/NonStringifiedBytes", 1330 x: struct{ X []byte }{[]byte("\xde\xad\xbe\xef")}, 1331 y: struct{ X []byte }{}, 1332 reason: "[]byte should not be printed as text since it is binary data", 1333 }, { 1334 label: label + "/StringifiedNamedBytes", 1335 x: struct{ X MyBytes }{MyBytes("hello, world!")}, 1336 y: struct{ X MyBytes }{}, 1337 reason: "MyBytes should be printed as text since it is printable text", 1338 }, { 1339 label: label + "/NonStringifiedNamedBytes", 1340 x: struct{ X MyBytes }{MyBytes("\xde\xad\xbe\xef")}, 1341 y: struct{ X MyBytes }{}, 1342 reason: "MyBytes should not be printed as text since it is binary data", 1343 }, { 1344 label: label + "/ShortJSON", 1345 x: `{ 1346 "id": 1, 1347 "foo": true, 1348 "bar": true, 1349}`, 1350 y: `{ 1351 "id": 1434180, 1352 "foo": true, 1353 "bar": true, 1354}`, 1355 reason: "short multiline JSON should prefer triple-quoted string diff as it is more readable", 1356 }, { 1357 label: label + "/PointerToStringOrAny", 1358 x: func() *string { 1359 var v string = "hello" 1360 return &v 1361 }(), 1362 y: func() *interface{} { 1363 var v interface{} = "hello" 1364 return &v 1365 }(), 1366 reason: "mismatched types between any and *any should print differently", 1367 }, { 1368 label: label + "/NamedPointer", 1369 x: func() *string { 1370 v := "hello" 1371 return &v 1372 }(), 1373 y: func() PointerString { 1374 v := "hello" 1375 return &v 1376 }(), 1377 reason: "mismatched pointer types should print differently", 1378 }, { 1379 label: label + "/MapStringAny", 1380 x: map[string]interface{}{"key": int(0)}, 1381 y: map[string]interface{}{"key": uint(0)}, 1382 reason: "mismatched underlying value within interface", 1383 }, { 1384 label: label + "/StructFieldAny", 1385 x: struct{ X interface{} }{int(0)}, 1386 y: struct{ X interface{} }{uint(0)}, 1387 reason: "mismatched underlying value within interface", 1388 }, { 1389 label: label + "/SliceOfBytesText", 1390 x: [][]byte{ 1391 []byte("hello"), []byte("foo"), []byte("barbaz"), []byte("blahdieblah"), 1392 }, 1393 y: [][]byte{ 1394 []byte("foo"), []byte("foo"), []byte("barbaz"), []byte("added"), []byte("here"), []byte("hrmph"), 1395 }, 1396 reason: "should print text byte slices as strings", 1397 }, { 1398 label: label + "/SliceOfBytesBinary", 1399 x: [][]byte{ 1400 []byte("\xde\xad\xbe\xef"), []byte("\xffoo"), []byte("barbaz"), []byte("blahdieblah"), 1401 }, 1402 y: [][]byte{ 1403 []byte("\xffoo"), []byte("foo"), []byte("barbaz"), []byte("added"), []byte("here"), []byte("hrmph\xff"), 1404 }, 1405 reason: "should print text byte slices as strings except those with binary", 1406 }, { 1407 label: label + "/ManyEscapeCharacters", 1408 x: `[ 1409 {"Base32": "NA======"}, 1410 {"Base32": "NBSQ===="}, 1411 {"Base32": "NBSWY==="}, 1412 {"Base32": "NBSWY3A="}, 1413 {"Base32": "NBSWY3DP"} 1414]`, 1415 y: `[ 1416 {"Base32": "NB======"}, 1417 {"Base32": "NBSQ===="}, 1418 {"Base32": "NBSWY==="}, 1419 {"Base32": "NBSWY3A="}, 1420 {"Base32": "NBSWY3DP"} 1421]`, 1422 reason: "should use line-based diffing since byte-based diffing is unreadable due to heavy amounts of escaping", 1423 }} 1424} 1425 1426func embeddedTests() []test { 1427 const label = "EmbeddedStruct" 1428 1429 privateStruct := *new(ts.ParentStructA).PrivateStruct() 1430 1431 createStructA := func(i int) ts.ParentStructA { 1432 s := ts.ParentStructA{} 1433 s.PrivateStruct().Public = 1 + i 1434 s.PrivateStruct().SetPrivate(2 + i) 1435 return s 1436 } 1437 1438 createStructB := func(i int) ts.ParentStructB { 1439 s := ts.ParentStructB{} 1440 s.PublicStruct.Public = 1 + i 1441 s.PublicStruct.SetPrivate(2 + i) 1442 return s 1443 } 1444 1445 createStructC := func(i int) ts.ParentStructC { 1446 s := ts.ParentStructC{} 1447 s.PrivateStruct().Public = 1 + i 1448 s.PrivateStruct().SetPrivate(2 + i) 1449 s.Public = 3 + i 1450 s.SetPrivate(4 + i) 1451 return s 1452 } 1453 1454 createStructD := func(i int) ts.ParentStructD { 1455 s := ts.ParentStructD{} 1456 s.PublicStruct.Public = 1 + i 1457 s.PublicStruct.SetPrivate(2 + i) 1458 s.Public = 3 + i 1459 s.SetPrivate(4 + i) 1460 return s 1461 } 1462 1463 createStructE := func(i int) ts.ParentStructE { 1464 s := ts.ParentStructE{} 1465 s.PrivateStruct().Public = 1 + i 1466 s.PrivateStruct().SetPrivate(2 + i) 1467 s.PublicStruct.Public = 3 + i 1468 s.PublicStruct.SetPrivate(4 + i) 1469 return s 1470 } 1471 1472 createStructF := func(i int) ts.ParentStructF { 1473 s := ts.ParentStructF{} 1474 s.PrivateStruct().Public = 1 + i 1475 s.PrivateStruct().SetPrivate(2 + i) 1476 s.PublicStruct.Public = 3 + i 1477 s.PublicStruct.SetPrivate(4 + i) 1478 s.Public = 5 + i 1479 s.SetPrivate(6 + i) 1480 return s 1481 } 1482 1483 createStructG := func(i int) *ts.ParentStructG { 1484 s := ts.NewParentStructG() 1485 s.PrivateStruct().Public = 1 + i 1486 s.PrivateStruct().SetPrivate(2 + i) 1487 return s 1488 } 1489 1490 createStructH := func(i int) *ts.ParentStructH { 1491 s := ts.NewParentStructH() 1492 s.PublicStruct.Public = 1 + i 1493 s.PublicStruct.SetPrivate(2 + i) 1494 return s 1495 } 1496 1497 createStructI := func(i int) *ts.ParentStructI { 1498 s := ts.NewParentStructI() 1499 s.PrivateStruct().Public = 1 + i 1500 s.PrivateStruct().SetPrivate(2 + i) 1501 s.PublicStruct.Public = 3 + i 1502 s.PublicStruct.SetPrivate(4 + i) 1503 return s 1504 } 1505 1506 createStructJ := func(i int) *ts.ParentStructJ { 1507 s := ts.NewParentStructJ() 1508 s.PrivateStruct().Public = 1 + i 1509 s.PrivateStruct().SetPrivate(2 + i) 1510 s.PublicStruct.Public = 3 + i 1511 s.PublicStruct.SetPrivate(4 + i) 1512 s.Private().Public = 5 + i 1513 s.Private().SetPrivate(6 + i) 1514 s.Public.Public = 7 + i 1515 s.Public.SetPrivate(8 + i) 1516 return s 1517 } 1518 1519 return []test{{ 1520 label: label + "/ParentStructA/PanicUnexported1", 1521 x: ts.ParentStructA{}, 1522 y: ts.ParentStructA{}, 1523 wantPanic: "cannot handle unexported field", 1524 reason: "ParentStructA has an unexported field", 1525 }, { 1526 label: label + "/ParentStructA/Ignored", 1527 x: ts.ParentStructA{}, 1528 y: ts.ParentStructA{}, 1529 opts: []cmp.Option{ 1530 cmpopts.IgnoreUnexported(ts.ParentStructA{}), 1531 }, 1532 wantEqual: true, 1533 reason: "the only field (which is unexported) of ParentStructA is ignored", 1534 }, { 1535 label: label + "/ParentStructA/PanicUnexported2", 1536 x: createStructA(0), 1537 y: createStructA(0), 1538 opts: []cmp.Option{ 1539 cmp.AllowUnexported(ts.ParentStructA{}), 1540 }, 1541 wantPanic: "cannot handle unexported field", 1542 reason: "privateStruct also has unexported fields", 1543 }, { 1544 label: label + "/ParentStructA/Equal", 1545 x: createStructA(0), 1546 y: createStructA(0), 1547 opts: []cmp.Option{ 1548 cmp.AllowUnexported(ts.ParentStructA{}, privateStruct), 1549 }, 1550 wantEqual: true, 1551 reason: "unexported fields of both ParentStructA and privateStruct are allowed", 1552 }, { 1553 label: label + "/ParentStructA/Inequal", 1554 x: createStructA(0), 1555 y: createStructA(1), 1556 opts: []cmp.Option{ 1557 cmp.AllowUnexported(ts.ParentStructA{}, privateStruct), 1558 }, 1559 wantEqual: false, 1560 reason: "the two values differ on some fields", 1561 }, { 1562 label: label + "/ParentStructB/PanicUnexported1", 1563 x: ts.ParentStructB{}, 1564 y: ts.ParentStructB{}, 1565 opts: []cmp.Option{ 1566 cmpopts.IgnoreUnexported(ts.ParentStructB{}), 1567 }, 1568 wantPanic: "cannot handle unexported field", 1569 reason: "PublicStruct has an unexported field", 1570 }, { 1571 label: label + "/ParentStructB/Ignored", 1572 x: ts.ParentStructB{}, 1573 y: ts.ParentStructB{}, 1574 opts: []cmp.Option{ 1575 cmpopts.IgnoreUnexported(ts.ParentStructB{}), 1576 cmpopts.IgnoreUnexported(ts.PublicStruct{}), 1577 }, 1578 wantEqual: true, 1579 reason: "unexported fields of both ParentStructB and PublicStruct are ignored", 1580 }, { 1581 label: label + "/ParentStructB/PanicUnexported2", 1582 x: createStructB(0), 1583 y: createStructB(0), 1584 opts: []cmp.Option{ 1585 cmp.AllowUnexported(ts.ParentStructB{}), 1586 }, 1587 wantPanic: "cannot handle unexported field", 1588 reason: "PublicStruct also has unexported fields", 1589 }, { 1590 label: label + "/ParentStructB/Equal", 1591 x: createStructB(0), 1592 y: createStructB(0), 1593 opts: []cmp.Option{ 1594 cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}), 1595 }, 1596 wantEqual: true, 1597 reason: "unexported fields of both ParentStructB and PublicStruct are allowed", 1598 }, { 1599 label: label + "/ParentStructB/Inequal", 1600 x: createStructB(0), 1601 y: createStructB(1), 1602 opts: []cmp.Option{ 1603 cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}), 1604 }, 1605 wantEqual: false, 1606 reason: "the two values differ on some fields", 1607 }, { 1608 label: label + "/ParentStructC/PanicUnexported1", 1609 x: ts.ParentStructC{}, 1610 y: ts.ParentStructC{}, 1611 wantPanic: "cannot handle unexported field", 1612 reason: "ParentStructC has unexported fields", 1613 }, { 1614 label: label + "/ParentStructC/Ignored", 1615 x: ts.ParentStructC{}, 1616 y: ts.ParentStructC{}, 1617 opts: []cmp.Option{ 1618 cmpopts.IgnoreUnexported(ts.ParentStructC{}), 1619 }, 1620 wantEqual: true, 1621 reason: "unexported fields of ParentStructC are ignored", 1622 }, { 1623 label: label + "/ParentStructC/PanicUnexported2", 1624 x: createStructC(0), 1625 y: createStructC(0), 1626 opts: []cmp.Option{ 1627 cmp.AllowUnexported(ts.ParentStructC{}), 1628 }, 1629 wantPanic: "cannot handle unexported field", 1630 reason: "privateStruct also has unexported fields", 1631 }, { 1632 label: label + "/ParentStructC/Equal", 1633 x: createStructC(0), 1634 y: createStructC(0), 1635 opts: []cmp.Option{ 1636 cmp.AllowUnexported(ts.ParentStructC{}, privateStruct), 1637 }, 1638 wantEqual: true, 1639 reason: "unexported fields of both ParentStructC and privateStruct are allowed", 1640 }, { 1641 label: label + "/ParentStructC/Inequal", 1642 x: createStructC(0), 1643 y: createStructC(1), 1644 opts: []cmp.Option{ 1645 cmp.AllowUnexported(ts.ParentStructC{}, privateStruct), 1646 }, 1647 wantEqual: false, 1648 reason: "the two values differ on some fields", 1649 }, { 1650 label: label + "/ParentStructD/PanicUnexported1", 1651 x: ts.ParentStructD{}, 1652 y: ts.ParentStructD{}, 1653 opts: []cmp.Option{ 1654 cmpopts.IgnoreUnexported(ts.ParentStructD{}), 1655 }, 1656 wantPanic: "cannot handle unexported field", 1657 reason: "ParentStructD has unexported fields", 1658 }, { 1659 label: label + "/ParentStructD/Ignored", 1660 x: ts.ParentStructD{}, 1661 y: ts.ParentStructD{}, 1662 opts: []cmp.Option{ 1663 cmpopts.IgnoreUnexported(ts.ParentStructD{}), 1664 cmpopts.IgnoreUnexported(ts.PublicStruct{}), 1665 }, 1666 wantEqual: true, 1667 reason: "unexported fields of ParentStructD and PublicStruct are ignored", 1668 }, { 1669 label: label + "/ParentStructD/PanicUnexported2", 1670 x: createStructD(0), 1671 y: createStructD(0), 1672 opts: []cmp.Option{ 1673 cmp.AllowUnexported(ts.ParentStructD{}), 1674 }, 1675 wantPanic: "cannot handle unexported field", 1676 reason: "PublicStruct also has unexported fields", 1677 }, { 1678 label: label + "/ParentStructD/Equal", 1679 x: createStructD(0), 1680 y: createStructD(0), 1681 opts: []cmp.Option{ 1682 cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}), 1683 }, 1684 wantEqual: true, 1685 reason: "unexported fields of both ParentStructD and PublicStruct are allowed", 1686 }, { 1687 label: label + "/ParentStructD/Inequal", 1688 x: createStructD(0), 1689 y: createStructD(1), 1690 opts: []cmp.Option{ 1691 cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}), 1692 }, 1693 wantEqual: false, 1694 reason: "the two values differ on some fields", 1695 }, { 1696 label: label + "/ParentStructE/PanicUnexported1", 1697 x: ts.ParentStructE{}, 1698 y: ts.ParentStructE{}, 1699 opts: []cmp.Option{ 1700 cmpopts.IgnoreUnexported(ts.ParentStructE{}), 1701 }, 1702 wantPanic: "cannot handle unexported field", 1703 reason: "ParentStructE has unexported fields", 1704 }, { 1705 label: label + "/ParentStructE/Ignored", 1706 x: ts.ParentStructE{}, 1707 y: ts.ParentStructE{}, 1708 opts: []cmp.Option{ 1709 cmpopts.IgnoreUnexported(ts.ParentStructE{}), 1710 cmpopts.IgnoreUnexported(ts.PublicStruct{}), 1711 }, 1712 wantEqual: true, 1713 reason: "unexported fields of ParentStructE and PublicStruct are ignored", 1714 }, { 1715 label: label + "/ParentStructE/PanicUnexported2", 1716 x: createStructE(0), 1717 y: createStructE(0), 1718 opts: []cmp.Option{ 1719 cmp.AllowUnexported(ts.ParentStructE{}), 1720 }, 1721 wantPanic: "cannot handle unexported field", 1722 reason: "PublicStruct and privateStruct also has unexported fields", 1723 }, { 1724 label: label + "/ParentStructE/PanicUnexported3", 1725 x: createStructE(0), 1726 y: createStructE(0), 1727 opts: []cmp.Option{ 1728 cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}), 1729 }, 1730 wantPanic: "cannot handle unexported field", 1731 reason: "privateStruct also has unexported fields", 1732 }, { 1733 label: label + "/ParentStructE/Equal", 1734 x: createStructE(0), 1735 y: createStructE(0), 1736 opts: []cmp.Option{ 1737 cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct), 1738 }, 1739 wantEqual: true, 1740 reason: "unexported fields of both ParentStructE, PublicStruct, and privateStruct are allowed", 1741 }, { 1742 label: label + "/ParentStructE/Inequal", 1743 x: createStructE(0), 1744 y: createStructE(1), 1745 opts: []cmp.Option{ 1746 cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct), 1747 }, 1748 wantEqual: false, 1749 reason: "the two values differ on some fields", 1750 }, { 1751 label: label + "/ParentStructF/PanicUnexported1", 1752 x: ts.ParentStructF{}, 1753 y: ts.ParentStructF{}, 1754 opts: []cmp.Option{ 1755 cmpopts.IgnoreUnexported(ts.ParentStructF{}), 1756 }, 1757 wantPanic: "cannot handle unexported field", 1758 reason: "ParentStructF has unexported fields", 1759 }, { 1760 label: label + "/ParentStructF/Ignored", 1761 x: ts.ParentStructF{}, 1762 y: ts.ParentStructF{}, 1763 opts: []cmp.Option{ 1764 cmpopts.IgnoreUnexported(ts.ParentStructF{}), 1765 cmpopts.IgnoreUnexported(ts.PublicStruct{}), 1766 }, 1767 wantEqual: true, 1768 reason: "unexported fields of ParentStructF and PublicStruct are ignored", 1769 }, { 1770 label: label + "/ParentStructF/PanicUnexported2", 1771 x: createStructF(0), 1772 y: createStructF(0), 1773 opts: []cmp.Option{ 1774 cmp.AllowUnexported(ts.ParentStructF{}), 1775 }, 1776 wantPanic: "cannot handle unexported field", 1777 reason: "PublicStruct and privateStruct also has unexported fields", 1778 }, { 1779 label: label + "/ParentStructF/PanicUnexported3", 1780 x: createStructF(0), 1781 y: createStructF(0), 1782 opts: []cmp.Option{ 1783 cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}), 1784 }, 1785 wantPanic: "cannot handle unexported field", 1786 reason: "privateStruct also has unexported fields", 1787 }, { 1788 label: label + "/ParentStructF/Equal", 1789 x: createStructF(0), 1790 y: createStructF(0), 1791 opts: []cmp.Option{ 1792 cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct), 1793 }, 1794 wantEqual: true, 1795 reason: "unexported fields of both ParentStructF, PublicStruct, and privateStruct are allowed", 1796 }, { 1797 label: label + "/ParentStructF/Inequal", 1798 x: createStructF(0), 1799 y: createStructF(1), 1800 opts: []cmp.Option{ 1801 cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct), 1802 }, 1803 wantEqual: false, 1804 reason: "the two values differ on some fields", 1805 }, { 1806 label: label + "/ParentStructG/PanicUnexported1", 1807 x: ts.ParentStructG{}, 1808 y: ts.ParentStructG{}, 1809 wantPanic: "cannot handle unexported field", 1810 reason: "ParentStructG has unexported fields", 1811 }, { 1812 label: label + "/ParentStructG/Ignored", 1813 x: ts.ParentStructG{}, 1814 y: ts.ParentStructG{}, 1815 opts: []cmp.Option{ 1816 cmpopts.IgnoreUnexported(ts.ParentStructG{}), 1817 }, 1818 wantEqual: true, 1819 reason: "unexported fields of ParentStructG are ignored", 1820 }, { 1821 label: label + "/ParentStructG/PanicUnexported2", 1822 x: createStructG(0), 1823 y: createStructG(0), 1824 opts: []cmp.Option{ 1825 cmp.AllowUnexported(ts.ParentStructG{}), 1826 }, 1827 wantPanic: "cannot handle unexported field", 1828 reason: "privateStruct also has unexported fields", 1829 }, { 1830 label: label + "/ParentStructG/Equal", 1831 x: createStructG(0), 1832 y: createStructG(0), 1833 opts: []cmp.Option{ 1834 cmp.AllowUnexported(ts.ParentStructG{}, privateStruct), 1835 }, 1836 wantEqual: true, 1837 reason: "unexported fields of both ParentStructG and privateStruct are allowed", 1838 }, { 1839 label: label + "/ParentStructG/Inequal", 1840 x: createStructG(0), 1841 y: createStructG(1), 1842 opts: []cmp.Option{ 1843 cmp.AllowUnexported(ts.ParentStructG{}, privateStruct), 1844 }, 1845 wantEqual: false, 1846 reason: "the two values differ on some fields", 1847 }, { 1848 label: label + "/ParentStructH/EqualNil", 1849 x: ts.ParentStructH{}, 1850 y: ts.ParentStructH{}, 1851 wantEqual: true, 1852 reason: "PublicStruct is not compared because the pointer is nil", 1853 }, { 1854 label: label + "/ParentStructH/PanicUnexported1", 1855 x: createStructH(0), 1856 y: createStructH(0), 1857 wantPanic: "cannot handle unexported field", 1858 reason: "PublicStruct has unexported fields", 1859 }, { 1860 label: label + "/ParentStructH/Ignored", 1861 x: ts.ParentStructH{}, 1862 y: ts.ParentStructH{}, 1863 opts: []cmp.Option{ 1864 cmpopts.IgnoreUnexported(ts.ParentStructH{}), 1865 }, 1866 wantEqual: true, 1867 reason: "unexported fields of ParentStructH are ignored (it has none)", 1868 }, { 1869 label: label + "/ParentStructH/PanicUnexported2", 1870 x: createStructH(0), 1871 y: createStructH(0), 1872 opts: []cmp.Option{ 1873 cmp.AllowUnexported(ts.ParentStructH{}), 1874 }, 1875 wantPanic: "cannot handle unexported field", 1876 reason: "PublicStruct also has unexported fields", 1877 }, { 1878 label: label + "/ParentStructH/Equal", 1879 x: createStructH(0), 1880 y: createStructH(0), 1881 opts: []cmp.Option{ 1882 cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}), 1883 }, 1884 wantEqual: true, 1885 reason: "unexported fields of both ParentStructH and PublicStruct are allowed", 1886 }, { 1887 label: label + "/ParentStructH/Inequal", 1888 x: createStructH(0), 1889 y: createStructH(1), 1890 opts: []cmp.Option{ 1891 cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}), 1892 }, 1893 wantEqual: false, 1894 reason: "the two values differ on some fields", 1895 }, { 1896 label: label + "/ParentStructI/PanicUnexported1", 1897 x: ts.ParentStructI{}, 1898 y: ts.ParentStructI{}, 1899 wantPanic: "cannot handle unexported field", 1900 reason: "ParentStructI has unexported fields", 1901 }, { 1902 label: label + "/ParentStructI/Ignored1", 1903 x: ts.ParentStructI{}, 1904 y: ts.ParentStructI{}, 1905 opts: []cmp.Option{ 1906 cmpopts.IgnoreUnexported(ts.ParentStructI{}), 1907 }, 1908 wantEqual: true, 1909 reason: "unexported fields of ParentStructI are ignored", 1910 }, { 1911 label: label + "/ParentStructI/PanicUnexported2", 1912 x: createStructI(0), 1913 y: createStructI(0), 1914 opts: []cmp.Option{ 1915 cmpopts.IgnoreUnexported(ts.ParentStructI{}), 1916 }, 1917 wantPanic: "cannot handle unexported field", 1918 reason: "PublicStruct and privateStruct also has unexported fields", 1919 }, { 1920 label: label + "/ParentStructI/Ignored2", 1921 x: createStructI(0), 1922 y: createStructI(0), 1923 opts: []cmp.Option{ 1924 cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}), 1925 }, 1926 wantEqual: true, 1927 reason: "unexported fields of ParentStructI and PublicStruct are ignored", 1928 }, { 1929 label: label + "/ParentStructI/PanicUnexported3", 1930 x: createStructI(0), 1931 y: createStructI(0), 1932 opts: []cmp.Option{ 1933 cmp.AllowUnexported(ts.ParentStructI{}), 1934 }, 1935 wantPanic: "cannot handle unexported field", 1936 reason: "PublicStruct and privateStruct also has unexported fields", 1937 }, { 1938 label: label + "/ParentStructI/Equal", 1939 x: createStructI(0), 1940 y: createStructI(0), 1941 opts: []cmp.Option{ 1942 cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct), 1943 }, 1944 wantEqual: true, 1945 reason: "unexported fields of both ParentStructI, PublicStruct, and privateStruct are allowed", 1946 }, { 1947 label: label + "/ParentStructI/Inequal", 1948 x: createStructI(0), 1949 y: createStructI(1), 1950 opts: []cmp.Option{ 1951 cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct), 1952 }, 1953 wantEqual: false, 1954 reason: "the two values differ on some fields", 1955 }, { 1956 label: label + "/ParentStructJ/PanicUnexported1", 1957 x: ts.ParentStructJ{}, 1958 y: ts.ParentStructJ{}, 1959 wantPanic: "cannot handle unexported field", 1960 reason: "ParentStructJ has unexported fields", 1961 }, { 1962 label: label + "/ParentStructJ/PanicUnexported2", 1963 x: ts.ParentStructJ{}, 1964 y: ts.ParentStructJ{}, 1965 opts: []cmp.Option{ 1966 cmpopts.IgnoreUnexported(ts.ParentStructJ{}), 1967 }, 1968 wantPanic: "cannot handle unexported field", 1969 reason: "PublicStruct and privateStruct also has unexported fields", 1970 }, { 1971 label: label + "/ParentStructJ/Ignored", 1972 x: ts.ParentStructJ{}, 1973 y: ts.ParentStructJ{}, 1974 opts: []cmp.Option{ 1975 cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}), 1976 }, 1977 wantEqual: true, 1978 reason: "unexported fields of ParentStructJ and PublicStruct are ignored", 1979 }, { 1980 label: label + "/ParentStructJ/PanicUnexported3", 1981 x: createStructJ(0), 1982 y: createStructJ(0), 1983 opts: []cmp.Option{ 1984 cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}), 1985 }, 1986 wantPanic: "cannot handle unexported field", 1987 reason: "privateStruct also has unexported fields", 1988 }, { 1989 label: label + "/ParentStructJ/Equal", 1990 x: createStructJ(0), 1991 y: createStructJ(0), 1992 opts: []cmp.Option{ 1993 cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct), 1994 }, 1995 wantEqual: true, 1996 reason: "unexported fields of both ParentStructJ, PublicStruct, and privateStruct are allowed", 1997 }, { 1998 label: label + "/ParentStructJ/Inequal", 1999 x: createStructJ(0), 2000 y: createStructJ(1), 2001 opts: []cmp.Option{ 2002 cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct), 2003 }, 2004 wantEqual: false, 2005 reason: "the two values differ on some fields", 2006 }} 2007} 2008 2009func methodTests() []test { 2010 const label = "EqualMethod" 2011 2012 // A common mistake that the Equal method is on a pointer receiver, 2013 // but only a non-pointer value is present in the struct. 2014 // A transform can be used to forcibly reference the value. 2015 addrTransform := cmp.FilterPath(func(p cmp.Path) bool { 2016 if len(p) == 0 { 2017 return false 2018 } 2019 t := p[len(p)-1].Type() 2020 if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr { 2021 return false 2022 } 2023 if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok { 2024 tf := m.Func.Type() 2025 return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 && 2026 tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true) 2027 } 2028 return false 2029 }, cmp.Transformer("Addr", func(x interface{}) interface{} { 2030 v := reflect.ValueOf(x) 2031 vp := reflect.New(v.Type()) 2032 vp.Elem().Set(v) 2033 return vp.Interface() 2034 })) 2035 2036 // For each of these types, there is an Equal method defined, which always 2037 // returns true, while the underlying data are fundamentally different. 2038 // Since the method should be called, these are expected to be equal. 2039 return []test{{ 2040 label: label + "/StructA/ValueEqual", 2041 x: ts.StructA{X: "NotEqual"}, 2042 y: ts.StructA{X: "not_equal"}, 2043 wantEqual: true, 2044 reason: "Equal method on StructA value called", 2045 }, { 2046 label: label + "/StructA/PointerEqual", 2047 x: &ts.StructA{X: "NotEqual"}, 2048 y: &ts.StructA{X: "not_equal"}, 2049 wantEqual: true, 2050 reason: "Equal method on StructA pointer called", 2051 }, { 2052 label: label + "/StructB/ValueInequal", 2053 x: ts.StructB{X: "NotEqual"}, 2054 y: ts.StructB{X: "not_equal"}, 2055 wantEqual: false, 2056 reason: "Equal method on StructB value not called", 2057 }, { 2058 label: label + "/StructB/ValueAddrEqual", 2059 x: ts.StructB{X: "NotEqual"}, 2060 y: ts.StructB{X: "not_equal"}, 2061 opts: []cmp.Option{addrTransform}, 2062 wantEqual: true, 2063 reason: "Equal method on StructB pointer called due to shallow copy transform", 2064 }, { 2065 label: label + "/StructB/PointerEqual", 2066 x: &ts.StructB{X: "NotEqual"}, 2067 y: &ts.StructB{X: "not_equal"}, 2068 wantEqual: true, 2069 reason: "Equal method on StructB pointer called", 2070 }, { 2071 label: label + "/StructC/ValueEqual", 2072 x: ts.StructC{X: "NotEqual"}, 2073 y: ts.StructC{X: "not_equal"}, 2074 wantEqual: true, 2075 reason: "Equal method on StructC value called", 2076 }, { 2077 label: label + "/StructC/PointerEqual", 2078 x: &ts.StructC{X: "NotEqual"}, 2079 y: &ts.StructC{X: "not_equal"}, 2080 wantEqual: true, 2081 reason: "Equal method on StructC pointer called", 2082 }, { 2083 label: label + "/StructD/ValueInequal", 2084 x: ts.StructD{X: "NotEqual"}, 2085 y: ts.StructD{X: "not_equal"}, 2086 wantEqual: false, 2087 reason: "Equal method on StructD value not called", 2088 }, { 2089 label: label + "/StructD/ValueAddrEqual", 2090 x: ts.StructD{X: "NotEqual"}, 2091 y: ts.StructD{X: "not_equal"}, 2092 opts: []cmp.Option{addrTransform}, 2093 wantEqual: true, 2094 reason: "Equal method on StructD pointer called due to shallow copy transform", 2095 }, { 2096 label: label + "/StructD/PointerEqual", 2097 x: &ts.StructD{X: "NotEqual"}, 2098 y: &ts.StructD{X: "not_equal"}, 2099 wantEqual: true, 2100 reason: "Equal method on StructD pointer called", 2101 }, { 2102 label: label + "/StructE/ValueInequal", 2103 x: ts.StructE{X: "NotEqual"}, 2104 y: ts.StructE{X: "not_equal"}, 2105 wantEqual: false, 2106 reason: "Equal method on StructE value not called", 2107 }, { 2108 label: label + "/StructE/ValueAddrEqual", 2109 x: ts.StructE{X: "NotEqual"}, 2110 y: ts.StructE{X: "not_equal"}, 2111 opts: []cmp.Option{addrTransform}, 2112 wantEqual: true, 2113 reason: "Equal method on StructE pointer called due to shallow copy transform", 2114 }, { 2115 label: label + "/StructE/PointerEqual", 2116 x: &ts.StructE{X: "NotEqual"}, 2117 y: &ts.StructE{X: "not_equal"}, 2118 wantEqual: true, 2119 reason: "Equal method on StructE pointer called", 2120 }, { 2121 label: label + "/StructF/ValueInequal", 2122 x: ts.StructF{X: "NotEqual"}, 2123 y: ts.StructF{X: "not_equal"}, 2124 wantEqual: false, 2125 reason: "Equal method on StructF value not called", 2126 }, { 2127 label: label + "/StructF/PointerEqual", 2128 x: &ts.StructF{X: "NotEqual"}, 2129 y: &ts.StructF{X: "not_equal"}, 2130 wantEqual: true, 2131 reason: "Equal method on StructF pointer called", 2132 }, { 2133 label: label + "/StructA1/ValueEqual", 2134 x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"}, 2135 y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"}, 2136 wantEqual: true, 2137 reason: "Equal method on StructA value called with equal X field", 2138 }, { 2139 label: label + "/StructA1/ValueInequal", 2140 x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"}, 2141 y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"}, 2142 wantEqual: false, 2143 reason: "Equal method on StructA value called, but inequal X field", 2144 }, { 2145 label: label + "/StructA1/PointerEqual", 2146 x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"}, 2147 y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"}, 2148 wantEqual: true, 2149 reason: "Equal method on StructA value called with equal X field", 2150 }, { 2151 label: label + "/StructA1/PointerInequal", 2152 x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"}, 2153 y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"}, 2154 wantEqual: false, 2155 reason: "Equal method on StructA value called, but inequal X field", 2156 }, { 2157 label: label + "/StructB1/ValueEqual", 2158 x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"}, 2159 y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"}, 2160 opts: []cmp.Option{addrTransform}, 2161 wantEqual: true, 2162 reason: "Equal method on StructB pointer called due to shallow copy transform with equal X field", 2163 }, { 2164 label: label + "/StructB1/ValueInequal", 2165 x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"}, 2166 y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"}, 2167 opts: []cmp.Option{addrTransform}, 2168 wantEqual: false, 2169 reason: "Equal method on StructB pointer called due to shallow copy transform, but inequal X field", 2170 }, { 2171 label: label + "/StructB1/PointerEqual", 2172 x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"}, 2173 y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"}, 2174 opts: []cmp.Option{addrTransform}, 2175 wantEqual: true, 2176 reason: "Equal method on StructB pointer called due to shallow copy transform with equal X field", 2177 }, { 2178 label: label + "/StructB1/PointerInequal", 2179 x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"}, 2180 y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"}, 2181 opts: []cmp.Option{addrTransform}, 2182 wantEqual: false, 2183 reason: "Equal method on StructB pointer called due to shallow copy transform, but inequal X field", 2184 }, { 2185 label: label + "/StructC1/ValueEqual", 2186 x: ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"}, 2187 y: ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"}, 2188 wantEqual: true, 2189 reason: "Equal method on StructC1 value called", 2190 }, { 2191 label: label + "/StructC1/PointerEqual", 2192 x: &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"}, 2193 y: &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"}, 2194 wantEqual: true, 2195 reason: "Equal method on StructC1 pointer called", 2196 }, { 2197 label: label + "/StructD1/ValueInequal", 2198 x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"}, 2199 y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"}, 2200 wantEqual: false, 2201 reason: "Equal method on StructD1 value not called", 2202 }, { 2203 label: label + "/StructD1/PointerAddrEqual", 2204 x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"}, 2205 y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"}, 2206 opts: []cmp.Option{addrTransform}, 2207 wantEqual: true, 2208 reason: "Equal method on StructD1 pointer called due to shallow copy transform", 2209 }, { 2210 label: label + "/StructD1/PointerEqual", 2211 x: &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"}, 2212 y: &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"}, 2213 wantEqual: true, 2214 reason: "Equal method on StructD1 pointer called", 2215 }, { 2216 label: label + "/StructE1/ValueInequal", 2217 x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"}, 2218 y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"}, 2219 wantEqual: false, 2220 reason: "Equal method on StructE1 value not called", 2221 }, { 2222 label: label + "/StructE1/ValueAddrEqual", 2223 x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"}, 2224 y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"}, 2225 opts: []cmp.Option{addrTransform}, 2226 wantEqual: true, 2227 reason: "Equal method on StructE1 pointer called due to shallow copy transform", 2228 }, { 2229 label: label + "/StructE1/PointerEqual", 2230 x: &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"}, 2231 y: &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"}, 2232 wantEqual: true, 2233 reason: "Equal method on StructE1 pointer called", 2234 }, { 2235 label: label + "/StructF1/ValueInequal", 2236 x: ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"}, 2237 y: ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"}, 2238 wantEqual: false, 2239 reason: "Equal method on StructF1 value not called", 2240 }, { 2241 label: label + "/StructF1/PointerEqual", 2242 x: &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"}, 2243 y: &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"}, 2244 wantEqual: true, 2245 reason: "Equal method on StructF1 pointer called", 2246 }, { 2247 label: label + "/StructA2/ValueEqual", 2248 x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"}, 2249 y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"}, 2250 wantEqual: true, 2251 reason: "Equal method on StructA pointer called with equal X field", 2252 }, { 2253 label: label + "/StructA2/ValueInequal", 2254 x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"}, 2255 y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"}, 2256 wantEqual: false, 2257 reason: "Equal method on StructA pointer called, but inequal X field", 2258 }, { 2259 label: label + "/StructA2/PointerEqual", 2260 x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"}, 2261 y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"}, 2262 wantEqual: true, 2263 reason: "Equal method on StructA pointer called with equal X field", 2264 }, { 2265 label: label + "/StructA2/PointerInequal", 2266 x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"}, 2267 y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"}, 2268 wantEqual: false, 2269 reason: "Equal method on StructA pointer called, but inequal X field", 2270 }, { 2271 label: label + "/StructB2/ValueEqual", 2272 x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"}, 2273 y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"}, 2274 wantEqual: true, 2275 reason: "Equal method on StructB pointer called with equal X field", 2276 }, { 2277 label: label + "/StructB2/ValueInequal", 2278 x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"}, 2279 y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"}, 2280 wantEqual: false, 2281 reason: "Equal method on StructB pointer called, but inequal X field", 2282 }, { 2283 label: label + "/StructB2/PointerEqual", 2284 x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"}, 2285 y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"}, 2286 wantEqual: true, 2287 reason: "Equal method on StructB pointer called with equal X field", 2288 }, { 2289 label: label + "/StructB2/PointerInequal", 2290 x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"}, 2291 y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"}, 2292 wantEqual: false, 2293 reason: "Equal method on StructB pointer called, but inequal X field", 2294 }, { 2295 label: label + "/StructC2/ValueEqual", 2296 x: ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"}, 2297 y: ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"}, 2298 wantEqual: true, 2299 reason: "Equal method called on StructC2 value due to forwarded StructC pointer", 2300 }, { 2301 label: label + "/StructC2/PointerEqual", 2302 x: &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"}, 2303 y: &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"}, 2304 wantEqual: true, 2305 reason: "Equal method called on StructC2 pointer due to forwarded StructC pointer", 2306 }, { 2307 label: label + "/StructD2/ValueEqual", 2308 x: ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"}, 2309 y: ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"}, 2310 wantEqual: true, 2311 reason: "Equal method called on StructD2 value due to forwarded StructD pointer", 2312 }, { 2313 label: label + "/StructD2/PointerEqual", 2314 x: &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"}, 2315 y: &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"}, 2316 wantEqual: true, 2317 reason: "Equal method called on StructD2 pointer due to forwarded StructD pointer", 2318 }, { 2319 label: label + "/StructE2/ValueEqual", 2320 x: ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"}, 2321 y: ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"}, 2322 wantEqual: true, 2323 reason: "Equal method called on StructE2 value due to forwarded StructE pointer", 2324 }, { 2325 label: label + "/StructE2/PointerEqual", 2326 x: &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"}, 2327 y: &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"}, 2328 wantEqual: true, 2329 reason: "Equal method called on StructE2 pointer due to forwarded StructE pointer", 2330 }, { 2331 label: label + "/StructF2/ValueEqual", 2332 x: ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"}, 2333 y: ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"}, 2334 wantEqual: true, 2335 reason: "Equal method called on StructF2 value due to forwarded StructF pointer", 2336 }, { 2337 label: label + "/StructF2/PointerEqual", 2338 x: &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"}, 2339 y: &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"}, 2340 wantEqual: true, 2341 reason: "Equal method called on StructF2 pointer due to forwarded StructF pointer", 2342 }, { 2343 label: label + "/StructNo/Inequal", 2344 x: ts.StructNo{X: "NotEqual"}, 2345 y: ts.StructNo{X: "not_equal"}, 2346 wantEqual: false, 2347 reason: "Equal method not called since StructNo is not assignable to InterfaceA", 2348 }, { 2349 label: label + "/AssignA/Equal", 2350 x: ts.AssignA(func() int { return 0 }), 2351 y: ts.AssignA(func() int { return 1 }), 2352 wantEqual: true, 2353 reason: "Equal method called since named func is assignable to unnamed func", 2354 }, { 2355 label: label + "/AssignB/Equal", 2356 x: ts.AssignB(struct{ A int }{0}), 2357 y: ts.AssignB(struct{ A int }{1}), 2358 wantEqual: true, 2359 reason: "Equal method called since named struct is assignable to unnamed struct", 2360 }, { 2361 label: label + "/AssignC/Equal", 2362 x: ts.AssignC(make(chan bool)), 2363 y: ts.AssignC(make(chan bool)), 2364 wantEqual: true, 2365 reason: "Equal method called since named channel is assignable to unnamed channel", 2366 }, { 2367 label: label + "/AssignD/Equal", 2368 x: ts.AssignD(make(chan bool)), 2369 y: ts.AssignD(make(chan bool)), 2370 wantEqual: true, 2371 reason: "Equal method called since named channel is assignable to unnamed channel", 2372 }} 2373} 2374 2375type ( 2376 CycleAlpha struct { 2377 Name string 2378 Bravos map[string]*CycleBravo 2379 } 2380 CycleBravo struct { 2381 ID int 2382 Name string 2383 Mods int 2384 Alphas map[string]*CycleAlpha 2385 } 2386) 2387 2388func cycleTests() []test { 2389 const label = "Cycle" 2390 2391 type ( 2392 P *P 2393 S []S 2394 M map[int]M 2395 ) 2396 2397 makeGraph := func() map[string]*CycleAlpha { 2398 v := map[string]*CycleAlpha{ 2399 "Foo": &CycleAlpha{ 2400 Name: "Foo", 2401 Bravos: map[string]*CycleBravo{ 2402 "FooBravo": &CycleBravo{ 2403 Name: "FooBravo", 2404 ID: 101, 2405 Mods: 100, 2406 Alphas: map[string]*CycleAlpha{ 2407 "Foo": nil, // cyclic reference 2408 }, 2409 }, 2410 }, 2411 }, 2412 "Bar": &CycleAlpha{ 2413 Name: "Bar", 2414 Bravos: map[string]*CycleBravo{ 2415 "BarBuzzBravo": &CycleBravo{ 2416 Name: "BarBuzzBravo", 2417 ID: 102, 2418 Mods: 2, 2419 Alphas: map[string]*CycleAlpha{ 2420 "Bar": nil, // cyclic reference 2421 "Buzz": nil, // cyclic reference 2422 }, 2423 }, 2424 "BuzzBarBravo": &CycleBravo{ 2425 Name: "BuzzBarBravo", 2426 ID: 103, 2427 Mods: 0, 2428 Alphas: map[string]*CycleAlpha{ 2429 "Bar": nil, // cyclic reference 2430 "Buzz": nil, // cyclic reference 2431 }, 2432 }, 2433 }, 2434 }, 2435 "Buzz": &CycleAlpha{ 2436 Name: "Buzz", 2437 Bravos: map[string]*CycleBravo{ 2438 "BarBuzzBravo": nil, // cyclic reference 2439 "BuzzBarBravo": nil, // cyclic reference 2440 }, 2441 }, 2442 } 2443 v["Foo"].Bravos["FooBravo"].Alphas["Foo"] = v["Foo"] 2444 v["Bar"].Bravos["BarBuzzBravo"].Alphas["Bar"] = v["Bar"] 2445 v["Bar"].Bravos["BarBuzzBravo"].Alphas["Buzz"] = v["Buzz"] 2446 v["Bar"].Bravos["BuzzBarBravo"].Alphas["Bar"] = v["Bar"] 2447 v["Bar"].Bravos["BuzzBarBravo"].Alphas["Buzz"] = v["Buzz"] 2448 v["Buzz"].Bravos["BarBuzzBravo"] = v["Bar"].Bravos["BarBuzzBravo"] 2449 v["Buzz"].Bravos["BuzzBarBravo"] = v["Bar"].Bravos["BuzzBarBravo"] 2450 return v 2451 } 2452 2453 var tests []test 2454 type XY struct{ x, y interface{} } 2455 for _, tt := range []struct { 2456 label string 2457 in XY 2458 wantEqual bool 2459 reason string 2460 }{{ 2461 label: "PointersEqual", 2462 in: func() XY { 2463 x := new(P) 2464 *x = x 2465 y := new(P) 2466 *y = y 2467 return XY{x, y} 2468 }(), 2469 wantEqual: true, 2470 reason: "equal pair of single-node pointers", 2471 }, { 2472 label: "PointersInequal", 2473 in: func() XY { 2474 x := new(P) 2475 *x = x 2476 y1, y2 := new(P), new(P) 2477 *y1 = y2 2478 *y2 = y1 2479 return XY{x, y1} 2480 }(), 2481 wantEqual: false, 2482 reason: "inequal pair of single-node and double-node pointers", 2483 }, { 2484 label: "SlicesEqual", 2485 in: func() XY { 2486 x := S{nil} 2487 x[0] = x 2488 y := S{nil} 2489 y[0] = y 2490 return XY{x, y} 2491 }(), 2492 wantEqual: true, 2493 reason: "equal pair of single-node slices", 2494 }, { 2495 label: "SlicesInequal", 2496 in: func() XY { 2497 x := S{nil} 2498 x[0] = x 2499 y1, y2 := S{nil}, S{nil} 2500 y1[0] = y2 2501 y2[0] = y1 2502 return XY{x, y1} 2503 }(), 2504 wantEqual: false, 2505 reason: "inequal pair of single-node and double node slices", 2506 }, { 2507 label: "MapsEqual", 2508 in: func() XY { 2509 x := M{0: nil} 2510 x[0] = x 2511 y := M{0: nil} 2512 y[0] = y 2513 return XY{x, y} 2514 }(), 2515 wantEqual: true, 2516 reason: "equal pair of single-node maps", 2517 }, { 2518 label: "MapsInequal", 2519 in: func() XY { 2520 x := M{0: nil} 2521 x[0] = x 2522 y1, y2 := M{0: nil}, M{0: nil} 2523 y1[0] = y2 2524 y2[0] = y1 2525 return XY{x, y1} 2526 }(), 2527 wantEqual: false, 2528 reason: "inequal pair of single-node and double-node maps", 2529 }, { 2530 label: "GraphEqual", 2531 in: XY{makeGraph(), makeGraph()}, 2532 wantEqual: true, 2533 reason: "graphs are equal since they have identical forms", 2534 }, { 2535 label: "GraphInequalZeroed", 2536 in: func() XY { 2537 x := makeGraph() 2538 y := makeGraph() 2539 y["Foo"].Bravos["FooBravo"].ID = 0 2540 y["Bar"].Bravos["BarBuzzBravo"].ID = 0 2541 y["Bar"].Bravos["BuzzBarBravo"].ID = 0 2542 return XY{x, y} 2543 }(), 2544 wantEqual: false, 2545 reason: "graphs are inequal because the ID fields are different", 2546 }, { 2547 label: "GraphInequalStruct", 2548 in: func() XY { 2549 x := makeGraph() 2550 y := makeGraph() 2551 x["Buzz"].Bravos["BuzzBarBravo"] = &CycleBravo{ 2552 Name: "BuzzBarBravo", 2553 ID: 103, 2554 } 2555 return XY{x, y} 2556 }(), 2557 wantEqual: false, 2558 reason: "graphs are inequal because they differ on a map element", 2559 }} { 2560 tests = append(tests, test{ 2561 label: label + "/" + tt.label, 2562 x: tt.in.x, 2563 y: tt.in.y, 2564 wantEqual: tt.wantEqual, 2565 reason: tt.reason, 2566 }) 2567 } 2568 return tests 2569} 2570 2571func project1Tests() []test { 2572 const label = "Project1" 2573 2574 ignoreUnexported := cmpopts.IgnoreUnexported( 2575 ts.EagleImmutable{}, 2576 ts.DreamerImmutable{}, 2577 ts.SlapImmutable{}, 2578 ts.GoatImmutable{}, 2579 ts.DonkeyImmutable{}, 2580 ts.LoveRadius{}, 2581 ts.SummerLove{}, 2582 ts.SummerLoveSummary{}, 2583 ) 2584 2585 createEagle := func() ts.Eagle { 2586 return ts.Eagle{ 2587 Name: "eagle", 2588 Hounds: []string{"buford", "tannen"}, 2589 Desc: "some description", 2590 Dreamers: []ts.Dreamer{{}, { 2591 Name: "dreamer2", 2592 Animal: []interface{}{ 2593 ts.Goat{ 2594 Target: "corporation", 2595 Immutable: &ts.GoatImmutable{ 2596 ID: "southbay", 2597 State: (*pb.Goat_States)(newInt(5)), 2598 Started: now, 2599 }, 2600 }, 2601 ts.Donkey{}, 2602 }, 2603 Amoeba: 53, 2604 }}, 2605 Slaps: []ts.Slap{{ 2606 Name: "slapID", 2607 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, 2608 Immutable: &ts.SlapImmutable{ 2609 ID: "immutableSlap", 2610 MildSlap: true, 2611 Started: now, 2612 LoveRadius: &ts.LoveRadius{ 2613 Summer: &ts.SummerLove{ 2614 Summary: &ts.SummerLoveSummary{ 2615 Devices: []string{"foo", "bar", "baz"}, 2616 ChangeType: []pb.SummerType{1, 2, 3}, 2617 }, 2618 }, 2619 }, 2620 }, 2621 }}, 2622 Immutable: &ts.EagleImmutable{ 2623 ID: "eagleID", 2624 Birthday: now, 2625 MissingCall: (*pb.Eagle_MissingCalls)(newInt(55)), 2626 }, 2627 } 2628 } 2629 2630 return []test{{ 2631 label: label + "/PanicUnexported", 2632 x: ts.Eagle{Slaps: []ts.Slap{{ 2633 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, 2634 }}}, 2635 y: ts.Eagle{Slaps: []ts.Slap{{ 2636 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, 2637 }}}, 2638 wantPanic: "cannot handle unexported field", 2639 reason: "struct contains unexported fields", 2640 }, { 2641 label: label + "/ProtoEqual", 2642 x: ts.Eagle{Slaps: []ts.Slap{{ 2643 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, 2644 }}}, 2645 y: ts.Eagle{Slaps: []ts.Slap{{ 2646 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, 2647 }}}, 2648 opts: []cmp.Option{cmp.Comparer(pb.Equal)}, 2649 wantEqual: true, 2650 reason: "simulated protobuf messages contain the same values", 2651 }, { 2652 label: label + "/ProtoInequal", 2653 x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, { 2654 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, 2655 }}}, 2656 y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, { 2657 Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata2"}}, 2658 }}}, 2659 opts: []cmp.Option{cmp.Comparer(pb.Equal)}, 2660 wantEqual: false, 2661 reason: "simulated protobuf messages contain different values", 2662 }, { 2663 label: label + "/Equal", 2664 x: createEagle(), 2665 y: createEagle(), 2666 opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)}, 2667 wantEqual: true, 2668 reason: "equal because values are the same", 2669 }, { 2670 label: label + "/Inequal", 2671 x: func() ts.Eagle { 2672 eg := createEagle() 2673 eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2" 2674 eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(newInt(6)) 2675 eg.Slaps[0].Immutable.MildSlap = false 2676 return eg 2677 }(), 2678 y: func() ts.Eagle { 2679 eg := createEagle() 2680 devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices 2681 eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1] 2682 return eg 2683 }(), 2684 opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)}, 2685 wantEqual: false, 2686 reason: "inequal because some values are different", 2687 }} 2688} 2689 2690type germSorter []*pb.Germ 2691 2692func (gs germSorter) Len() int { return len(gs) } 2693func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() } 2694func (gs germSorter) Swap(i, j int) { gs[i], gs[j] = gs[j], gs[i] } 2695 2696func project2Tests() []test { 2697 const label = "Project2" 2698 2699 sortGerms := cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ { 2700 out := append([]*pb.Germ(nil), in...) // Make copy 2701 sort.Sort(germSorter(out)) 2702 return out 2703 }) 2704 2705 equalDish := cmp.Comparer(func(x, y *ts.Dish) bool { 2706 if x == nil || y == nil { 2707 return x == nil && y == nil 2708 } 2709 px, err1 := x.Proto() 2710 py, err2 := y.Proto() 2711 if err1 != nil || err2 != nil { 2712 return err1 == err2 2713 } 2714 return pb.Equal(px, py) 2715 }) 2716 2717 createBatch := func() ts.GermBatch { 2718 return ts.GermBatch{ 2719 DirtyGerms: map[int32][]*pb.Germ{ 2720 17: { 2721 {Stringer: pb.Stringer{X: "germ1"}}, 2722 }, 2723 18: { 2724 {Stringer: pb.Stringer{X: "germ2"}}, 2725 {Stringer: pb.Stringer{X: "germ3"}}, 2726 {Stringer: pb.Stringer{X: "germ4"}}, 2727 }, 2728 }, 2729 GermMap: map[int32]*pb.Germ{ 2730 13: {Stringer: pb.Stringer{X: "germ13"}}, 2731 21: {Stringer: pb.Stringer{X: "germ21"}}, 2732 }, 2733 DishMap: map[int32]*ts.Dish{ 2734 0: ts.CreateDish(nil, io.EOF), 2735 1: ts.CreateDish(nil, io.ErrUnexpectedEOF), 2736 2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{X: "dish"}}, nil), 2737 }, 2738 HasPreviousResult: true, 2739 DirtyID: 10, 2740 GermStrain: 421, 2741 InfectedAt: now, 2742 } 2743 } 2744 2745 return []test{{ 2746 label: label + "/PanicUnexported", 2747 x: createBatch(), 2748 y: createBatch(), 2749 wantPanic: "cannot handle unexported field", 2750 reason: "struct contains unexported fields", 2751 }, { 2752 label: label + "/Equal", 2753 x: createBatch(), 2754 y: createBatch(), 2755 opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish}, 2756 wantEqual: true, 2757 reason: "equal because identical values are compared", 2758 }, { 2759 label: label + "/InequalOrder", 2760 x: createBatch(), 2761 y: func() ts.GermBatch { 2762 gb := createBatch() 2763 s := gb.DirtyGerms[18] 2764 s[0], s[1], s[2] = s[1], s[2], s[0] 2765 return gb 2766 }(), 2767 opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish}, 2768 wantEqual: false, 2769 reason: "inequal because slice contains elements in differing order", 2770 }, { 2771 label: label + "/EqualOrder", 2772 x: createBatch(), 2773 y: func() ts.GermBatch { 2774 gb := createBatch() 2775 s := gb.DirtyGerms[18] 2776 s[0], s[1], s[2] = s[1], s[2], s[0] 2777 return gb 2778 }(), 2779 opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish}, 2780 wantEqual: true, 2781 reason: "equal because unordered slice is sorted using transformer", 2782 }, { 2783 label: label + "/Inequal", 2784 x: func() ts.GermBatch { 2785 gb := createBatch() 2786 delete(gb.DirtyGerms, 17) 2787 gb.DishMap[1] = nil 2788 return gb 2789 }(), 2790 y: func() ts.GermBatch { 2791 gb := createBatch() 2792 gb.DirtyGerms[18] = gb.DirtyGerms[18][:2] 2793 gb.GermStrain = 22 2794 return gb 2795 }(), 2796 opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish}, 2797 wantEqual: false, 2798 reason: "inequal because some values are different", 2799 }} 2800} 2801 2802func project3Tests() []test { 2803 const label = "Project3" 2804 2805 allowVisibility := cmp.AllowUnexported(ts.Dirt{}) 2806 2807 ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{}) 2808 2809 transformProtos := cmp.Transformer("λ", func(x pb.Dirt) *pb.Dirt { 2810 return &x 2811 }) 2812 2813 equalTable := cmp.Comparer(func(x, y ts.Table) bool { 2814 tx, ok1 := x.(*ts.MockTable) 2815 ty, ok2 := y.(*ts.MockTable) 2816 if !ok1 || !ok2 { 2817 panic("table type must be MockTable") 2818 } 2819 return cmp.Equal(tx.State(), ty.State()) 2820 }) 2821 2822 createDirt := func() (d ts.Dirt) { 2823 d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"})) 2824 d.SetTimestamp(12345) 2825 d.Discord = 554 2826 d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "proto"}} 2827 d.SetWizard(map[string]*pb.Wizard{ 2828 "harry": {Stringer: pb.Stringer{X: "potter"}}, 2829 "albus": {Stringer: pb.Stringer{X: "dumbledore"}}, 2830 }) 2831 d.SetLastTime(54321) 2832 return d 2833 } 2834 2835 return []test{{ 2836 label: label + "/PanicUnexported1", 2837 x: createDirt(), 2838 y: createDirt(), 2839 wantPanic: "cannot handle unexported field", 2840 reason: "struct contains unexported fields", 2841 }, { 2842 label: label + "/PanicUnexported2", 2843 x: createDirt(), 2844 y: createDirt(), 2845 opts: []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, 2846 wantPanic: "cannot handle unexported field", 2847 reason: "struct contains references to simulated protobuf types with unexported fields", 2848 }, { 2849 label: label + "/Equal", 2850 x: createDirt(), 2851 y: createDirt(), 2852 opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, 2853 wantEqual: true, 2854 reason: "transformer used to create reference to protobuf message so it works with pb.Equal", 2855 }, { 2856 label: label + "/Inequal", 2857 x: func() ts.Dirt { 2858 d := createDirt() 2859 d.SetTable(ts.CreateMockTable([]string{"a", "c"})) 2860 d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "blah"}} 2861 return d 2862 }(), 2863 y: func() ts.Dirt { 2864 d := createDirt() 2865 d.Discord = 500 2866 d.SetWizard(map[string]*pb.Wizard{ 2867 "harry": {Stringer: pb.Stringer{X: "otter"}}, 2868 }) 2869 return d 2870 }(), 2871 opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, 2872 wantEqual: false, 2873 reason: "inequal because some values are different", 2874 }} 2875} 2876 2877func project4Tests() []test { 2878 const label = "Project4" 2879 2880 allowVisibility := cmp.AllowUnexported( 2881 ts.Cartel{}, 2882 ts.Headquarter{}, 2883 ts.Poison{}, 2884 ) 2885 2886 transformProtos := cmp.Transformer("λ", func(x pb.Restrictions) *pb.Restrictions { 2887 return &x 2888 }) 2889 2890 createCartel := func() ts.Cartel { 2891 var p ts.Poison 2892 p.SetPoisonType(5) 2893 p.SetExpiration(now) 2894 p.SetManufacturer("acme") 2895 2896 var hq ts.Headquarter 2897 hq.SetID(5) 2898 hq.SetLocation("moon") 2899 hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"}) 2900 hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}) 2901 hq.SetPublicMessage([]byte{1, 2, 3, 4, 5}) 2902 hq.SetHorseBack("abcdef") 2903 hq.SetStatus(44) 2904 2905 var c ts.Cartel 2906 c.Headquarter = hq 2907 c.SetSource("mars") 2908 c.SetCreationTime(now) 2909 c.SetBoss("al capone") 2910 c.SetPoisons([]*ts.Poison{&p}) 2911 2912 return c 2913 } 2914 2915 return []test{{ 2916 label: label + "/PanicUnexported1", 2917 x: createCartel(), 2918 y: createCartel(), 2919 wantPanic: "cannot handle unexported field", 2920 reason: "struct contains unexported fields", 2921 }, { 2922 label: label + "/PanicUnexported2", 2923 x: createCartel(), 2924 y: createCartel(), 2925 opts: []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)}, 2926 wantPanic: "cannot handle unexported field", 2927 reason: "struct contains references to simulated protobuf types with unexported fields", 2928 }, { 2929 label: label + "/Equal", 2930 x: createCartel(), 2931 y: createCartel(), 2932 opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)}, 2933 wantEqual: true, 2934 reason: "transformer used to create reference to protobuf message so it works with pb.Equal", 2935 }, { 2936 label: label + "/Inequal", 2937 x: func() ts.Cartel { 2938 d := createCartel() 2939 var p1, p2 ts.Poison 2940 p1.SetPoisonType(1) 2941 p1.SetExpiration(now) 2942 p1.SetManufacturer("acme") 2943 p2.SetPoisonType(2) 2944 p2.SetManufacturer("acme2") 2945 d.SetPoisons([]*ts.Poison{&p1, &p2}) 2946 return d 2947 }(), 2948 y: func() ts.Cartel { 2949 d := createCartel() 2950 d.SetSubDivisions([]string{"bravo", "charlie"}) 2951 d.SetPublicMessage([]byte{1, 2, 4, 3, 5}) 2952 return d 2953 }(), 2954 opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)}, 2955 wantEqual: false, 2956 reason: "inequal because some values are different", 2957 }} 2958} 2959 2960// BenchmarkBytes benchmarks the performance of performing Equal or Diff on 2961// large slices of bytes. 2962func BenchmarkBytes(b *testing.B) { 2963 // Create a list of PathFilters that never apply, but are evaluated. 2964 const maxFilters = 5 2965 var filters cmp.Options 2966 errorIface := reflect.TypeOf((*error)(nil)).Elem() 2967 for i := 0; i <= maxFilters; i++ { 2968 filters = append(filters, cmp.FilterPath(func(p cmp.Path) bool { 2969 return p.Last().Type().AssignableTo(errorIface) // Never true 2970 }, cmp.Ignore())) 2971 } 2972 2973 type benchSize struct { 2974 label string 2975 size int64 2976 } 2977 for _, ts := range []benchSize{ 2978 {"4KiB", 1 << 12}, 2979 {"64KiB", 1 << 16}, 2980 {"1MiB", 1 << 20}, 2981 {"16MiB", 1 << 24}, 2982 } { 2983 bx := append(append(make([]byte, ts.size/2), 'x'), make([]byte, ts.size/2)...) 2984 by := append(append(make([]byte, ts.size/2), 'y'), make([]byte, ts.size/2)...) 2985 b.Run(ts.label, func(b *testing.B) { 2986 // Iteratively add more filters that never apply, but are evaluated 2987 // to measure the cost of simply evaluating each filter. 2988 for i := 0; i <= maxFilters; i++ { 2989 b.Run(fmt.Sprintf("EqualFilter%d", i), func(b *testing.B) { 2990 b.ReportAllocs() 2991 b.SetBytes(2 * ts.size) 2992 for j := 0; j < b.N; j++ { 2993 cmp.Equal(bx, by, filters[:i]...) 2994 } 2995 }) 2996 } 2997 for i := 0; i <= maxFilters; i++ { 2998 b.Run(fmt.Sprintf("DiffFilter%d", i), func(b *testing.B) { 2999 b.ReportAllocs() 3000 b.SetBytes(2 * ts.size) 3001 for j := 0; j < b.N; j++ { 3002 cmp.Diff(bx, by, filters[:i]...) 3003 } 3004 }) 3005 } 3006 }) 3007 } 3008} 3009