| // Copyright 2017, The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE.md file. |
| |
| package cmp_test |
| |
| import ( |
| "bytes" |
| "crypto/md5" |
| "encoding/json" |
| "fmt" |
| "io" |
| "math" |
| "math/rand" |
| "reflect" |
| "regexp" |
| "sort" |
| "strings" |
| "sync" |
| "testing" |
| "time" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| "github.com/google/go-cmp/cmp/internal/flags" |
| |
| pb "github.com/google/go-cmp/cmp/internal/testprotos" |
| ts "github.com/google/go-cmp/cmp/internal/teststructs" |
| ) |
| |
| func init() { |
| flags.Deterministic = true |
| } |
| |
| var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC) |
| |
| func intPtr(n int) *int { return &n } |
| |
| type test struct { |
| label string // Test name |
| x, y interface{} // Input values to compare |
| opts []cmp.Option // Input options |
| wantDiff string // The exact difference string |
| wantPanic string // Sub-string of an expected panic message |
| reason string // The reason for the expected outcome |
| } |
| |
| func TestDiff(t *testing.T) { |
| var tests []test |
| tests = append(tests, comparerTests()...) |
| tests = append(tests, transformerTests()...) |
| tests = append(tests, embeddedTests()...) |
| tests = append(tests, methodTests()...) |
| tests = append(tests, project1Tests()...) |
| tests = append(tests, project2Tests()...) |
| tests = append(tests, project3Tests()...) |
| tests = append(tests, project4Tests()...) |
| |
| for _, tt := range tests { |
| tt := tt |
| t.Run(tt.label, func(t *testing.T) { |
| t.Parallel() |
| var gotDiff, gotPanic string |
| func() { |
| defer func() { |
| if ex := recover(); ex != nil { |
| if s, ok := ex.(string); ok { |
| gotPanic = s |
| } else { |
| panic(ex) |
| } |
| } |
| }() |
| gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...) |
| }() |
| // TODO: Require every test case to provide a reason. |
| if tt.wantPanic == "" { |
| if gotPanic != "" { |
| t.Fatalf("unexpected panic message: %s\nreason: %v", gotPanic, tt.reason) |
| } |
| tt.wantDiff = strings.TrimPrefix(tt.wantDiff, "\n") |
| if gotDiff != tt.wantDiff { |
| t.Fatalf("difference message:\ngot:\n%s\nwant:\n%s\nreason: %v", gotDiff, tt.wantDiff, tt.reason) |
| } |
| } else { |
| if !strings.Contains(gotPanic, tt.wantPanic) { |
| t.Fatalf("panic message:\ngot: %s\nwant: %s\nreason: %v", gotPanic, tt.wantPanic, tt.reason) |
| } |
| } |
| }) |
| } |
| } |
| |
| func comparerTests() []test { |
| const label = "Comparer" |
| |
| type Iface1 interface { |
| Method() |
| } |
| type Iface2 interface { |
| Method() |
| } |
| |
| type tarHeader struct { |
| Name string |
| Mode int64 |
| Uid int |
| Gid int |
| Size int64 |
| ModTime time.Time |
| Typeflag byte |
| Linkname string |
| Uname string |
| Gname string |
| Devmajor int64 |
| Devminor int64 |
| AccessTime time.Time |
| ChangeTime time.Time |
| Xattrs map[string]string |
| } |
| |
| makeTarHeaders := func(tf byte) (hs []tarHeader) { |
| for i := 0; i < 5; i++ { |
| hs = append(hs, tarHeader{ |
| Name: fmt.Sprintf("some/dummy/test/file%d", i), |
| Mode: 0664, Uid: i * 1000, Gid: i * 1000, Size: 1 << uint(i), |
| ModTime: now.Add(time.Duration(i) * time.Hour), |
| Uname: "user", Gname: "group", |
| Typeflag: tf, |
| }) |
| } |
| return hs |
| } |
| |
| return []test{{ |
| label: label, |
| x: nil, |
| y: nil, |
| }, { |
| label: label, |
| x: 1, |
| y: 1, |
| }, { |
| label: label, |
| x: 1, |
| y: 1, |
| opts: []cmp.Option{cmp.Ignore()}, |
| wantPanic: "cannot use an unfiltered option", |
| }, { |
| label: label, |
| x: 1, |
| y: 1, |
| opts: []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })}, |
| wantPanic: "cannot use an unfiltered option", |
| }, { |
| label: label, |
| x: 1, |
| y: 1, |
| opts: []cmp.Option{cmp.Transformer("λ", func(x interface{}) interface{} { return x })}, |
| wantPanic: "cannot use an unfiltered option", |
| }, { |
| label: label, |
| x: 1, |
| y: 1, |
| opts: []cmp.Option{ |
| cmp.Comparer(func(x, y int) bool { return true }), |
| cmp.Transformer("λ", func(x int) float64 { return float64(x) }), |
| }, |
| wantPanic: "ambiguous set of applicable options", |
| }, { |
| label: label, |
| x: 1, |
| y: 1, |
| opts: []cmp.Option{ |
| cmp.FilterPath(func(p cmp.Path) bool { |
| return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int |
| }, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}), |
| cmp.Comparer(func(x, y int) bool { return true }), |
| cmp.Transformer("λ", func(x int) float64 { return float64(x) }), |
| }, |
| }, { |
| label: label, |
| opts: []cmp.Option{struct{ cmp.Option }{}}, |
| wantPanic: "unknown option", |
| }, { |
| label: label, |
| x: struct{ A, B, C int }{1, 2, 3}, |
| y: struct{ A, B, C int }{1, 2, 3}, |
| }, { |
| label: label, |
| x: struct{ A, B, C int }{1, 2, 3}, |
| y: struct{ A, B, C int }{1, 2, 4}, |
| wantDiff: ` |
| struct{ A int; B int; C int }{ |
| A: 1, |
| B: 2, |
| - C: 3, |
| + C: 4, |
| } |
| `, |
| }, { |
| label: label, |
| x: struct{ a, b, c int }{1, 2, 3}, |
| y: struct{ a, b, c int }{1, 2, 4}, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: &struct{ A *int }{intPtr(4)}, |
| y: &struct{ A *int }{intPtr(4)}, |
| }, { |
| label: label, |
| x: &struct{ A *int }{intPtr(4)}, |
| y: &struct{ A *int }{intPtr(5)}, |
| wantDiff: ` |
| &struct{ A *int }{ |
| - A: &4, |
| + A: &5, |
| } |
| `, |
| }, { |
| label: label, |
| x: &struct{ A *int }{intPtr(4)}, |
| y: &struct{ A *int }{intPtr(5)}, |
| opts: []cmp.Option{ |
| cmp.Comparer(func(x, y int) bool { return true }), |
| }, |
| }, { |
| label: label, |
| x: &struct{ A *int }{intPtr(4)}, |
| y: &struct{ A *int }{intPtr(5)}, |
| opts: []cmp.Option{ |
| cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }), |
| }, |
| }, { |
| label: label, |
| x: &struct{ R *bytes.Buffer }{}, |
| y: &struct{ R *bytes.Buffer }{}, |
| }, { |
| label: label, |
| x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)}, |
| y: &struct{ R *bytes.Buffer }{}, |
| wantDiff: ` |
| &struct{ R *bytes.Buffer }{ |
| - R: s"", |
| + R: nil, |
| } |
| `, |
| }, { |
| label: label, |
| x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)}, |
| y: &struct{ R *bytes.Buffer }{}, |
| opts: []cmp.Option{ |
| cmp.Comparer(func(x, y io.Reader) bool { return true }), |
| }, |
| }, { |
| label: label, |
| x: &struct{ R bytes.Buffer }{}, |
| y: &struct{ R bytes.Buffer }{}, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: &struct{ R bytes.Buffer }{}, |
| y: &struct{ R bytes.Buffer }{}, |
| opts: []cmp.Option{ |
| cmp.Comparer(func(x, y io.Reader) bool { return true }), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: &struct{ R bytes.Buffer }{}, |
| y: &struct{ R bytes.Buffer }{}, |
| opts: []cmp.Option{ |
| cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }), |
| cmp.Comparer(func(x, y io.Reader) bool { return true }), |
| }, |
| }, { |
| label: label, |
| x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, |
| y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, |
| y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, |
| opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool { |
| if x == nil || y == nil { |
| return x == nil && y == nil |
| } |
| return x.String() == y.String() |
| })}, |
| }, { |
| label: label, |
| x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, |
| y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")}, |
| opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool { |
| if x == nil || y == nil { |
| return x == nil && y == nil |
| } |
| return x.String() == y.String() |
| })}, |
| wantDiff: ` |
| []*regexp.Regexp{ |
| nil, |
| - s"a*b*c*", |
| + s"a*b*d*", |
| } |
| `, |
| }, { |
| label: label, |
| x: func() ***int { |
| a := 0 |
| b := &a |
| c := &b |
| return &c |
| }(), |
| y: func() ***int { |
| a := 0 |
| b := &a |
| c := &b |
| return &c |
| }(), |
| }, { |
| label: label, |
| x: func() ***int { |
| a := 0 |
| b := &a |
| c := &b |
| return &c |
| }(), |
| y: func() ***int { |
| a := 1 |
| b := &a |
| c := &b |
| return &c |
| }(), |
| wantDiff: ` |
| &&&int( |
| - 0, |
| + 1, |
| ) |
| `, |
| }, { |
| label: label, |
| x: []int{1, 2, 3, 4, 5}[:3], |
| y: []int{1, 2, 3}, |
| }, { |
| label: label, |
| x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")}, |
| y: struct{ fmt.Stringer }{regexp.MustCompile("hello")}, |
| opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })}, |
| }, { |
| label: label, |
| x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")}, |
| y: struct{ fmt.Stringer }{regexp.MustCompile("hello2")}, |
| opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })}, |
| wantDiff: ` |
| struct{ fmt.Stringer }( |
| - s"hello", |
| + s"hello2", |
| ) |
| `, |
| }, { |
| label: label, |
| x: md5.Sum([]byte{'a'}), |
| y: md5.Sum([]byte{'b'}), |
| wantDiff: ` |
| [16]uint8{ |
| - 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61, |
| + 0x92, 0xeb, 0x5f, 0xfe, 0xe6, 0xae, 0x2f, 0xec, 0x3a, 0xd7, 0x1c, 0x77, 0x75, 0x31, 0x57, 0x8f, |
| } |
| `, |
| }, { |
| label: label, |
| x: new(fmt.Stringer), |
| y: nil, |
| wantDiff: ` |
| interface{}( |
| - &fmt.Stringer(nil), |
| ) |
| `, |
| }, { |
| label: label, |
| x: makeTarHeaders('0'), |
| y: makeTarHeaders('\x00'), |
| wantDiff: ` |
| []cmp_test.tarHeader{ |
| { |
| ... // 4 identical fields |
| Size: 1, |
| ModTime: s"2009-11-10 23:00:00 +0000 UTC", |
| - Typeflag: 0x30, |
| + Typeflag: 0x00, |
| Linkname: "", |
| Uname: "user", |
| ... // 6 identical fields |
| }, |
| { |
| ... // 4 identical fields |
| Size: 2, |
| ModTime: s"2009-11-11 00:00:00 +0000 UTC", |
| - Typeflag: 0x30, |
| + Typeflag: 0x00, |
| Linkname: "", |
| Uname: "user", |
| ... // 6 identical fields |
| }, |
| { |
| ... // 4 identical fields |
| Size: 4, |
| ModTime: s"2009-11-11 01:00:00 +0000 UTC", |
| - Typeflag: 0x30, |
| + Typeflag: 0x00, |
| Linkname: "", |
| Uname: "user", |
| ... // 6 identical fields |
| }, |
| { |
| ... // 4 identical fields |
| Size: 8, |
| ModTime: s"2009-11-11 02:00:00 +0000 UTC", |
| - Typeflag: 0x30, |
| + Typeflag: 0x00, |
| Linkname: "", |
| Uname: "user", |
| ... // 6 identical fields |
| }, |
| { |
| ... // 4 identical fields |
| Size: 16, |
| ModTime: s"2009-11-11 03:00:00 +0000 UTC", |
| - Typeflag: 0x30, |
| + Typeflag: 0x00, |
| Linkname: "", |
| Uname: "user", |
| ... // 6 identical fields |
| }, |
| } |
| `, |
| }, { |
| label: label, |
| x: make([]int, 1000), |
| y: make([]int, 1000), |
| opts: []cmp.Option{ |
| cmp.Comparer(func(_, _ int) bool { |
| return rand.Intn(2) == 0 |
| }), |
| }, |
| wantPanic: "non-deterministic or non-symmetric function detected", |
| }, { |
| label: label, |
| x: make([]int, 1000), |
| y: make([]int, 1000), |
| opts: []cmp.Option{ |
| cmp.FilterValues(func(_, _ int) bool { |
| return rand.Intn(2) == 0 |
| }, cmp.Ignore()), |
| }, |
| wantPanic: "non-deterministic or non-symmetric function detected", |
| }, { |
| label: label, |
| x: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, |
| y: []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, |
| opts: []cmp.Option{ |
| cmp.Comparer(func(x, y int) bool { |
| return x < y |
| }), |
| }, |
| wantPanic: "non-deterministic or non-symmetric function detected", |
| }, { |
| label: label, |
| x: make([]string, 1000), |
| y: make([]string, 1000), |
| opts: []cmp.Option{ |
| cmp.Transformer("λ", func(x string) int { |
| return rand.Int() |
| }), |
| }, |
| wantPanic: "non-deterministic function detected", |
| }, { |
| // Make sure the dynamic checks don't raise a false positive for |
| // non-reflexive comparisons. |
| label: label, |
| x: make([]int, 10), |
| y: make([]int, 10), |
| opts: []cmp.Option{ |
| cmp.Transformer("λ", func(x int) float64 { |
| return math.NaN() |
| }), |
| }, |
| wantDiff: ` |
| []int{ |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| - Inverse(λ, float64(NaN)), |
| + Inverse(λ, float64(NaN)), |
| } |
| `, |
| }, { |
| // Ensure reasonable Stringer formatting of map keys. |
| label: label, |
| x: map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}}, |
| y: map[*pb.Stringer]*pb.Stringer(nil), |
| wantDiff: ` |
| map[*testprotos.Stringer]*testprotos.Stringer( |
| - {s"hello": s"world"}, |
| + nil, |
| ) |
| `, |
| }, { |
| // Ensure Stringer avoids double-quote escaping if possible. |
| label: label, |
| x: []*pb.Stringer{{`multi\nline\nline\nline`}}, |
| wantDiff: strings.Replace(` |
| interface{}( |
| - []*testprotos.Stringer{s'multi\nline\nline\nline'}, |
| ) |
| `, "'", "`", -1), |
| }, { |
| label: label, |
| x: struct{ I Iface2 }{}, |
| y: struct{ I Iface2 }{}, |
| opts: []cmp.Option{ |
| cmp.Comparer(func(x, y Iface1) bool { |
| return x == nil && y == nil |
| }), |
| }, |
| }, { |
| label: label, |
| x: struct{ I Iface2 }{}, |
| y: struct{ I Iface2 }{}, |
| opts: []cmp.Option{ |
| cmp.Transformer("λ", func(v Iface1) bool { |
| return v == nil |
| }), |
| }, |
| }, { |
| label: label, |
| x: struct{ I Iface2 }{}, |
| y: struct{ I Iface2 }{}, |
| opts: []cmp.Option{ |
| cmp.FilterValues(func(x, y Iface1) bool { |
| return x == nil && y == nil |
| }, cmp.Ignore()), |
| }, |
| }, { |
| label: label, |
| x: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63, "name": "Sammy Sosa"}}, |
| 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"}}, |
| wantDiff: ` |
| []interface{}{ |
| map[string]interface{}{ |
| "avg": float64(0.278), |
| - "hr": int(65), |
| + "hr": float64(65), |
| "name": string("Mark McGwire"), |
| }, |
| map[string]interface{}{ |
| "avg": float64(0.288), |
| - "hr": int(63), |
| + "hr": float64(63), |
| "name": string("Sammy Sosa"), |
| }, |
| } |
| `, |
| }, { |
| label: label, |
| x: map[*int]string{ |
| new(int): "hello", |
| }, |
| y: map[*int]string{ |
| new(int): "world", |
| }, |
| wantDiff: ` |
| map[*int]string{ |
| - ⟪0xdeadf00f⟫: "hello", |
| + ⟪0xdeadf00f⟫: "world", |
| } |
| `, |
| }, { |
| label: label, |
| x: intPtr(0), |
| y: intPtr(0), |
| opts: []cmp.Option{ |
| cmp.Comparer(func(x, y *int) bool { return x == y }), |
| }, |
| // TODO: This output is unhelpful and should show the address. |
| wantDiff: ` |
| (*int)( |
| - &0, |
| + &0, |
| ) |
| `, |
| }, { |
| label: label, |
| x: [2][]int{ |
| {0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 7, 8, 0, 9, 0, 0}, |
| {0, 1, 0, 0, 0, 20}, |
| }, |
| y: [2][]int{ |
| {1, 2, 3, 0, 4, 5, 6, 7, 0, 8, 9, 0, 0, 0}, |
| {0, 0, 1, 2, 0, 0, 0}, |
| }, |
| opts: []cmp.Option{ |
| cmp.FilterPath(func(p cmp.Path) bool { |
| vx, vy := p.Last().Values() |
| if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 { |
| return true |
| } |
| if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 { |
| return true |
| } |
| return false |
| }, cmp.Ignore()), |
| }, |
| wantDiff: ` |
| [2][]int{ |
| {..., 1, 2, 3, ..., 4, 5, 6, 7, ..., 8, ..., 9, ...}, |
| { |
| ... // 6 ignored and 1 identical elements |
| - 20, |
| + 2, |
| ... // 3 ignored elements |
| }, |
| } |
| `, |
| reason: "all zero slice elements are ignored (even if missing)", |
| }, { |
| label: label, |
| x: [2]map[string]int{ |
| {"ignore1": 0, "ignore2": 0, "keep1": 1, "keep2": 2, "KEEP3": 3, "IGNORE3": 0}, |
| {"keep1": 1, "ignore1": 0}, |
| }, |
| y: [2]map[string]int{ |
| {"ignore1": 0, "ignore3": 0, "ignore4": 0, "keep1": 1, "keep2": 2, "KEEP3": 3}, |
| {"keep1": 1, "keep2": 2, "ignore2": 0}, |
| }, |
| opts: []cmp.Option{ |
| cmp.FilterPath(func(p cmp.Path) bool { |
| vx, vy := p.Last().Values() |
| if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 { |
| return true |
| } |
| if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 { |
| return true |
| } |
| return false |
| }, cmp.Ignore()), |
| }, |
| wantDiff: ` |
| [2]map[string]int{ |
| {"KEEP3": 3, "keep1": 1, "keep2": 2, ...}, |
| { |
| ... // 2 ignored entries |
| "keep1": 1, |
| + "keep2": 2, |
| }, |
| } |
| `, |
| reason: "all zero map entries are ignored (even if missing)", |
| }} |
| } |
| |
| func transformerTests() []test { |
| type StringBytes struct { |
| String string |
| Bytes []byte |
| } |
| |
| const label = "Transformer" |
| |
| transformOnce := func(name string, f interface{}) cmp.Option { |
| xform := cmp.Transformer(name, f) |
| return cmp.FilterPath(func(p cmp.Path) bool { |
| for _, ps := range p { |
| if tr, ok := ps.(cmp.Transform); ok && tr.Option() == xform { |
| return false |
| } |
| } |
| return true |
| }, xform) |
| } |
| |
| return []test{{ |
| label: label, |
| x: uint8(0), |
| y: uint8(1), |
| opts: []cmp.Option{ |
| cmp.Transformer("λ", func(in uint8) uint16 { return uint16(in) }), |
| cmp.Transformer("λ", func(in uint16) uint32 { return uint32(in) }), |
| cmp.Transformer("λ", func(in uint32) uint64 { return uint64(in) }), |
| }, |
| wantDiff: ` |
| uint8(Inverse(λ, uint16(Inverse(λ, uint32(Inverse(λ, uint64( |
| - 0x00, |
| + 0x01, |
| ))))))) |
| `, |
| }, { |
| label: label, |
| x: 0, |
| y: 1, |
| opts: []cmp.Option{ |
| cmp.Transformer("λ", func(in int) int { return in / 2 }), |
| cmp.Transformer("λ", func(in int) int { return in }), |
| }, |
| wantPanic: "ambiguous set of applicable options", |
| }, { |
| label: label, |
| x: []int{0, -5, 0, -1}, |
| y: []int{1, 3, 0, -5}, |
| opts: []cmp.Option{ |
| cmp.FilterValues( |
| func(x, y int) bool { return x+y >= 0 }, |
| cmp.Transformer("λ", func(in int) int64 { return int64(in / 2) }), |
| ), |
| cmp.FilterValues( |
| func(x, y int) bool { return x+y < 0 }, |
| cmp.Transformer("λ", func(in int) int64 { return int64(in) }), |
| ), |
| }, |
| wantDiff: ` |
| []int{ |
| Inverse(λ, int64(0)), |
| - Inverse(λ, int64(-5)), |
| + Inverse(λ, int64(3)), |
| Inverse(λ, int64(0)), |
| - Inverse(λ, int64(-1)), |
| + Inverse(λ, int64(-5)), |
| } |
| `, |
| }, { |
| label: label, |
| x: 0, |
| y: 1, |
| opts: []cmp.Option{ |
| cmp.Transformer("λ", func(in int) interface{} { |
| if in == 0 { |
| return "zero" |
| } |
| return float64(in) |
| }), |
| }, |
| wantDiff: ` |
| int(Inverse(λ, interface{}( |
| - string("zero"), |
| + float64(1), |
| ))) |
| `, |
| }, { |
| label: label, |
| x: `{ |
| "firstName": "John", |
| "lastName": "Smith", |
| "age": 25, |
| "isAlive": true, |
| "address": { |
| "city": "Los Angeles", |
| "postalCode": "10021-3100", |
| "state": "CA", |
| "streetAddress": "21 2nd Street" |
| }, |
| "phoneNumbers": [{ |
| "type": "home", |
| "number": "212 555-4321" |
| },{ |
| "type": "office", |
| "number": "646 555-4567" |
| },{ |
| "number": "123 456-7890", |
| "type": "mobile" |
| }], |
| "children": [] |
| }`, |
| y: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":25, |
| "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}`, |
| opts: []cmp.Option{ |
| transformOnce("ParseJSON", func(s string) (m map[string]interface{}) { |
| if err := json.Unmarshal([]byte(s), &m); err != nil { |
| panic(err) |
| } |
| return m |
| }), |
| }, |
| wantDiff: ` |
| string(Inverse(ParseJSON, map[string]interface{}{ |
| "address": map[string]interface{}{ |
| - "city": string("Los Angeles"), |
| + "city": string("New York"), |
| "postalCode": string("10021-3100"), |
| - "state": string("CA"), |
| + "state": string("NY"), |
| "streetAddress": string("21 2nd Street"), |
| }, |
| "age": float64(25), |
| "children": []interface{}{}, |
| "firstName": string("John"), |
| "isAlive": bool(true), |
| "lastName": string("Smith"), |
| "phoneNumbers": []interface{}{ |
| map[string]interface{}{ |
| - "number": string("212 555-4321"), |
| + "number": string("212 555-1234"), |
| "type": string("home"), |
| }, |
| map[string]interface{}{"number": string("646 555-4567"), "type": string("office")}, |
| map[string]interface{}{"number": string("123 456-7890"), "type": string("mobile")}, |
| }, |
| + "spouse": nil, |
| })) |
| `, |
| }, { |
| label: label, |
| x: StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")}, |
| y: StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")}, |
| opts: []cmp.Option{ |
| transformOnce("SplitString", func(s string) []string { return strings.Split(s, "\n") }), |
| transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }), |
| }, |
| wantDiff: ` |
| cmp_test.StringBytes{ |
| String: Inverse(SplitString, []string{ |
| "some", |
| "multi", |
| - "Line", |
| + "line", |
| "string", |
| }), |
| Bytes: []uint8(Inverse(SplitBytes, [][]uint8{ |
| {0x73, 0x6f, 0x6d, 0x65}, |
| {0x6d, 0x75, 0x6c, 0x74, 0x69}, |
| {0x6c, 0x69, 0x6e, 0x65}, |
| { |
| - 0x62, |
| + 0x42, |
| 0x79, |
| 0x74, |
| ... // 2 identical elements |
| }, |
| })), |
| } |
| `, |
| }, { |
| x: "a\nb\nc\n", |
| y: "a\nb\nc\n", |
| opts: []cmp.Option{ |
| cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }), |
| }, |
| wantPanic: "recursive set of Transformers detected", |
| }, { |
| x: complex64(0), |
| y: complex64(0), |
| opts: []cmp.Option{ |
| cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }), |
| cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }), |
| cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }), |
| }, |
| wantPanic: "recursive set of Transformers detected", |
| }} |
| } |
| |
| func reporterTests() []test { |
| const label = "Reporter" |
| |
| type ( |
| MyString string |
| MyByte byte |
| MyBytes []byte |
| MyInt int8 |
| MyInts []int8 |
| MyUint int16 |
| MyUints []int16 |
| MyFloat float32 |
| MyFloats []float32 |
| MyComposite struct { |
| StringA string |
| StringB MyString |
| BytesA []byte |
| BytesB []MyByte |
| BytesC MyBytes |
| IntsA []int8 |
| IntsB []MyInt |
| IntsC MyInts |
| UintsA []uint16 |
| UintsB []MyUint |
| UintsC MyUints |
| FloatsA []float32 |
| FloatsB []MyFloat |
| FloatsC MyFloats |
| } |
| ) |
| |
| return []test{{ |
| label: label, |
| x: MyComposite{IntsA: []int8{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}}, |
| y: MyComposite{IntsA: []int8{10, 11, 21, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}}, |
| wantDiff: ` |
| cmp_test.MyComposite{ |
| ... // 3 identical fields |
| BytesB: nil, |
| BytesC: nil, |
| IntsA: []int8{ |
| + 10, |
| 11, |
| - 12, |
| + 21, |
| 13, |
| 14, |
| ... // 15 identical elements |
| }, |
| IntsB: nil, |
| IntsC: nil, |
| ... // 6 identical fields |
| } |
| `, |
| reason: "unbatched diffing desired since few elements differ", |
| }, { |
| label: label, |
| x: MyComposite{IntsA: []int8{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}}, |
| y: MyComposite{IntsA: []int8{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}}, |
| wantDiff: ` |
| cmp_test.MyComposite{ |
| ... // 3 identical fields |
| BytesB: nil, |
| BytesC: nil, |
| IntsA: []int8{ |
| - 10, 11, 12, 13, 14, 15, 16, |
| + 12, 29, 13, 27, 22, 23, |
| 17, 18, 19, 20, 21, |
| - 22, 23, 24, 25, 26, 27, 28, 29, |
| + 10, 26, 16, 25, 28, 11, 15, 24, 14, |
| }, |
| IntsB: nil, |
| IntsC: nil, |
| ... // 6 identical fields |
| } |
| `, |
| reason: "batched diffing desired since many elements differ", |
| }, { |
| label: label, |
| x: MyComposite{ |
| BytesA: []byte{1, 2, 3}, |
| BytesB: []MyByte{4, 5, 6}, |
| BytesC: MyBytes{7, 8, 9}, |
| IntsA: []int8{-1, -2, -3}, |
| IntsB: []MyInt{-4, -5, -6}, |
| IntsC: MyInts{-7, -8, -9}, |
| UintsA: []uint16{1000, 2000, 3000}, |
| UintsB: []MyUint{4000, 5000, 6000}, |
| UintsC: MyUints{7000, 8000, 9000}, |
| FloatsA: []float32{1.5, 2.5, 3.5}, |
| FloatsB: []MyFloat{4.5, 5.5, 6.5}, |
| FloatsC: MyFloats{7.5, 8.5, 9.5}, |
| }, |
| y: MyComposite{ |
| BytesA: []byte{3, 2, 1}, |
| BytesB: []MyByte{6, 5, 4}, |
| BytesC: MyBytes{9, 8, 7}, |
| IntsA: []int8{-3, -2, -1}, |
| IntsB: []MyInt{-6, -5, -4}, |
| IntsC: MyInts{-9, -8, -7}, |
| UintsA: []uint16{3000, 2000, 1000}, |
| UintsB: []MyUint{6000, 5000, 4000}, |
| UintsC: MyUints{9000, 8000, 7000}, |
| FloatsA: []float32{3.5, 2.5, 1.5}, |
| FloatsB: []MyFloat{6.5, 5.5, 4.5}, |
| FloatsC: MyFloats{9.5, 8.5, 7.5}, |
| }, |
| wantDiff: ` |
| cmp_test.MyComposite{ |
| StringA: "", |
| StringB: "", |
| BytesA: []uint8{ |
| - 0x01, 0x02, 0x03, // -|...| |
| + 0x03, 0x02, 0x01, // +|...| |
| }, |
| BytesB: []cmp_test.MyByte{ |
| - 0x04, 0x05, 0x06, |
| + 0x06, 0x05, 0x04, |
| }, |
| BytesC: cmp_test.MyBytes{ |
| - 0x07, 0x08, 0x09, // -|...| |
| + 0x09, 0x08, 0x07, // +|...| |
| }, |
| IntsA: []int8{ |
| - -1, -2, -3, |
| + -3, -2, -1, |
| }, |
| IntsB: []cmp_test.MyInt{ |
| - -4, -5, -6, |
| + -6, -5, -4, |
| }, |
| IntsC: cmp_test.MyInts{ |
| - -7, -8, -9, |
| + -9, -8, -7, |
| }, |
| UintsA: []uint16{ |
| - 0x03e8, 0x07d0, 0x0bb8, |
| + 0x0bb8, 0x07d0, 0x03e8, |
| }, |
| UintsB: []cmp_test.MyUint{ |
| - 4000, 5000, 6000, |
| + 6000, 5000, 4000, |
| }, |
| UintsC: cmp_test.MyUints{ |
| - 7000, 8000, 9000, |
| + 9000, 8000, 7000, |
| }, |
| FloatsA: []float32{ |
| - 1.5, 2.5, 3.5, |
| + 3.5, 2.5, 1.5, |
| }, |
| FloatsB: []cmp_test.MyFloat{ |
| - 4.5, 5.5, 6.5, |
| + 6.5, 5.5, 4.5, |
| }, |
| FloatsC: cmp_test.MyFloats{ |
| - 7.5, 8.5, 9.5, |
| + 9.5, 8.5, 7.5, |
| }, |
| } |
| `, |
| reason: "batched diffing available for both named and unnamed slices", |
| }, { |
| label: label, |
| 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]")}, |
| 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]")}, |
| wantDiff: ` |
| cmp_test.MyComposite{ |
| StringA: "", |
| StringB: "", |
| BytesA: []uint8{ |
| 0xf3, 0x0f, 0x8a, 0xa4, 0xd3, 0x12, 0x52, 0x09, 0x24, 0xbe, // |......R.$.| |
| - 0x58, 0x95, 0x41, 0xfd, 0x24, 0x66, 0x58, 0x8b, 0x79, // -|X.A.$fX.y| |
| 0x54, 0xac, 0x0d, 0xd8, 0x71, 0x77, 0x70, 0x20, 0x6a, 0x5c, 0x73, 0x7f, 0x8c, 0x17, 0x55, 0xc0, // |T...qwp j\s...U.| |
| 0x34, 0xce, 0x6e, 0xf7, 0xaa, 0x47, 0xee, 0x32, 0x9d, 0xc5, 0xca, 0x1e, 0x58, 0xaf, 0x8f, 0x27, // |4.n..G.2....X..'| |
| 0xf3, 0x02, 0x4a, 0x90, 0xed, 0x69, 0x2e, 0x70, 0x32, 0xb4, 0xab, 0x30, 0x20, 0xb6, 0xbd, 0x5c, // |..J..i.p2..0 ..\| |
| 0x62, 0x34, 0x17, 0xb0, 0x00, 0xbb, 0x4f, 0x7e, 0x27, 0x47, 0x06, 0xf4, 0x2e, 0x66, 0xfd, 0x63, // |b4....O~'G...f.c| |
| 0xd7, 0x04, 0xdd, 0xb7, 0x30, 0xb7, 0xd1, // |....0..| |
| - 0x55, 0x7e, 0x7b, 0xf6, 0xb3, 0x7e, 0x1d, 0x57, 0x69, // -|U~{..~.Wi| |
| + 0x75, 0x2d, 0x5b, 0x5d, 0x5d, 0xf6, 0xb3, 0x68, 0x61, 0x68, 0x61, 0x7e, 0x1d, 0x57, 0x49, // +|u-[]]..haha~.WI| |
| 0x20, 0x9e, 0xbc, 0xdf, 0xe1, 0x4d, 0xa9, 0xef, 0xa2, 0xd2, 0xed, 0xb4, 0x47, 0x78, 0xc9, 0xc9, // | ....M......Gx..| |
| 0x27, 0xa4, 0xc6, 0xce, 0xec, 0x44, 0x70, 0x5d, // |'....Dp]| |
| }, |
| BytesB: nil, |
| BytesC: nil, |
| ... // 9 identical fields |
| } |
| `, |
| reason: "binary diff in hexdump form since data is binary data", |
| }, { |
| label: label, |
| 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")}, |
| 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")}, |
| wantDiff: ` |
| cmp_test.MyComposite{ |
| StringA: "", |
| StringB: cmp_test.MyString{ |
| - 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, // -|readme| |
| + 0x67, 0x6f, 0x70, 0x68, 0x65, 0x72, // +|gopher| |
| 0x2e, 0x74, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |.txt............| |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |................| |
| ... // 64 identical bytes |
| 0x30, 0x30, 0x36, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, // |00600.0000000.00| |
| 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, // |00000.0000000004| |
| - 0x36, // -|6| |
| + 0x33, // +|3| |
| 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x31, 0x31, // |.00000000000.011| |
| - 0x31, 0x37, 0x33, // -|173| |
| + 0x32, 0x31, 0x37, // +|217| |
| 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |. 0.............| |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // |................| |
| ... // 326 identical bytes |
| }, |
| BytesA: nil, |
| BytesB: nil, |
| ... // 10 identical fields |
| } |
| `, |
| reason: "binary diff desired since string looks like binary data", |
| }, { |
| label: label, |
| 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}`)}, |
| 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}`)}, |
| wantDiff: strings.Replace(` |
| cmp_test.MyComposite{ |
| StringA: "", |
| StringB: "", |
| BytesA: bytes.Join({ |
| '{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"', |
| 'address":{"streetAddress":"', |
| - "314 54th Avenue", |
| + "21 2nd Street", |
| '","city":"New York","state":"NY","postalCode":"10021-3100"},"pho', |
| 'neNumbers":[{"type":"home","number":"212 555-1234"},{"type":"off', |
| ... // 101 identical bytes |
| }, ""), |
| BytesB: nil, |
| BytesC: nil, |
| ... // 9 identical fields |
| } |
| `, "'", "`", -1), |
| reason: "batched textual diff desired since bytes looks like textual data", |
| }, { |
| label: label, |
| x: MyComposite{ |
| StringA: strings.TrimPrefix(` |
| Package cmp determines equality of values. |
| |
| This package is intended to be a more powerful and safer alternative to |
| reflect.DeepEqual for comparing whether two values are semantically equal. |
| |
| The primary features of cmp are: |
| |
| • When the default behavior of equality does not suit the needs of the test, |
| custom equality functions can override the equality operation. |
| For example, an equality function may report floats as equal so long as they |
| are within some tolerance of each other. |
| |
| • Types that have an Equal method may use that method to determine equality. |
| This allows package authors to determine the equality operation for the types |
| that they define. |
| |
| • If no custom equality functions are used and no Equal method is defined, |
| equality is determined by recursively comparing the primitive kinds on both |
| values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported |
| fields are not compared by default; they result in panics unless suppressed |
| by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared |
| using the AllowUnexported option. |
| `, "\n"), |
| }, |
| y: MyComposite{ |
| StringA: strings.TrimPrefix(` |
| Package cmp determines equality of value. |
| |
| This package is intended to be a more powerful and safer alternative to |
| reflect.DeepEqual for comparing whether two values are semantically equal. |
| |
| The primary features of cmp are: |
| |
| • When the default behavior of equality does not suit the needs of the test, |
| custom equality functions can override the equality operation. |
| For example, an equality function may report floats as equal so long as they |
| are within some tolerance of each other. |
| |
| • If no custom equality functions are used and no Equal method is defined, |
| equality is determined by recursively comparing the primitive kinds on both |
| values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported |
| fields are not compared by default; they result in panics unless suppressed |
| by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared |
| using the AllowUnexported option.`, "\n"), |
| }, |
| wantDiff: ` |
| cmp_test.MyComposite{ |
| StringA: strings.Join({ |
| - "Package cmp determines equality of values.", |
| + "Package cmp determines equality of value.", |
| "", |
| "This package is intended to be a more powerful and safer alternative to", |
| ... // 6 identical lines |
| "For example, an equality function may report floats as equal so long as they", |
| "are within some tolerance of each other.", |
| - "", |
| - "• Types that have an Equal method may use that method to determine equality.", |
| - "This allows package authors to determine the equality operation for the types", |
| - "that they define.", |
| "", |
| "• If no custom equality functions are used and no Equal method is defined,", |
| ... // 3 identical lines |
| "by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared", |
| "using the AllowUnexported option.", |
| - "", |
| }, "\n"), |
| StringB: "", |
| BytesA: nil, |
| ... // 11 identical fields |
| } |
| `, |
| reason: "batched per-line diff desired since string looks like multi-line textual data", |
| }} |
| } |
| |
| func embeddedTests() []test { |
| const label = "EmbeddedStruct/" |
| |
| privateStruct := *new(ts.ParentStructA).PrivateStruct() |
| |
| createStructA := func(i int) ts.ParentStructA { |
| s := ts.ParentStructA{} |
| s.PrivateStruct().Public = 1 + i |
| s.PrivateStruct().SetPrivate(2 + i) |
| return s |
| } |
| |
| createStructB := func(i int) ts.ParentStructB { |
| s := ts.ParentStructB{} |
| s.PublicStruct.Public = 1 + i |
| s.PublicStruct.SetPrivate(2 + i) |
| return s |
| } |
| |
| createStructC := func(i int) ts.ParentStructC { |
| s := ts.ParentStructC{} |
| s.PrivateStruct().Public = 1 + i |
| s.PrivateStruct().SetPrivate(2 + i) |
| s.Public = 3 + i |
| s.SetPrivate(4 + i) |
| return s |
| } |
| |
| createStructD := func(i int) ts.ParentStructD { |
| s := ts.ParentStructD{} |
| s.PublicStruct.Public = 1 + i |
| s.PublicStruct.SetPrivate(2 + i) |
| s.Public = 3 + i |
| s.SetPrivate(4 + i) |
| return s |
| } |
| |
| createStructE := func(i int) ts.ParentStructE { |
| s := ts.ParentStructE{} |
| s.PrivateStruct().Public = 1 + i |
| s.PrivateStruct().SetPrivate(2 + i) |
| s.PublicStruct.Public = 3 + i |
| s.PublicStruct.SetPrivate(4 + i) |
| return s |
| } |
| |
| createStructF := func(i int) ts.ParentStructF { |
| s := ts.ParentStructF{} |
| s.PrivateStruct().Public = 1 + i |
| s.PrivateStruct().SetPrivate(2 + i) |
| s.PublicStruct.Public = 3 + i |
| s.PublicStruct.SetPrivate(4 + i) |
| s.Public = 5 + i |
| s.SetPrivate(6 + i) |
| return s |
| } |
| |
| createStructG := func(i int) *ts.ParentStructG { |
| s := ts.NewParentStructG() |
| s.PrivateStruct().Public = 1 + i |
| s.PrivateStruct().SetPrivate(2 + i) |
| return s |
| } |
| |
| createStructH := func(i int) *ts.ParentStructH { |
| s := ts.NewParentStructH() |
| s.PublicStruct.Public = 1 + i |
| s.PublicStruct.SetPrivate(2 + i) |
| return s |
| } |
| |
| createStructI := func(i int) *ts.ParentStructI { |
| s := ts.NewParentStructI() |
| s.PrivateStruct().Public = 1 + i |
| s.PrivateStruct().SetPrivate(2 + i) |
| s.PublicStruct.Public = 3 + i |
| s.PublicStruct.SetPrivate(4 + i) |
| return s |
| } |
| |
| createStructJ := func(i int) *ts.ParentStructJ { |
| s := ts.NewParentStructJ() |
| s.PrivateStruct().Public = 1 + i |
| s.PrivateStruct().SetPrivate(2 + i) |
| s.PublicStruct.Public = 3 + i |
| s.PublicStruct.SetPrivate(4 + i) |
| s.Private().Public = 5 + i |
| s.Private().SetPrivate(6 + i) |
| s.Public.Public = 7 + i |
| s.Public.SetPrivate(8 + i) |
| return s |
| } |
| |
| // TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/21122). |
| wantPanicNotGo110 := func(s string) string { |
| if !flags.AtLeastGo110 { |
| return "" |
| } |
| return s |
| } |
| |
| return []test{{ |
| label: label + "ParentStructA", |
| x: ts.ParentStructA{}, |
| y: ts.ParentStructA{}, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructA", |
| x: ts.ParentStructA{}, |
| y: ts.ParentStructA{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructA{}), |
| }, |
| }, { |
| label: label + "ParentStructA", |
| x: createStructA(0), |
| y: createStructA(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructA{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructA", |
| x: createStructA(0), |
| y: createStructA(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructA{}, privateStruct), |
| }, |
| }, { |
| label: label + "ParentStructA", |
| x: createStructA(0), |
| y: createStructA(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructA{}, privateStruct), |
| }, |
| wantDiff: ` |
| teststructs.ParentStructA{ |
| privateStruct: teststructs.privateStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| } |
| `, |
| }, { |
| label: label + "ParentStructB", |
| x: ts.ParentStructB{}, |
| y: ts.ParentStructB{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructB{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructB", |
| x: ts.ParentStructB{}, |
| y: ts.ParentStructB{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructB{}), |
| cmpopts.IgnoreUnexported(ts.PublicStruct{}), |
| }, |
| }, { |
| label: label + "ParentStructB", |
| x: createStructB(0), |
| y: createStructB(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructB{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructB", |
| x: createStructB(0), |
| y: createStructB(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}), |
| }, |
| }, { |
| label: label + "ParentStructB", |
| x: createStructB(0), |
| y: createStructB(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}), |
| }, |
| wantDiff: ` |
| teststructs.ParentStructB{ |
| PublicStruct: teststructs.PublicStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| } |
| `, |
| }, { |
| label: label + "ParentStructC", |
| x: ts.ParentStructC{}, |
| y: ts.ParentStructC{}, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructC", |
| x: ts.ParentStructC{}, |
| y: ts.ParentStructC{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructC{}), |
| }, |
| }, { |
| label: label + "ParentStructC", |
| x: createStructC(0), |
| y: createStructC(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructC{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructC", |
| x: createStructC(0), |
| y: createStructC(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructC{}, privateStruct), |
| }, |
| }, { |
| label: label + "ParentStructC", |
| x: createStructC(0), |
| y: createStructC(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructC{}, privateStruct), |
| }, |
| wantDiff: ` |
| teststructs.ParentStructC{ |
| privateStruct: teststructs.privateStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| - Public: 3, |
| + Public: 4, |
| - private: 4, |
| + private: 5, |
| } |
| `, |
| }, { |
| label: label + "ParentStructD", |
| x: ts.ParentStructD{}, |
| y: ts.ParentStructD{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructD{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructD", |
| x: ts.ParentStructD{}, |
| y: ts.ParentStructD{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructD{}), |
| cmpopts.IgnoreUnexported(ts.PublicStruct{}), |
| }, |
| }, { |
| label: label + "ParentStructD", |
| x: createStructD(0), |
| y: createStructD(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructD{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructD", |
| x: createStructD(0), |
| y: createStructD(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}), |
| }, |
| }, { |
| label: label + "ParentStructD", |
| x: createStructD(0), |
| y: createStructD(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}), |
| }, |
| wantDiff: ` |
| teststructs.ParentStructD{ |
| PublicStruct: teststructs.PublicStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| - Public: 3, |
| + Public: 4, |
| - private: 4, |
| + private: 5, |
| } |
| `, |
| }, { |
| label: label + "ParentStructE", |
| x: ts.ParentStructE{}, |
| y: ts.ParentStructE{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructE{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructE", |
| x: ts.ParentStructE{}, |
| y: ts.ParentStructE{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructE{}), |
| cmpopts.IgnoreUnexported(ts.PublicStruct{}), |
| }, |
| }, { |
| label: label + "ParentStructE", |
| x: createStructE(0), |
| y: createStructE(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructE{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructE", |
| x: createStructE(0), |
| y: createStructE(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructE", |
| x: createStructE(0), |
| y: createStructE(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct), |
| }, |
| }, { |
| label: label + "ParentStructE", |
| x: createStructE(0), |
| y: createStructE(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct), |
| }, |
| wantDiff: ` |
| teststructs.ParentStructE{ |
| privateStruct: teststructs.privateStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| PublicStruct: teststructs.PublicStruct{ |
| - Public: 3, |
| + Public: 4, |
| - private: 4, |
| + private: 5, |
| }, |
| } |
| `, |
| }, { |
| label: label + "ParentStructF", |
| x: ts.ParentStructF{}, |
| y: ts.ParentStructF{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructF{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructF", |
| x: ts.ParentStructF{}, |
| y: ts.ParentStructF{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructF{}), |
| cmpopts.IgnoreUnexported(ts.PublicStruct{}), |
| }, |
| }, { |
| label: label + "ParentStructF", |
| x: createStructF(0), |
| y: createStructF(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructF{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructF", |
| x: createStructF(0), |
| y: createStructF(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructF", |
| x: createStructF(0), |
| y: createStructF(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct), |
| }, |
| }, { |
| label: label + "ParentStructF", |
| x: createStructF(0), |
| y: createStructF(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct), |
| }, |
| wantDiff: ` |
| teststructs.ParentStructF{ |
| privateStruct: teststructs.privateStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| PublicStruct: teststructs.PublicStruct{ |
| - Public: 3, |
| + Public: 4, |
| - private: 4, |
| + private: 5, |
| }, |
| - Public: 5, |
| + Public: 6, |
| - private: 6, |
| + private: 7, |
| } |
| `, |
| }, { |
| label: label + "ParentStructG", |
| x: ts.ParentStructG{}, |
| y: ts.ParentStructG{}, |
| wantPanic: wantPanicNotGo110("cannot handle unexported field"), |
| }, { |
| label: label + "ParentStructG", |
| x: ts.ParentStructG{}, |
| y: ts.ParentStructG{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructG{}), |
| }, |
| }, { |
| label: label + "ParentStructG", |
| x: createStructG(0), |
| y: createStructG(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructG{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructG", |
| x: createStructG(0), |
| y: createStructG(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructG{}, privateStruct), |
| }, |
| }, { |
| label: label + "ParentStructG", |
| x: createStructG(0), |
| y: createStructG(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructG{}, privateStruct), |
| }, |
| wantDiff: ` |
| &teststructs.ParentStructG{ |
| privateStruct: &teststructs.privateStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| } |
| `, |
| }, { |
| label: label + "ParentStructH", |
| x: ts.ParentStructH{}, |
| y: ts.ParentStructH{}, |
| }, { |
| label: label + "ParentStructH", |
| x: createStructH(0), |
| y: createStructH(0), |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructH", |
| x: ts.ParentStructH{}, |
| y: ts.ParentStructH{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructH{}), |
| }, |
| }, { |
| label: label + "ParentStructH", |
| x: createStructH(0), |
| y: createStructH(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructH{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructH", |
| x: createStructH(0), |
| y: createStructH(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}), |
| }, |
| }, { |
| label: label + "ParentStructH", |
| x: createStructH(0), |
| y: createStructH(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}), |
| }, |
| wantDiff: ` |
| &teststructs.ParentStructH{ |
| PublicStruct: &teststructs.PublicStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| } |
| `, |
| }, { |
| label: label + "ParentStructI", |
| x: ts.ParentStructI{}, |
| y: ts.ParentStructI{}, |
| wantPanic: wantPanicNotGo110("cannot handle unexported field"), |
| }, { |
| label: label + "ParentStructI", |
| x: ts.ParentStructI{}, |
| y: ts.ParentStructI{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructI{}), |
| }, |
| }, { |
| label: label + "ParentStructI", |
| x: createStructI(0), |
| y: createStructI(0), |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructI{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructI", |
| x: createStructI(0), |
| y: createStructI(0), |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}), |
| }, |
| }, { |
| label: label + "ParentStructI", |
| x: createStructI(0), |
| y: createStructI(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructI{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructI", |
| x: createStructI(0), |
| y: createStructI(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct), |
| }, |
| }, { |
| label: label + "ParentStructI", |
| x: createStructI(0), |
| y: createStructI(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct), |
| }, |
| wantDiff: ` |
| &teststructs.ParentStructI{ |
| privateStruct: &teststructs.privateStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| PublicStruct: &teststructs.PublicStruct{ |
| - Public: 3, |
| + Public: 4, |
| - private: 4, |
| + private: 5, |
| }, |
| } |
| `, |
| }, { |
| label: label + "ParentStructJ", |
| x: ts.ParentStructJ{}, |
| y: ts.ParentStructJ{}, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructJ", |
| x: ts.ParentStructJ{}, |
| y: ts.ParentStructJ{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructJ{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructJ", |
| x: ts.ParentStructJ{}, |
| y: ts.ParentStructJ{}, |
| opts: []cmp.Option{ |
| cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}), |
| }, |
| }, { |
| label: label + "ParentStructJ", |
| x: createStructJ(0), |
| y: createStructJ(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}), |
| }, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label + "ParentStructJ", |
| x: createStructJ(0), |
| y: createStructJ(0), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct), |
| }, |
| }, { |
| label: label + "ParentStructJ", |
| x: createStructJ(0), |
| y: createStructJ(1), |
| opts: []cmp.Option{ |
| cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct), |
| }, |
| wantDiff: ` |
| &teststructs.ParentStructJ{ |
| privateStruct: &teststructs.privateStruct{ |
| - Public: 1, |
| + Public: 2, |
| - private: 2, |
| + private: 3, |
| }, |
| PublicStruct: &teststructs.PublicStruct{ |
| - Public: 3, |
| + Public: 4, |
| - private: 4, |
| + private: 5, |
| }, |
| Public: teststructs.PublicStruct{ |
| - Public: 7, |
| + Public: 8, |
| - private: 8, |
| + private: 9, |
| }, |
| private: teststructs.privateStruct{ |
| - Public: 5, |
| + Public: 6, |
| - private: 6, |
| + private: 7, |
| }, |
| } |
| `, |
| }} |
| } |
| |
| func methodTests() []test { |
| const label = "EqualMethod/" |
| |
| // A common mistake that the Equal method is on a pointer receiver, |
| // but only a non-pointer value is present in the struct. |
| // A transform can be used to forcibly reference the value. |
| derefTransform := cmp.FilterPath(func(p cmp.Path) bool { |
| if len(p) == 0 { |
| return false |
| } |
| t := p[len(p)-1].Type() |
| if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr { |
| return false |
| } |
| if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok { |
| tf := m.Func.Type() |
| return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 && |
| tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true) |
| } |
| return false |
| }, cmp.Transformer("Ref", func(x interface{}) interface{} { |
| v := reflect.ValueOf(x) |
| vp := reflect.New(v.Type()) |
| vp.Elem().Set(v) |
| return vp.Interface() |
| })) |
| |
| // For each of these types, there is an Equal method defined, which always |
| // returns true, while the underlying data are fundamentally different. |
| // Since the method should be called, these are expected to be equal. |
| return []test{{ |
| label: label + "StructA", |
| x: ts.StructA{X: "NotEqual"}, |
| y: ts.StructA{X: "not_equal"}, |
| }, { |
| label: label + "StructA", |
| x: &ts.StructA{X: "NotEqual"}, |
| y: &ts.StructA{X: "not_equal"}, |
| }, { |
| label: label + "StructB", |
| x: ts.StructB{X: "NotEqual"}, |
| y: ts.StructB{X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructB{ |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructB", |
| x: ts.StructB{X: "NotEqual"}, |
| y: ts.StructB{X: "not_equal"}, |
| opts: []cmp.Option{derefTransform}, |
| }, { |
| label: label + "StructB", |
| x: &ts.StructB{X: "NotEqual"}, |
| y: &ts.StructB{X: "not_equal"}, |
| }, { |
| label: label + "StructC", |
| x: ts.StructC{X: "NotEqual"}, |
| y: ts.StructC{X: "not_equal"}, |
| }, { |
| label: label + "StructC", |
| x: &ts.StructC{X: "NotEqual"}, |
| y: &ts.StructC{X: "not_equal"}, |
| }, { |
| label: label + "StructD", |
| x: ts.StructD{X: "NotEqual"}, |
| y: ts.StructD{X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructD{ |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructD", |
| x: ts.StructD{X: "NotEqual"}, |
| y: ts.StructD{X: "not_equal"}, |
| opts: []cmp.Option{derefTransform}, |
| }, { |
| label: label + "StructD", |
| x: &ts.StructD{X: "NotEqual"}, |
| y: &ts.StructD{X: "not_equal"}, |
| }, { |
| label: label + "StructE", |
| x: ts.StructE{X: "NotEqual"}, |
| y: ts.StructE{X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructE{ |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructE", |
| x: ts.StructE{X: "NotEqual"}, |
| y: ts.StructE{X: "not_equal"}, |
| opts: []cmp.Option{derefTransform}, |
| }, { |
| label: label + "StructE", |
| x: &ts.StructE{X: "NotEqual"}, |
| y: &ts.StructE{X: "not_equal"}, |
| }, { |
| label: label + "StructF", |
| x: ts.StructF{X: "NotEqual"}, |
| y: ts.StructF{X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructF{ |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructF", |
| x: &ts.StructF{X: "NotEqual"}, |
| y: &ts.StructF{X: "not_equal"}, |
| }, { |
| label: label + "StructA1", |
| x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"}, |
| y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"}, |
| }, { |
| label: label + "StructA1", |
| x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructA1{ |
| StructA: teststructs.StructA{X: "NotEqual"}, |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructA1", |
| x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"}, |
| y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"}, |
| }, { |
| label: label + "StructA1", |
| x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"}, |
| wantDiff: ` |
| &teststructs.StructA1{ |
| StructA: teststructs.StructA{X: "NotEqual"}, |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructB1", |
| x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"}, |
| y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"}, |
| opts: []cmp.Option{derefTransform}, |
| }, { |
| label: label + "StructB1", |
| x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"}, |
| opts: []cmp.Option{derefTransform}, |
| wantDiff: ` |
| teststructs.StructB1{ |
| StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})), |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructB1", |
| x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"}, |
| y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"}, |
| opts: []cmp.Option{derefTransform}, |
| }, { |
| label: label + "StructB1", |
| x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"}, |
| opts: []cmp.Option{derefTransform}, |
| wantDiff: ` |
| &teststructs.StructB1{ |
| StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})), |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructC1", |
| x: ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructC1", |
| x: &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructD1", |
| x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructD1{ |
| - StructD: teststructs.StructD{X: "NotEqual"}, |
| + StructD: teststructs.StructD{X: "not_equal"}, |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructD1", |
| x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"}, |
| opts: []cmp.Option{derefTransform}, |
| }, { |
| label: label + "StructD1", |
| x: &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructE1", |
| x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructE1{ |
| - StructE: teststructs.StructE{X: "NotEqual"}, |
| + StructE: teststructs.StructE{X: "not_equal"}, |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructE1", |
| x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"}, |
| opts: []cmp.Option{derefTransform}, |
| }, { |
| label: label + "StructE1", |
| x: &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructF1", |
| x: ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructF1{ |
| - StructF: teststructs.StructF{X: "NotEqual"}, |
| + StructF: teststructs.StructF{X: "not_equal"}, |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructF1", |
| x: &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructA2", |
| x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"}, |
| y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"}, |
| }, { |
| label: label + "StructA2", |
| x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructA2{ |
| StructA: &teststructs.StructA{X: "NotEqual"}, |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructA2", |
| x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"}, |
| y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"}, |
| }, { |
| label: label + "StructA2", |
| x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"}, |
| wantDiff: ` |
| &teststructs.StructA2{ |
| StructA: &teststructs.StructA{X: "NotEqual"}, |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructB2", |
| x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"}, |
| y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"}, |
| }, { |
| label: label + "StructB2", |
| x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructB2{ |
| StructB: &teststructs.StructB{X: "NotEqual"}, |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructB2", |
| x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"}, |
| y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"}, |
| }, { |
| label: label + "StructB2", |
| x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"}, |
| wantDiff: ` |
| &teststructs.StructB2{ |
| StructB: &teststructs.StructB{X: "NotEqual"}, |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "StructC2", |
| x: ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructC2", |
| x: &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructD2", |
| x: ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructD2", |
| x: &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructE2", |
| x: ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructE2", |
| x: &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructF2", |
| x: ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"}, |
| y: ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructF2", |
| x: &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"}, |
| y: &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"}, |
| }, { |
| label: label + "StructNo", |
| x: ts.StructNo{X: "NotEqual"}, |
| y: ts.StructNo{X: "not_equal"}, |
| wantDiff: ` |
| teststructs.StructNo{ |
| - X: "NotEqual", |
| + X: "not_equal", |
| } |
| `, |
| }, { |
| label: label + "AssignA", |
| x: ts.AssignA(func() int { return 0 }), |
| y: ts.AssignA(func() int { return 1 }), |
| }, { |
| label: label + "AssignB", |
| x: ts.AssignB(struct{ A int }{0}), |
| y: ts.AssignB(struct{ A int }{1}), |
| }, { |
| label: label + "AssignC", |
| x: ts.AssignC(make(chan bool)), |
| y: ts.AssignC(make(chan bool)), |
| }, { |
| label: label + "AssignD", |
| x: ts.AssignD(make(chan bool)), |
| y: ts.AssignD(make(chan bool)), |
| }} |
| } |
| |
| func project1Tests() []test { |
| const label = "Project1" |
| |
| ignoreUnexported := cmpopts.IgnoreUnexported( |
| ts.EagleImmutable{}, |
| ts.DreamerImmutable{}, |
| ts.SlapImmutable{}, |
| ts.GoatImmutable{}, |
| ts.DonkeyImmutable{}, |
| ts.LoveRadius{}, |
| ts.SummerLove{}, |
| ts.SummerLoveSummary{}, |
| ) |
| |
| createEagle := func() ts.Eagle { |
| return ts.Eagle{ |
| Name: "eagle", |
| Hounds: []string{"buford", "tannen"}, |
| Desc: "some description", |
| Dreamers: []ts.Dreamer{{}, { |
| Name: "dreamer2", |
| Animal: []interface{}{ |
| ts.Goat{ |
| Target: "corporation", |
| Immutable: &ts.GoatImmutable{ |
| ID: "southbay", |
| State: (*pb.Goat_States)(intPtr(5)), |
| Started: now, |
| }, |
| }, |
| ts.Donkey{}, |
| }, |
| Amoeba: 53, |
| }}, |
| Slaps: []ts.Slap{{ |
| Name: "slapID", |
| Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, |
| Immutable: &ts.SlapImmutable{ |
| ID: "immutableSlap", |
| MildSlap: true, |
| Started: now, |
| LoveRadius: &ts.LoveRadius{ |
| Summer: &ts.SummerLove{ |
| Summary: &ts.SummerLoveSummary{ |
| Devices: []string{"foo", "bar", "baz"}, |
| ChangeType: []pb.SummerType{1, 2, 3}, |
| }, |
| }, |
| }, |
| }, |
| }}, |
| Immutable: &ts.EagleImmutable{ |
| ID: "eagleID", |
| Birthday: now, |
| MissingCall: (*pb.Eagle_MissingCalls)(intPtr(55)), |
| }, |
| } |
| } |
| |
| return []test{{ |
| label: label, |
| x: ts.Eagle{Slaps: []ts.Slap{{ |
| Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, |
| }}}, |
| y: ts.Eagle{Slaps: []ts.Slap{{ |
| Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, |
| }}}, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: ts.Eagle{Slaps: []ts.Slap{{ |
| Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, |
| }}}, |
| y: ts.Eagle{Slaps: []ts.Slap{{ |
| Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, |
| }}}, |
| opts: []cmp.Option{cmp.Comparer(pb.Equal)}, |
| }, { |
| label: label, |
| x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, { |
| Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}, |
| }}}, |
| y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, { |
| Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata2"}}, |
| }}}, |
| opts: []cmp.Option{cmp.Comparer(pb.Equal)}, |
| wantDiff: ` |
| teststructs.Eagle{ |
| ... // 4 identical fields |
| Dreamers: nil, |
| Prong: 0, |
| Slaps: []teststructs.Slap{ |
| ... // 2 identical elements |
| {}, |
| {}, |
| { |
| Name: "", |
| Desc: "", |
| DescLong: "", |
| - Args: s"metadata", |
| + Args: s"metadata2", |
| Tense: 0, |
| Interval: 0, |
| ... // 3 identical fields |
| }, |
| }, |
| StateGoverner: "", |
| PrankRating: "", |
| ... // 2 identical fields |
| } |
| `, |
| }, { |
| label: label, |
| x: createEagle(), |
| y: createEagle(), |
| opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)}, |
| }, { |
| label: label, |
| x: func() ts.Eagle { |
| eg := createEagle() |
| eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2" |
| eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(intPtr(6)) |
| eg.Slaps[0].Immutable.MildSlap = false |
| return eg |
| }(), |
| y: func() ts.Eagle { |
| eg := createEagle() |
| devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices |
| eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1] |
| return eg |
| }(), |
| opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)}, |
| wantDiff: ` |
| teststructs.Eagle{ |
| ... // 2 identical fields |
| Desc: "some description", |
| DescLong: "", |
| Dreamers: []teststructs.Dreamer{ |
| {}, |
| { |
| ... // 4 identical fields |
| ContSlaps: nil, |
| ContSlapsInterval: 0, |
| Animal: []interface{}{ |
| teststructs.Goat{ |
| Target: "corporation", |
| Slaps: nil, |
| FunnyPrank: "", |
| Immutable: &teststructs.GoatImmutable{ |
| - ID: "southbay2", |
| + ID: "southbay", |
| - State: &6, |
| + State: &5, |
| Started: s"2009-11-10 23:00:00 +0000 UTC", |
| Stopped: s"0001-01-01 00:00:00 +0000 UTC", |
| ... // 1 ignored and 1 identical fields |
| }, |
| }, |
| teststructs.Donkey{}, |
| }, |
| Ornamental: false, |
| Amoeba: 53, |
| ... // 5 identical fields |
| }, |
| }, |
| Prong: 0, |
| Slaps: []teststructs.Slap{ |
| { |
| ... // 6 identical fields |
| Homeland: 0x00, |
| FunnyPrank: "", |
| Immutable: &teststructs.SlapImmutable{ |
| ID: "immutableSlap", |
| Out: nil, |
| - MildSlap: false, |
| + MildSlap: true, |
| PrettyPrint: "", |
| State: nil, |
| Started: s"2009-11-10 23:00:00 +0000 UTC", |
| Stopped: s"0001-01-01 00:00:00 +0000 UTC", |
| LastUpdate: s"0001-01-01 00:00:00 +0000 UTC", |
| LoveRadius: &teststructs.LoveRadius{ |
| Summer: &teststructs.SummerLove{ |
| Summary: &teststructs.SummerLoveSummary{ |
| Devices: []string{ |
| "foo", |
| - "bar", |
| - "baz", |
| }, |
| ChangeType: []testprotos.SummerType{1, 2, 3}, |
| ... // 1 ignored field |
| }, |
| ... // 1 ignored field |
| }, |
| ... // 1 ignored field |
| }, |
| ... // 1 ignored field |
| }, |
| }, |
| }, |
| StateGoverner: "", |
| PrankRating: "", |
| ... // 2 identical fields |
| } |
| `, |
| }} |
| } |
| |
| type germSorter []*pb.Germ |
| |
| func (gs germSorter) Len() int { return len(gs) } |
| func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() } |
| func (gs germSorter) Swap(i, j int) { gs[i], gs[j] = gs[j], gs[i] } |
| |
| func project2Tests() []test { |
| const label = "Project2" |
| |
| sortGerms := cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ { |
| out := append([]*pb.Germ(nil), in...) // Make copy |
| sort.Sort(germSorter(out)) |
| return out |
| }) |
| |
| equalDish := cmp.Comparer(func(x, y *ts.Dish) bool { |
| if x == nil || y == nil { |
| return x == nil && y == nil |
| } |
| px, err1 := x.Proto() |
| py, err2 := y.Proto() |
| if err1 != nil || err2 != nil { |
| return err1 == err2 |
| } |
| return pb.Equal(px, py) |
| }) |
| |
| createBatch := func() ts.GermBatch { |
| return ts.GermBatch{ |
| DirtyGerms: map[int32][]*pb.Germ{ |
| 17: { |
| {Stringer: pb.Stringer{X: "germ1"}}, |
| }, |
| 18: { |
| {Stringer: pb.Stringer{X: "germ2"}}, |
| {Stringer: pb.Stringer{X: "germ3"}}, |
| {Stringer: pb.Stringer{X: "germ4"}}, |
| }, |
| }, |
| GermMap: map[int32]*pb.Germ{ |
| 13: {Stringer: pb.Stringer{X: "germ13"}}, |
| 21: {Stringer: pb.Stringer{X: "germ21"}}, |
| }, |
| DishMap: map[int32]*ts.Dish{ |
| 0: ts.CreateDish(nil, io.EOF), |
| 1: ts.CreateDish(nil, io.ErrUnexpectedEOF), |
| 2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{X: "dish"}}, nil), |
| }, |
| HasPreviousResult: true, |
| DirtyID: 10, |
| GermStrain: 421, |
| InfectedAt: now, |
| } |
| } |
| |
| return []test{{ |
| label: label, |
| x: createBatch(), |
| y: createBatch(), |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: createBatch(), |
| y: createBatch(), |
| opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish}, |
| }, { |
| label: label, |
| x: createBatch(), |
| y: func() ts.GermBatch { |
| gb := createBatch() |
| s := gb.DirtyGerms[18] |
| s[0], s[1], s[2] = s[1], s[2], s[0] |
| return gb |
| }(), |
| opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish}, |
| wantDiff: ` |
| teststructs.GermBatch{ |
| DirtyGerms: map[int32][]*testprotos.Germ{ |
| 17: {s"germ1"}, |
| 18: { |
| - s"germ2", |
| s"germ3", |
| s"germ4", |
| + s"germ2", |
| }, |
| }, |
| CleanGerms: nil, |
| GermMap: map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"}, |
| ... // 7 identical fields |
| } |
| `, |
| }, { |
| label: label, |
| x: createBatch(), |
| y: func() ts.GermBatch { |
| gb := createBatch() |
| s := gb.DirtyGerms[18] |
| s[0], s[1], s[2] = s[1], s[2], s[0] |
| return gb |
| }(), |
| opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish}, |
| }, { |
| label: label, |
| x: func() ts.GermBatch { |
| gb := createBatch() |
| delete(gb.DirtyGerms, 17) |
| gb.DishMap[1] = nil |
| return gb |
| }(), |
| y: func() ts.GermBatch { |
| gb := createBatch() |
| gb.DirtyGerms[18] = gb.DirtyGerms[18][:2] |
| gb.GermStrain = 22 |
| return gb |
| }(), |
| opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish}, |
| wantDiff: ` |
| teststructs.GermBatch{ |
| DirtyGerms: map[int32][]*testprotos.Germ{ |
| + 17: {s"germ1"}, |
| 18: Inverse(Sort, []*testprotos.Germ{ |
| s"germ2", |
| s"germ3", |
| - s"germ4", |
| }), |
| }, |
| CleanGerms: nil, |
| GermMap: map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"}, |
| DishMap: map[int32]*teststructs.Dish{ |
| 0: &{err: &errors.errorString{s: "EOF"}}, |
| - 1: nil, |
| + 1: &{err: &errors.errorString{s: "unexpected EOF"}}, |
| 2: &{pb: &testprotos.Dish{Stringer: testprotos.Stringer{X: "dish"}}}, |
| }, |
| HasPreviousResult: true, |
| DirtyID: 10, |
| CleanID: 0, |
| - GermStrain: 421, |
| + GermStrain: 22, |
| TotalDirtyGerms: 0, |
| InfectedAt: s"2009-11-10 23:00:00 +0000 UTC", |
| } |
| `, |
| }} |
| } |
| |
| func project3Tests() []test { |
| const label = "Project3" |
| |
| allowVisibility := cmp.AllowUnexported(ts.Dirt{}) |
| |
| ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{}) |
| |
| transformProtos := cmp.Transformer("λ", func(x pb.Dirt) *pb.Dirt { |
| return &x |
| }) |
| |
| equalTable := cmp.Comparer(func(x, y ts.Table) bool { |
| tx, ok1 := x.(*ts.MockTable) |
| ty, ok2 := y.(*ts.MockTable) |
| if !ok1 || !ok2 { |
| panic("table type must be MockTable") |
| } |
| return cmp.Equal(tx.State(), ty.State()) |
| }) |
| |
| createDirt := func() (d ts.Dirt) { |
| d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"})) |
| d.SetTimestamp(12345) |
| d.Discord = 554 |
| d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "proto"}} |
| d.SetWizard(map[string]*pb.Wizard{ |
| "harry": {Stringer: pb.Stringer{X: "potter"}}, |
| "albus": {Stringer: pb.Stringer{X: "dumbledore"}}, |
| }) |
| d.SetLastTime(54321) |
| return d |
| } |
| |
| return []test{{ |
| label: label, |
| x: createDirt(), |
| y: createDirt(), |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: createDirt(), |
| y: createDirt(), |
| opts: []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: createDirt(), |
| y: createDirt(), |
| opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, |
| }, { |
| label: label, |
| x: func() ts.Dirt { |
| d := createDirt() |
| d.SetTable(ts.CreateMockTable([]string{"a", "c"})) |
| d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "blah"}} |
| return d |
| }(), |
| y: func() ts.Dirt { |
| d := createDirt() |
| d.Discord = 500 |
| d.SetWizard(map[string]*pb.Wizard{ |
| "harry": {Stringer: pb.Stringer{X: "otter"}}, |
| }) |
| return d |
| }(), |
| opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, |
| wantDiff: ` |
| teststructs.Dirt{ |
| - table: &teststructs.MockTable{state: []string{"a", "c"}}, |
| + table: &teststructs.MockTable{state: []string{"a", "b", "c"}}, |
| ts: 12345, |
| - Discord: 554, |
| + Discord: 500, |
| - Proto: testprotos.Dirt(Inverse(λ, s"blah")), |
| + Proto: testprotos.Dirt(Inverse(λ, s"proto")), |
| wizard: map[string]*testprotos.Wizard{ |
| - "albus": s"dumbledore", |
| - "harry": s"potter", |
| + "harry": s"otter", |
| }, |
| sadistic: nil, |
| lastTime: 54321, |
| ... // 1 ignored field |
| } |
| `, |
| }} |
| } |
| |
| func project4Tests() []test { |
| const label = "Project4" |
| |
| allowVisibility := cmp.AllowUnexported( |
| ts.Cartel{}, |
| ts.Headquarter{}, |
| ts.Poison{}, |
| ) |
| |
| transformProtos := cmp.Transformer("λ", func(x pb.Restrictions) *pb.Restrictions { |
| return &x |
| }) |
| |
| createCartel := func() ts.Cartel { |
| var p ts.Poison |
| p.SetPoisonType(5) |
| p.SetExpiration(now) |
| p.SetManufacturer("acme") |
| |
| var hq ts.Headquarter |
| hq.SetID(5) |
| hq.SetLocation("moon") |
| hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"}) |
| hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{X: "metadata"}}) |
| hq.SetPublicMessage([]byte{1, 2, 3, 4, 5}) |
| hq.SetHorseBack("abcdef") |
| hq.SetStatus(44) |
| |
| var c ts.Cartel |
| c.Headquarter = hq |
| c.SetSource("mars") |
| c.SetCreationTime(now) |
| c.SetBoss("al capone") |
| c.SetPoisons([]*ts.Poison{&p}) |
| |
| return c |
| } |
| |
| return []test{{ |
| label: label, |
| x: createCartel(), |
| y: createCartel(), |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: createCartel(), |
| y: createCartel(), |
| opts: []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)}, |
| wantPanic: "cannot handle unexported field", |
| }, { |
| label: label, |
| x: createCartel(), |
| y: createCartel(), |
| opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)}, |
| }, { |
| label: label, |
| x: func() ts.Cartel { |
| d := createCartel() |
| var p1, p2 ts.Poison |
| p1.SetPoisonType(1) |
| p1.SetExpiration(now) |
| p1.SetManufacturer("acme") |
| p2.SetPoisonType(2) |
| p2.SetManufacturer("acme2") |
| d.SetPoisons([]*ts.Poison{&p1, &p2}) |
| return d |
| }(), |
| y: func() ts.Cartel { |
| d := createCartel() |
| d.SetSubDivisions([]string{"bravo", "charlie"}) |
| d.SetPublicMessage([]byte{1, 2, 4, 3, 5}) |
| return d |
| }(), |
| opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)}, |
| wantDiff: ` |
| teststructs.Cartel{ |
| Headquarter: teststructs.Headquarter{ |
| id: 0x05, |
| location: "moon", |
| subDivisions: []string{ |
| - "alpha", |
| "bravo", |
| "charlie", |
| }, |
| incorporatedDate: s"0001-01-01 00:00:00 +0000 UTC", |
| metaData: s"metadata", |
| privateMessage: nil, |
| publicMessage: []uint8{ |
| 0x01, |
| 0x02, |
| - 0x03, |
| + 0x04, |
| - 0x04, |
| + 0x03, |
| 0x05, |
| }, |
| horseBack: "abcdef", |
| rattle: "", |
| ... // 5 identical fields |
| }, |
| source: "mars", |
| creationDate: s"0001-01-01 00:00:00 +0000 UTC", |
| boss: "al capone", |
| lastCrimeDate: s"0001-01-01 00:00:00 +0000 UTC", |
| poisons: []*teststructs.Poison{ |
| &{ |
| - poisonType: 1, |
| + poisonType: 5, |
| expiration: s"2009-11-10 23:00:00 +0000 UTC", |
| manufacturer: "acme", |
| potency: 0, |
| }, |
| - &{poisonType: 2, manufacturer: "acme2"}, |
| }, |
| } |
| `, |
| }} |
| } |
| |
| // BenchmarkBytes benchmarks the performance of performing Equal or Diff on |
| // large slices of bytes. |
| func BenchmarkBytes(b *testing.B) { |
| // Create a list of PathFilters that never apply, but are evaluated. |
| const maxFilters = 5 |
| var filters cmp.Options |
| errorIface := reflect.TypeOf((*error)(nil)).Elem() |
| for i := 0; i <= maxFilters; i++ { |
| filters = append(filters, cmp.FilterPath(func(p cmp.Path) bool { |
| return p.Last().Type().AssignableTo(errorIface) // Never true |
| }, cmp.Ignore())) |
| } |
| |
| type benchSize struct { |
| label string |
| size int64 |
| } |
| for _, ts := range []benchSize{ |
| {"4KiB", 1 << 12}, |
| {"64KiB", 1 << 16}, |
| {"1MiB", 1 << 20}, |
| {"16MiB", 1 << 24}, |
| } { |
| bx := append(append(make([]byte, ts.size/2), 'x'), make([]byte, ts.size/2)...) |
| by := append(append(make([]byte, ts.size/2), 'y'), make([]byte, ts.size/2)...) |
| b.Run(ts.label, func(b *testing.B) { |
| // Iteratively add more filters that never apply, but are evaluated |
| // to measure the cost of simply evaluating each filter. |
| for i := 0; i <= maxFilters; i++ { |
| b.Run(fmt.Sprintf("EqualFilter%d", i), func(b *testing.B) { |
| b.ReportAllocs() |
| b.SetBytes(2 * ts.size) |
| for j := 0; j < b.N; j++ { |
| cmp.Equal(bx, by, filters[:i]...) |
| } |
| }) |
| } |
| for i := 0; i <= maxFilters; i++ { |
| b.Run(fmt.Sprintf("DiffFilter%d", i), func(b *testing.B) { |
| b.ReportAllocs() |
| b.SetBytes(2 * ts.size) |
| for j := 0; j < b.N; j++ { |
| cmp.Diff(bx, by, filters[:i]...) |
| } |
| }) |
| } |
| }) |
| } |
| } |