pax_global_header00006660000000000000000000000064135221133340014507gustar00rootroot0000000000000052 comment=2d0692c2e9617365a95b295612ac0d4415ba4627 go-cmp-0.3.1/000077500000000000000000000000001352211333400126725ustar00rootroot00000000000000go-cmp-0.3.1/.travis.yml000066400000000000000000000010141352211333400147770ustar00rootroot00000000000000sudo: false language: go matrix: include: - go: 1.8.x script: - go test -v -race ./... - go: 1.9.x script: - go test -v -race ./... - go: 1.10.x script: - go test -v -race ./... - go: 1.11.x script: - go test -v -race ./... - go: 1.12.x script: - diff -u <(echo -n) <(gofmt -d .) - go test -v -race ./... - go: master script: - go test -v -race ./... allow_failures: - go: master fast_finish: true go-cmp-0.3.1/CONTRIBUTING.md000066400000000000000000000017111352211333400151230ustar00rootroot00000000000000# How to Contribute We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. ## Contributor License Agreement Contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution, this simply gives us permission to use and redistribute your contributions as part of the project. Head over to to see your current agreements on file or to sign a new one. You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. ## Code reviews All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. go-cmp-0.3.1/LICENSE000066400000000000000000000027071352211333400137050ustar00rootroot00000000000000Copyright (c) 2017 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. go-cmp-0.3.1/README.md000066400000000000000000000031501352211333400141500ustar00rootroot00000000000000# Package for equality of Go values [![GoDoc](https://godoc.org/github.com/google/go-cmp/cmp?status.svg)][godoc] [![Build Status](https://travis-ci.org/google/go-cmp.svg?branch=master)][travis] 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. See the [GoDoc documentation][godoc] for more information. This is not an official Google product. [godoc]: https://godoc.org/github.com/google/go-cmp/cmp [travis]: https://travis-ci.org/google/go-cmp ## Install ``` go get -u github.com/google/go-cmp/cmp ``` ## License BSD - See [LICENSE][license] file [license]: https://github.com/google/go-cmp/blob/master/LICENSE go-cmp-0.3.1/cmp/000077500000000000000000000000001352211333400134515ustar00rootroot00000000000000go-cmp-0.3.1/cmp/cmpopts/000077500000000000000000000000001352211333400151365ustar00rootroot00000000000000go-cmp-0.3.1/cmp/cmpopts/equate.go000066400000000000000000000060131352211333400167510ustar00rootroot00000000000000// 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 cmpopts provides common options for the cmp package. package cmpopts import ( "math" "reflect" "github.com/google/go-cmp/cmp" ) func equateAlways(_, _ interface{}) bool { return true } // EquateEmpty returns a Comparer option that determines all maps and slices // with a length of zero to be equal, regardless of whether they are nil. // // EquateEmpty can be used in conjunction with SortSlices and SortMaps. func EquateEmpty() cmp.Option { return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways)) } func isEmpty(x, y interface{}) bool { vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) return (x != nil && y != nil && vx.Type() == vy.Type()) && (vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) && (vx.Len() == 0 && vy.Len() == 0) } // EquateApprox returns a Comparer option that determines float32 or float64 // values to be equal if they are within a relative fraction or absolute margin. // This option is not used when either x or y is NaN or infinite. // // The fraction determines that the difference of two values must be within the // smaller fraction of the two values, while the margin determines that the two // values must be within some absolute margin. // To express only a fraction or only a margin, use 0 for the other parameter. // The fraction and margin must be non-negative. // // The mathematical expression used is equivalent to: // |x-y| ≤ max(fraction*min(|x|, |y|), margin) // // EquateApprox can be used in conjunction with EquateNaNs. func EquateApprox(fraction, margin float64) cmp.Option { if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) { panic("margin or fraction must be a non-negative number") } a := approximator{fraction, margin} return cmp.Options{ cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)), cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)), } } type approximator struct{ frac, marg float64 } func areRealF64s(x, y float64) bool { return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0) } func areRealF32s(x, y float32) bool { return areRealF64s(float64(x), float64(y)) } func (a approximator) compareF64(x, y float64) bool { relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y)) return math.Abs(x-y) <= math.Max(a.marg, relMarg) } func (a approximator) compareF32(x, y float32) bool { return a.compareF64(float64(x), float64(y)) } // EquateNaNs returns a Comparer option that determines float32 and float64 // NaN values to be equal. // // EquateNaNs can be used in conjunction with EquateApprox. func EquateNaNs() cmp.Option { return cmp.Options{ cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)), cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)), } } func areNaNsF64s(x, y float64) bool { return math.IsNaN(x) && math.IsNaN(y) } func areNaNsF32s(x, y float32) bool { return areNaNsF64s(float64(x), float64(y)) } go-cmp-0.3.1/cmp/cmpopts/ignore.go000066400000000000000000000144601352211333400167550ustar00rootroot00000000000000// 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 cmpopts import ( "fmt" "reflect" "unicode" "unicode/utf8" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/internal/function" ) // IgnoreFields returns an Option that ignores exported fields of the // given names on a single struct type. // The struct type is specified by passing in a value of that type. // // The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a // specific sub-field that is embedded or nested within the parent struct. // // This does not handle unexported fields; use IgnoreUnexported instead. func IgnoreFields(typ interface{}, names ...string) cmp.Option { sf := newStructFilter(typ, names...) return cmp.FilterPath(sf.filter, cmp.Ignore()) } // IgnoreTypes returns an Option that ignores all values assignable to // certain types, which are specified by passing in a value of each type. func IgnoreTypes(typs ...interface{}) cmp.Option { tf := newTypeFilter(typs...) return cmp.FilterPath(tf.filter, cmp.Ignore()) } type typeFilter []reflect.Type func newTypeFilter(typs ...interface{}) (tf typeFilter) { for _, typ := range typs { t := reflect.TypeOf(typ) if t == nil { // This occurs if someone tries to pass in sync.Locker(nil) panic("cannot determine type; consider using IgnoreInterfaces") } tf = append(tf, t) } return tf } func (tf typeFilter) filter(p cmp.Path) bool { if len(p) < 1 { return false } t := p.Last().Type() for _, ti := range tf { if t.AssignableTo(ti) { return true } } return false } // IgnoreInterfaces returns an Option that ignores all values or references of // values assignable to certain interface types. These interfaces are specified // by passing in an anonymous struct with the interface types embedded in it. // For example, to ignore sync.Locker, pass in struct{sync.Locker}{}. func IgnoreInterfaces(ifaces interface{}) cmp.Option { tf := newIfaceFilter(ifaces) return cmp.FilterPath(tf.filter, cmp.Ignore()) } type ifaceFilter []reflect.Type func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) { t := reflect.TypeOf(ifaces) if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct { panic("input must be an anonymous struct") } for i := 0; i < t.NumField(); i++ { fi := t.Field(i) switch { case !fi.Anonymous: panic("struct cannot have named fields") case fi.Type.Kind() != reflect.Interface: panic("embedded field must be an interface type") case fi.Type.NumMethod() == 0: // This matches everything; why would you ever want this? panic("cannot ignore empty interface") default: tf = append(tf, fi.Type) } } return tf } func (tf ifaceFilter) filter(p cmp.Path) bool { if len(p) < 1 { return false } t := p.Last().Type() for _, ti := range tf { if t.AssignableTo(ti) { return true } if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) { return true } } return false } // IgnoreUnexported returns an Option that only ignores the immediate unexported // fields of a struct, including anonymous fields of unexported types. // In particular, unexported fields within the struct's exported fields // of struct types, including anonymous fields, will not be ignored unless the // type of the field itself is also passed to IgnoreUnexported. // // Avoid ignoring unexported fields of a type which you do not control (i.e. a // type from another repository), as changes to the implementation of such types // may change how the comparison behaves. Prefer a custom Comparer instead. func IgnoreUnexported(typs ...interface{}) cmp.Option { ux := newUnexportedFilter(typs...) return cmp.FilterPath(ux.filter, cmp.Ignore()) } type unexportedFilter struct{ m map[reflect.Type]bool } func newUnexportedFilter(typs ...interface{}) unexportedFilter { ux := unexportedFilter{m: make(map[reflect.Type]bool)} for _, typ := range typs { t := reflect.TypeOf(typ) if t == nil || t.Kind() != reflect.Struct { panic(fmt.Sprintf("invalid struct type: %T", typ)) } ux.m[t] = true } return ux } func (xf unexportedFilter) filter(p cmp.Path) bool { sf, ok := p.Index(-1).(cmp.StructField) if !ok { return false } return xf.m[p.Index(-2).Type()] && !isExported(sf.Name()) } // isExported reports whether the identifier is exported. func isExported(id string) bool { r, _ := utf8.DecodeRuneInString(id) return unicode.IsUpper(r) } // IgnoreSliceElements returns an Option that ignores elements of []V. // The discard function must be of the form "func(T) bool" which is used to // ignore slice elements of type V, where V is assignable to T. // Elements are ignored if the function reports true. func IgnoreSliceElements(discardFunc interface{}) cmp.Option { vf := reflect.ValueOf(discardFunc) if !function.IsType(vf.Type(), function.ValuePredicate) || vf.IsNil() { panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) } return cmp.FilterPath(func(p cmp.Path) bool { si, ok := p.Index(-1).(cmp.SliceIndex) if !ok { return false } if !si.Type().AssignableTo(vf.Type().In(0)) { return false } vx, vy := si.Values() if vx.IsValid() && vf.Call([]reflect.Value{vx})[0].Bool() { return true } if vy.IsValid() && vf.Call([]reflect.Value{vy})[0].Bool() { return true } return false }, cmp.Ignore()) } // IgnoreMapEntries returns an Option that ignores entries of map[K]V. // The discard function must be of the form "func(T, R) bool" which is used to // ignore map entries of type K and V, where K and V are assignable to T and R. // Entries are ignored if the function reports true. func IgnoreMapEntries(discardFunc interface{}) cmp.Option { vf := reflect.ValueOf(discardFunc) if !function.IsType(vf.Type(), function.KeyValuePredicate) || vf.IsNil() { panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) } return cmp.FilterPath(func(p cmp.Path) bool { mi, ok := p.Index(-1).(cmp.MapIndex) if !ok { return false } if !mi.Key().Type().AssignableTo(vf.Type().In(0)) || !mi.Type().AssignableTo(vf.Type().In(1)) { return false } k := mi.Key() vx, vy := mi.Values() if vx.IsValid() && vf.Call([]reflect.Value{k, vx})[0].Bool() { return true } if vy.IsValid() && vf.Call([]reflect.Value{k, vy})[0].Bool() { return true } return false }, cmp.Ignore()) } go-cmp-0.3.1/cmp/cmpopts/sort.go000066400000000000000000000121171352211333400164560ustar00rootroot00000000000000// 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 cmpopts import ( "fmt" "reflect" "sort" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/internal/function" ) // SortSlices returns a Transformer option that sorts all []V. // The less function must be of the form "func(T, T) bool" which is used to // sort any slice with element type V that is assignable to T. // // The less function must be: // • Deterministic: less(x, y) == less(x, y) // • Irreflexive: !less(x, x) // • Transitive: if !less(x, y) and !less(y, z), then !less(x, z) // // The less function does not have to be "total". That is, if !less(x, y) and // !less(y, x) for two elements x and y, their relative order is maintained. // // SortSlices can be used in conjunction with EquateEmpty. func SortSlices(lessFunc interface{}) cmp.Option { vf := reflect.ValueOf(lessFunc) if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { panic(fmt.Sprintf("invalid less function: %T", lessFunc)) } ss := sliceSorter{vf.Type().In(0), vf} return cmp.FilterValues(ss.filter, cmp.Transformer("cmpopts.SortSlices", ss.sort)) } type sliceSorter struct { in reflect.Type // T fnc reflect.Value // func(T, T) bool } func (ss sliceSorter) filter(x, y interface{}) bool { vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) if !(x != nil && y != nil && vx.Type() == vy.Type()) || !(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) || (vx.Len() <= 1 && vy.Len() <= 1) { return false } // Check whether the slices are already sorted to avoid an infinite // recursion cycle applying the same transform to itself. ok1 := sort.SliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) }) ok2 := sort.SliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) }) return !ok1 || !ok2 } func (ss sliceSorter) sort(x interface{}) interface{} { src := reflect.ValueOf(x) dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len()) for i := 0; i < src.Len(); i++ { dst.Index(i).Set(src.Index(i)) } sort.SliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) }) ss.checkSort(dst) return dst.Interface() } func (ss sliceSorter) checkSort(v reflect.Value) { start := -1 // Start of a sequence of equal elements. for i := 1; i < v.Len(); i++ { if ss.less(v, i-1, i) { // Check that first and last elements in v[start:i] are equal. if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) { panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i))) } start = -1 } else if start == -1 { start = i } } } func (ss sliceSorter) less(v reflect.Value, i, j int) bool { vx, vy := v.Index(i), v.Index(j) return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool() } // SortMaps returns a Transformer option that flattens map[K]V types to be a // sorted []struct{K, V}. The less function must be of the form // "func(T, T) bool" which is used to sort any map with key K that is // assignable to T. // // Flattening the map into a slice has the property that cmp.Equal is able to // use Comparers on K or the K.Equal method if it exists. // // The less function must be: // • Deterministic: less(x, y) == less(x, y) // • Irreflexive: !less(x, x) // • Transitive: if !less(x, y) and !less(y, z), then !less(x, z) // • Total: if x != y, then either less(x, y) or less(y, x) // // SortMaps can be used in conjunction with EquateEmpty. func SortMaps(lessFunc interface{}) cmp.Option { vf := reflect.ValueOf(lessFunc) if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { panic(fmt.Sprintf("invalid less function: %T", lessFunc)) } ms := mapSorter{vf.Type().In(0), vf} return cmp.FilterValues(ms.filter, cmp.Transformer("cmpopts.SortMaps", ms.sort)) } type mapSorter struct { in reflect.Type // T fnc reflect.Value // func(T, T) bool } func (ms mapSorter) filter(x, y interface{}) bool { vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) return (x != nil && y != nil && vx.Type() == vy.Type()) && (vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) && (vx.Len() != 0 || vy.Len() != 0) } func (ms mapSorter) sort(x interface{}) interface{} { src := reflect.ValueOf(x) outType := reflect.StructOf([]reflect.StructField{ {Name: "K", Type: src.Type().Key()}, {Name: "V", Type: src.Type().Elem()}, }) dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len()) for i, k := range src.MapKeys() { v := reflect.New(outType).Elem() v.Field(0).Set(k) v.Field(1).Set(src.MapIndex(k)) dst.Index(i).Set(v) } sort.Slice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) }) ms.checkSort(dst) return dst.Interface() } func (ms mapSorter) checkSort(v reflect.Value) { for i := 1; i < v.Len(); i++ { if !ms.less(v, i-1, i) { panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i))) } } } func (ms mapSorter) less(v reflect.Value, i, j int) bool { vx, vy := v.Index(i).Field(0), v.Index(j).Field(0) return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool() } go-cmp-0.3.1/cmp/cmpopts/struct_filter.go000066400000000000000000000120131352211333400203530ustar00rootroot00000000000000// 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 cmpopts import ( "fmt" "reflect" "strings" "github.com/google/go-cmp/cmp" ) // filterField returns a new Option where opt is only evaluated on paths that // include a specific exported field on a single struct type. // The struct type is specified by passing in a value of that type. // // The name may be a dot-delimited string (e.g., "Foo.Bar") to select a // specific sub-field that is embedded or nested within the parent struct. func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option { // TODO: This is currently unexported over concerns of how helper filters // can be composed together easily. // TODO: Add tests for FilterField. sf := newStructFilter(typ, name) return cmp.FilterPath(sf.filter, opt) } type structFilter struct { t reflect.Type // The root struct type to match on ft fieldTree // Tree of fields to match on } func newStructFilter(typ interface{}, names ...string) structFilter { // TODO: Perhaps allow * as a special identifier to allow ignoring any // number of path steps until the next field match? // This could be useful when a concrete struct gets transformed into // an anonymous struct where it is not possible to specify that by type, // but the transformer happens to provide guarantees about the names of // the transformed fields. t := reflect.TypeOf(typ) if t == nil || t.Kind() != reflect.Struct { panic(fmt.Sprintf("%T must be a struct", typ)) } var ft fieldTree for _, name := range names { cname, err := canonicalName(t, name) if err != nil { panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err)) } ft.insert(cname) } return structFilter{t, ft} } func (sf structFilter) filter(p cmp.Path) bool { for i, ps := range p { if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) { return true } } return false } // fieldTree represents a set of dot-separated identifiers. // // For example, inserting the following selectors: // Foo // Foo.Bar.Baz // Foo.Buzz // Nuka.Cola.Quantum // // Results in a tree of the form: // {sub: { // "Foo": {ok: true, sub: { // "Bar": {sub: { // "Baz": {ok: true}, // }}, // "Buzz": {ok: true}, // }}, // "Nuka": {sub: { // "Cola": {sub: { // "Quantum": {ok: true}, // }}, // }}, // }} type fieldTree struct { ok bool // Whether this is a specified node sub map[string]fieldTree // The sub-tree of fields under this node } // insert inserts a sequence of field accesses into the tree. func (ft *fieldTree) insert(cname []string) { if ft.sub == nil { ft.sub = make(map[string]fieldTree) } if len(cname) == 0 { ft.ok = true return } sub := ft.sub[cname[0]] sub.insert(cname[1:]) ft.sub[cname[0]] = sub } // matchPrefix reports whether any selector in the fieldTree matches // the start of path p. func (ft fieldTree) matchPrefix(p cmp.Path) bool { for _, ps := range p { switch ps := ps.(type) { case cmp.StructField: ft = ft.sub[ps.Name()] if ft.ok { return true } if len(ft.sub) == 0 { return false } case cmp.Indirect: default: return false } } return false } // canonicalName returns a list of identifiers where any struct field access // through an embedded field is expanded to include the names of the embedded // types themselves. // // For example, suppose field "Foo" is not directly in the parent struct, // but actually from an embedded struct of type "Bar". Then, the canonical name // of "Foo" is actually "Bar.Foo". // // Suppose field "Foo" is not directly in the parent struct, but actually // a field in two different embedded structs of types "Bar" and "Baz". // Then the selector "Foo" causes a panic since it is ambiguous which one it // refers to. The user must specify either "Bar.Foo" or "Baz.Foo". func canonicalName(t reflect.Type, sel string) ([]string, error) { var name string sel = strings.TrimPrefix(sel, ".") if sel == "" { return nil, fmt.Errorf("name must not be empty") } if i := strings.IndexByte(sel, '.'); i < 0 { name, sel = sel, "" } else { name, sel = sel[:i], sel[i:] } // Type must be a struct or pointer to struct. if t.Kind() == reflect.Ptr { t = t.Elem() } if t.Kind() != reflect.Struct { return nil, fmt.Errorf("%v must be a struct", t) } // Find the canonical name for this current field name. // If the field exists in an embedded struct, then it will be expanded. if !isExported(name) { // Disallow unexported fields: // * To discourage people from actually touching unexported fields // * FieldByName is buggy (https://golang.org/issue/4876) return []string{name}, fmt.Errorf("name must be exported") } sf, ok := t.FieldByName(name) if !ok { return []string{name}, fmt.Errorf("does not exist") } var ss []string for i := range sf.Index { ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name) } if sel == "" { return ss, nil } ssPost, err := canonicalName(sf.Type, sel) return append(ss, ssPost...), err } go-cmp-0.3.1/cmp/cmpopts/util_test.go000066400000000000000000001105101352211333400174770ustar00rootroot00000000000000// 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 cmpopts import ( "bytes" "fmt" "io" "math" "reflect" "strings" "sync" "testing" "time" "github.com/google/go-cmp/cmp" ) type ( MyInt int MyInts []int MyFloat float32 MyString string MyTime struct{ time.Time } MyStruct struct { A, B []int C, D map[time.Time]string } Foo1 struct{ Alpha, Bravo, Charlie int } Foo2 struct{ *Foo1 } Foo3 struct{ *Foo2 } Bar1 struct{ Foo3 } Bar2 struct { Bar1 *Foo3 Bravo float32 } Bar3 struct { Bar1 Bravo *Bar2 Delta struct{ Echo Foo1 } *Foo3 Alpha string } privateStruct struct{ Public, private int } PublicStruct struct{ Public, private int } ParentStruct struct { *privateStruct *PublicStruct Public int private int } Everything struct { MyInt MyFloat MyTime MyStruct Bar3 ParentStruct } EmptyInterface interface{} ) func TestOptions(t *testing.T) { createBar3X := func() *Bar3 { return &Bar3{ Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 2}}}}, Bravo: &Bar2{ Bar1: Bar1{Foo3{&Foo2{&Foo1{Charlie: 7}}}}, Foo3: &Foo3{&Foo2{&Foo1{Bravo: 5}}}, Bravo: 4, }, Delta: struct{ Echo Foo1 }{Foo1{Charlie: 3}}, Foo3: &Foo3{&Foo2{&Foo1{Alpha: 1}}}, Alpha: "alpha", } } createBar3Y := func() *Bar3 { return &Bar3{ Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 3}}}}, Bravo: &Bar2{ Bar1: Bar1{Foo3{&Foo2{&Foo1{Charlie: 8}}}}, Foo3: &Foo3{&Foo2{&Foo1{Bravo: 6}}}, Bravo: 5, }, Delta: struct{ Echo Foo1 }{Foo1{Charlie: 4}}, Foo3: &Foo3{&Foo2{&Foo1{Alpha: 2}}}, Alpha: "ALPHA", } } tests := []struct { label string // Test name x, y interface{} // Input values to compare opts []cmp.Option // Input options wantEqual bool // Whether the inputs are equal wantPanic bool // Whether Equal should panic reason string // The reason for the expected outcome }{{ label: "EquateEmpty", x: []int{}, y: []int(nil), wantEqual: false, reason: "not equal because empty non-nil and nil slice differ", }, { label: "EquateEmpty", x: []int{}, y: []int(nil), opts: []cmp.Option{EquateEmpty()}, wantEqual: true, reason: "equal because EquateEmpty equates empty slices", }, { label: "SortSlices", x: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, y: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7}, wantEqual: false, reason: "not equal because element order differs", }, { label: "SortSlices", x: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, y: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7}, opts: []cmp.Option{SortSlices(func(x, y int) bool { return x < y })}, wantEqual: true, reason: "equal because SortSlices sorts the slices", }, { label: "SortSlices", x: []MyInt{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, y: []MyInt{1, 0, 5, 2, 8, 9, 4, 3, 6, 7}, opts: []cmp.Option{SortSlices(func(x, y int) bool { return x < y })}, wantEqual: false, reason: "not equal because MyInt is not the same type as int", }, { label: "SortSlices", x: []float64{0, 1, 1, 2, 2, 2}, y: []float64{2, 0, 2, 1, 2, 1}, opts: []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })}, wantEqual: true, reason: "equal even when sorted with duplicate elements", }, { label: "SortSlices", x: []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4}, y: []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2}, opts: []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })}, wantPanic: true, reason: "panics because SortSlices used with non-transitive less function", }, { label: "SortSlices", x: []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4}, y: []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2}, opts: []cmp.Option{SortSlices(func(x, y float64) bool { return (!math.IsNaN(x) && math.IsNaN(y)) || x < y })}, wantEqual: false, reason: "no panics because SortSlices used with valid less function; not equal because NaN != NaN", }, { label: "SortSlices+EquateNaNs", x: []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, math.NaN(), 3, 4, 4, 4, 4}, y: []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, math.NaN(), 2}, opts: []cmp.Option{ EquateNaNs(), SortSlices(func(x, y float64) bool { return (!math.IsNaN(x) && math.IsNaN(y)) || x < y }), }, wantEqual: true, reason: "no panics because SortSlices used with valid less function; equal because EquateNaNs is used", }, { label: "SortMaps", x: map[time.Time]string{ time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday", time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday", time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday", }, y: map[time.Time]string{ time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday", time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday", time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday", }, wantEqual: false, reason: "not equal because timezones differ", }, { label: "SortMaps", x: map[time.Time]string{ time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday", time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday", time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday", }, y: map[time.Time]string{ time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday", time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday", time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday", }, opts: []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })}, wantEqual: true, reason: "equal because SortMaps flattens to a slice where Time.Equal can be used", }, { label: "SortMaps", x: map[MyTime]string{ {time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}: "0th birthday", {time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)}: "1st birthday", {time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC)}: "2nd birthday", }, y: map[MyTime]string{ {time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "0th birthday", {time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "1st birthday", {time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "2nd birthday", }, opts: []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })}, wantEqual: false, reason: "not equal because MyTime is not assignable to time.Time", }, { label: "SortMaps", x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""}, // => {0, 1, 2, 3, -1, -2, -3}, y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""}, // => {0, 1, 2, 3, 100, 200, 300}, opts: []cmp.Option{SortMaps(func(a, b int) bool { if -10 < a && a <= 0 { a *= -100 } if -10 < b && b <= 0 { b *= -100 } return a < b })}, wantEqual: false, reason: "not equal because values differ even though SortMap provides valid ordering", }, { label: "SortMaps", x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""}, // => {0, 1, 2, 3, -1, -2, -3}, y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""}, // => {0, 1, 2, 3, 100, 200, 300}, opts: []cmp.Option{ SortMaps(func(x, y int) bool { if -10 < x && x <= 0 { x *= -100 } if -10 < y && y <= 0 { y *= -100 } return x < y }), cmp.Comparer(func(x, y int) bool { if -10 < x && x <= 0 { x *= -100 } if -10 < y && y <= 0 { y *= -100 } return x == y }), }, wantEqual: true, reason: "equal because Comparer used to equate differences", }, { label: "SortMaps", x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""}, y: map[int]string{}, opts: []cmp.Option{SortMaps(func(x, y int) bool { return x < y && x >= 0 && y >= 0 })}, wantPanic: true, reason: "panics because SortMaps used with non-transitive less function", }, { label: "SortMaps", x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""}, y: map[int]string{}, opts: []cmp.Option{SortMaps(func(x, y int) bool { return math.Abs(float64(x)) < math.Abs(float64(y)) })}, wantPanic: true, reason: "panics because SortMaps used with partial less function", }, { label: "EquateEmpty+SortSlices+SortMaps", x: MyStruct{ A: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, C: map[time.Time]string{ time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday", time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday", }, D: map[time.Time]string{}, }, y: MyStruct{ A: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7}, B: []int{}, C: map[time.Time]string{ time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday", time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday", }, }, opts: []cmp.Option{ EquateEmpty(), SortSlices(func(x, y int) bool { return x < y }), SortMaps(func(x, y time.Time) bool { return x.Before(y) }), }, wantEqual: true, reason: "no panics because EquateEmpty should compose with the sort options", }, { label: "EquateApprox", x: 3.09, y: 3.10, wantEqual: false, reason: "not equal because floats do not exactly matches", }, { label: "EquateApprox", x: 3.09, y: 3.10, opts: []cmp.Option{EquateApprox(0, 0)}, wantEqual: false, reason: "not equal because EquateApprox(0 ,0) is equivalent to using ==", }, { label: "EquateApprox", x: 3.09, y: 3.10, opts: []cmp.Option{EquateApprox(0.003, 0.009)}, wantEqual: false, reason: "not equal because EquateApprox is too strict", }, { label: "EquateApprox", x: 3.09, y: 3.10, opts: []cmp.Option{EquateApprox(0, 0.011)}, wantEqual: true, reason: "equal because margin is loose enough to match", }, { label: "EquateApprox", x: 3.09, y: 3.10, opts: []cmp.Option{EquateApprox(0.004, 0)}, wantEqual: true, reason: "equal because fraction is loose enough to match", }, { label: "EquateApprox", x: 3.09, y: 3.10, opts: []cmp.Option{EquateApprox(0.004, 0.011)}, wantEqual: true, reason: "equal because both the margin and fraction are loose enough to match", }, { label: "EquateApprox", x: float32(3.09), y: float64(3.10), opts: []cmp.Option{EquateApprox(0.004, 0)}, wantEqual: false, reason: "not equal because the types differ", }, { label: "EquateApprox", x: float32(3.09), y: float32(3.10), opts: []cmp.Option{EquateApprox(0.004, 0)}, wantEqual: true, reason: "equal because EquateApprox also applies on float32s", }, { label: "EquateApprox", x: []float64{math.Inf(+1), math.Inf(-1)}, y: []float64{math.Inf(+1), math.Inf(-1)}, opts: []cmp.Option{EquateApprox(0, 1)}, wantEqual: true, reason: "equal because we fall back on == which matches Inf (EquateApprox does not apply on Inf) ", }, { label: "EquateApprox", x: []float64{math.Inf(+1), -1e100}, y: []float64{+1e100, math.Inf(-1)}, opts: []cmp.Option{EquateApprox(0, 1)}, wantEqual: false, reason: "not equal because we fall back on == where Inf != 1e100 (EquateApprox does not apply on Inf)", }, { label: "EquateApprox", x: float64(+1e100), y: float64(-1e100), opts: []cmp.Option{EquateApprox(math.Inf(+1), 0)}, wantEqual: true, reason: "equal because infinite fraction matches everything", }, { label: "EquateApprox", x: float64(+1e100), y: float64(-1e100), opts: []cmp.Option{EquateApprox(0, math.Inf(+1))}, wantEqual: true, reason: "equal because infinite margin matches everything", }, { label: "EquateApprox", x: math.Pi, y: math.Pi, opts: []cmp.Option{EquateApprox(0, 0)}, wantEqual: true, reason: "equal because EquateApprox(0, 0) is equivalent to ==", }, { label: "EquateApprox", x: math.Pi, y: math.Nextafter(math.Pi, math.Inf(+1)), opts: []cmp.Option{EquateApprox(0, 0)}, wantEqual: false, reason: "not equal because EquateApprox(0, 0) is equivalent to ==", }, { label: "EquateNaNs", x: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)}, y: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)}, wantEqual: false, reason: "not equal because NaN != NaN", }, { label: "EquateNaNs", x: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)}, y: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)}, opts: []cmp.Option{EquateNaNs()}, wantEqual: true, reason: "equal because EquateNaNs allows NaN == NaN", }, { label: "EquateNaNs", x: []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0}, y: []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0}, opts: []cmp.Option{EquateNaNs()}, wantEqual: true, reason: "equal because EquateNaNs operates on float32", }, { label: "EquateApprox+EquateNaNs", x: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.01, 5001}, y: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.02, 5002}, opts: []cmp.Option{ EquateNaNs(), EquateApprox(0.01, 0), }, wantEqual: true, reason: "equal because EquateNaNs and EquateApprox compose together", }, { label: "EquateApprox+EquateNaNs", x: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.01, 5001}, y: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.02, 5002}, opts: []cmp.Option{ EquateNaNs(), EquateApprox(0.01, 0), }, wantEqual: false, reason: "not equal because EquateApprox and EquateNaNs do not apply on a named type", }, { label: "EquateApprox+EquateNaNs+Transform", x: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.01, 5001}, y: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.02, 5002}, opts: []cmp.Option{ cmp.Transformer("", func(x MyFloat) float64 { return float64(x) }), EquateNaNs(), EquateApprox(0.01, 0), }, wantEqual: true, reason: "equal because named type is transformed to float64", }, { label: "IgnoreFields", x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}}, y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}}, wantEqual: false, reason: "not equal because values do not match in deeply embedded field", }, { label: "IgnoreFields", x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}}, y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}}, opts: []cmp.Option{IgnoreFields(Bar1{}, "Alpha")}, wantEqual: true, reason: "equal because IgnoreField ignores deeply embedded field: Alpha", }, { label: "IgnoreFields", x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}}, y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}}, opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo1.Alpha")}, wantEqual: true, reason: "equal because IgnoreField ignores deeply embedded field: Foo1.Alpha", }, { label: "IgnoreFields", x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}}, y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}}, opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo2.Alpha")}, wantEqual: true, reason: "equal because IgnoreField ignores deeply embedded field: Foo2.Alpha", }, { label: "IgnoreFields", x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}}, y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}}, opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Alpha")}, wantEqual: true, reason: "equal because IgnoreField ignores deeply embedded field: Foo3.Alpha", }, { label: "IgnoreFields", x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}}, y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}}, opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Foo2.Alpha")}, wantEqual: true, reason: "equal because IgnoreField ignores deeply embedded field: Foo3.Foo2.Alpha", }, { label: "IgnoreFields", x: createBar3X(), y: createBar3Y(), wantEqual: false, reason: "not equal because many deeply nested or embedded fields differ", }, { label: "IgnoreFields", x: createBar3X(), y: createBar3Y(), opts: []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Foo3", "Alpha")}, wantEqual: true, reason: "equal because IgnoreFields ignores fields at the highest levels", }, { label: "IgnoreFields", x: createBar3X(), y: createBar3Y(), opts: []cmp.Option{ IgnoreFields(Bar3{}, "Bar1.Foo3.Bravo", "Bravo.Bar1.Foo3.Foo2.Foo1.Charlie", "Bravo.Foo3.Foo2.Foo1.Bravo", "Bravo.Bravo", "Delta.Echo.Charlie", "Foo3.Foo2.Foo1.Alpha", "Alpha", ), }, wantEqual: true, reason: "equal because IgnoreFields ignores fields using fully-qualified field", }, { label: "IgnoreFields", x: createBar3X(), y: createBar3Y(), opts: []cmp.Option{ IgnoreFields(Bar3{}, "Bar1.Foo3.Bravo", "Bravo.Foo3.Foo2.Foo1.Bravo", "Bravo.Bravo", "Delta.Echo.Charlie", "Foo3.Foo2.Foo1.Alpha", "Alpha", ), }, wantEqual: false, reason: "not equal because one fully-qualified field is not ignored: Bravo.Bar1.Foo3.Foo2.Foo1.Charlie", }, { label: "IgnoreFields", x: createBar3X(), y: createBar3Y(), opts: []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha")}, wantEqual: false, reason: "not equal because highest-level field is not ignored: Foo3", }, { label: "IgnoreTypes", x: []interface{}{5, "same"}, y: []interface{}{6, "same"}, wantEqual: false, reason: "not equal because 5 != 6", }, { label: "IgnoreTypes", x: []interface{}{5, "same"}, y: []interface{}{6, "same"}, opts: []cmp.Option{IgnoreTypes(0)}, wantEqual: true, reason: "equal because ints are ignored", }, { label: "IgnoreTypes+IgnoreInterfaces", x: []interface{}{5, "same", new(bytes.Buffer)}, y: []interface{}{6, "same", new(bytes.Buffer)}, opts: []cmp.Option{IgnoreTypes(0)}, wantPanic: true, reason: "panics because bytes.Buffer has unexported fields", }, { label: "IgnoreTypes+IgnoreInterfaces", x: []interface{}{5, "same", new(bytes.Buffer)}, y: []interface{}{6, "diff", new(bytes.Buffer)}, opts: []cmp.Option{ IgnoreTypes(0, ""), IgnoreInterfaces(struct{ io.Reader }{}), }, wantEqual: true, reason: "equal because bytes.Buffer is ignored by match on interface type", }, { label: "IgnoreTypes+IgnoreInterfaces", x: []interface{}{5, "same", new(bytes.Buffer)}, y: []interface{}{6, "same", new(bytes.Buffer)}, opts: []cmp.Option{ IgnoreTypes(0, ""), IgnoreInterfaces(struct { io.Reader io.Writer fmt.Stringer }{}), }, wantEqual: true, reason: "equal because bytes.Buffer is ignored by match on multiple interface types", }, { label: "IgnoreInterfaces", x: struct{ mu sync.Mutex }{}, y: struct{ mu sync.Mutex }{}, wantPanic: true, reason: "panics because sync.Mutex has unexported fields", }, { label: "IgnoreInterfaces", x: struct{ mu sync.Mutex }{}, y: struct{ mu sync.Mutex }{}, opts: []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})}, wantEqual: true, reason: "equal because IgnoreInterfaces applies on values (with pointer receiver)", }, { label: "IgnoreInterfaces", x: struct{ mu *sync.Mutex }{}, y: struct{ mu *sync.Mutex }{}, opts: []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})}, wantEqual: true, reason: "equal because IgnoreInterfaces applies on pointers", }, { label: "IgnoreUnexported", x: ParentStruct{Public: 1, private: 2}, y: ParentStruct{Public: 1, private: -2}, opts: []cmp.Option{cmp.AllowUnexported(ParentStruct{})}, wantEqual: false, reason: "not equal because ParentStruct.private differs with AllowUnexported", }, { label: "IgnoreUnexported", x: ParentStruct{Public: 1, private: 2}, y: ParentStruct{Public: 1, private: -2}, opts: []cmp.Option{IgnoreUnexported(ParentStruct{})}, wantEqual: true, reason: "equal because IgnoreUnexported ignored ParentStruct.private", }, { label: "IgnoreUnexported", x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}}, y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: 4}}, opts: []cmp.Option{ cmp.AllowUnexported(PublicStruct{}), IgnoreUnexported(ParentStruct{}), }, wantEqual: true, reason: "equal because ParentStruct.private is ignored", }, { label: "IgnoreUnexported", x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}}, y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}}, opts: []cmp.Option{ cmp.AllowUnexported(PublicStruct{}), IgnoreUnexported(ParentStruct{}), }, wantEqual: false, reason: "not equal because ParentStruct.PublicStruct.private differs and not ignored by IgnoreUnexported(ParentStruct{})", }, { label: "IgnoreUnexported", x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}}, y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}}, opts: []cmp.Option{ IgnoreUnexported(ParentStruct{}, PublicStruct{}), }, wantEqual: true, reason: "equal because both ParentStruct.PublicStruct and ParentStruct.PublicStruct.private are ignored", }, { label: "IgnoreUnexported", x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}}, y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}}, opts: []cmp.Option{ cmp.AllowUnexported(privateStruct{}, PublicStruct{}, ParentStruct{}), }, wantEqual: false, reason: "not equal since ParentStruct.privateStruct differs", }, { label: "IgnoreUnexported", x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}}, y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}}, opts: []cmp.Option{ cmp.AllowUnexported(privateStruct{}, PublicStruct{}), IgnoreUnexported(ParentStruct{}), }, wantEqual: true, reason: "equal because ParentStruct.privateStruct ignored by IgnoreUnexported(ParentStruct{})", }, { label: "IgnoreUnexported", x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}}, y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: -4}}, opts: []cmp.Option{ cmp.AllowUnexported(PublicStruct{}, ParentStruct{}), IgnoreUnexported(privateStruct{}), }, wantEqual: true, reason: "equal because privateStruct.private ignored by IgnoreUnexported(privateStruct{})", }, { label: "IgnoreUnexported", x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}}, y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}}, opts: []cmp.Option{ cmp.AllowUnexported(PublicStruct{}, ParentStruct{}), IgnoreUnexported(privateStruct{}), }, wantEqual: false, reason: "not equal because privateStruct.Public differs and not ignored by IgnoreUnexported(privateStruct{})", }, { label: "IgnoreFields+IgnoreTypes+IgnoreUnexported", x: &Everything{ MyInt: 5, MyFloat: 3.3, MyTime: MyTime{time.Now()}, Bar3: *createBar3X(), ParentStruct: ParentStruct{ Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}, }, }, y: &Everything{ MyInt: -5, MyFloat: 3.3, MyTime: MyTime{time.Now()}, Bar3: *createBar3Y(), ParentStruct: ParentStruct{ Public: 1, private: -2, PublicStruct: &PublicStruct{Public: -3, private: -4}, }, }, opts: []cmp.Option{ IgnoreFields(Everything{}, "MyTime", "Bar3.Foo3"), IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha"), IgnoreTypes(MyInt(0), PublicStruct{}), IgnoreUnexported(ParentStruct{}), }, wantEqual: true, reason: "equal because all Ignore options can be composed together", }, { label: "IgnoreSliceElements", x: []int{1, 0, 2, 3, 0, 4, 0, 0}, y: []int{0, 0, 0, 0, 1, 2, 3, 4}, opts: []cmp.Option{ IgnoreSliceElements(func(v int) bool { return v == 0 }), }, wantEqual: true, reason: "equal because zero elements are ignored", }, { label: "IgnoreSliceElements", x: []MyInt{1, 0, 2, 3, 0, 4, 0, 0}, y: []MyInt{0, 0, 0, 0, 1, 2, 3, 4}, opts: []cmp.Option{ IgnoreSliceElements(func(v int) bool { return v == 0 }), }, wantEqual: false, reason: "not equal because MyInt is not assignable to int", }, { label: "IgnoreSliceElements", x: MyInts{1, 0, 2, 3, 0, 4, 0, 0}, y: MyInts{0, 0, 0, 0, 1, 2, 3, 4}, opts: []cmp.Option{ IgnoreSliceElements(func(v int) bool { return v == 0 }), }, wantEqual: true, reason: "equal because the element type of MyInts is assignable to int", }, { label: "IgnoreSliceElements+EquateEmpty", x: []MyInt{}, y: []MyInt{0, 0, 0, 0}, opts: []cmp.Option{ IgnoreSliceElements(func(v int) bool { return v == 0 }), EquateEmpty(), }, wantEqual: false, reason: "not equal because ignored elements does not imply empty slice", }, { label: "IgnoreMapEntries", x: map[string]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5}, y: map[string]int{"one": 1, "three": 3, "TEN": 10}, opts: []cmp.Option{ IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }), }, wantEqual: true, reason: "equal because uppercase keys are ignored", }, { label: "IgnoreMapEntries", x: map[MyString]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5}, y: map[MyString]int{"one": 1, "three": 3, "TEN": 10}, opts: []cmp.Option{ IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }), }, wantEqual: false, reason: "not equal because MyString is not assignable to string", }, { label: "IgnoreMapEntries", x: map[string]MyInt{"one": 1, "TWO": 2, "three": 3, "FIVE": 5}, y: map[string]MyInt{"one": 1, "three": 3, "TEN": 10}, opts: []cmp.Option{ IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }), }, wantEqual: false, reason: "not equal because MyInt is not assignable to int", }, { label: "IgnoreMapEntries+EquateEmpty", x: map[string]MyInt{"ONE": 1, "TWO": 2, "THREE": 3}, y: nil, opts: []cmp.Option{ IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }), EquateEmpty(), }, wantEqual: false, reason: "not equal because ignored entries does not imply empty map", }, { label: "AcyclicTransformer", x: "a\nb\nc\nd", y: "a\nb\nd\nd", opts: []cmp.Option{ AcyclicTransformer("", func(s string) []string { return strings.Split(s, "\n") }), }, wantEqual: false, reason: "not equal because 3rd line differs, but should not recurse infinitely", }, { label: "AcyclicTransformer", x: []string{"foo", "Bar", "BAZ"}, y: []string{"Foo", "BAR", "baz"}, opts: []cmp.Option{ AcyclicTransformer("", strings.ToUpper), }, wantEqual: true, reason: "equal because of strings.ToUpper; AcyclicTransformer unnecessary, but check this still works", }, { label: "AcyclicTransformer", x: "this is a sentence", y: "this is a sentence", opts: []cmp.Option{ AcyclicTransformer("", strings.Fields), }, wantEqual: true, reason: "equal because acyclic transformer splits on any contiguous whitespace", }} for _, tt := range tests { t.Run(tt.label, func(t *testing.T) { var gotEqual bool var gotPanic string func() { defer func() { if ex := recover(); ex != nil { gotPanic = fmt.Sprint(ex) } }() gotEqual = cmp.Equal(tt.x, tt.y, tt.opts...) }() switch { case tt.reason == "": t.Errorf("reason must be provided") case gotPanic == "" && tt.wantPanic: t.Errorf("expected Equal panic\nreason: %s", tt.reason) case gotPanic != "" && !tt.wantPanic: t.Errorf("unexpected Equal panic: got %v\nreason: %v", gotPanic, tt.reason) case gotEqual != tt.wantEqual: t.Errorf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason) } }) } } func TestPanic(t *testing.T) { args := func(x ...interface{}) []interface{} { return x } tests := []struct { label string // Test name fnc interface{} // Option function to call args []interface{} // Arguments to pass in wantPanic string // Expected panic message reason string // The reason for the expected outcome }{{ label: "EquateApprox", fnc: EquateApprox, args: args(0.0, 0.0), reason: "zero margin and fraction is equivalent to exact equality", }, { label: "EquateApprox", fnc: EquateApprox, args: args(-0.1, 0.0), wantPanic: "margin or fraction must be a non-negative number", reason: "negative inputs are invalid", }, { label: "EquateApprox", fnc: EquateApprox, args: args(0.0, -0.1), wantPanic: "margin or fraction must be a non-negative number", reason: "negative inputs are invalid", }, { label: "EquateApprox", fnc: EquateApprox, args: args(math.NaN(), 0.0), wantPanic: "margin or fraction must be a non-negative number", reason: "NaN inputs are invalid", }, { label: "EquateApprox", fnc: EquateApprox, args: args(1.0, 0.0), reason: "fraction of 1.0 or greater is valid", }, { label: "EquateApprox", fnc: EquateApprox, args: args(0.0, math.Inf(+1)), reason: "margin of infinity is valid", }, { label: "SortSlices", fnc: SortSlices, args: args(strings.Compare), wantPanic: "invalid less function", reason: "func(x, y string) int is wrong signature for less", }, { label: "SortSlices", fnc: SortSlices, args: args((func(_, _ int) bool)(nil)), wantPanic: "invalid less function", reason: "nil value is not valid", }, { label: "SortMaps", fnc: SortMaps, args: args(strings.Compare), wantPanic: "invalid less function", reason: "func(x, y string) int is wrong signature for less", }, { label: "SortMaps", fnc: SortMaps, args: args((func(_, _ int) bool)(nil)), wantPanic: "invalid less function", reason: "nil value is not valid", }, { label: "IgnoreFields", fnc: IgnoreFields, args: args(Foo1{}, ""), wantPanic: "name must not be empty", reason: "empty selector is invalid", }, { label: "IgnoreFields", fnc: IgnoreFields, args: args(Foo1{}, "."), wantPanic: "name must not be empty", reason: "single dot selector is invalid", }, { label: "IgnoreFields", fnc: IgnoreFields, args: args(Foo1{}, ".Alpha"), reason: "dot-prefix is okay since Foo1.Alpha reads naturally", }, { label: "IgnoreFields", fnc: IgnoreFields, args: args(Foo1{}, "Alpha."), wantPanic: "name must not be empty", reason: "dot-suffix is invalid", }, { label: "IgnoreFields", fnc: IgnoreFields, args: args(Foo1{}, "Alpha "), wantPanic: "does not exist", reason: "identifiers must not have spaces", }, { label: "IgnoreFields", fnc: IgnoreFields, args: args(Foo1{}, "Zulu"), wantPanic: "does not exist", reason: "name of non-existent field is invalid", }, { label: "IgnoreFields", fnc: IgnoreFields, args: args(Foo1{}, "Alpha.NoExist"), wantPanic: "must be a struct", reason: "cannot select into a non-struct", }, { label: "IgnoreFields", fnc: IgnoreFields, args: args(&Foo1{}, "Alpha"), wantPanic: "must be a struct", reason: "the type must be a struct (not pointer to a struct)", }, { label: "IgnoreFields", fnc: IgnoreFields, args: args(Foo1{}, "unexported"), wantPanic: "name must be exported", reason: "unexported fields must not be specified", }, { label: "IgnoreTypes", fnc: IgnoreTypes, reason: "empty input is valid", }, { label: "IgnoreTypes", fnc: IgnoreTypes, args: args(nil), wantPanic: "cannot determine type", reason: "input must not be nil value", }, { label: "IgnoreTypes", fnc: IgnoreTypes, args: args(0, 0, 0), reason: "duplicate inputs of the same type is valid", }, { label: "IgnoreInterfaces", fnc: IgnoreInterfaces, args: args(nil), wantPanic: "input must be an anonymous struct", reason: "input must not be nil value", }, { label: "IgnoreInterfaces", fnc: IgnoreInterfaces, args: args(Foo1{}), wantPanic: "input must be an anonymous struct", reason: "input must not be a named struct type", }, { label: "IgnoreInterfaces", fnc: IgnoreInterfaces, args: args(struct{ _ io.Reader }{}), wantPanic: "struct cannot have named fields", reason: "input must not have named fields", }, { label: "IgnoreInterfaces", fnc: IgnoreInterfaces, args: args(struct{ Foo1 }{}), wantPanic: "embedded field must be an interface type", reason: "field types must be interfaces", }, { label: "IgnoreInterfaces", fnc: IgnoreInterfaces, args: args(struct{ EmptyInterface }{}), wantPanic: "cannot ignore empty interface", reason: "field types must not be the empty interface", }, { label: "IgnoreInterfaces", fnc: IgnoreInterfaces, args: args(struct { io.Reader io.Writer io.Closer io.ReadWriteCloser }{}), reason: "multiple interfaces may be specified, even if they overlap", }, { label: "IgnoreUnexported", fnc: IgnoreUnexported, reason: "empty input is valid", }, { label: "IgnoreUnexported", fnc: IgnoreUnexported, args: args(nil), wantPanic: "invalid struct type", reason: "input must not be nil value", }, { label: "IgnoreUnexported", fnc: IgnoreUnexported, args: args(&Foo1{}), wantPanic: "invalid struct type", reason: "input must be a struct type (not a pointer to a struct)", }, { label: "IgnoreUnexported", fnc: IgnoreUnexported, args: args(Foo1{}, struct{ x, X int }{}), reason: "input may be named or unnamed structs", }, { label: "AcyclicTransformer", fnc: AcyclicTransformer, args: args("", "not a func"), wantPanic: "invalid transformer function", reason: "AcyclicTransformer has same input requirements as Transformer", }} for _, tt := range tests { t.Run(tt.label, func(t *testing.T) { // Prepare function arguments. vf := reflect.ValueOf(tt.fnc) var vargs []reflect.Value for i, arg := range tt.args { if arg == nil { tf := vf.Type() if i == tf.NumIn()-1 && tf.IsVariadic() { vargs = append(vargs, reflect.Zero(tf.In(i).Elem())) } else { vargs = append(vargs, reflect.Zero(tf.In(i))) } } else { vargs = append(vargs, reflect.ValueOf(arg)) } } // Call the function and capture any panics. var gotPanic string func() { defer func() { if ex := recover(); ex != nil { if s, ok := ex.(string); ok { gotPanic = s } else { panic(ex) } } }() vf.Call(vargs) }() switch { case tt.reason == "": t.Errorf("reason must be provided") case tt.wantPanic == "" && gotPanic != "": t.Errorf("unexpected panic message: %s\nreason: %s", gotPanic, tt.reason) case tt.wantPanic != "" && !strings.Contains(gotPanic, tt.wantPanic): t.Errorf("panic message:\ngot: %s\nwant: %s\nreason: %s", gotPanic, tt.wantPanic, tt.reason) } }) } } go-cmp-0.3.1/cmp/cmpopts/xform.go000066400000000000000000000021151352211333400166170ustar00rootroot00000000000000// Copyright 2018, 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 cmpopts import ( "github.com/google/go-cmp/cmp" ) type xformFilter struct{ xform cmp.Option } func (xf xformFilter) filter(p cmp.Path) bool { for _, ps := range p { if t, ok := ps.(cmp.Transform); ok && t.Option() == xf.xform { return false } } return true } // AcyclicTransformer returns a Transformer with a filter applied that ensures // that the transformer cannot be recursively applied upon its own output. // // An example use case is a transformer that splits a string by lines: // AcyclicTransformer("SplitLines", func(s string) []string{ // return strings.Split(s, "\n") // }) // // Had this been an unfiltered Transformer instead, this would result in an // infinite cycle converting a string to []string to [][]string and so on. func AcyclicTransformer(name string, xformFunc interface{}) cmp.Option { xf := xformFilter{cmp.Transformer(name, xformFunc)} return cmp.FilterPath(xf.filter, xf.xform) } go-cmp-0.3.1/cmp/compare.go000066400000000000000000000501461352211333400154340ustar00rootroot00000000000000// 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 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. package cmp import ( "fmt" "reflect" "strings" "github.com/google/go-cmp/cmp/internal/diff" "github.com/google/go-cmp/cmp/internal/flags" "github.com/google/go-cmp/cmp/internal/function" "github.com/google/go-cmp/cmp/internal/value" ) // Equal reports whether x and y are equal by recursively applying the // following rules in the given order to x and y and all of their sub-values: // // • Let S be the set of all Ignore, Transformer, and Comparer options that // remain after applying all path filters, value filters, and type filters. // If at least one Ignore exists in S, then the comparison is ignored. // If the number of Transformer and Comparer options in S is greater than one, // then Equal panics because it is ambiguous which option to use. // If S contains a single Transformer, then use that to transform the current // values and recursively call Equal on the output values. // If S contains a single Comparer, then use that to compare the current values. // Otherwise, evaluation proceeds to the next rule. // // • If the values have an Equal method of the form "(T) Equal(T) bool" or // "(T) Equal(I) bool" where T is assignable to I, then use the result of // x.Equal(y) even if x or y is nil. Otherwise, no such method exists and // evaluation proceeds to the next rule. // // • Lastly, try to compare x and y based on their basic kinds. // Simple kinds like booleans, integers, floats, complex numbers, strings, and // channels are compared using the equivalent of the == operator in Go. // Functions are only equal if they are both nil, otherwise they are unequal. // // Structs are equal if recursively calling Equal on all fields report equal. // If a struct contains unexported fields, Equal panics unless an Ignore option // (e.g., cmpopts.IgnoreUnexported) ignores that field or the AllowUnexported // option explicitly permits comparing the unexported field. // // Slices are equal if they are both nil or both non-nil, where recursively // calling Equal on all non-ignored slice or array elements report equal. // Empty non-nil slices and nil slices are not equal; to equate empty slices, // consider using cmpopts.EquateEmpty. // // Maps are equal if they are both nil or both non-nil, where recursively // calling Equal on all non-ignored map entries report equal. // Map keys are equal according to the == operator. // To use custom comparisons for map keys, consider using cmpopts.SortMaps. // Empty non-nil maps and nil maps are not equal; to equate empty maps, // consider using cmpopts.EquateEmpty. // // Pointers and interfaces are equal if they are both nil or both non-nil, // where they have the same underlying concrete type and recursively // calling Equal on the underlying values reports equal. func Equal(x, y interface{}, opts ...Option) bool { vx := reflect.ValueOf(x) vy := reflect.ValueOf(y) // If the inputs are different types, auto-wrap them in an empty interface // so that they have the same parent type. var t reflect.Type if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() { t = reflect.TypeOf((*interface{})(nil)).Elem() if vx.IsValid() { vvx := reflect.New(t).Elem() vvx.Set(vx) vx = vvx } if vy.IsValid() { vvy := reflect.New(t).Elem() vvy.Set(vy) vy = vvy } } else { t = vx.Type() } s := newState(opts) s.compareAny(&pathStep{t, vx, vy}) return s.result.Equal() } // Diff returns a human-readable report of the differences between two values. // It returns an empty string if and only if Equal returns true for the same // input values and options. // // The output is displayed as a literal in pseudo-Go syntax. // At the start of each line, a "-" prefix indicates an element removed from x, // a "+" prefix to indicates an element added to y, and the lack of a prefix // indicates an element common to both x and y. If possible, the output // uses fmt.Stringer.String or error.Error methods to produce more humanly // readable outputs. In such cases, the string is prefixed with either an // 's' or 'e' character, respectively, to indicate that the method was called. // // Do not depend on this output being stable. If you need the ability to // programmatically interpret the difference, consider using a custom Reporter. func Diff(x, y interface{}, opts ...Option) string { r := new(defaultReporter) eq := Equal(x, y, Options(opts), Reporter(r)) d := r.String() if (d == "") != eq { panic("inconsistent difference and equality results") } return d } type state struct { // These fields represent the "comparison state". // Calling statelessCompare must not result in observable changes to these. result diff.Result // The current result of comparison curPath Path // The current path in the value tree reporters []reporter // Optional reporters // recChecker checks for infinite cycles applying the same set of // transformers upon the output of itself. recChecker recChecker // dynChecker triggers pseudo-random checks for option correctness. // It is safe for statelessCompare to mutate this value. dynChecker dynChecker // These fields, once set by processOption, will not change. exporters map[reflect.Type]bool // Set of structs with unexported field visibility opts Options // List of all fundamental and filter options } func newState(opts []Option) *state { // Always ensure a validator option exists to validate the inputs. s := &state{opts: Options{validator{}}} s.processOption(Options(opts)) return s } func (s *state) processOption(opt Option) { switch opt := opt.(type) { case nil: case Options: for _, o := range opt { s.processOption(o) } case coreOption: type filtered interface { isFiltered() bool } if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() { panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt)) } s.opts = append(s.opts, opt) case visibleStructs: if s.exporters == nil { s.exporters = make(map[reflect.Type]bool) } for t := range opt { s.exporters[t] = true } case reporter: s.reporters = append(s.reporters, opt) default: panic(fmt.Sprintf("unknown option %T", opt)) } } // statelessCompare compares two values and returns the result. // This function is stateless in that it does not alter the current result, // or output to any registered reporters. func (s *state) statelessCompare(step PathStep) diff.Result { // We do not save and restore the curPath because all of the compareX // methods should properly push and pop from the path. // It is an implementation bug if the contents of curPath differs from // when calling this function to when returning from it. oldResult, oldReporters := s.result, s.reporters s.result = diff.Result{} // Reset result s.reporters = nil // Remove reporters to avoid spurious printouts s.compareAny(step) res := s.result s.result, s.reporters = oldResult, oldReporters return res } func (s *state) compareAny(step PathStep) { // Update the path stack. s.curPath.push(step) defer s.curPath.pop() for _, r := range s.reporters { r.PushStep(step) defer r.PopStep() } s.recChecker.Check(s.curPath) // Obtain the current type and values. t := step.Type() vx, vy := step.Values() // Rule 1: Check whether an option applies on this node in the value tree. if s.tryOptions(t, vx, vy) { return } // Rule 2: Check whether the type has a valid Equal method. if s.tryMethod(t, vx, vy) { return } // Rule 3: Compare based on the underlying kind. switch t.Kind() { case reflect.Bool: s.report(vx.Bool() == vy.Bool(), 0) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: s.report(vx.Int() == vy.Int(), 0) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: s.report(vx.Uint() == vy.Uint(), 0) case reflect.Float32, reflect.Float64: s.report(vx.Float() == vy.Float(), 0) case reflect.Complex64, reflect.Complex128: s.report(vx.Complex() == vy.Complex(), 0) case reflect.String: s.report(vx.String() == vy.String(), 0) case reflect.Chan, reflect.UnsafePointer: s.report(vx.Pointer() == vy.Pointer(), 0) case reflect.Func: s.report(vx.IsNil() && vy.IsNil(), 0) case reflect.Struct: s.compareStruct(t, vx, vy) case reflect.Slice, reflect.Array: s.compareSlice(t, vx, vy) case reflect.Map: s.compareMap(t, vx, vy) case reflect.Ptr: s.comparePtr(t, vx, vy) case reflect.Interface: s.compareInterface(t, vx, vy) default: panic(fmt.Sprintf("%v kind not handled", t.Kind())) } } func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool { // Evaluate all filters and apply the remaining options. if opt := s.opts.filter(s, t, vx, vy); opt != nil { opt.apply(s, vx, vy) return true } return false } func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool { // Check if this type even has an Equal method. m, ok := t.MethodByName("Equal") if !ok || !function.IsType(m.Type, function.EqualAssignable) { return false } eq := s.callTTBFunc(m.Func, vx, vy) s.report(eq, reportByMethod) return true } func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { v = sanitizeValue(v, f.Type().In(0)) if !s.dynChecker.Next() { return f.Call([]reflect.Value{v})[0] } // Run the function twice and ensure that we get the same results back. // We run in goroutines so that the race detector (if enabled) can detect // unsafe mutations to the input. c := make(chan reflect.Value) go detectRaces(c, f, v) got := <-c want := f.Call([]reflect.Value{v})[0] if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() { // To avoid false-positives with non-reflexive equality operations, // we sanity check whether a value is equal to itself. if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() { return want } panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f))) } return want } func (s *state) callTTBFunc(f, x, y reflect.Value) bool { x = sanitizeValue(x, f.Type().In(0)) y = sanitizeValue(y, f.Type().In(1)) if !s.dynChecker.Next() { return f.Call([]reflect.Value{x, y})[0].Bool() } // Swapping the input arguments is sufficient to check that // f is symmetric and deterministic. // We run in goroutines so that the race detector (if enabled) can detect // unsafe mutations to the input. c := make(chan reflect.Value) go detectRaces(c, f, y, x) got := <-c want := f.Call([]reflect.Value{x, y})[0].Bool() if !got.IsValid() || got.Bool() != want { panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f))) } return want } func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) { var ret reflect.Value defer func() { recover() // Ignore panics, let the other call to f panic instead c <- ret }() ret = f.Call(vs)[0] } // sanitizeValue converts nil interfaces of type T to those of type R, // assuming that T is assignable to R. // Otherwise, it returns the input value as is. func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value { // TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/22143). if !flags.AtLeastGo110 { if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t { return reflect.New(t).Elem() } } return v } func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { var vax, vay reflect.Value // Addressable versions of vx and vy step := StructField{&structField{}} for i := 0; i < t.NumField(); i++ { step.typ = t.Field(i).Type step.vx = vx.Field(i) step.vy = vy.Field(i) step.name = t.Field(i).Name step.idx = i step.unexported = !isExported(step.name) if step.unexported { if step.name == "_" { continue } // Defer checking of unexported fields until later to give an // Ignore a chance to ignore the field. if !vax.IsValid() || !vay.IsValid() { // For retrieveUnexportedField to work, the parent struct must // be addressable. Create a new copy of the values if // necessary to make them addressable. vax = makeAddressable(vx) vay = makeAddressable(vy) } step.mayForce = s.exporters[t] step.pvx = vax step.pvy = vay step.field = t.Field(i) } s.compareAny(step) } } func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) { isSlice := t.Kind() == reflect.Slice if isSlice && (vx.IsNil() || vy.IsNil()) { s.report(vx.IsNil() && vy.IsNil(), 0) return } // TODO: Support cyclic data structures. step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}}} withIndexes := func(ix, iy int) SliceIndex { if ix >= 0 { step.vx, step.xkey = vx.Index(ix), ix } else { step.vx, step.xkey = reflect.Value{}, -1 } if iy >= 0 { step.vy, step.ykey = vy.Index(iy), iy } else { step.vy, step.ykey = reflect.Value{}, -1 } return step } // Ignore options are able to ignore missing elements in a slice. // However, detecting these reliably requires an optimal differencing // algorithm, for which diff.Difference is not. // // Instead, we first iterate through both slices to detect which elements // would be ignored if standing alone. The index of non-discarded elements // are stored in a separate slice, which diffing is then performed on. var indexesX, indexesY []int var ignoredX, ignoredY []bool for ix := 0; ix < vx.Len(); ix++ { ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0 if !ignored { indexesX = append(indexesX, ix) } ignoredX = append(ignoredX, ignored) } for iy := 0; iy < vy.Len(); iy++ { ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0 if !ignored { indexesY = append(indexesY, iy) } ignoredY = append(ignoredY, ignored) } // Compute an edit-script for slices vx and vy (excluding ignored elements). edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result { return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy])) }) // Replay the ignore-scripts and the edit-script. var ix, iy int for ix < vx.Len() || iy < vy.Len() { var e diff.EditType switch { case ix < len(ignoredX) && ignoredX[ix]: e = diff.UniqueX case iy < len(ignoredY) && ignoredY[iy]: e = diff.UniqueY default: e, edits = edits[0], edits[1:] } switch e { case diff.UniqueX: s.compareAny(withIndexes(ix, -1)) ix++ case diff.UniqueY: s.compareAny(withIndexes(-1, iy)) iy++ default: s.compareAny(withIndexes(ix, iy)) ix++ iy++ } } } func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) { if vx.IsNil() || vy.IsNil() { s.report(vx.IsNil() && vy.IsNil(), 0) return } // TODO: Support cyclic data structures. // We combine and sort the two map keys so that we can perform the // comparisons in a deterministic order. step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}} for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) { step.vx = vx.MapIndex(k) step.vy = vy.MapIndex(k) step.key = k if !step.vx.IsValid() && !step.vy.IsValid() { // It is possible for both vx and vy to be invalid if the // key contained a NaN value in it. // // Even with the ability to retrieve NaN keys in Go 1.12, // there still isn't a sensible way to compare the values since // a NaN key may map to multiple unordered values. // The most reasonable way to compare NaNs would be to compare the // set of values. However, this is impossible to do efficiently // since set equality is provably an O(n^2) operation given only // an Equal function. If we had a Less function or Hash function, // this could be done in O(n*log(n)) or O(n), respectively. // // Rather than adding complex logic to deal with NaNs, make it // the user's responsibility to compare such obscure maps. const help = "consider providing a Comparer to compare the map" panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help)) } s.compareAny(step) } } func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) { if vx.IsNil() || vy.IsNil() { s.report(vx.IsNil() && vy.IsNil(), 0) return } // TODO: Support cyclic data structures. vx, vy = vx.Elem(), vy.Elem() s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}}) } func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) { if vx.IsNil() || vy.IsNil() { s.report(vx.IsNil() && vy.IsNil(), 0) return } vx, vy = vx.Elem(), vy.Elem() if vx.Type() != vy.Type() { s.report(false, 0) return } s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}}) } func (s *state) report(eq bool, rf resultFlags) { if rf&reportByIgnore == 0 { if eq { s.result.NumSame++ rf |= reportEqual } else { s.result.NumDiff++ rf |= reportUnequal } } for _, r := range s.reporters { r.Report(Result{flags: rf}) } } // recChecker tracks the state needed to periodically perform checks that // user provided transformers are not stuck in an infinitely recursive cycle. type recChecker struct{ next int } // Check scans the Path for any recursive transformers and panics when any // recursive transformers are detected. Note that the presence of a // recursive Transformer does not necessarily imply an infinite cycle. // As such, this check only activates after some minimal number of path steps. func (rc *recChecker) Check(p Path) { const minLen = 1 << 16 if rc.next == 0 { rc.next = minLen } if len(p) < rc.next { return } rc.next <<= 1 // Check whether the same transformer has appeared at least twice. var ss []string m := map[Option]int{} for _, ps := range p { if t, ok := ps.(Transform); ok { t := t.Option() if m[t] == 1 { // Transformer was used exactly once before tf := t.(*transformer).fnc.Type() ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0))) } m[t]++ } } if len(ss) > 0 { const warning = "recursive set of Transformers detected" const help = "consider using cmpopts.AcyclicTransformer" set := strings.Join(ss, "\n\t") panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help)) } } // dynChecker tracks the state needed to periodically perform checks that // user provided functions are symmetric and deterministic. // The zero value is safe for immediate use. type dynChecker struct{ curr, next int } // Next increments the state and reports whether a check should be performed. // // Checks occur every Nth function call, where N is a triangular number: // 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ... // See https://en.wikipedia.org/wiki/Triangular_number // // This sequence ensures that the cost of checks drops significantly as // the number of functions calls grows larger. func (dc *dynChecker) Next() bool { ok := dc.curr == dc.next if ok { dc.curr = 0 dc.next++ } dc.curr++ return ok } // makeAddressable returns a value that is always addressable. // It returns the input verbatim if it is already addressable, // otherwise it creates a new value and returns an addressable copy. func makeAddressable(v reflect.Value) reflect.Value { if v.CanAddr() { return v } vc := reflect.New(v.Type()).Elem() vc.Set(v) return vc } go-cmp-0.3.1/cmp/compare_test.go000066400000000000000000002367501352211333400165020ustar00rootroot00000000000000// 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]...) } }) } }) } } go-cmp-0.3.1/cmp/example_reporter_test.go000066400000000000000000000024461352211333400204220ustar00rootroot00000000000000// Copyright 2019, 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 ( "fmt" "strings" "github.com/google/go-cmp/cmp" ) // DiffReporter is a simple custom reporter that only records differences // detected during comparison. type DiffReporter struct { path cmp.Path diffs []string } func (r *DiffReporter) PushStep(ps cmp.PathStep) { r.path = append(r.path, ps) } func (r *DiffReporter) Report(rs cmp.Result) { if !rs.Equal() { vx, vy := r.path.Last().Values() r.diffs = append(r.diffs, fmt.Sprintf("%#v:\n\t-: %+v\n\t+: %+v\n", r.path, vx, vy)) } } func (r *DiffReporter) PopStep() { r.path = r.path[:len(r.path)-1] } func (r *DiffReporter) String() string { return strings.Join(r.diffs, "\n") } func ExampleReporter() { x, y := MakeGatewayInfo() var r DiffReporter cmp.Equal(x, y, cmp.Reporter(&r)) fmt.Print(r.String()) // Output: // {cmp_test.Gateway}.IPAddress: // -: 192.168.0.1 // +: 192.168.0.2 // // {cmp_test.Gateway}.Clients[4].IPAddress: // -: 192.168.0.219 // +: 192.168.0.221 // // {cmp_test.Gateway}.Clients[5->?]: // -: {Hostname:americano IPAddress:192.168.0.188 LastSeen:2009-11-10 23:03:05 +0000 UTC} // +: } go-cmp-0.3.1/cmp/example_test.go000066400000000000000000000263701352211333400165020ustar00rootroot00000000000000// 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 ( "fmt" "math" "net" "reflect" "sort" "strings" "time" "github.com/google/go-cmp/cmp" ) // TODO: Re-write these examples in terms of how you actually use the // fundamental options and filters and not in terms of what cool things you can // do with them since that overlaps with cmp/cmpopts. // Use Diff to print out a human-readable report of differences for tests // comparing nested or structured data. func ExampleDiff_testing() { // Let got be the hypothetical value obtained from some logic under test // and want be the expected golden data. got, want := MakeGatewayInfo() if diff := cmp.Diff(want, got); diff != "" { t.Errorf("MakeGatewayInfo() mismatch (-want +got):\n%s", diff) } // Output: // MakeGatewayInfo() mismatch (-want +got): // cmp_test.Gateway{ // SSID: "CoffeeShopWiFi", // - IPAddress: s"192.168.0.2", // + IPAddress: s"192.168.0.1", // NetMask: net.IPMask{0xff, 0xff, 0x00, 0x00}, // Clients: []cmp_test.Client{ // ... // 2 identical elements // {Hostname: "macchiato", IPAddress: s"192.168.0.153", LastSeen: s"2009-11-10 23:39:43 +0000 UTC"}, // {Hostname: "espresso", IPAddress: s"192.168.0.121"}, // { // Hostname: "latte", // - IPAddress: s"192.168.0.221", // + IPAddress: s"192.168.0.219", // LastSeen: s"2009-11-10 23:00:23 +0000 UTC", // }, // + { // + Hostname: "americano", // + IPAddress: s"192.168.0.188", // + LastSeen: s"2009-11-10 23:03:05 +0000 UTC", // + }, // }, // } } // Approximate equality for floats can be handled by defining a custom // comparer on floats that determines two values to be equal if they are within // some range of each other. // // This example is for demonstrative purposes; use cmpopts.EquateApprox instead. func ExampleOption_approximateFloats() { // This Comparer only operates on float64. // To handle float32s, either define a similar function for that type // or use a Transformer to convert float32s into float64s. opt := cmp.Comparer(func(x, y float64) bool { delta := math.Abs(x - y) mean := math.Abs(x+y) / 2.0 return delta/mean < 0.00001 }) x := []float64{1.0, 1.1, 1.2, math.Pi} y := []float64{1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi z := []float64{1.0, 1.1, 1.2, 3.1415} // Diverges too far from Pi fmt.Println(cmp.Equal(x, y, opt)) fmt.Println(cmp.Equal(y, z, opt)) fmt.Println(cmp.Equal(z, x, opt)) // Output: // true // false // false } // Normal floating-point arithmetic defines == to be false when comparing // NaN with itself. In certain cases, this is not the desired property. // // This example is for demonstrative purposes; use cmpopts.EquateNaNs instead. func ExampleOption_equalNaNs() { // This Comparer only operates on float64. // To handle float32s, either define a similar function for that type // or use a Transformer to convert float32s into float64s. opt := cmp.Comparer(func(x, y float64) bool { return (math.IsNaN(x) && math.IsNaN(y)) || x == y }) x := []float64{1.0, math.NaN(), math.E, -0.0, +0.0} y := []float64{1.0, math.NaN(), math.E, -0.0, +0.0} z := []float64{1.0, math.NaN(), math.Pi, -0.0, +0.0} // Pi constant instead of E fmt.Println(cmp.Equal(x, y, opt)) fmt.Println(cmp.Equal(y, z, opt)) fmt.Println(cmp.Equal(z, x, opt)) // Output: // true // false // false } // To have floating-point comparisons combine both properties of NaN being // equal to itself and also approximate equality of values, filters are needed // to restrict the scope of the comparison so that they are composable. // // This example is for demonstrative purposes; // use cmpopts.EquateNaNs and cmpopts.EquateApprox instead. func ExampleOption_equalNaNsAndApproximateFloats() { alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true }) opts := cmp.Options{ // This option declares that a float64 comparison is equal only if // both inputs are NaN. cmp.FilterValues(func(x, y float64) bool { return math.IsNaN(x) && math.IsNaN(y) }, alwaysEqual), // This option declares approximate equality on float64s only if // both inputs are not NaN. cmp.FilterValues(func(x, y float64) bool { return !math.IsNaN(x) && !math.IsNaN(y) }, cmp.Comparer(func(x, y float64) bool { delta := math.Abs(x - y) mean := math.Abs(x+y) / 2.0 return delta/mean < 0.00001 })), } x := []float64{math.NaN(), 1.0, 1.1, 1.2, math.Pi} y := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi z := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.1415} // Diverges too far from Pi fmt.Println(cmp.Equal(x, y, opts)) fmt.Println(cmp.Equal(y, z, opts)) fmt.Println(cmp.Equal(z, x, opts)) // Output: // true // false // false } // Sometimes, an empty map or slice is considered equal to an allocated one // of zero length. // // This example is for demonstrative purposes; use cmpopts.EquateEmpty instead. func ExampleOption_equalEmpty() { alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true }) // This option handles slices and maps of any type. opt := cmp.FilterValues(func(x, y interface{}) bool { vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) return (vx.IsValid() && vy.IsValid() && vx.Type() == vy.Type()) && (vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) && (vx.Len() == 0 && vy.Len() == 0) }, alwaysEqual) type S struct { A []int B map[string]bool } x := S{nil, make(map[string]bool, 100)} y := S{make([]int, 0, 200), nil} z := S{[]int{0}, nil} // []int has a single element (i.e., not empty) fmt.Println(cmp.Equal(x, y, opt)) fmt.Println(cmp.Equal(y, z, opt)) fmt.Println(cmp.Equal(z, x, opt)) // Output: // true // false // false } // Two slices may be considered equal if they have the same elements, // regardless of the order that they appear in. Transformations can be used // to sort the slice. // // This example is for demonstrative purposes; use cmpopts.SortSlices instead. func ExampleOption_sortedSlice() { // This Transformer sorts a []int. trans := cmp.Transformer("Sort", func(in []int) []int { out := append([]int(nil), in...) // Copy input to avoid mutating it sort.Ints(out) return out }) x := struct{ Ints []int }{[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}} y := struct{ Ints []int }{[]int{2, 8, 0, 9, 6, 1, 4, 7, 3, 5}} z := struct{ Ints []int }{[]int{0, 0, 1, 2, 3, 4, 5, 6, 7, 8}} fmt.Println(cmp.Equal(x, y, trans)) fmt.Println(cmp.Equal(y, z, trans)) fmt.Println(cmp.Equal(z, x, trans)) // Output: // true // false // false } type otherString string func (x otherString) Equal(y otherString) bool { return strings.ToLower(string(x)) == strings.ToLower(string(y)) } // If the Equal method defined on a type is not suitable, the type can be be // dynamically transformed to be stripped of the Equal method (or any method // for that matter). func ExampleOption_avoidEqualMethod() { // Suppose otherString.Equal performs a case-insensitive equality, // which is too loose for our needs. // We can avoid the methods of otherString by declaring a new type. type myString otherString // This transformer converts otherString to myString, allowing Equal to use // other Options to determine equality. trans := cmp.Transformer("", func(in otherString) myString { return myString(in) }) x := []otherString{"foo", "bar", "baz"} y := []otherString{"fOO", "bAr", "Baz"} // Same as before, but with different case fmt.Println(cmp.Equal(x, y)) // Equal because of case-insensitivity fmt.Println(cmp.Equal(x, y, trans)) // Not equal because of more exact equality // Output: // true // false } func roundF64(z float64) float64 { if z < 0 { return math.Ceil(z - 0.5) } return math.Floor(z + 0.5) } // The complex numbers complex64 and complex128 can really just be decomposed // into a pair of float32 or float64 values. It would be convenient to be able // define only a single comparator on float64 and have float32, complex64, and // complex128 all be able to use that comparator. Transformations can be used // to handle this. func ExampleOption_transformComplex() { opts := []cmp.Option{ // This transformer decomposes complex128 into a pair of float64s. cmp.Transformer("T1", func(in complex128) (out struct{ Real, Imag float64 }) { out.Real, out.Imag = real(in), imag(in) return out }), // This transformer converts complex64 to complex128 to allow the // above transform to take effect. cmp.Transformer("T2", func(in complex64) complex128 { return complex128(in) }), // This transformer converts float32 to float64. cmp.Transformer("T3", func(in float32) float64 { return float64(in) }), // This equality function compares float64s as rounded integers. cmp.Comparer(func(x, y float64) bool { return roundF64(x) == roundF64(y) }), } x := []interface{}{ complex128(3.0), complex64(5.1 + 2.9i), float32(-1.2), float64(12.3), } y := []interface{}{ complex128(3.1), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7), } z := []interface{}{ complex128(3.8), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7), } fmt.Println(cmp.Equal(x, y, opts...)) fmt.Println(cmp.Equal(y, z, opts...)) fmt.Println(cmp.Equal(z, x, opts...)) // Output: // true // false // false } type ( Gateway struct { SSID string IPAddress net.IP NetMask net.IPMask Clients []Client } Client struct { Hostname string IPAddress net.IP LastSeen time.Time } ) func MakeGatewayInfo() (x, y Gateway) { x = Gateway{ SSID: "CoffeeShopWiFi", IPAddress: net.IPv4(192, 168, 0, 1), NetMask: net.IPv4Mask(255, 255, 0, 0), Clients: []Client{{ Hostname: "ristretto", IPAddress: net.IPv4(192, 168, 0, 116), }, { Hostname: "aribica", IPAddress: net.IPv4(192, 168, 0, 104), LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC), }, { Hostname: "macchiato", IPAddress: net.IPv4(192, 168, 0, 153), LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC), }, { Hostname: "espresso", IPAddress: net.IPv4(192, 168, 0, 121), }, { Hostname: "latte", IPAddress: net.IPv4(192, 168, 0, 219), LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC), }, { Hostname: "americano", IPAddress: net.IPv4(192, 168, 0, 188), LastSeen: time.Date(2009, time.November, 10, 23, 3, 5, 0, time.UTC), }}, } y = Gateway{ SSID: "CoffeeShopWiFi", IPAddress: net.IPv4(192, 168, 0, 2), NetMask: net.IPv4Mask(255, 255, 0, 0), Clients: []Client{{ Hostname: "ristretto", IPAddress: net.IPv4(192, 168, 0, 116), }, { Hostname: "aribica", IPAddress: net.IPv4(192, 168, 0, 104), LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC), }, { Hostname: "macchiato", IPAddress: net.IPv4(192, 168, 0, 153), LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC), }, { Hostname: "espresso", IPAddress: net.IPv4(192, 168, 0, 121), }, { Hostname: "latte", IPAddress: net.IPv4(192, 168, 0, 221), LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC), }}, } return x, y } var t fakeT type fakeT struct{} func (t fakeT) Errorf(format string, args ...interface{}) { fmt.Printf(format+"\n", args...) } go-cmp-0.3.1/cmp/export_panic.go000066400000000000000000000006031352211333400164720ustar00rootroot00000000000000// 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. // +build purego package cmp import "reflect" const supportAllowUnexported = false func retrieveUnexportedField(reflect.Value, reflect.StructField) reflect.Value { panic("retrieveUnexportedField is not implemented") } go-cmp-0.3.1/cmp/export_unsafe.go000066400000000000000000000012521352211333400166620ustar00rootroot00000000000000// 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. // +build !purego package cmp import ( "reflect" "unsafe" ) const supportAllowUnexported = true // retrieveUnexportedField uses unsafe to forcibly retrieve any field from // a struct such that the value has read-write permissions. // // The parent struct, v, must be addressable, while f must be a StructField // describing the field to retrieve. func retrieveUnexportedField(v reflect.Value, f reflect.StructField) reflect.Value { return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem() } go-cmp-0.3.1/cmp/internal/000077500000000000000000000000001352211333400152655ustar00rootroot00000000000000go-cmp-0.3.1/cmp/internal/diff/000077500000000000000000000000001352211333400161755ustar00rootroot00000000000000go-cmp-0.3.1/cmp/internal/diff/debug_disable.go000066400000000000000000000006031352211333400212740ustar00rootroot00000000000000// 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. // +build !cmp_debug package diff var debug debugger type debugger struct{} func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc { return f } func (debugger) Update() {} func (debugger) Finish() {} go-cmp-0.3.1/cmp/internal/diff/debug_enable.go000066400000000000000000000077401352211333400211300ustar00rootroot00000000000000// 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. // +build cmp_debug package diff import ( "fmt" "strings" "sync" "time" ) // The algorithm can be seen running in real-time by enabling debugging: // go test -tags=cmp_debug -v // // Example output: // === RUN TestDifference/#34 // ┌───────────────────────────────┐ // │ \ · · · · · · · · · · · · · · │ // │ · # · · · · · · · · · · · · · │ // │ · \ · · · · · · · · · · · · · │ // │ · · \ · · · · · · · · · · · · │ // │ · · · X # · · · · · · · · · · │ // │ · · · # \ · · · · · · · · · · │ // │ · · · · · # # · · · · · · · · │ // │ · · · · · # \ · · · · · · · · │ // │ · · · · · · · \ · · · · · · · │ // │ · · · · · · · · \ · · · · · · │ // │ · · · · · · · · · \ · · · · · │ // │ · · · · · · · · · · \ · · # · │ // │ · · · · · · · · · · · \ # # · │ // │ · · · · · · · · · · · # # # · │ // │ · · · · · · · · · · # # # # · │ // │ · · · · · · · · · # # # # # · │ // │ · · · · · · · · · · · · · · \ │ // └───────────────────────────────┘ // [.Y..M.XY......YXYXY.|] // // The grid represents the edit-graph where the horizontal axis represents // list X and the vertical axis represents list Y. The start of the two lists // is the top-left, while the ends are the bottom-right. The '·' represents // an unexplored node in the graph. The '\' indicates that the two symbols // from list X and Y are equal. The 'X' indicates that two symbols are similar // (but not exactly equal) to each other. The '#' indicates that the two symbols // are different (and not similar). The algorithm traverses this graph trying to // make the paths starting in the top-left and the bottom-right connect. // // The series of '.', 'X', 'Y', and 'M' characters at the bottom represents // the currently established path from the forward and reverse searches, // separated by a '|' character. const ( updateDelay = 100 * time.Millisecond finishDelay = 500 * time.Millisecond ansiTerminal = true // ANSI escape codes used to move terminal cursor ) var debug debugger type debugger struct { sync.Mutex p1, p2 EditScript fwdPath, revPath *EditScript grid []byte lines int } func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc { dbg.Lock() dbg.fwdPath, dbg.revPath = p1, p2 top := "┌─" + strings.Repeat("──", nx) + "┐\n" row := "│ " + strings.Repeat("· ", nx) + "│\n" btm := "└─" + strings.Repeat("──", nx) + "┘\n" dbg.grid = []byte(top + strings.Repeat(row, ny) + btm) dbg.lines = strings.Count(dbg.String(), "\n") fmt.Print(dbg) // Wrap the EqualFunc so that we can intercept each result. return func(ix, iy int) (r Result) { cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")] for i := range cell { cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot } switch r = f(ix, iy); { case r.Equal(): cell[0] = '\\' case r.Similar(): cell[0] = 'X' default: cell[0] = '#' } return } } func (dbg *debugger) Update() { dbg.print(updateDelay) } func (dbg *debugger) Finish() { dbg.print(finishDelay) dbg.Unlock() } func (dbg *debugger) String() string { dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0] for i := len(*dbg.revPath) - 1; i >= 0; i-- { dbg.p2 = append(dbg.p2, (*dbg.revPath)[i]) } return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2) } func (dbg *debugger) print(d time.Duration) { if ansiTerminal { fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor } fmt.Print(dbg) time.Sleep(d) } go-cmp-0.3.1/cmp/internal/diff/diff.go000066400000000000000000000304041352211333400174350ustar00rootroot00000000000000// 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 diff implements an algorithm for producing edit-scripts. // The edit-script is a sequence of operations needed to transform one list // of symbols into another (or vice-versa). The edits allowed are insertions, // deletions, and modifications. The summation of all edits is called the // Levenshtein distance as this problem is well-known in computer science. // // This package prioritizes performance over accuracy. That is, the run time // is more important than obtaining a minimal Levenshtein distance. package diff // EditType represents a single operation within an edit-script. type EditType uint8 const ( // Identity indicates that a symbol pair is identical in both list X and Y. Identity EditType = iota // UniqueX indicates that a symbol only exists in X and not Y. UniqueX // UniqueY indicates that a symbol only exists in Y and not X. UniqueY // Modified indicates that a symbol pair is a modification of each other. Modified ) // EditScript represents the series of differences between two lists. type EditScript []EditType // String returns a human-readable string representing the edit-script where // Identity, UniqueX, UniqueY, and Modified are represented by the // '.', 'X', 'Y', and 'M' characters, respectively. func (es EditScript) String() string { b := make([]byte, len(es)) for i, e := range es { switch e { case Identity: b[i] = '.' case UniqueX: b[i] = 'X' case UniqueY: b[i] = 'Y' case Modified: b[i] = 'M' default: panic("invalid edit-type") } } return string(b) } // stats returns a histogram of the number of each type of edit operation. func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) { for _, e := range es { switch e { case Identity: s.NI++ case UniqueX: s.NX++ case UniqueY: s.NY++ case Modified: s.NM++ default: panic("invalid edit-type") } } return } // Dist is the Levenshtein distance and is guaranteed to be 0 if and only if // lists X and Y are equal. func (es EditScript) Dist() int { return len(es) - es.stats().NI } // LenX is the length of the X list. func (es EditScript) LenX() int { return len(es) - es.stats().NY } // LenY is the length of the Y list. func (es EditScript) LenY() int { return len(es) - es.stats().NX } // EqualFunc reports whether the symbols at indexes ix and iy are equal. // When called by Difference, the index is guaranteed to be within nx and ny. type EqualFunc func(ix int, iy int) Result // Result is the result of comparison. // NumSame is the number of sub-elements that are equal. // NumDiff is the number of sub-elements that are not equal. type Result struct{ NumSame, NumDiff int } // BoolResult returns a Result that is either Equal or not Equal. func BoolResult(b bool) Result { if b { return Result{NumSame: 1} // Equal, Similar } else { return Result{NumDiff: 2} // Not Equal, not Similar } } // Equal indicates whether the symbols are equal. Two symbols are equal // if and only if NumDiff == 0. If Equal, then they are also Similar. func (r Result) Equal() bool { return r.NumDiff == 0 } // Similar indicates whether two symbols are similar and may be represented // by using the Modified type. As a special case, we consider binary comparisons // (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar. // // The exact ratio of NumSame to NumDiff to determine similarity may change. func (r Result) Similar() bool { // Use NumSame+1 to offset NumSame so that binary comparisons are similar. return r.NumSame+1 >= r.NumDiff } // Difference reports whether two lists of lengths nx and ny are equal // given the definition of equality provided as f. // // This function returns an edit-script, which is a sequence of operations // needed to convert one list into the other. The following invariants for // the edit-script are maintained: // • eq == (es.Dist()==0) // • nx == es.LenX() // • ny == es.LenY() // // This algorithm is not guaranteed to be an optimal solution (i.e., one that // produces an edit-script with a minimal Levenshtein distance). This algorithm // favors performance over optimality. The exact output is not guaranteed to // be stable and may change over time. func Difference(nx, ny int, f EqualFunc) (es EditScript) { // This algorithm is based on traversing what is known as an "edit-graph". // See Figure 1 from "An O(ND) Difference Algorithm and Its Variations" // by Eugene W. Myers. Since D can be as large as N itself, this is // effectively O(N^2). Unlike the algorithm from that paper, we are not // interested in the optimal path, but at least some "decent" path. // // For example, let X and Y be lists of symbols: // X = [A B C A B B A] // Y = [C B A B A C] // // The edit-graph can be drawn as the following: // A B C A B B A // ┌─────────────┐ // C │_|_|\|_|_|_|_│ 0 // B │_|\|_|_|\|\|_│ 1 // A │\|_|_|\|_|_|\│ 2 // B │_|\|_|_|\|\|_│ 3 // A │\|_|_|\|_|_|\│ 4 // C │ | |\| | | | │ 5 // └─────────────┘ 6 // 0 1 2 3 4 5 6 7 // // List X is written along the horizontal axis, while list Y is written // along the vertical axis. At any point on this grid, if the symbol in // list X matches the corresponding symbol in list Y, then a '\' is drawn. // The goal of any minimal edit-script algorithm is to find a path from the // top-left corner to the bottom-right corner, while traveling through the // fewest horizontal or vertical edges. // A horizontal edge is equivalent to inserting a symbol from list X. // A vertical edge is equivalent to inserting a symbol from list Y. // A diagonal edge is equivalent to a matching symbol between both X and Y. // Invariants: // • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx // • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny // // In general: // • fwdFrontier.X < revFrontier.X // • fwdFrontier.Y < revFrontier.Y // Unless, it is time for the algorithm to terminate. fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)} revPath := path{-1, point{nx, ny}, make(EditScript, 0)} fwdFrontier := fwdPath.point // Forward search frontier revFrontier := revPath.point // Reverse search frontier // Search budget bounds the cost of searching for better paths. // The longest sequence of non-matching symbols that can be tolerated is // approximately the square-root of the search budget. searchBudget := 4 * (nx + ny) // O(n) // The algorithm below is a greedy, meet-in-the-middle algorithm for // computing sub-optimal edit-scripts between two lists. // // The algorithm is approximately as follows: // • Searching for differences switches back-and-forth between // a search that starts at the beginning (the top-left corner), and // a search that starts at the end (the bottom-right corner). The goal of // the search is connect with the search from the opposite corner. // • As we search, we build a path in a greedy manner, where the first // match seen is added to the path (this is sub-optimal, but provides a // decent result in practice). When matches are found, we try the next pair // of symbols in the lists and follow all matches as far as possible. // • When searching for matches, we search along a diagonal going through // through the "frontier" point. If no matches are found, we advance the // frontier towards the opposite corner. // • This algorithm terminates when either the X coordinates or the // Y coordinates of the forward and reverse frontier points ever intersect. // // This algorithm is correct even if searching only in the forward direction // or in the reverse direction. We do both because it is commonly observed // that two lists commonly differ because elements were added to the front // or end of the other list. // // Running the tests with the "cmp_debug" build tag prints a visualization // of the algorithm running in real-time. This is educational for // understanding how the algorithm works. See debug_enable.go. f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es) for { // Forward search from the beginning. if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 { break } for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ { // Search in a diagonal pattern for a match. z := zigzag(i) p := point{fwdFrontier.X + z, fwdFrontier.Y - z} switch { case p.X >= revPath.X || p.Y < fwdPath.Y: stop1 = true // Hit top-right corner case p.Y >= revPath.Y || p.X < fwdPath.X: stop2 = true // Hit bottom-left corner case f(p.X, p.Y).Equal(): // Match found, so connect the path to this point. fwdPath.connect(p, f) fwdPath.append(Identity) // Follow sequence of matches as far as possible. for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y { if !f(fwdPath.X, fwdPath.Y).Equal() { break } fwdPath.append(Identity) } fwdFrontier = fwdPath.point stop1, stop2 = true, true default: searchBudget-- // Match not found } debug.Update() } // Advance the frontier towards reverse point. if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y { fwdFrontier.X++ } else { fwdFrontier.Y++ } // Reverse search from the end. if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 { break } for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ { // Search in a diagonal pattern for a match. z := zigzag(i) p := point{revFrontier.X - z, revFrontier.Y + z} switch { case fwdPath.X >= p.X || revPath.Y < p.Y: stop1 = true // Hit bottom-left corner case fwdPath.Y >= p.Y || revPath.X < p.X: stop2 = true // Hit top-right corner case f(p.X-1, p.Y-1).Equal(): // Match found, so connect the path to this point. revPath.connect(p, f) revPath.append(Identity) // Follow sequence of matches as far as possible. for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y { if !f(revPath.X-1, revPath.Y-1).Equal() { break } revPath.append(Identity) } revFrontier = revPath.point stop1, stop2 = true, true default: searchBudget-- // Match not found } debug.Update() } // Advance the frontier towards forward point. if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y { revFrontier.X-- } else { revFrontier.Y-- } } // Join the forward and reverse paths and then append the reverse path. fwdPath.connect(revPath.point, f) for i := len(revPath.es) - 1; i >= 0; i-- { t := revPath.es[i] revPath.es = revPath.es[:i] fwdPath.append(t) } debug.Finish() return fwdPath.es } type path struct { dir int // +1 if forward, -1 if reverse point // Leading point of the EditScript path es EditScript } // connect appends any necessary Identity, Modified, UniqueX, or UniqueY types // to the edit-script to connect p.point to dst. func (p *path) connect(dst point, f EqualFunc) { if p.dir > 0 { // Connect in forward direction. for dst.X > p.X && dst.Y > p.Y { switch r := f(p.X, p.Y); { case r.Equal(): p.append(Identity) case r.Similar(): p.append(Modified) case dst.X-p.X >= dst.Y-p.Y: p.append(UniqueX) default: p.append(UniqueY) } } for dst.X > p.X { p.append(UniqueX) } for dst.Y > p.Y { p.append(UniqueY) } } else { // Connect in reverse direction. for p.X > dst.X && p.Y > dst.Y { switch r := f(p.X-1, p.Y-1); { case r.Equal(): p.append(Identity) case r.Similar(): p.append(Modified) case p.Y-dst.Y >= p.X-dst.X: p.append(UniqueY) default: p.append(UniqueX) } } for p.X > dst.X { p.append(UniqueX) } for p.Y > dst.Y { p.append(UniqueY) } } } func (p *path) append(t EditType) { p.es = append(p.es, t) switch t { case Identity, Modified: p.add(p.dir, p.dir) case UniqueX: p.add(p.dir, 0) case UniqueY: p.add(0, p.dir) } debug.Update() } type point struct{ X, Y int } func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy } // zigzag maps a consecutive sequence of integers to a zig-zag sequence. // [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...] func zigzag(x int) int { if x&1 != 0 { x = ^x } return x >> 1 } go-cmp-0.3.1/cmp/internal/diff/diff_test.go000066400000000000000000000260061352211333400204770ustar00rootroot00000000000000// 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 diff import ( "fmt" "math/rand" "strings" "testing" "unicode" ) func TestDifference(t *testing.T) { tests := []struct { // Before passing x and y to Difference, we strip all spaces so that // they can be used by the test author to indicate a missing symbol // in one of the lists. x, y string want string }{{ x: "", y: "", want: "", }, { x: "#", y: "#", want: ".", }, { x: "##", y: "# ", want: ".X", }, { x: "a#", y: "A ", want: "MX", }, { x: "#a", y: " A", want: "XM", }, { x: "# ", y: "##", want: ".Y", }, { x: " #", y: "@#", want: "Y.", }, { x: "@#", y: " #", want: "X.", }, { x: "##########0123456789", y: " 0123456789", want: "XXXXXXXXXX..........", }, { x: " 0123456789", y: "##########0123456789", want: "YYYYYYYYYY..........", }, { x: "#####0123456789#####", y: " 0123456789 ", want: "XXXXX..........XXXXX", }, { x: " 0123456789 ", y: "#####0123456789#####", want: "YYYYY..........YYYYY", }, { x: "01234##########56789", y: "01234 56789", want: ".....XXXXXXXXXX.....", }, { x: "01234 56789", y: "01234##########56789", want: ".....YYYYYYYYYY.....", }, { x: "0123456789##########", y: "0123456789 ", want: "..........XXXXXXXXXX", }, { x: "0123456789 ", y: "0123456789##########", want: "..........YYYYYYYYYY", }, { x: "abcdefghij0123456789", y: "ABCDEFGHIJ0123456789", want: "MMMMMMMMMM..........", }, { x: "ABCDEFGHIJ0123456789", y: "abcdefghij0123456789", want: "MMMMMMMMMM..........", }, { x: "01234abcdefghij56789", y: "01234ABCDEFGHIJ56789", want: ".....MMMMMMMMMM.....", }, { x: "01234ABCDEFGHIJ56789", y: "01234abcdefghij56789", want: ".....MMMMMMMMMM.....", }, { x: "0123456789abcdefghij", y: "0123456789ABCDEFGHIJ", want: "..........MMMMMMMMMM", }, { x: "0123456789ABCDEFGHIJ", y: "0123456789abcdefghij", want: "..........MMMMMMMMMM", }, { x: "ABCDEFGHIJ0123456789 ", y: " 0123456789abcdefghij", want: "XXXXXXXXXX..........YYYYYYYYYY", }, { x: " 0123456789abcdefghij", y: "ABCDEFGHIJ0123456789 ", want: "YYYYYYYYYY..........XXXXXXXXXX", }, { x: "ABCDE0123456789 FGHIJ", y: " 0123456789abcdefghij", want: "XXXXX..........YYYYYMMMMM", }, { x: " 0123456789abcdefghij", y: "ABCDE0123456789 FGHIJ", want: "YYYYY..........XXXXXMMMMM", }, { x: "ABCDE01234F G H I J 56789 ", y: " 01234 a b c d e56789fghij", want: "XXXXX.....XYXYXYXYXY.....YYYYY", }, { x: " 01234a b c d e 56789fghij", y: "ABCDE01234 F G H I J56789 ", want: "YYYYY.....XYXYXYXYXY.....XXXXX", }, { x: "FGHIJ01234ABCDE56789 ", y: " 01234abcde56789fghij", want: "XXXXX.....MMMMM.....YYYYY", }, { x: " 01234abcde56789fghij", y: "FGHIJ01234ABCDE56789 ", want: "YYYYY.....MMMMM.....XXXXX", }, { x: "ABCAB BA ", y: " C BABAC", want: "XX.X.Y..Y", }, { x: "# #### ###", y: "#y####yy###", want: ".Y....YY...", }, { x: "# #### # ##x#x", y: "#y####y y## # ", want: ".Y....YXY..X.X", }, { x: "###z#z###### x #", y: "#y##Z#Z###### yy#", want: ".Y..M.M......XYY.", }, { x: "0 12z3x 456789 x x 0", y: "0y12Z3 y456789y y y0", want: ".Y..M.XY......YXYXY.", }, { x: "0 2 4 6 8 ..................abXXcdEXF.ghXi", y: " 1 3 5 7 9..................AB CDE F.GH I", want: "XYXYXYXYXY..................MMXXMM.X..MMXM", }, { x: "I HG.F EDC BA..................9 7 5 3 1 ", y: "iXhg.FXEdcXXba.................. 8 6 4 2 0", want: "MYMM..Y.MMYYMM..................XYXYXYXYXY", }, { x: "x1234", y: " 1234", want: "X....", }, { x: "x123x4", y: " 123 4", want: "X...X.", }, { x: "x1234x56", y: " 1234 ", want: "X....XXX", }, { x: "x1234xxx56", y: " 1234 56", want: "X....XXX..", }, { x: ".1234...ab", y: " 1234 AB", want: "X....XXXMM", }, { x: "x1234xxab.", y: " 1234 AB ", want: "X....XXMMX", }, { x: " 0123456789", y: "9012345678 ", want: "Y.........X", }, { x: " 0123456789", y: "8901234567 ", want: "YY........XX", }, { x: " 0123456789", y: "7890123456 ", want: "YYY.......XXX", }, { x: " 0123456789", y: "6789012345 ", want: "YYYY......XXXX", }, { x: "0123456789 ", y: " 5678901234", want: "XXXXX.....YYYYY", }, { x: "0123456789 ", y: " 4567890123", want: "XXXX......YYYY", }, { x: "0123456789 ", y: " 3456789012", want: "XXX.......YYY", }, { x: "0123456789 ", y: " 2345678901", want: "XX........YY", }, { x: "0123456789 ", y: " 1234567890", want: "X.........Y", }, { x: "0 1 2 3 45 6 7 8 9 ", y: " 9 8 7 6 54 3 2 1 0", want: "XYXYXYXYX.YXYXYXYXY", }, { x: "0 1 2345678 9 ", y: " 6 72 5 819034", want: "XYXY.XX.XX.Y.YYY", }, { x: "F B Q M O I G T L N72X90 E 4S P 651HKRJU DA 83CVZW", y: " 5 W H XO10R9IV K ZLCTAJ8P3N SEQM4 7 2G6 UBD F ", want: "XYXYXYXY.YYYY.YXYXY.YYYYYYY.XXXXXY.YY.XYXYY.XXXXXX.Y.XYXXXXXX", }} for _, tt := range tests { t.Run("", func(t *testing.T) { x := strings.Replace(tt.x, " ", "", -1) y := strings.Replace(tt.y, " ", "", -1) es := testStrings(t, x, y) if got := es.String(); got != tt.want { t.Errorf("Difference(%s, %s):\ngot %s\nwant %s", x, y, got, tt.want) } }) } } func TestDifferenceFuzz(t *testing.T) { tests := []struct{ px, py, pm float32 }{ {px: 0.0, py: 0.0, pm: 0.1}, {px: 0.0, py: 0.1, pm: 0.0}, {px: 0.1, py: 0.0, pm: 0.0}, {px: 0.0, py: 0.1, pm: 0.1}, {px: 0.1, py: 0.0, pm: 0.1}, {px: 0.2, py: 0.2, pm: 0.2}, {px: 0.3, py: 0.1, pm: 0.2}, {px: 0.1, py: 0.3, pm: 0.2}, {px: 0.2, py: 0.2, pm: 0.2}, {px: 0.3, py: 0.3, pm: 0.3}, {px: 0.1, py: 0.1, pm: 0.5}, {px: 0.4, py: 0.1, pm: 0.5}, {px: 0.3, py: 0.2, pm: 0.5}, {px: 0.2, py: 0.3, pm: 0.5}, {px: 0.1, py: 0.4, pm: 0.5}, } for i, tt := range tests { t.Run(fmt.Sprintf("P%d", i), func(t *testing.T) { // Sweep from 1B to 1KiB. for n := 1; n <= 1024; n <<= 1 { t.Run(fmt.Sprintf("N%d", n), func(t *testing.T) { for j := 0; j < 10; j++ { x, y := generateStrings(n, tt.px, tt.py, tt.pm, int64(j)) testStrings(t, x, y) } }) } }) } } func BenchmarkDifference(b *testing.B) { for n := 1 << 10; n <= 1<<20; n <<= 2 { b.Run(fmt.Sprintf("N%d", n), func(b *testing.B) { x, y := generateStrings(n, 0.05, 0.05, 0.10, 0) b.ReportAllocs() b.SetBytes(int64(len(x) + len(y))) for i := 0; i < b.N; i++ { Difference(len(x), len(y), func(ix, iy int) Result { return compareByte(x[ix], y[iy]) }) } }) } } func generateStrings(n int, px, py, pm float32, seed int64) (string, string) { if px+py+pm > 1.0 { panic("invalid probabilities") } py += px pm += py b := make([]byte, n) r := rand.New(rand.NewSource(seed)) r.Read(b) var x, y []byte for len(b) > 0 { switch p := r.Float32(); { case p < px: // UniqueX x = append(x, b[0]) case p < py: // UniqueY y = append(y, b[0]) case p < pm: // Modified x = append(x, 'A'+(b[0]%26)) y = append(y, 'a'+(b[0]%26)) default: // Identity x = append(x, b[0]) y = append(y, b[0]) } b = b[1:] } return string(x), string(y) } func testStrings(t *testing.T, x, y string) EditScript { es := Difference(len(x), len(y), func(ix, iy int) Result { return compareByte(x[ix], y[iy]) }) if es.LenX() != len(x) { t.Errorf("es.LenX = %d, want %d", es.LenX(), len(x)) } if es.LenY() != len(y) { t.Errorf("es.LenY = %d, want %d", es.LenY(), len(y)) } if !validateScript(x, y, es) { t.Errorf("invalid edit script: %v", es) } return es } func validateScript(x, y string, es EditScript) bool { var bx, by []byte for _, e := range es { switch e { case Identity: if !compareByte(x[len(bx)], y[len(by)]).Equal() { return false } bx = append(bx, x[len(bx)]) by = append(by, y[len(by)]) case UniqueX: bx = append(bx, x[len(bx)]) case UniqueY: by = append(by, y[len(by)]) case Modified: if !compareByte(x[len(bx)], y[len(by)]).Similar() { return false } bx = append(bx, x[len(bx)]) by = append(by, y[len(by)]) } } return string(bx) == x && string(by) == y } // compareByte returns a Result where the result is Equal if x == y, // similar if x and y differ only in casing, and different otherwise. func compareByte(x, y byte) (r Result) { switch { case x == y: return equalResult // Identity case unicode.ToUpper(rune(x)) == unicode.ToUpper(rune(y)): return similarResult // Modified default: return differentResult // UniqueX or UniqueY } } var ( equalResult = Result{NumDiff: 0} similarResult = Result{NumDiff: 1} differentResult = Result{NumDiff: 2} ) func TestResult(t *testing.T) { tests := []struct { result Result wantEqual bool wantSimilar bool }{ // equalResult is equal since NumDiff == 0, by definition of Equal method. {equalResult, true, true}, // similarResult is similar since it is a binary result where only one // element was compared (i.e., Either NumSame==1 or NumDiff==1). {similarResult, false, true}, // differentResult is different since there are enough differences that // it isn't even considered similar. {differentResult, false, false}, // Zero value is always equal. {Result{NumSame: 0, NumDiff: 0}, true, true}, // Binary comparisons (where NumSame+NumDiff == 1) are always similar. {Result{NumSame: 1, NumDiff: 0}, true, true}, {Result{NumSame: 0, NumDiff: 1}, false, true}, // More complex ratios. The exact ratio for similarity may change, // and may require updates to these test cases. {Result{NumSame: 1, NumDiff: 1}, false, true}, {Result{NumSame: 1, NumDiff: 2}, false, true}, {Result{NumSame: 1, NumDiff: 3}, false, false}, {Result{NumSame: 2, NumDiff: 1}, false, true}, {Result{NumSame: 2, NumDiff: 2}, false, true}, {Result{NumSame: 2, NumDiff: 3}, false, true}, {Result{NumSame: 3, NumDiff: 1}, false, true}, {Result{NumSame: 3, NumDiff: 2}, false, true}, {Result{NumSame: 3, NumDiff: 3}, false, true}, {Result{NumSame: 1000, NumDiff: 0}, true, true}, {Result{NumSame: 1000, NumDiff: 1}, false, true}, {Result{NumSame: 1000, NumDiff: 2}, false, true}, {Result{NumSame: 0, NumDiff: 1000}, false, false}, {Result{NumSame: 1, NumDiff: 1000}, false, false}, {Result{NumSame: 2, NumDiff: 1000}, false, false}, } for _, tt := range tests { if got := tt.result.Equal(); got != tt.wantEqual { t.Errorf("%#v.Equal() = %v, want %v", tt.result, got, tt.wantEqual) } if got := tt.result.Similar(); got != tt.wantSimilar { t.Errorf("%#v.Similar() = %v, want %v", tt.result, got, tt.wantSimilar) } } } go-cmp-0.3.1/cmp/internal/flags/000077500000000000000000000000001352211333400163615ustar00rootroot00000000000000go-cmp-0.3.1/cmp/internal/flags/flags.go000066400000000000000000000004721352211333400200070ustar00rootroot00000000000000// Copyright 2019, 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 flags // Deterministic controls whether the output of Diff should be deterministic. // This is only used for testing. var Deterministic bool go-cmp-0.3.1/cmp/internal/flags/toolchain_legacy.go000066400000000000000000000004471352211333400222210ustar00rootroot00000000000000// Copyright 2019, 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. // +build !go1.10 package flags // AtLeastGo110 reports whether the Go toolchain is at least Go 1.10. const AtLeastGo110 = false go-cmp-0.3.1/cmp/internal/flags/toolchain_recent.go000066400000000000000000000004451352211333400222330ustar00rootroot00000000000000// Copyright 2019, 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. // +build go1.10 package flags // AtLeastGo110 reports whether the Go toolchain is at least Go 1.10. const AtLeastGo110 = true go-cmp-0.3.1/cmp/internal/function/000077500000000000000000000000001352211333400171125ustar00rootroot00000000000000go-cmp-0.3.1/cmp/internal/function/func.go000066400000000000000000000050261352211333400203770ustar00rootroot00000000000000// 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 function provides functionality for identifying function types. package function import ( "reflect" "regexp" "runtime" "strings" ) type funcType int const ( _ funcType = iota tbFunc // func(T) bool ttbFunc // func(T, T) bool trbFunc // func(T, R) bool tibFunc // func(T, I) bool trFunc // func(T) R Equal = ttbFunc // func(T, T) bool EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool Transformer = trFunc // func(T) R ValueFilter = ttbFunc // func(T, T) bool Less = ttbFunc // func(T, T) bool ValuePredicate = tbFunc // func(T) bool KeyValuePredicate = trbFunc // func(T, R) bool ) var boolType = reflect.TypeOf(true) // IsType reports whether the reflect.Type is of the specified function type. func IsType(t reflect.Type, ft funcType) bool { if t == nil || t.Kind() != reflect.Func || t.IsVariadic() { return false } ni, no := t.NumIn(), t.NumOut() switch ft { case tbFunc: // func(T) bool if ni == 1 && no == 1 && t.Out(0) == boolType { return true } case ttbFunc: // func(T, T) bool if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType { return true } case trbFunc: // func(T, R) bool if ni == 2 && no == 1 && t.Out(0) == boolType { return true } case tibFunc: // func(T, I) bool if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType { return true } case trFunc: // func(T) R if ni == 1 && no == 1 { return true } } return false } var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`) // NameOf returns the name of the function value. func NameOf(v reflect.Value) string { fnc := runtime.FuncForPC(v.Pointer()) if fnc == nil { return "" } fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm" // Method closures have a "-fm" suffix. fullName = strings.TrimSuffix(fullName, "-fm") var name string for len(fullName) > 0 { inParen := strings.HasSuffix(fullName, ")") fullName = strings.TrimSuffix(fullName, ")") s := lastIdentRx.FindString(fullName) if s == "" { break } name = s + "." + name fullName = strings.TrimSuffix(fullName, s) if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 { fullName = fullName[:i] } fullName = strings.TrimSuffix(fullName, ".") } return strings.TrimSuffix(name, ".") } go-cmp-0.3.1/cmp/internal/function/func_test.go000066400000000000000000000030461352211333400214360ustar00rootroot00000000000000// Copyright 2019, 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 function import ( "bytes" "reflect" "testing" ) type myType struct{ bytes.Buffer } func (myType) valueMethod() {} func (myType) ValueMethod() {} func (*myType) pointerMethod() {} func (*myType) PointerMethod() {} func TestNameOf(t *testing.T) { tests := []struct { fnc interface{} want string }{ {TestNameOf, "function.TestNameOf"}, {func() {}, "function.TestNameOf.func1"}, {(myType).valueMethod, "function.myType.valueMethod"}, {(myType).ValueMethod, "function.myType.ValueMethod"}, {(myType{}).valueMethod, "function.myType.valueMethod"}, {(myType{}).ValueMethod, "function.myType.ValueMethod"}, {(*myType).valueMethod, "function.myType.valueMethod"}, {(*myType).ValueMethod, "function.myType.ValueMethod"}, {(&myType{}).valueMethod, "function.myType.valueMethod"}, {(&myType{}).ValueMethod, "function.myType.ValueMethod"}, {(*myType).pointerMethod, "function.myType.pointerMethod"}, {(*myType).PointerMethod, "function.myType.PointerMethod"}, {(&myType{}).pointerMethod, "function.myType.pointerMethod"}, {(&myType{}).PointerMethod, "function.myType.PointerMethod"}, {(*myType).Write, "function.myType.Write"}, {(&myType{}).Write, "bytes.Buffer.Write"}, } for _, tt := range tests { t.Run("", func(t *testing.T) { got := NameOf(reflect.ValueOf(tt.fnc)) if got != tt.want { t.Errorf("NameOf() = %v, want %v", got, tt.want) } }) } } go-cmp-0.3.1/cmp/internal/testprotos/000077500000000000000000000000001352211333400175135ustar00rootroot00000000000000go-cmp-0.3.1/cmp/internal/testprotos/protos.go000066400000000000000000000031021352211333400213640ustar00rootroot00000000000000// 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 testprotos func Equal(x, y Message) bool { if x == nil || y == nil { return x == nil && y == nil } return x.String() == y.String() } type Message interface { Proto() String() string } type proto interface { Proto() } type notComparable struct { unexportedField func() } type Stringer struct{ X string } func (s *Stringer) String() string { return s.X } // Project1 protocol buffers type ( Eagle_States int Eagle_MissingCalls int Dreamer_States int Dreamer_MissingCalls int Slap_States int Goat_States int Donkey_States int SummerType int Eagle struct { proto notComparable Stringer } Dreamer struct { proto notComparable Stringer } Slap struct { proto notComparable Stringer } Goat struct { proto notComparable Stringer } Donkey struct { proto notComparable Stringer } ) // Project2 protocol buffers type ( Germ struct { proto notComparable Stringer } Dish struct { proto notComparable Stringer } ) // Project3 protocol buffers type ( Dirt struct { proto notComparable Stringer } Wizard struct { proto notComparable Stringer } Sadistic struct { proto notComparable Stringer } ) // Project4 protocol buffers type ( HoneyStatus int PoisonType int MetaData struct { proto notComparable Stringer } Restrictions struct { proto notComparable Stringer } ) go-cmp-0.3.1/cmp/internal/teststructs/000077500000000000000000000000001352211333400176745ustar00rootroot00000000000000go-cmp-0.3.1/cmp/internal/teststructs/project1.go000066400000000000000000000132751352211333400217620ustar00rootroot00000000000000// 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 teststructs import ( "time" pb "github.com/google/go-cmp/cmp/internal/testprotos" ) // This is an sanitized example of equality from a real use-case. // The original equality function was as follows: /* func equalEagle(x, y Eagle) bool { if x.Name != y.Name && !reflect.DeepEqual(x.Hounds, y.Hounds) && x.Desc != y.Desc && x.DescLong != y.DescLong && x.Prong != y.Prong && x.StateGoverner != y.StateGoverner && x.PrankRating != y.PrankRating && x.FunnyPrank != y.FunnyPrank && !pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) { return false } if len(x.Dreamers) != len(y.Dreamers) { return false } for i := range x.Dreamers { if !equalDreamer(x.Dreamers[i], y.Dreamers[i]) { return false } } if len(x.Slaps) != len(y.Slaps) { return false } for i := range x.Slaps { if !equalSlap(x.Slaps[i], y.Slaps[i]) { return false } } return true } func equalDreamer(x, y Dreamer) bool { if x.Name != y.Name || x.Desc != y.Desc || x.DescLong != y.DescLong || x.ContSlapsInterval != y.ContSlapsInterval || x.Ornamental != y.Ornamental || x.Amoeba != y.Amoeba || x.Heroes != y.Heroes || x.FloppyDisk != y.FloppyDisk || x.MightiestDuck != y.MightiestDuck || x.FunnyPrank != y.FunnyPrank || !pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) { return false } if len(x.Animal) != len(y.Animal) { return false } for i := range x.Animal { vx := x.Animal[i] vy := y.Animal[i] if reflect.TypeOf(x.Animal) != reflect.TypeOf(y.Animal) { return false } switch vx.(type) { case Goat: if !equalGoat(vx.(Goat), vy.(Goat)) { return false } case Donkey: if !equalDonkey(vx.(Donkey), vy.(Donkey)) { return false } default: panic(fmt.Sprintf("unknown type: %T", vx)) } } if len(x.PreSlaps) != len(y.PreSlaps) { return false } for i := range x.PreSlaps { if !equalSlap(x.PreSlaps[i], y.PreSlaps[i]) { return false } } if len(x.ContSlaps) != len(y.ContSlaps) { return false } for i := range x.ContSlaps { if !equalSlap(x.ContSlaps[i], y.ContSlaps[i]) { return false } } return true } func equalSlap(x, y Slap) bool { return x.Name == y.Name && x.Desc == y.Desc && x.DescLong == y.DescLong && pb.Equal(x.Args, y.Args) && x.Tense == y.Tense && x.Interval == y.Interval && x.Homeland == y.Homeland && x.FunnyPrank == y.FunnyPrank && pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) } func equalGoat(x, y Goat) bool { if x.Target != y.Target || x.FunnyPrank != y.FunnyPrank || !pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) { return false } if len(x.Slaps) != len(y.Slaps) { return false } for i := range x.Slaps { if !equalSlap(x.Slaps[i], y.Slaps[i]) { return false } } return true } func equalDonkey(x, y Donkey) bool { return x.Pause == y.Pause && x.Sleep == y.Sleep && x.FunnyPrank == y.FunnyPrank && pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) } */ type Eagle struct { Name string Hounds []string Desc string DescLong string Dreamers []Dreamer Prong int64 Slaps []Slap StateGoverner string PrankRating string FunnyPrank string Immutable *EagleImmutable } type EagleImmutable struct { ID string State *pb.Eagle_States MissingCall *pb.Eagle_MissingCalls Birthday time.Time Death time.Time Started time.Time LastUpdate time.Time Creator string empty bool } type Dreamer struct { Name string Desc string DescLong string PreSlaps []Slap ContSlaps []Slap ContSlapsInterval int32 Animal []interface{} // Could be either Goat or Donkey Ornamental bool Amoeba int64 Heroes int32 FloppyDisk int32 MightiestDuck bool FunnyPrank string Immutable *DreamerImmutable } type DreamerImmutable struct { ID string State *pb.Dreamer_States MissingCall *pb.Dreamer_MissingCalls Calls int32 Started time.Time Stopped time.Time LastUpdate time.Time empty bool } type Slap struct { Name string Desc string DescLong string Args pb.Message Tense int32 Interval int32 Homeland uint32 FunnyPrank string Immutable *SlapImmutable } type SlapImmutable struct { ID string Out pb.Message MildSlap bool PrettyPrint string State *pb.Slap_States Started time.Time Stopped time.Time LastUpdate time.Time LoveRadius *LoveRadius empty bool } type Goat struct { Target string Slaps []Slap FunnyPrank string Immutable *GoatImmutable } type GoatImmutable struct { ID string State *pb.Goat_States Started time.Time Stopped time.Time LastUpdate time.Time empty bool } type Donkey struct { Pause bool Sleep int32 FunnyPrank string Immutable *DonkeyImmutable } type DonkeyImmutable struct { ID string State *pb.Donkey_States Started time.Time Stopped time.Time LastUpdate time.Time empty bool } type LoveRadius struct { Summer *SummerLove empty bool } type SummerLove struct { Summary *SummerLoveSummary empty bool } type SummerLoveSummary struct { Devices []string ChangeType []pb.SummerType empty bool } func (EagleImmutable) Proto() *pb.Eagle { return nil } func (DreamerImmutable) Proto() *pb.Dreamer { return nil } func (SlapImmutable) Proto() *pb.Slap { return nil } func (GoatImmutable) Proto() *pb.Goat { return nil } func (DonkeyImmutable) Proto() *pb.Donkey { return nil } go-cmp-0.3.1/cmp/internal/teststructs/project2.go000066400000000000000000000035501352211333400217560ustar00rootroot00000000000000// 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 teststructs import ( "time" pb "github.com/google/go-cmp/cmp/internal/testprotos" ) // This is an sanitized example of equality from a real use-case. // The original equality function was as follows: /* func equalBatch(b1, b2 *GermBatch) bool { for _, b := range []*GermBatch{b1, b2} { for _, l := range b.DirtyGerms { sort.Slice(l, func(i, j int) bool { return l[i].String() < l[j].String() }) } for _, l := range b.CleanGerms { sort.Slice(l, func(i, j int) bool { return l[i].String() < l[j].String() }) } } if !pb.DeepEqual(b1.DirtyGerms, b2.DirtyGerms) || !pb.DeepEqual(b1.CleanGerms, b2.CleanGerms) || !pb.DeepEqual(b1.GermMap, b2.GermMap) { return false } if len(b1.DishMap) != len(b2.DishMap) { return false } for id := range b1.DishMap { kpb1, err1 := b1.DishMap[id].Proto() kpb2, err2 := b2.DishMap[id].Proto() if !pb.Equal(kpb1, kpb2) || !reflect.DeepEqual(err1, err2) { return false } } return b1.HasPreviousResult == b2.HasPreviousResult && b1.DirtyID == b2.DirtyID && b1.CleanID == b2.CleanID && b1.GermStrain == b2.GermStrain && b1.TotalDirtyGerms == b2.TotalDirtyGerms && b1.InfectedAt.Equal(b2.InfectedAt) } */ type GermBatch struct { DirtyGerms, CleanGerms map[int32][]*pb.Germ GermMap map[int32]*pb.Germ DishMap map[int32]*Dish HasPreviousResult bool DirtyID, CleanID int32 GermStrain int32 TotalDirtyGerms int InfectedAt time.Time } type Dish struct { pb *pb.Dish err error } func CreateDish(m *pb.Dish, err error) *Dish { return &Dish{pb: m, err: err} } func (d *Dish) Proto() (*pb.Dish, error) { if d.err != nil { return nil, d.err } return d.pb, nil } go-cmp-0.3.1/cmp/internal/teststructs/project3.go000066400000000000000000000040041352211333400217520ustar00rootroot00000000000000// 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 teststructs import ( "sync" pb "github.com/google/go-cmp/cmp/internal/testprotos" ) // This is an sanitized example of equality from a real use-case. // The original equality function was as follows: /* func equalDirt(x, y *Dirt) bool { if !reflect.DeepEqual(x.table, y.table) || !reflect.DeepEqual(x.ts, y.ts) || x.Discord != y.Discord || !pb.Equal(&x.Proto, &y.Proto) || len(x.wizard) != len(y.wizard) || len(x.sadistic) != len(y.sadistic) || x.lastTime != y.lastTime { return false } for k, vx := range x.wizard { vy, ok := y.wizard[k] if !ok || !pb.Equal(vx, vy) { return false } } for k, vx := range x.sadistic { vy, ok := y.sadistic[k] if !ok || !pb.Equal(vx, vy) { return false } } return true } */ type FakeMutex struct { sync.Locker x struct{} } type Dirt struct { table Table // Always concrete type of MockTable ts Timestamp Discord DiscordState Proto pb.Dirt wizard map[string]*pb.Wizard sadistic map[string]*pb.Sadistic lastTime int64 mu FakeMutex } type DiscordState int type Timestamp int64 func (d *Dirt) SetTable(t Table) { d.table = t } func (d *Dirt) SetTimestamp(t Timestamp) { d.ts = t } func (d *Dirt) SetWizard(m map[string]*pb.Wizard) { d.wizard = m } func (d *Dirt) SetSadistic(m map[string]*pb.Sadistic) { d.sadistic = m } func (d *Dirt) SetLastTime(t int64) { d.lastTime = t } type Table interface { Operation1() error Operation2() error Operation3() error } type MockTable struct { state []string } func CreateMockTable(s []string) *MockTable { return &MockTable{s} } func (mt *MockTable) Operation1() error { return nil } func (mt *MockTable) Operation2() error { return nil } func (mt *MockTable) Operation3() error { return nil } func (mt *MockTable) State() []string { return mt.state } go-cmp-0.3.1/cmp/internal/teststructs/project4.go000066400000000000000000000132131352211333400217550ustar00rootroot00000000000000// 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 teststructs import ( "time" pb "github.com/google/go-cmp/cmp/internal/testprotos" ) // This is an sanitized example of equality from a real use-case. // The original equality function was as follows: /* func equalCartel(x, y Cartel) bool { if !(equalHeadquarter(x.Headquarter, y.Headquarter) && x.Source() == y.Source() && x.CreationDate().Equal(y.CreationDate()) && x.Boss() == y.Boss() && x.LastCrimeDate().Equal(y.LastCrimeDate())) { return false } if len(x.Poisons()) != len(y.Poisons()) { return false } for i := range x.Poisons() { if !equalPoison(*x.Poisons()[i], *y.Poisons()[i]) { return false } } return true } func equalHeadquarter(x, y Headquarter) bool { xr, yr := x.Restrictions(), y.Restrictions() return x.ID() == y.ID() && x.Location() == y.Location() && reflect.DeepEqual(x.SubDivisions(), y.SubDivisions()) && x.IncorporatedDate().Equal(y.IncorporatedDate()) && pb.Equal(x.MetaData(), y.MetaData()) && bytes.Equal(x.PrivateMessage(), y.PrivateMessage()) && bytes.Equal(x.PublicMessage(), y.PublicMessage()) && x.HorseBack() == y.HorseBack() && x.Rattle() == y.Rattle() && x.Convulsion() == y.Convulsion() && x.Expansion() == y.Expansion() && x.Status() == y.Status() && pb.Equal(&xr, &yr) && x.CreationTime().Equal(y.CreationTime()) } func equalPoison(x, y Poison) bool { return x.PoisonType() == y.PoisonType() && x.Expiration().Equal(y.Expiration()) && x.Manufacturer() == y.Manufacturer() && x.Potency() == y.Potency() } */ type Cartel struct { Headquarter source string creationDate time.Time boss string lastCrimeDate time.Time poisons []*Poison } func (p Cartel) Source() string { return p.source } func (p Cartel) CreationDate() time.Time { return p.creationDate } func (p Cartel) Boss() string { return p.boss } func (p Cartel) LastCrimeDate() time.Time { return p.lastCrimeDate } func (p Cartel) Poisons() []*Poison { return p.poisons } func (p *Cartel) SetSource(x string) { p.source = x } func (p *Cartel) SetCreationDate(x time.Time) { p.creationDate = x } func (p *Cartel) SetBoss(x string) { p.boss = x } func (p *Cartel) SetLastCrimeDate(x time.Time) { p.lastCrimeDate = x } func (p *Cartel) SetPoisons(x []*Poison) { p.poisons = x } type Headquarter struct { id uint64 location string subDivisions []string incorporatedDate time.Time metaData *pb.MetaData privateMessage []byte publicMessage []byte horseBack string rattle string convulsion bool expansion uint64 status pb.HoneyStatus restrictions pb.Restrictions creationTime time.Time } func (hq Headquarter) ID() uint64 { return hq.id } func (hq Headquarter) Location() string { return hq.location } func (hq Headquarter) SubDivisions() []string { return hq.subDivisions } func (hq Headquarter) IncorporatedDate() time.Time { return hq.incorporatedDate } func (hq Headquarter) MetaData() *pb.MetaData { return hq.metaData } func (hq Headquarter) PrivateMessage() []byte { return hq.privateMessage } func (hq Headquarter) PublicMessage() []byte { return hq.publicMessage } func (hq Headquarter) HorseBack() string { return hq.horseBack } func (hq Headquarter) Rattle() string { return hq.rattle } func (hq Headquarter) Convulsion() bool { return hq.convulsion } func (hq Headquarter) Expansion() uint64 { return hq.expansion } func (hq Headquarter) Status() pb.HoneyStatus { return hq.status } func (hq Headquarter) Restrictions() pb.Restrictions { return hq.restrictions } func (hq Headquarter) CreationTime() time.Time { return hq.creationTime } func (hq *Headquarter) SetID(x uint64) { hq.id = x } func (hq *Headquarter) SetLocation(x string) { hq.location = x } func (hq *Headquarter) SetSubDivisions(x []string) { hq.subDivisions = x } func (hq *Headquarter) SetIncorporatedDate(x time.Time) { hq.incorporatedDate = x } func (hq *Headquarter) SetMetaData(x *pb.MetaData) { hq.metaData = x } func (hq *Headquarter) SetPrivateMessage(x []byte) { hq.privateMessage = x } func (hq *Headquarter) SetPublicMessage(x []byte) { hq.publicMessage = x } func (hq *Headquarter) SetHorseBack(x string) { hq.horseBack = x } func (hq *Headquarter) SetRattle(x string) { hq.rattle = x } func (hq *Headquarter) SetConvulsion(x bool) { hq.convulsion = x } func (hq *Headquarter) SetExpansion(x uint64) { hq.expansion = x } func (hq *Headquarter) SetStatus(x pb.HoneyStatus) { hq.status = x } func (hq *Headquarter) SetRestrictions(x pb.Restrictions) { hq.restrictions = x } func (hq *Headquarter) SetCreationTime(x time.Time) { hq.creationTime = x } type Poison struct { poisonType pb.PoisonType expiration time.Time manufacturer string potency int } func (p Poison) PoisonType() pb.PoisonType { return p.poisonType } func (p Poison) Expiration() time.Time { return p.expiration } func (p Poison) Manufacturer() string { return p.manufacturer } func (p Poison) Potency() int { return p.potency } func (p *Poison) SetPoisonType(x pb.PoisonType) { p.poisonType = x } func (p *Poison) SetExpiration(x time.Time) { p.expiration = x } func (p *Poison) SetManufacturer(x string) { p.manufacturer = x } func (p *Poison) SetPotency(x int) { p.potency = x } go-cmp-0.3.1/cmp/internal/teststructs/structs.go000066400000000000000000000121701352211333400217330ustar00rootroot00000000000000// 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 teststructs type InterfaceA interface { InterfaceA() } type ( StructA struct{ X string } // Equal method on value receiver StructB struct{ X string } // Equal method on pointer receiver StructC struct{ X string } // Equal method (with interface argument) on value receiver StructD struct{ X string } // Equal method (with interface argument) on pointer receiver StructE struct{ X string } // Equal method (with interface argument on value receiver) on pointer receiver StructF struct{ X string } // Equal method (with interface argument on pointer receiver) on value receiver // These embed the above types as a value. StructA1 struct { StructA X string } StructB1 struct { StructB X string } StructC1 struct { StructC X string } StructD1 struct { StructD X string } StructE1 struct { StructE X string } StructF1 struct { StructF X string } // These embed the above types as a pointer. StructA2 struct { *StructA X string } StructB2 struct { *StructB X string } StructC2 struct { *StructC X string } StructD2 struct { *StructD X string } StructE2 struct { *StructE X string } StructF2 struct { *StructF X string } StructNo struct{ X string } // Equal method (with interface argument) on non-satisfying receiver AssignA func() int AssignB struct{ A int } AssignC chan bool AssignD <-chan bool ) func (x StructA) Equal(y StructA) bool { return true } func (x *StructB) Equal(y *StructB) bool { return true } func (x StructC) Equal(y InterfaceA) bool { return true } func (x StructC) InterfaceA() {} func (x *StructD) Equal(y InterfaceA) bool { return true } func (x *StructD) InterfaceA() {} func (x *StructE) Equal(y InterfaceA) bool { return true } func (x StructE) InterfaceA() {} func (x StructF) Equal(y InterfaceA) bool { return true } func (x *StructF) InterfaceA() {} func (x StructNo) Equal(y InterfaceA) bool { return true } func (x AssignA) Equal(y func() int) bool { return true } func (x AssignB) Equal(y struct{ A int }) bool { return true } func (x AssignC) Equal(y chan bool) bool { return true } func (x AssignD) Equal(y <-chan bool) bool { return true } var _ = func( a StructA, b StructB, c StructC, d StructD, e StructE, f StructF, ap *StructA, bp *StructB, cp *StructC, dp *StructD, ep *StructE, fp *StructF, a1 StructA1, b1 StructB1, c1 StructC1, d1 StructD1, e1 StructE1, f1 StructF1, a2 StructA2, b2 StructB2, c2 StructC2, d2 StructD2, e2 StructE2, f2 StructF1, ) { a.Equal(a) b.Equal(&b) c.Equal(c) d.Equal(&d) e.Equal(e) f.Equal(&f) ap.Equal(*ap) bp.Equal(bp) cp.Equal(*cp) dp.Equal(dp) ep.Equal(*ep) fp.Equal(fp) a1.Equal(a1.StructA) b1.Equal(&b1.StructB) c1.Equal(c1) d1.Equal(&d1) e1.Equal(e1) f1.Equal(&f1) a2.Equal(*a2.StructA) b2.Equal(b2.StructB) c2.Equal(c2) d2.Equal(&d2) e2.Equal(e2) f2.Equal(&f2) } type ( privateStruct struct{ Public, private int } PublicStruct struct{ Public, private int } ParentStructA struct{ privateStruct } ParentStructB struct{ PublicStruct } ParentStructC struct { privateStruct Public, private int } ParentStructD struct { PublicStruct Public, private int } ParentStructE struct { privateStruct PublicStruct } ParentStructF struct { privateStruct PublicStruct Public, private int } ParentStructG struct { *privateStruct } ParentStructH struct { *PublicStruct } ParentStructI struct { *privateStruct *PublicStruct } ParentStructJ struct { *privateStruct *PublicStruct Public PublicStruct private privateStruct } ) func NewParentStructG() *ParentStructG { return &ParentStructG{new(privateStruct)} } func NewParentStructH() *ParentStructH { return &ParentStructH{new(PublicStruct)} } func NewParentStructI() *ParentStructI { return &ParentStructI{new(privateStruct), new(PublicStruct)} } func NewParentStructJ() *ParentStructJ { return &ParentStructJ{ privateStruct: new(privateStruct), PublicStruct: new(PublicStruct), } } func (s *privateStruct) SetPrivate(i int) { s.private = i } func (s *PublicStruct) SetPrivate(i int) { s.private = i } func (s *ParentStructC) SetPrivate(i int) { s.private = i } func (s *ParentStructD) SetPrivate(i int) { s.private = i } func (s *ParentStructF) SetPrivate(i int) { s.private = i } func (s *ParentStructA) PrivateStruct() *privateStruct { return &s.privateStruct } func (s *ParentStructC) PrivateStruct() *privateStruct { return &s.privateStruct } func (s *ParentStructE) PrivateStruct() *privateStruct { return &s.privateStruct } func (s *ParentStructF) PrivateStruct() *privateStruct { return &s.privateStruct } func (s *ParentStructG) PrivateStruct() *privateStruct { return s.privateStruct } func (s *ParentStructI) PrivateStruct() *privateStruct { return s.privateStruct } func (s *ParentStructJ) PrivateStruct() *privateStruct { return s.privateStruct } func (s *ParentStructJ) Private() *privateStruct { return &s.private } go-cmp-0.3.1/cmp/internal/value/000077500000000000000000000000001352211333400164015ustar00rootroot00000000000000go-cmp-0.3.1/cmp/internal/value/pointer_purego.go000066400000000000000000000012371352211333400217740ustar00rootroot00000000000000// Copyright 2018, 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. // +build purego package value import "reflect" // Pointer is an opaque typed pointer and is guaranteed to be comparable. type Pointer struct { p uintptr t reflect.Type } // PointerOf returns a Pointer from v, which must be a // reflect.Ptr, reflect.Slice, or reflect.Map. func PointerOf(v reflect.Value) Pointer { // NOTE: Storing a pointer as an uintptr is technically incorrect as it // assumes that the GC implementation does not use a moving collector. return Pointer{v.Pointer(), v.Type()} } go-cmp-0.3.1/cmp/internal/value/pointer_unsafe.go000066400000000000000000000012621352211333400217520ustar00rootroot00000000000000// Copyright 2018, 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. // +build !purego package value import ( "reflect" "unsafe" ) // Pointer is an opaque typed pointer and is guaranteed to be comparable. type Pointer struct { p unsafe.Pointer t reflect.Type } // PointerOf returns a Pointer from v, which must be a // reflect.Ptr, reflect.Slice, or reflect.Map. func PointerOf(v reflect.Value) Pointer { // The proper representation of a pointer is unsafe.Pointer, // which is necessary if the GC ever uses a moving collector. return Pointer{unsafe.Pointer(v.Pointer()), v.Type()} } go-cmp-0.3.1/cmp/internal/value/sort.go000066400000000000000000000060521352211333400177220ustar00rootroot00000000000000// 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 value import ( "fmt" "math" "reflect" "sort" ) // SortKeys sorts a list of map keys, deduplicating keys if necessary. // The type of each value must be comparable. func SortKeys(vs []reflect.Value) []reflect.Value { if len(vs) == 0 { return vs } // Sort the map keys. sort.SliceStable(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) }) // Deduplicate keys (fails for NaNs). vs2 := vs[:1] for _, v := range vs[1:] { if isLess(vs2[len(vs2)-1], v) { vs2 = append(vs2, v) } } return vs2 } // isLess is a generic function for sorting arbitrary map keys. // The inputs must be of the same type and must be comparable. func isLess(x, y reflect.Value) bool { switch x.Type().Kind() { case reflect.Bool: return !x.Bool() && y.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return x.Int() < y.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return x.Uint() < y.Uint() case reflect.Float32, reflect.Float64: // NOTE: This does not sort -0 as less than +0 // since Go maps treat -0 and +0 as equal keys. fx, fy := x.Float(), y.Float() return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy) case reflect.Complex64, reflect.Complex128: cx, cy := x.Complex(), y.Complex() rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy) if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) { return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy) } return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry) case reflect.Ptr, reflect.UnsafePointer, reflect.Chan: return x.Pointer() < y.Pointer() case reflect.String: return x.String() < y.String() case reflect.Array: for i := 0; i < x.Len(); i++ { if isLess(x.Index(i), y.Index(i)) { return true } if isLess(y.Index(i), x.Index(i)) { return false } } return false case reflect.Struct: for i := 0; i < x.NumField(); i++ { if isLess(x.Field(i), y.Field(i)) { return true } if isLess(y.Field(i), x.Field(i)) { return false } } return false case reflect.Interface: vx, vy := x.Elem(), y.Elem() if !vx.IsValid() || !vy.IsValid() { return !vx.IsValid() && vy.IsValid() } tx, ty := vx.Type(), vy.Type() if tx == ty { return isLess(x.Elem(), y.Elem()) } if tx.Kind() != ty.Kind() { return vx.Kind() < vy.Kind() } if tx.String() != ty.String() { return tx.String() < ty.String() } if tx.PkgPath() != ty.PkgPath() { return tx.PkgPath() < ty.PkgPath() } // This can happen in rare situations, so we fallback to just comparing // the unique pointer for a reflect.Type. This guarantees deterministic // ordering within a program, but it is obviously not stable. return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer() default: // Must be Func, Map, or Slice; which are not comparable. panic(fmt.Sprintf("%T is not comparable", x.Type())) } } go-cmp-0.3.1/cmp/internal/value/sort_test.go000066400000000000000000000112721352211333400207610ustar00rootroot00000000000000// 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 value_test import ( "math" "reflect" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/internal/value" ) func TestSortKeys(t *testing.T) { type ( MyString string MyArray [2]int MyStruct struct { A MyString B MyArray C chan float64 } EmptyStruct struct{} ) opts := []cmp.Option{ cmp.Comparer(func(x, y float64) bool { if math.IsNaN(x) && math.IsNaN(y) { return true } return x == y }), cmp.Comparer(func(x, y complex128) bool { rx, ix, ry, iy := real(x), imag(x), real(y), imag(y) if math.IsNaN(rx) && math.IsNaN(ry) { rx, ry = 0, 0 } if math.IsNaN(ix) && math.IsNaN(iy) { ix, iy = 0, 0 } return rx == ry && ix == iy }), cmp.Comparer(func(x, y chan bool) bool { return true }), cmp.Comparer(func(x, y chan int) bool { return true }), cmp.Comparer(func(x, y chan float64) bool { return true }), cmp.Comparer(func(x, y chan interface{}) bool { return true }), cmp.Comparer(func(x, y *int) bool { return true }), } tests := []struct { in map[interface{}]bool // Set of keys to sort want []interface{} }{{ in: map[interface{}]bool{1: true, 2: true, 3: true}, want: []interface{}{1, 2, 3}, }, { in: map[interface{}]bool{ nil: true, true: true, false: true, -5: true, -55: true, -555: true, uint(1): true, uint(11): true, uint(111): true, "abc": true, "abcd": true, "abcde": true, "foo": true, "bar": true, MyString("abc"): true, MyString("abcd"): true, MyString("abcde"): true, new(int): true, new(int): true, make(chan bool): true, make(chan bool): true, make(chan int): true, make(chan interface{}): true, math.Inf(+1): true, math.Inf(-1): true, 1.2345: true, 12.345: true, 123.45: true, 1234.5: true, 0 + 0i: true, 1 + 0i: true, 2 + 0i: true, 0 + 1i: true, 0 + 2i: true, 0 + 3i: true, [2]int{2, 3}: true, [2]int{4, 0}: true, [2]int{2, 4}: true, MyArray([2]int{2, 4}): true, EmptyStruct{}: true, MyStruct{ "bravo", [2]int{2, 3}, make(chan float64), }: true, MyStruct{ "alpha", [2]int{3, 3}, make(chan float64), }: true, }, want: []interface{}{ nil, false, true, -555, -55, -5, uint(1), uint(11), uint(111), math.Inf(-1), 1.2345, 12.345, 123.45, 1234.5, math.Inf(+1), (0 + 0i), (0 + 1i), (0 + 2i), (0 + 3i), (1 + 0i), (2 + 0i), [2]int{2, 3}, [2]int{2, 4}, [2]int{4, 0}, MyArray([2]int{2, 4}), make(chan bool), make(chan bool), make(chan int), make(chan interface{}), new(int), new(int), "abc", "abcd", "abcde", "bar", "foo", MyString("abc"), MyString("abcd"), MyString("abcde"), EmptyStruct{}, MyStruct{"alpha", [2]int{3, 3}, make(chan float64)}, MyStruct{"bravo", [2]int{2, 3}, make(chan float64)}, }, }, { // NaN values cannot be properly deduplicated. // This is okay since map entries with NaN in the keys cannot be // retrieved anyways. in: map[interface{}]bool{ math.NaN(): true, math.NaN(): true, complex(0, math.NaN()): true, complex(0, math.NaN()): true, complex(math.NaN(), 0): true, complex(math.NaN(), 0): true, complex(math.NaN(), math.NaN()): true, }, want: []interface{}{ math.NaN(), complex(math.NaN(), math.NaN()), complex(math.NaN(), 0), complex(0, math.NaN()), }, }} for i, tt := range tests { // Intentionally pass the map via an unexported field to detect panics. // Unfortunately, we cannot actually test the keys without using unsafe. v := reflect.ValueOf(struct{ x map[interface{}]bool }{tt.in}).Field(0) value.SortKeys(append(v.MapKeys(), v.MapKeys()...)) // Try again, with keys that have read-write access in reflect. v = reflect.ValueOf(tt.in) keys := append(v.MapKeys(), v.MapKeys()...) var got []interface{} for _, k := range value.SortKeys(keys) { got = append(got, k.Interface()) } if d := cmp.Diff(got, tt.want, opts...); d != "" { t.Errorf("test %d, Sort() mismatch (-got +want):\n%s", i, d) } } } go-cmp-0.3.1/cmp/internal/value/zero.go000066400000000000000000000025241352211333400177120ustar00rootroot00000000000000// 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 value import ( "math" "reflect" ) // IsZero reports whether v is the zero value. // This does not rely on Interface and so can be used on unexported fields. func IsZero(v reflect.Value) bool { switch v.Kind() { case reflect.Bool: return v.Bool() == false case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return math.Float64bits(v.Float()) == 0 case reflect.Complex64, reflect.Complex128: return math.Float64bits(real(v.Complex())) == 0 && math.Float64bits(imag(v.Complex())) == 0 case reflect.String: return v.String() == "" case reflect.UnsafePointer: return v.Pointer() == 0 case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: return v.IsNil() case reflect.Array: for i := 0; i < v.Len(); i++ { if !IsZero(v.Index(i)) { return false } } return true case reflect.Struct: for i := 0; i < v.NumField(); i++ { if !IsZero(v.Field(i)) { return false } } return true } return false } go-cmp-0.3.1/cmp/internal/value/zero_test.go000066400000000000000000000023721352211333400207520ustar00rootroot00000000000000// Copyright 2019, 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 value import ( "archive/tar" "math" "reflect" "testing" ) func TestIsZero(t *testing.T) { tests := []struct { in interface{} want bool }{ {0, true}, {1, false}, {"", true}, {"foo", false}, {[]byte(nil), true}, {[]byte{}, false}, {map[string]bool(nil), true}, {map[string]bool{}, false}, {tar.Header{}, true}, {&tar.Header{}, false}, {tar.Header{Name: "foo"}, false}, {(chan bool)(nil), true}, {make(chan bool), false}, {(func(*testing.T))(nil), true}, {TestIsZero, false}, {[...]int{0, 0, 0}, true}, {[...]int{0, 1, 0}, false}, {math.Copysign(0, +1), true}, {math.Copysign(0, -1), false}, {complex(math.Copysign(0, +1), math.Copysign(0, +1)), true}, {complex(math.Copysign(0, -1), math.Copysign(0, +1)), false}, {complex(math.Copysign(0, +1), math.Copysign(0, -1)), false}, {complex(math.Copysign(0, -1), math.Copysign(0, -1)), false}, } for _, tt := range tests { t.Run("", func(t *testing.T) { got := IsZero(reflect.ValueOf(tt.in)) if got != tt.want { t.Errorf("IsZero(%v) = %v, want %v", tt.in, got, tt.want) } }) } } go-cmp-0.3.1/cmp/options.go000066400000000000000000000426311352211333400155010ustar00rootroot00000000000000// 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 import ( "fmt" "reflect" "regexp" "strings" "github.com/google/go-cmp/cmp/internal/function" ) // Option configures for specific behavior of Equal and Diff. In particular, // the fundamental Option functions (Ignore, Transformer, and Comparer), // configure how equality is determined. // // The fundamental options may be composed with filters (FilterPath and // FilterValues) to control the scope over which they are applied. // // The cmp/cmpopts package provides helper functions for creating options that // may be used with Equal and Diff. type Option interface { // filter applies all filters and returns the option that remains. // Each option may only read s.curPath and call s.callTTBFunc. // // An Options is returned only if multiple comparers or transformers // can apply simultaneously and will only contain values of those types // or sub-Options containing values of those types. filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption } // applicableOption represents the following types: // Fundamental: ignore | validator | *comparer | *transformer // Grouping: Options type applicableOption interface { Option // apply executes the option, which may mutate s or panic. apply(s *state, vx, vy reflect.Value) } // coreOption represents the following types: // Fundamental: ignore | validator | *comparer | *transformer // Filters: *pathFilter | *valuesFilter type coreOption interface { Option isCore() } type core struct{} func (core) isCore() {} // Options is a list of Option values that also satisfies the Option interface. // Helper comparison packages may return an Options value when packing multiple // Option values into a single Option. When this package processes an Options, // it will be implicitly expanded into a flat list. // // Applying a filter on an Options is equivalent to applying that same filter // on all individual options held within. type Options []Option func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) { for _, opt := range opts { switch opt := opt.filter(s, t, vx, vy); opt.(type) { case ignore: return ignore{} // Only ignore can short-circuit evaluation case validator: out = validator{} // Takes precedence over comparer or transformer case *comparer, *transformer, Options: switch out.(type) { case nil: out = opt case validator: // Keep validator case *comparer, *transformer, Options: out = Options{out, opt} // Conflicting comparers or transformers } } } return out } func (opts Options) apply(s *state, _, _ reflect.Value) { const warning = "ambiguous set of applicable options" const help = "consider using filters to ensure at most one Comparer or Transformer may apply" var ss []string for _, opt := range flattenOptions(nil, opts) { ss = append(ss, fmt.Sprint(opt)) } set := strings.Join(ss, "\n\t") panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help)) } func (opts Options) String() string { var ss []string for _, opt := range opts { ss = append(ss, fmt.Sprint(opt)) } return fmt.Sprintf("Options{%s}", strings.Join(ss, ", ")) } // FilterPath returns a new Option where opt is only evaluated if filter f // returns true for the current Path in the value tree. // // This filter is called even if a slice element or map entry is missing and // provides an opportunity to ignore such cases. The filter function must be // symmetric such that the filter result is identical regardless of whether the // missing value is from x or y. // // The option passed in may be an Ignore, Transformer, Comparer, Options, or // a previously filtered Option. func FilterPath(f func(Path) bool, opt Option) Option { if f == nil { panic("invalid path filter function") } if opt := normalizeOption(opt); opt != nil { return &pathFilter{fnc: f, opt: opt} } return nil } type pathFilter struct { core fnc func(Path) bool opt Option } func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption { if f.fnc(s.curPath) { return f.opt.filter(s, t, vx, vy) } return nil } func (f pathFilter) String() string { return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt) } // FilterValues returns a new Option where opt is only evaluated if filter f, // which is a function of the form "func(T, T) bool", returns true for the // current pair of values being compared. If either value is invalid or // the type of the values is not assignable to T, then this filter implicitly // returns false. // // The filter function must be // symmetric (i.e., agnostic to the order of the inputs) and // deterministic (i.e., produces the same result when given the same inputs). // If T is an interface, it is possible that f is called with two values with // different concrete types that both implement T. // // The option passed in may be an Ignore, Transformer, Comparer, Options, or // a previously filtered Option. func FilterValues(f interface{}, opt Option) Option { v := reflect.ValueOf(f) if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() { panic(fmt.Sprintf("invalid values filter function: %T", f)) } if opt := normalizeOption(opt); opt != nil { vf := &valuesFilter{fnc: v, opt: opt} if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { vf.typ = ti } return vf } return nil } type valuesFilter struct { core typ reflect.Type // T fnc reflect.Value // func(T, T) bool opt Option } func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption { if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() { return nil } if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) { return f.opt.filter(s, t, vx, vy) } return nil } func (f valuesFilter) String() string { return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt) } // Ignore is an Option that causes all comparisons to be ignored. // This value is intended to be combined with FilterPath or FilterValues. // It is an error to pass an unfiltered Ignore option to Equal. func Ignore() Option { return ignore{} } type ignore struct{ core } func (ignore) isFiltered() bool { return false } func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} } func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) } func (ignore) String() string { return "Ignore()" } // validator is a sentinel Option type to indicate that some options could not // be evaluated due to unexported fields, missing slice elements, or // missing map entries. Both values are validator only for unexported fields. type validator struct{ core } func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption { if !vx.IsValid() || !vy.IsValid() { return validator{} } if !vx.CanInterface() || !vy.CanInterface() { return validator{} } return nil } func (validator) apply(s *state, vx, vy reflect.Value) { // Implies missing slice element or map entry. if !vx.IsValid() || !vy.IsValid() { s.report(vx.IsValid() == vy.IsValid(), 0) return } // Unable to Interface implies unexported field without visibility access. if !vx.CanInterface() || !vy.CanInterface() { const help = "consider using a custom Comparer; if you control the implementation of type, you can also consider AllowUnexported or cmpopts.IgnoreUnexported" panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help)) } panic("not reachable") } // identRx represents a valid identifier according to the Go specification. const identRx = `[_\p{L}][_\p{L}\p{N}]*` var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) // Transformer returns an Option that applies a transformation function that // converts values of a certain type into that of another. // // The transformer f must be a function "func(T) R" that converts values of // type T to those of type R and is implicitly filtered to input values // assignable to T. The transformer must not mutate T in any way. // // To help prevent some cases of infinite recursive cycles applying the // same transform to the output of itself (e.g., in the case where the // input and output types are the same), an implicit filter is added such that // a transformer is applicable only if that exact transformer is not already // in the tail of the Path since the last non-Transform step. // For situations where the implicit filter is still insufficient, // consider using cmpopts.AcyclicTransformer, which adds a filter // to prevent the transformer from being recursively applied upon itself. // // The name is a user provided label that is used as the Transform.Name in the // transformation PathStep (and eventually shown in the Diff output). // The name must be a valid identifier or qualified identifier in Go syntax. // If empty, an arbitrary name is used. func Transformer(name string, f interface{}) Option { v := reflect.ValueOf(f) if !function.IsType(v.Type(), function.Transformer) || v.IsNil() { panic(fmt.Sprintf("invalid transformer function: %T", f)) } if name == "" { name = function.NameOf(v) if !identsRx.MatchString(name) { name = "λ" // Lambda-symbol as placeholder name } } else if !identsRx.MatchString(name) { panic(fmt.Sprintf("invalid name: %q", name)) } tr := &transformer{name: name, fnc: reflect.ValueOf(f)} if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { tr.typ = ti } return tr } type transformer struct { core name string typ reflect.Type // T fnc reflect.Value // func(T) R } func (tr *transformer) isFiltered() bool { return tr.typ != nil } func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption { for i := len(s.curPath) - 1; i >= 0; i-- { if t, ok := s.curPath[i].(Transform); !ok { break // Hit most recent non-Transform step } else if tr == t.trans { return nil // Cannot directly use same Transform } } if tr.typ == nil || t.AssignableTo(tr.typ) { return tr } return nil } func (tr *transformer) apply(s *state, vx, vy reflect.Value) { step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}} vvx := s.callTRFunc(tr.fnc, vx, step) vvy := s.callTRFunc(tr.fnc, vy, step) step.vx, step.vy = vvx, vvy s.compareAny(step) } func (tr transformer) String() string { return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc)) } // Comparer returns an Option that determines whether two values are equal // to each other. // // The comparer f must be a function "func(T, T) bool" and is implicitly // filtered to input values assignable to T. If T is an interface, it is // possible that f is called with two values of different concrete types that // both implement T. // // The equality function must be: // • Symmetric: equal(x, y) == equal(y, x) // • Deterministic: equal(x, y) == equal(x, y) // • Pure: equal(x, y) does not modify x or y func Comparer(f interface{}) Option { v := reflect.ValueOf(f) if !function.IsType(v.Type(), function.Equal) || v.IsNil() { panic(fmt.Sprintf("invalid comparer function: %T", f)) } cm := &comparer{fnc: v} if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { cm.typ = ti } return cm } type comparer struct { core typ reflect.Type // T fnc reflect.Value // func(T, T) bool } func (cm *comparer) isFiltered() bool { return cm.typ != nil } func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption { if cm.typ == nil || t.AssignableTo(cm.typ) { return cm } return nil } func (cm *comparer) apply(s *state, vx, vy reflect.Value) { eq := s.callTTBFunc(cm.fnc, vx, vy) s.report(eq, reportByFunc) } func (cm comparer) String() string { return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc)) } // AllowUnexported returns an Option that forcibly allows operations on // unexported fields in certain structs, which are specified by passing in a // value of each struct type. // // Users of this option must understand that comparing on unexported fields // from external packages is not safe since changes in the internal // implementation of some external package may cause the result of Equal // to unexpectedly change. However, it may be valid to use this option on types // defined in an internal package where the semantic meaning of an unexported // field is in the control of the user. // // In many cases, a custom Comparer should be used instead that defines // equality as a function of the public API of a type rather than the underlying // unexported implementation. // // For example, the reflect.Type documentation defines equality to be determined // by the == operator on the interface (essentially performing a shallow pointer // comparison) and most attempts to compare *regexp.Regexp types are interested // in only checking that the regular expression strings are equal. // Both of these are accomplished using Comparers: // // Comparer(func(x, y reflect.Type) bool { return x == y }) // Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() }) // // In other cases, the cmpopts.IgnoreUnexported option can be used to ignore // all unexported fields on specified struct types. func AllowUnexported(types ...interface{}) Option { if !supportAllowUnexported { panic("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS") } m := make(map[reflect.Type]bool) for _, typ := range types { t := reflect.TypeOf(typ) if t.Kind() != reflect.Struct { panic(fmt.Sprintf("invalid struct type: %T", typ)) } m[t] = true } return visibleStructs(m) } type visibleStructs map[reflect.Type]bool func (visibleStructs) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { panic("not implemented") } // Result represents the comparison result for a single node and // is provided by cmp when calling Result (see Reporter). type Result struct { _ [0]func() // Make Result incomparable flags resultFlags } // Equal reports whether the node was determined to be equal or not. // As a special case, ignored nodes are considered equal. func (r Result) Equal() bool { return r.flags&(reportEqual|reportByIgnore) != 0 } // ByIgnore reports whether the node is equal because it was ignored. // This never reports true if Equal reports false. func (r Result) ByIgnore() bool { return r.flags&reportByIgnore != 0 } // ByMethod reports whether the Equal method determined equality. func (r Result) ByMethod() bool { return r.flags&reportByMethod != 0 } // ByFunc reports whether a Comparer function determined equality. func (r Result) ByFunc() bool { return r.flags&reportByFunc != 0 } type resultFlags uint const ( _ resultFlags = (1 << iota) / 2 reportEqual reportUnequal reportByIgnore reportByMethod reportByFunc ) // Reporter is an Option that can be passed to Equal. When Equal traverses // the value trees, it calls PushStep as it descends into each node in the // tree and PopStep as it ascend out of the node. The leaves of the tree are // either compared (determined to be equal or not equal) or ignored and reported // as such by calling the Report method. func Reporter(r interface { // PushStep is called when a tree-traversal operation is performed. // The PathStep itself is only valid until the step is popped. // The PathStep.Values are valid for the duration of the entire traversal // and must not be mutated. // // Equal always calls PushStep at the start to provide an operation-less // PathStep used to report the root values. // // Within a slice, the exact set of inserted, removed, or modified elements // is unspecified and may change in future implementations. // The entries of a map are iterated through in an unspecified order. PushStep(PathStep) // Report is called exactly once on leaf nodes to report whether the // comparison identified the node as equal, unequal, or ignored. // A leaf node is one that is immediately preceded by and followed by // a pair of PushStep and PopStep calls. Report(Result) // PopStep ascends back up the value tree. // There is always a matching pop call for every push call. PopStep() }) Option { return reporter{r} } type reporter struct{ reporterIface } type reporterIface interface { PushStep(PathStep) Report(Result) PopStep() } func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { panic("not implemented") } // normalizeOption normalizes the input options such that all Options groups // are flattened and groups with a single element are reduced to that element. // Only coreOptions and Options containing coreOptions are allowed. func normalizeOption(src Option) Option { switch opts := flattenOptions(nil, Options{src}); len(opts) { case 0: return nil case 1: return opts[0] default: return opts } } // flattenOptions copies all options in src to dst as a flat list. // Only coreOptions and Options containing coreOptions are allowed. func flattenOptions(dst, src Options) Options { for _, opt := range src { switch opt := opt.(type) { case nil: continue case Options: dst = flattenOptions(dst, opt) case coreOption: dst = append(dst, opt) default: panic(fmt.Sprintf("invalid option type: %T", opt)) } } return dst } go-cmp-0.3.1/cmp/options_test.go000066400000000000000000000145631352211333400165430ustar00rootroot00000000000000// 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 import ( "io" "reflect" "strings" "testing" ts "github.com/google/go-cmp/cmp/internal/teststructs" ) // Test that the creation of Option values with non-sensible inputs produces // a run-time panic with a decent error message func TestOptionPanic(t *testing.T) { type myBool bool tests := []struct { label string // Test description fnc interface{} // Option function to call args []interface{} // Arguments to pass in wantPanic string // Expected panic message }{{ label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{}, }, { label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{1}, wantPanic: "invalid struct type", }, { label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{ts.StructA{}}, }, { label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{ts.StructA{}, ts.StructB{}, ts.StructA{}}, }, { label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{ts.StructA{}, &ts.StructB{}, ts.StructA{}}, wantPanic: "invalid struct type", }, { label: "Comparer", fnc: Comparer, args: []interface{}{5}, wantPanic: "invalid comparer function", }, { label: "Comparer", fnc: Comparer, args: []interface{}{func(x, y interface{}) bool { return true }}, }, { label: "Comparer", fnc: Comparer, args: []interface{}{func(x, y io.Reader) bool { return true }}, }, { label: "Comparer", fnc: Comparer, args: []interface{}{func(x, y io.Reader) myBool { return true }}, wantPanic: "invalid comparer function", }, { label: "Comparer", fnc: Comparer, args: []interface{}{func(x string, y interface{}) bool { return true }}, wantPanic: "invalid comparer function", }, { label: "Comparer", fnc: Comparer, args: []interface{}{(func(int, int) bool)(nil)}, wantPanic: "invalid comparer function", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", 0}, wantPanic: "invalid transformer function", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", func(int) int { return 0 }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", func(bool) bool { return true }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", func(int) bool { return true }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", func(int, int) bool { return true }}, wantPanic: "invalid transformer function", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", (func(int) uint)(nil)}, wantPanic: "invalid transformer function", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"Func", func(Path) Path { return nil }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"世界", func(int) bool { return true }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"/*", func(int) bool { return true }}, wantPanic: "invalid name", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"_", func(int) bool { return true }}, }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{(func(Path) bool)(nil), Ignore()}, wantPanic: "invalid path filter function", }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{func(Path) bool { return true }, Ignore()}, }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{func(Path) bool { return true }, Reporter(&defaultReporter{})}, wantPanic: "invalid option type", }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{func(Path) bool { return true }, Options{Ignore(), Ignore()}}, }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{func(Path) bool { return true }, Options{Ignore(), Reporter(&defaultReporter{})}}, wantPanic: "invalid option type", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{0, Ignore()}, wantPanic: "invalid values filter function", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(x, y int) bool { return true }, Ignore()}, }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(x, y interface{}) bool { return true }, Ignore()}, }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(x, y interface{}) myBool { return true }, Ignore()}, wantPanic: "invalid values filter function", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(x io.Reader, y interface{}) bool { return true }, Ignore()}, wantPanic: "invalid values filter function", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{(func(int, int) bool)(nil), Ignore()}, wantPanic: "invalid values filter function", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(int, int) bool { return true }, Reporter(&defaultReporter{})}, wantPanic: "invalid option type", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), Ignore()}}, }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), Reporter(&defaultReporter{})}}, wantPanic: "invalid option type", }} for _, tt := range tests { t.Run(tt.label, func(t *testing.T) { var gotPanic string func() { defer func() { if ex := recover(); ex != nil { if s, ok := ex.(string); ok { gotPanic = s } else { panic(ex) } } }() var vargs []reflect.Value for _, arg := range tt.args { vargs = append(vargs, reflect.ValueOf(arg)) } reflect.ValueOf(tt.fnc).Call(vargs) }() if tt.wantPanic == "" { if gotPanic != "" { t.Fatalf("unexpected panic message: %s", gotPanic) } } else { if !strings.Contains(gotPanic, tt.wantPanic) { t.Fatalf("panic message:\ngot: %s\nwant: %s", gotPanic, tt.wantPanic) } } }) } } go-cmp-0.3.1/cmp/path.go000066400000000000000000000231151352211333400147360ustar00rootroot00000000000000// 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 import ( "fmt" "reflect" "strings" "unicode" "unicode/utf8" ) // Path is a list of PathSteps describing the sequence of operations to get // from some root type to the current position in the value tree. // The first Path element is always an operation-less PathStep that exists // simply to identify the initial type. // // When traversing structs with embedded structs, the embedded struct will // always be accessed as a field before traversing the fields of the // embedded struct themselves. That is, an exported field from the // embedded struct will never be accessed directly from the parent struct. type Path []PathStep // PathStep is a union-type for specific operations to traverse // a value's tree structure. Users of this package never need to implement // these types as values of this type will be returned by this package. // // Implementations of this interface are // StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform. type PathStep interface { String() string // Type is the resulting type after performing the path step. Type() reflect.Type // Values is the resulting values after performing the path step. // The type of each valid value is guaranteed to be identical to Type. // // In some cases, one or both may be invalid or have restrictions: // • For StructField, both are not interface-able if the current field // is unexported and the struct type is not explicitly permitted by // AllowUnexported to traverse unexported fields. // • For SliceIndex, one may be invalid if an element is missing from // either the x or y slice. // • For MapIndex, one may be invalid if an entry is missing from // either the x or y map. // // The provided values must not be mutated. Values() (vx, vy reflect.Value) } var ( _ PathStep = StructField{} _ PathStep = SliceIndex{} _ PathStep = MapIndex{} _ PathStep = Indirect{} _ PathStep = TypeAssertion{} _ PathStep = Transform{} ) func (pa *Path) push(s PathStep) { *pa = append(*pa, s) } func (pa *Path) pop() { *pa = (*pa)[:len(*pa)-1] } // Last returns the last PathStep in the Path. // If the path is empty, this returns a non-nil PathStep that reports a nil Type. func (pa Path) Last() PathStep { return pa.Index(-1) } // Index returns the ith step in the Path and supports negative indexing. // A negative index starts counting from the tail of the Path such that -1 // refers to the last step, -2 refers to the second-to-last step, and so on. // If index is invalid, this returns a non-nil PathStep that reports a nil Type. func (pa Path) Index(i int) PathStep { if i < 0 { i = len(pa) + i } if i < 0 || i >= len(pa) { return pathStep{} } return pa[i] } // String returns the simplified path to a node. // The simplified path only contains struct field accesses. // // For example: // MyMap.MySlices.MyField func (pa Path) String() string { var ss []string for _, s := range pa { if _, ok := s.(StructField); ok { ss = append(ss, s.String()) } } return strings.TrimPrefix(strings.Join(ss, ""), ".") } // GoString returns the path to a specific node using Go syntax. // // For example: // (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField func (pa Path) GoString() string { var ssPre, ssPost []string var numIndirect int for i, s := range pa { var nextStep PathStep if i+1 < len(pa) { nextStep = pa[i+1] } switch s := s.(type) { case Indirect: numIndirect++ pPre, pPost := "(", ")" switch nextStep.(type) { case Indirect: continue // Next step is indirection, so let them batch up case StructField: numIndirect-- // Automatic indirection on struct fields case nil: pPre, pPost = "", "" // Last step; no need for parenthesis } if numIndirect > 0 { ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect)) ssPost = append(ssPost, pPost) } numIndirect = 0 continue case Transform: ssPre = append(ssPre, s.trans.name+"(") ssPost = append(ssPost, ")") continue } ssPost = append(ssPost, s.String()) } for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 { ssPre[i], ssPre[j] = ssPre[j], ssPre[i] } return strings.Join(ssPre, "") + strings.Join(ssPost, "") } type pathStep struct { typ reflect.Type vx, vy reflect.Value } func (ps pathStep) Type() reflect.Type { return ps.typ } func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy } func (ps pathStep) String() string { if ps.typ == nil { return "" } s := ps.typ.String() if s == "" || strings.ContainsAny(s, "{}\n") { return "root" // Type too simple or complex to print } return fmt.Sprintf("{%s}", s) } // StructField represents a struct field access on a field called Name. type StructField struct{ *structField } type structField struct { pathStep name string idx int // These fields are used for forcibly accessing an unexported field. // pvx, pvy, and field are only valid if unexported is true. unexported bool mayForce bool // Forcibly allow visibility pvx, pvy reflect.Value // Parent values field reflect.StructField // Field information } func (sf StructField) Type() reflect.Type { return sf.typ } func (sf StructField) Values() (vx, vy reflect.Value) { if !sf.unexported { return sf.vx, sf.vy // CanInterface reports true } // Forcibly obtain read-write access to an unexported struct field. if sf.mayForce { vx = retrieveUnexportedField(sf.pvx, sf.field) vy = retrieveUnexportedField(sf.pvy, sf.field) return vx, vy // CanInterface reports true } return sf.vx, sf.vy // CanInterface reports false } func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) } // Name is the field name. func (sf StructField) Name() string { return sf.name } // Index is the index of the field in the parent struct type. // See reflect.Type.Field. func (sf StructField) Index() int { return sf.idx } // SliceIndex is an index operation on a slice or array at some index Key. type SliceIndex struct{ *sliceIndex } type sliceIndex struct { pathStep xkey, ykey int } func (si SliceIndex) Type() reflect.Type { return si.typ } func (si SliceIndex) Values() (vx, vy reflect.Value) { return si.vx, si.vy } func (si SliceIndex) String() string { switch { case si.xkey == si.ykey: return fmt.Sprintf("[%d]", si.xkey) case si.ykey == -1: // [5->?] means "I don't know where X[5] went" return fmt.Sprintf("[%d->?]", si.xkey) case si.xkey == -1: // [?->3] means "I don't know where Y[3] came from" return fmt.Sprintf("[?->%d]", si.ykey) default: // [5->3] means "X[5] moved to Y[3]" return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey) } } // Key is the index key; it may return -1 if in a split state func (si SliceIndex) Key() int { if si.xkey != si.ykey { return -1 } return si.xkey } // SplitKeys are the indexes for indexing into slices in the // x and y values, respectively. These indexes may differ due to the // insertion or removal of an element in one of the slices, causing // all of the indexes to be shifted. If an index is -1, then that // indicates that the element does not exist in the associated slice. // // Key is guaranteed to return -1 if and only if the indexes returned // by SplitKeys are not the same. SplitKeys will never return -1 for // both indexes. func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey } // MapIndex is an index operation on a map at some index Key. type MapIndex struct{ *mapIndex } type mapIndex struct { pathStep key reflect.Value } func (mi MapIndex) Type() reflect.Type { return mi.typ } func (mi MapIndex) Values() (vx, vy reflect.Value) { return mi.vx, mi.vy } func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) } // Key is the value of the map key. func (mi MapIndex) Key() reflect.Value { return mi.key } // Indirect represents pointer indirection on the parent type. type Indirect struct{ *indirect } type indirect struct { pathStep } func (in Indirect) Type() reflect.Type { return in.typ } func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy } func (in Indirect) String() string { return "*" } // TypeAssertion represents a type assertion on an interface. type TypeAssertion struct{ *typeAssertion } type typeAssertion struct { pathStep } func (ta TypeAssertion) Type() reflect.Type { return ta.typ } func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy } func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) } // Transform is a transformation from the parent type to the current type. type Transform struct{ *transform } type transform struct { pathStep trans *transformer } func (tf Transform) Type() reflect.Type { return tf.typ } func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy } func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) } // Name is the name of the Transformer. func (tf Transform) Name() string { return tf.trans.name } // Func is the function pointer to the transformer function. func (tf Transform) Func() reflect.Value { return tf.trans.fnc } // Option returns the originally constructed Transformer option. // The == operator can be used to detect the exact option used. func (tf Transform) Option() Option { return tf.trans } // isExported reports whether the identifier is exported. func isExported(id string) bool { r, _ := utf8.DecodeRuneInString(id) return unicode.IsUpper(r) } go-cmp-0.3.1/cmp/report.go000066400000000000000000000026751352211333400153250ustar00rootroot00000000000000// 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 // defaultReporter implements the reporter interface. // // As Equal serially calls the PushStep, Report, and PopStep methods, the // defaultReporter constructs a tree-based representation of the compared value // and the result of each comparison (see valueNode). // // When the String method is called, the FormatDiff method transforms the // valueNode tree into a textNode tree, which is a tree-based representation // of the textual output (see textNode). // // Lastly, the textNode.String method produces the final report as a string. type defaultReporter struct { root *valueNode curr *valueNode } func (r *defaultReporter) PushStep(ps PathStep) { r.curr = r.curr.PushStep(ps) if r.root == nil { r.root = r.curr } } func (r *defaultReporter) Report(rs Result) { r.curr.Report(rs) } func (r *defaultReporter) PopStep() { r.curr = r.curr.PopStep() } // String provides a full report of the differences detected as a structured // literal in pseudo-Go syntax. String may only be called after the entire tree // has been traversed. func (r *defaultReporter) String() string { assert(r.root != nil && r.curr == nil) if r.root.NumDiff == 0 { return "" } return formatOptions{}.FormatDiff(r.root).String() } func assert(ok bool) { if !ok { panic("assertion failure") } } go-cmp-0.3.1/cmp/report_compare.go000066400000000000000000000216441352211333400170300ustar00rootroot00000000000000// Copyright 2019, 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 import ( "fmt" "reflect" "github.com/google/go-cmp/cmp/internal/value" ) // TODO: Enforce limits? // * Enforce maximum number of records to print per node? // * Enforce maximum size in bytes allowed? // * As a heuristic, use less verbosity for equal nodes than unequal nodes. // TODO: Enforce unique outputs? // * Avoid Stringer methods if it results in same output? // * Print pointer address if outputs still equal? // numContextRecords is the number of surrounding equal records to print. const numContextRecords = 2 type diffMode byte const ( diffUnknown diffMode = 0 diffIdentical diffMode = ' ' diffRemoved diffMode = '-' diffInserted diffMode = '+' ) type typeMode int const ( // emitType always prints the type. emitType typeMode = iota // elideType never prints the type. elideType // autoType prints the type only for composite kinds // (i.e., structs, slices, arrays, and maps). autoType ) type formatOptions struct { // DiffMode controls the output mode of FormatDiff. // // If diffUnknown, then produce a diff of the x and y values. // If diffIdentical, then emit values as if they were equal. // If diffRemoved, then only emit x values (ignoring y values). // If diffInserted, then only emit y values (ignoring x values). DiffMode diffMode // TypeMode controls whether to print the type for the current node. // // As a general rule of thumb, we always print the type of the next node // after an interface, and always elide the type of the next node after // a slice or map node. TypeMode typeMode // formatValueOptions are options specific to printing reflect.Values. formatValueOptions } func (opts formatOptions) WithDiffMode(d diffMode) formatOptions { opts.DiffMode = d return opts } func (opts formatOptions) WithTypeMode(t typeMode) formatOptions { opts.TypeMode = t return opts } // FormatDiff converts a valueNode tree into a textNode tree, where the later // is a textual representation of the differences detected in the former. func (opts formatOptions) FormatDiff(v *valueNode) textNode { // Check whether we have specialized formatting for this node. // This is not necessary, but helpful for producing more readable outputs. if opts.CanFormatDiffSlice(v) { return opts.FormatDiffSlice(v) } // For leaf nodes, format the value based on the reflect.Values alone. if v.MaxDepth == 0 { switch opts.DiffMode { case diffUnknown, diffIdentical: // Format Equal. if v.NumDiff == 0 { outx := opts.FormatValue(v.ValueX, visitedPointers{}) outy := opts.FormatValue(v.ValueY, visitedPointers{}) if v.NumIgnored > 0 && v.NumSame == 0 { return textEllipsis } else if outx.Len() < outy.Len() { return outx } else { return outy } } // Format unequal. assert(opts.DiffMode == diffUnknown) var list textList outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, visitedPointers{}) outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, visitedPointers{}) if outx != nil { list = append(list, textRecord{Diff: '-', Value: outx}) } if outy != nil { list = append(list, textRecord{Diff: '+', Value: outy}) } return opts.WithTypeMode(emitType).FormatType(v.Type, list) case diffRemoved: return opts.FormatValue(v.ValueX, visitedPointers{}) case diffInserted: return opts.FormatValue(v.ValueY, visitedPointers{}) default: panic("invalid diff mode") } } // Descend into the child value node. if v.TransformerName != "" { out := opts.WithTypeMode(emitType).FormatDiff(v.Value) out = textWrap{"Inverse(" + v.TransformerName + ", ", out, ")"} return opts.FormatType(v.Type, out) } else { switch k := v.Type.Kind(); k { case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map: return opts.FormatType(v.Type, opts.formatDiffList(v.Records, k)) case reflect.Ptr: return textWrap{"&", opts.FormatDiff(v.Value), ""} case reflect.Interface: return opts.WithTypeMode(emitType).FormatDiff(v.Value) default: panic(fmt.Sprintf("%v cannot have children", k)) } } } func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) textNode { // Derive record name based on the data structure kind. var name string var formatKey func(reflect.Value) string switch k { case reflect.Struct: name = "field" opts = opts.WithTypeMode(autoType) formatKey = func(v reflect.Value) string { return v.String() } case reflect.Slice, reflect.Array: name = "element" opts = opts.WithTypeMode(elideType) formatKey = func(reflect.Value) string { return "" } case reflect.Map: name = "entry" opts = opts.WithTypeMode(elideType) formatKey = formatMapKey } // Handle unification. switch opts.DiffMode { case diffIdentical, diffRemoved, diffInserted: var list textList var deferredEllipsis bool // Add final "..." to indicate records were dropped for _, r := range recs { // Elide struct fields that are zero value. if k == reflect.Struct { var isZero bool switch opts.DiffMode { case diffIdentical: isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY) case diffRemoved: isZero = value.IsZero(r.Value.ValueX) case diffInserted: isZero = value.IsZero(r.Value.ValueY) } if isZero { continue } } // Elide ignored nodes. if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 { deferredEllipsis = !(k == reflect.Slice || k == reflect.Array) if !deferredEllipsis { list.AppendEllipsis(diffStats{}) } continue } if out := opts.FormatDiff(r.Value); out != nil { list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) } } if deferredEllipsis { list.AppendEllipsis(diffStats{}) } return textWrap{"{", list, "}"} case diffUnknown: default: panic("invalid diff mode") } // Handle differencing. var list textList groups := coalesceAdjacentRecords(name, recs) for i, ds := range groups { // Handle equal records. if ds.NumDiff() == 0 { // Compute the number of leading and trailing records to print. var numLo, numHi int numEqual := ds.NumIgnored + ds.NumIdentical for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 { if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 { break } numLo++ } for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 { if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 { break } numHi++ } if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 { numHi++ // Avoid pointless coalescing of a single equal record } // Format the equal values. for _, r := range recs[:numLo] { out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value) list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) } if numEqual > numLo+numHi { ds.NumIdentical -= numLo + numHi list.AppendEllipsis(ds) } for _, r := range recs[numEqual-numHi : numEqual] { out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value) list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) } recs = recs[numEqual:] continue } // Handle unequal records. for _, r := range recs[:ds.NumDiff()] { switch { case opts.CanFormatDiffSlice(r.Value): out := opts.FormatDiffSlice(r.Value) list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) case r.Value.NumChildren == r.Value.MaxDepth: outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value) outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value) if outx != nil { list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx}) } if outy != nil { list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy}) } default: out := opts.FormatDiff(r.Value) list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) } } recs = recs[ds.NumDiff():] } assert(len(recs) == 0) return textWrap{"{", list, "}"} } // coalesceAdjacentRecords coalesces the list of records into groups of // adjacent equal, or unequal counts. func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) { var prevCase int // Arbitrary index into which case last occurred lastStats := func(i int) *diffStats { if prevCase != i { groups = append(groups, diffStats{Name: name}) prevCase = i } return &groups[len(groups)-1] } for _, r := range recs { switch rv := r.Value; { case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0: lastStats(1).NumIgnored++ case rv.NumDiff == 0: lastStats(1).NumIdentical++ case rv.NumDiff > 0 && !rv.ValueY.IsValid(): lastStats(2).NumRemoved++ case rv.NumDiff > 0 && !rv.ValueX.IsValid(): lastStats(2).NumInserted++ default: lastStats(2).NumModified++ } } return groups } go-cmp-0.3.1/cmp/report_reflect.go000066400000000000000000000176471352211333400170360ustar00rootroot00000000000000// Copyright 2019, 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 import ( "fmt" "reflect" "strconv" "strings" "unicode" "github.com/google/go-cmp/cmp/internal/flags" "github.com/google/go-cmp/cmp/internal/value" ) type formatValueOptions struct { // AvoidStringer controls whether to avoid calling custom stringer // methods like error.Error or fmt.Stringer.String. AvoidStringer bool // ShallowPointers controls whether to avoid descending into pointers. // Useful when printing map keys, where pointer comparison is performed // on the pointer address rather than the pointed-at value. ShallowPointers bool // PrintAddresses controls whether to print the address of all pointers, // slice elements, and maps. PrintAddresses bool } // FormatType prints the type as if it were wrapping s. // This may return s as-is depending on the current type and TypeMode mode. func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode { // Check whether to emit the type or not. switch opts.TypeMode { case autoType: switch t.Kind() { case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map: if s.Equal(textNil) { return s } default: return s } case elideType: return s } // Determine the type label, applying special handling for unnamed types. typeName := t.String() if t.Name() == "" { // According to Go grammar, certain type literals contain symbols that // do not strongly bind to the next lexicographical token (e.g., *T). switch t.Kind() { case reflect.Chan, reflect.Func, reflect.Ptr: typeName = "(" + typeName + ")" } typeName = strings.Replace(typeName, "struct {", "struct{", -1) typeName = strings.Replace(typeName, "interface {", "interface{", -1) } // Avoid wrap the value in parenthesis if unnecessary. if s, ok := s.(textWrap); ok { hasParens := strings.HasPrefix(s.Prefix, "(") && strings.HasSuffix(s.Suffix, ")") hasBraces := strings.HasPrefix(s.Prefix, "{") && strings.HasSuffix(s.Suffix, "}") if hasParens || hasBraces { return textWrap{typeName, s, ""} } } return textWrap{typeName + "(", s, ")"} } // FormatValue prints the reflect.Value, taking extra care to avoid descending // into pointers already in m. As pointers are visited, m is also updated. func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out textNode) { if !v.IsValid() { return nil } t := v.Type() // Check whether there is an Error or String method to call. if !opts.AvoidStringer && v.CanInterface() { // Avoid calling Error or String methods on nil receivers since many // implementations crash when doing so. if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() { switch v := v.Interface().(type) { case error: return textLine("e" + formatString(v.Error())) case fmt.Stringer: return textLine("s" + formatString(v.String())) } } } // Check whether to explicitly wrap the result with the type. var skipType bool defer func() { if !skipType { out = opts.FormatType(t, out) } }() var ptr string switch t.Kind() { case reflect.Bool: return textLine(fmt.Sprint(v.Bool())) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return textLine(fmt.Sprint(v.Int())) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: // Unnamed uints are usually bytes or words, so use hexadecimal. if t.PkgPath() == "" || t.Kind() == reflect.Uintptr { return textLine(formatHex(v.Uint())) } return textLine(fmt.Sprint(v.Uint())) case reflect.Float32, reflect.Float64: return textLine(fmt.Sprint(v.Float())) case reflect.Complex64, reflect.Complex128: return textLine(fmt.Sprint(v.Complex())) case reflect.String: return textLine(formatString(v.String())) case reflect.UnsafePointer, reflect.Chan, reflect.Func: return textLine(formatPointer(v)) case reflect.Struct: var list textList for i := 0; i < v.NumField(); i++ { vv := v.Field(i) if value.IsZero(vv) { continue // Elide fields with zero values } s := opts.WithTypeMode(autoType).FormatValue(vv, m) list = append(list, textRecord{Key: t.Field(i).Name, Value: s}) } return textWrap{"{", list, "}"} case reflect.Slice: if v.IsNil() { return textNil } if opts.PrintAddresses { ptr = formatPointer(v) } fallthrough case reflect.Array: var list textList for i := 0; i < v.Len(); i++ { vi := v.Index(i) if vi.CanAddr() { // Check for cyclic elements p := vi.Addr() if m.Visit(p) { var out textNode out = textLine(formatPointer(p)) out = opts.WithTypeMode(emitType).FormatType(p.Type(), out) out = textWrap{"*", out, ""} list = append(list, textRecord{Value: out}) continue } } s := opts.WithTypeMode(elideType).FormatValue(vi, m) list = append(list, textRecord{Value: s}) } return textWrap{ptr + "{", list, "}"} case reflect.Map: if v.IsNil() { return textNil } if m.Visit(v) { return textLine(formatPointer(v)) } var list textList for _, k := range value.SortKeys(v.MapKeys()) { sk := formatMapKey(k) sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), m) list = append(list, textRecord{Key: sk, Value: sv}) } if opts.PrintAddresses { ptr = formatPointer(v) } return textWrap{ptr + "{", list, "}"} case reflect.Ptr: if v.IsNil() { return textNil } if m.Visit(v) || opts.ShallowPointers { return textLine(formatPointer(v)) } if opts.PrintAddresses { ptr = formatPointer(v) } skipType = true // Let the underlying value print the type instead return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), m), ""} case reflect.Interface: if v.IsNil() { return textNil } // Interfaces accept different concrete types, // so configure the underlying value to explicitly print the type. skipType = true // Print the concrete type instead return opts.WithTypeMode(emitType).FormatValue(v.Elem(), m) default: panic(fmt.Sprintf("%v kind not handled", v.Kind())) } } // formatMapKey formats v as if it were a map key. // The result is guaranteed to be a single line. func formatMapKey(v reflect.Value) string { var opts formatOptions opts.TypeMode = elideType opts.ShallowPointers = true s := opts.FormatValue(v, visitedPointers{}).String() return strings.TrimSpace(s) } // formatString prints s as a double-quoted or backtick-quoted string. func formatString(s string) string { // Use quoted string if it the same length as a raw string literal. // Otherwise, attempt to use the raw string form. qs := strconv.Quote(s) if len(qs) == 1+len(s)+1 { return qs } // Disallow newlines to ensure output is a single line. // Only allow printable runes for readability purposes. rawInvalid := func(r rune) bool { return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t') } if strings.IndexFunc(s, rawInvalid) < 0 { return "`" + s + "`" } return qs } // formatHex prints u as a hexadecimal integer in Go notation. func formatHex(u uint64) string { var f string switch { case u <= 0xff: f = "0x%02x" case u <= 0xffff: f = "0x%04x" case u <= 0xffffff: f = "0x%06x" case u <= 0xffffffff: f = "0x%08x" case u <= 0xffffffffff: f = "0x%010x" case u <= 0xffffffffffff: f = "0x%012x" case u <= 0xffffffffffffff: f = "0x%014x" case u <= 0xffffffffffffffff: f = "0x%016x" } return fmt.Sprintf(f, u) } // formatPointer prints the address of the pointer. func formatPointer(v reflect.Value) string { p := v.Pointer() if flags.Deterministic { p = 0xdeadf00f // Only used for stable testing purposes } return fmt.Sprintf("⟪0x%x⟫", p) } type visitedPointers map[value.Pointer]struct{} // Visit inserts pointer v into the visited map and reports whether it had // already been visited before. func (m visitedPointers) Visit(v reflect.Value) bool { p := value.PointerOf(v) _, visited := m[p] m[p] = struct{}{} return visited } go-cmp-0.3.1/cmp/report_slices.go000066400000000000000000000246231352211333400166640ustar00rootroot00000000000000// Copyright 2019, 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 import ( "bytes" "fmt" "reflect" "strings" "unicode" "unicode/utf8" "github.com/google/go-cmp/cmp/internal/diff" ) // CanFormatDiffSlice reports whether we support custom formatting for nodes // that are slices of primitive kinds or strings. func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { switch { case opts.DiffMode != diffUnknown: return false // Must be formatting in diff mode case v.NumDiff == 0: return false // No differences detected case v.NumIgnored+v.NumCompared+v.NumTransformed > 0: // TODO: Handle the case where someone uses bytes.Equal on a large slice. return false // Some custom option was used to determined equality case !v.ValueX.IsValid() || !v.ValueY.IsValid(): return false // Both values must be valid } switch t := v.Type; t.Kind() { case reflect.String: case reflect.Array, reflect.Slice: // Only slices of primitive types have specialized handling. switch t.Elem().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: default: return false } // If a sufficient number of elements already differ, // use specialized formatting even if length requirement is not met. if v.NumDiff > v.NumSame { return true } default: return false } // Use specialized string diffing for longer slices or strings. const minLength = 64 return v.ValueX.Len() >= minLength && v.ValueY.Len() >= minLength } // FormatDiffSlice prints a diff for the slices (or strings) represented by v. // This provides custom-tailored logic to make printing of differences in // textual strings and slices of primitive kinds more readable. func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { assert(opts.DiffMode == diffUnknown) t, vx, vy := v.Type, v.ValueX, v.ValueY // Auto-detect the type of the data. var isLinedText, isText, isBinary bool var sx, sy string switch { case t.Kind() == reflect.String: sx, sy = vx.String(), vy.String() isText = true // Initial estimate, verify later case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)): sx, sy = string(vx.Bytes()), string(vy.Bytes()) isBinary = true // Initial estimate, verify later case t.Kind() == reflect.Array: // Arrays need to be addressable for slice operations to work. vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem() vx2.Set(vx) vy2.Set(vy) vx, vy = vx2, vy2 } if isText || isBinary { var numLines, lastLineIdx, maxLineLen int isBinary = false for i, r := range sx + sy { if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError { isBinary = true break } if r == '\n' { if maxLineLen < i-lastLineIdx { maxLineLen = i - lastLineIdx } lastLineIdx = i + 1 numLines++ } } isText = !isBinary isLinedText = isText && numLines >= 4 && maxLineLen <= 256 } // Format the string into printable records. var list textList var delim string switch { // If the text appears to be multi-lined text, // then perform differencing across individual lines. case isLinedText: ssx := strings.Split(sx, "\n") ssy := strings.Split(sy, "\n") list = opts.formatDiffSlice( reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line", func(v reflect.Value, d diffMode) textRecord { s := formatString(v.Index(0).String()) return textRecord{Diff: d, Value: textLine(s)} }, ) delim = "\n" // If the text appears to be single-lined text, // then perform differencing in approximately fixed-sized chunks. // The output is printed as quoted strings. case isText: list = opts.formatDiffSlice( reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte", func(v reflect.Value, d diffMode) textRecord { s := formatString(v.String()) return textRecord{Diff: d, Value: textLine(s)} }, ) delim = "" // If the text appears to be binary data, // then perform differencing in approximately fixed-sized chunks. // The output is inspired by hexdump. case isBinary: list = opts.formatDiffSlice( reflect.ValueOf(sx), reflect.ValueOf(sy), 16, "byte", func(v reflect.Value, d diffMode) textRecord { var ss []string for i := 0; i < v.Len(); i++ { ss = append(ss, formatHex(v.Index(i).Uint())) } s := strings.Join(ss, ", ") comment := commentString(fmt.Sprintf("%c|%v|", d, formatASCII(v.String()))) return textRecord{Diff: d, Value: textLine(s), Comment: comment} }, ) // For all other slices of primitive types, // then perform differencing in approximately fixed-sized chunks. // The size of each chunk depends on the width of the element kind. default: var chunkSize int if t.Elem().Kind() == reflect.Bool { chunkSize = 16 } else { switch t.Elem().Bits() { case 8: chunkSize = 16 case 16: chunkSize = 12 case 32: chunkSize = 8 default: chunkSize = 8 } } list = opts.formatDiffSlice( vx, vy, chunkSize, t.Elem().Kind().String(), func(v reflect.Value, d diffMode) textRecord { var ss []string for i := 0; i < v.Len(); i++ { switch t.Elem().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: ss = append(ss, fmt.Sprint(v.Index(i).Int())) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: ss = append(ss, formatHex(v.Index(i).Uint())) case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: ss = append(ss, fmt.Sprint(v.Index(i).Interface())) } } s := strings.Join(ss, ", ") return textRecord{Diff: d, Value: textLine(s)} }, ) } // Wrap the output with appropriate type information. var out textNode = textWrap{"{", list, "}"} if !isText { // The "{...}" byte-sequence literal is not valid Go syntax for strings. // Emit the type for extra clarity (e.g. "string{...}"). if t.Kind() == reflect.String { opts = opts.WithTypeMode(emitType) } return opts.FormatType(t, out) } switch t.Kind() { case reflect.String: out = textWrap{"strings.Join(", out, fmt.Sprintf(", %q)", delim)} if t != reflect.TypeOf(string("")) { out = opts.FormatType(t, out) } case reflect.Slice: out = textWrap{"bytes.Join(", out, fmt.Sprintf(", %q)", delim)} if t != reflect.TypeOf([]byte(nil)) { out = opts.FormatType(t, out) } } return out } // formatASCII formats s as an ASCII string. // This is useful for printing binary strings in a semi-legible way. func formatASCII(s string) string { b := bytes.Repeat([]byte{'.'}, len(s)) for i := 0; i < len(s); i++ { if ' ' <= s[i] && s[i] <= '~' { b[i] = s[i] } } return string(b) } func (opts formatOptions) formatDiffSlice( vx, vy reflect.Value, chunkSize int, name string, makeRec func(reflect.Value, diffMode) textRecord, ) (list textList) { es := diff.Difference(vx.Len(), vy.Len(), func(ix int, iy int) diff.Result { return diff.BoolResult(vx.Index(ix).Interface() == vy.Index(iy).Interface()) }) appendChunks := func(v reflect.Value, d diffMode) int { n0 := v.Len() for v.Len() > 0 { n := chunkSize if n > v.Len() { n = v.Len() } list = append(list, makeRec(v.Slice(0, n), d)) v = v.Slice(n, v.Len()) } return n0 - v.Len() } groups := coalesceAdjacentEdits(name, es) groups = coalesceInterveningIdentical(groups, chunkSize/4) for i, ds := range groups { // Print equal. if ds.NumDiff() == 0 { // Compute the number of leading and trailing equal bytes to print. var numLo, numHi int numEqual := ds.NumIgnored + ds.NumIdentical for numLo < chunkSize*numContextRecords && numLo+numHi < numEqual && i != 0 { numLo++ } for numHi < chunkSize*numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 { numHi++ } if numEqual-(numLo+numHi) <= chunkSize && ds.NumIgnored == 0 { numHi = numEqual - numLo // Avoid pointless coalescing of single equal row } // Print the equal bytes. appendChunks(vx.Slice(0, numLo), diffIdentical) if numEqual > numLo+numHi { ds.NumIdentical -= numLo + numHi list.AppendEllipsis(ds) } appendChunks(vx.Slice(numEqual-numHi, numEqual), diffIdentical) vx = vx.Slice(numEqual, vx.Len()) vy = vy.Slice(numEqual, vy.Len()) continue } // Print unequal. nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved) vx = vx.Slice(nx, vx.Len()) ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted) vy = vy.Slice(ny, vy.Len()) } assert(vx.Len() == 0 && vy.Len() == 0) return list } // coalesceAdjacentEdits coalesces the list of edits into groups of adjacent // equal or unequal counts. func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) { var prevCase int // Arbitrary index into which case last occurred lastStats := func(i int) *diffStats { if prevCase != i { groups = append(groups, diffStats{Name: name}) prevCase = i } return &groups[len(groups)-1] } for _, e := range es { switch e { case diff.Identity: lastStats(1).NumIdentical++ case diff.UniqueX: lastStats(2).NumRemoved++ case diff.UniqueY: lastStats(2).NumInserted++ case diff.Modified: lastStats(2).NumModified++ } } return groups } // coalesceInterveningIdentical coalesces sufficiently short (<= windowSize) // equal groups into adjacent unequal groups that currently result in a // dual inserted/removed printout. This acts as a high-pass filter to smooth // out high-frequency changes within the windowSize. func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats { groups, groupsOrig := groups[:0], groups for i, ds := range groupsOrig { if len(groups) >= 2 && ds.NumDiff() > 0 { prev := &groups[len(groups)-2] // Unequal group curr := &groups[len(groups)-1] // Equal group next := &groupsOrig[i] // Unequal group hadX, hadY := prev.NumRemoved > 0, prev.NumInserted > 0 hasX, hasY := next.NumRemoved > 0, next.NumInserted > 0 if ((hadX || hasX) && (hadY || hasY)) && curr.NumIdentical <= windowSize { *prev = prev.Append(*curr).Append(*next) groups = groups[:len(groups)-1] // Truncate off equal group continue } } groups = append(groups, ds) } return groups } go-cmp-0.3.1/cmp/report_text.go000066400000000000000000000245271352211333400163710ustar00rootroot00000000000000// Copyright 2019, 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 import ( "bytes" "fmt" "math/rand" "strings" "time" "github.com/google/go-cmp/cmp/internal/flags" ) var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0 type indentMode int func (n indentMode) appendIndent(b []byte, d diffMode) []byte { // The output of Diff is documented as being unstable to provide future // flexibility in changing the output for more humanly readable reports. // This logic intentionally introduces instability to the exact output // so that users can detect accidental reliance on stability early on, // rather than much later when an actual change to the format occurs. if flags.Deterministic || randBool { // Use regular spaces (U+0020). switch d { case diffUnknown, diffIdentical: b = append(b, " "...) case diffRemoved: b = append(b, "- "...) case diffInserted: b = append(b, "+ "...) } } else { // Use non-breaking spaces (U+00a0). switch d { case diffUnknown, diffIdentical: b = append(b, "  "...) case diffRemoved: b = append(b, "- "...) case diffInserted: b = append(b, "+ "...) } } return repeatCount(n).appendChar(b, '\t') } type repeatCount int func (n repeatCount) appendChar(b []byte, c byte) []byte { for ; n > 0; n-- { b = append(b, c) } return b } // textNode is a simplified tree-based representation of structured text. // Possible node types are textWrap, textList, or textLine. type textNode interface { // Len reports the length in bytes of a single-line version of the tree. // Nested textRecord.Diff and textRecord.Comment fields are ignored. Len() int // Equal reports whether the two trees are structurally identical. // Nested textRecord.Diff and textRecord.Comment fields are compared. Equal(textNode) bool // String returns the string representation of the text tree. // It is not guaranteed that len(x.String()) == x.Len(), // nor that x.String() == y.String() implies that x.Equal(y). String() string // formatCompactTo formats the contents of the tree as a single-line string // to the provided buffer. Any nested textRecord.Diff and textRecord.Comment // fields are ignored. // // However, not all nodes in the tree should be collapsed as a single-line. // If a node can be collapsed as a single-line, it is replaced by a textLine // node. Since the top-level node cannot replace itself, this also returns // the current node itself. // // This does not mutate the receiver. formatCompactTo([]byte, diffMode) ([]byte, textNode) // formatExpandedTo formats the contents of the tree as a multi-line string // to the provided buffer. In order for column alignment to operate well, // formatCompactTo must be called before calling formatExpandedTo. formatExpandedTo([]byte, diffMode, indentMode) []byte } // textWrap is a wrapper that concatenates a prefix and/or a suffix // to the underlying node. type textWrap struct { Prefix string // e.g., "bytes.Buffer{" Value textNode // textWrap | textList | textLine Suffix string // e.g., "}" } func (s textWrap) Len() int { return len(s.Prefix) + s.Value.Len() + len(s.Suffix) } func (s1 textWrap) Equal(s2 textNode) bool { if s2, ok := s2.(textWrap); ok { return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix } return false } func (s textWrap) String() string { var d diffMode var n indentMode _, s2 := s.formatCompactTo(nil, d) b := n.appendIndent(nil, d) // Leading indent b = s2.formatExpandedTo(b, d, n) // Main body b = append(b, '\n') // Trailing newline return string(b) } func (s textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) { n0 := len(b) // Original buffer length b = append(b, s.Prefix...) b, s.Value = s.Value.formatCompactTo(b, d) b = append(b, s.Suffix...) if _, ok := s.Value.(textLine); ok { return b, textLine(b[n0:]) } return b, s } func (s textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte { b = append(b, s.Prefix...) b = s.Value.formatExpandedTo(b, d, n) b = append(b, s.Suffix...) return b } // textList is a comma-separated list of textWrap or textLine nodes. // The list may be formatted as multi-lines or single-line at the discretion // of the textList.formatCompactTo method. type textList []textRecord type textRecord struct { Diff diffMode // e.g., 0 or '-' or '+' Key string // e.g., "MyField" Value textNode // textWrap | textLine Comment fmt.Stringer // e.g., "6 identical fields" } // AppendEllipsis appends a new ellipsis node to the list if none already // exists at the end. If cs is non-zero it coalesces the statistics with the // previous diffStats. func (s *textList) AppendEllipsis(ds diffStats) { hasStats := ds != diffStats{} if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) { if hasStats { *s = append(*s, textRecord{Value: textEllipsis, Comment: ds}) } else { *s = append(*s, textRecord{Value: textEllipsis}) } return } if hasStats { (*s)[len(*s)-1].Comment = (*s)[len(*s)-1].Comment.(diffStats).Append(ds) } } func (s textList) Len() (n int) { for i, r := range s { n += len(r.Key) if r.Key != "" { n += len(": ") } n += r.Value.Len() if i < len(s)-1 { n += len(", ") } } return n } func (s1 textList) Equal(s2 textNode) bool { if s2, ok := s2.(textList); ok { if len(s1) != len(s2) { return false } for i := range s1 { r1, r2 := s1[i], s2[i] if !(r1.Diff == r2.Diff && r1.Key == r2.Key && r1.Value.Equal(r2.Value) && r1.Comment == r2.Comment) { return false } } return true } return false } func (s textList) String() string { return textWrap{"{", s, "}"}.String() } func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) { s = append(textList(nil), s...) // Avoid mutating original // Determine whether we can collapse this list as a single line. n0 := len(b) // Original buffer length var multiLine bool for i, r := range s { if r.Diff == diffInserted || r.Diff == diffRemoved { multiLine = true } b = append(b, r.Key...) if r.Key != "" { b = append(b, ": "...) } b, s[i].Value = r.Value.formatCompactTo(b, d|r.Diff) if _, ok := s[i].Value.(textLine); !ok { multiLine = true } if r.Comment != nil { multiLine = true } if i < len(s)-1 { b = append(b, ", "...) } } // Force multi-lined output when printing a removed/inserted node that // is sufficiently long. if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > 80 { multiLine = true } if !multiLine { return b, textLine(b[n0:]) } return b, s } func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte { alignKeyLens := s.alignLens( func(r textRecord) bool { _, isLine := r.Value.(textLine) return r.Key == "" || !isLine }, func(r textRecord) int { return len(r.Key) }, ) alignValueLens := s.alignLens( func(r textRecord) bool { _, isLine := r.Value.(textLine) return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil }, func(r textRecord) int { return len(r.Value.(textLine)) }, ) // Format the list as a multi-lined output. n++ for i, r := range s { b = n.appendIndent(append(b, '\n'), d|r.Diff) if r.Key != "" { b = append(b, r.Key+": "...) } b = alignKeyLens[i].appendChar(b, ' ') b = r.Value.formatExpandedTo(b, d|r.Diff, n) if !r.Value.Equal(textEllipsis) { b = append(b, ',') } b = alignValueLens[i].appendChar(b, ' ') if r.Comment != nil { b = append(b, " // "+r.Comment.String()...) } } n-- return n.appendIndent(append(b, '\n'), d) } func (s textList) alignLens( skipFunc func(textRecord) bool, lenFunc func(textRecord) int, ) []repeatCount { var startIdx, endIdx, maxLen int lens := make([]repeatCount, len(s)) for i, r := range s { if skipFunc(r) { for j := startIdx; j < endIdx && j < len(s); j++ { lens[j] = repeatCount(maxLen - lenFunc(s[j])) } startIdx, endIdx, maxLen = i+1, i+1, 0 } else { if maxLen < lenFunc(r) { maxLen = lenFunc(r) } endIdx = i + 1 } } for j := startIdx; j < endIdx && j < len(s); j++ { lens[j] = repeatCount(maxLen - lenFunc(s[j])) } return lens } // textLine is a single-line segment of text and is always a leaf node // in the textNode tree. type textLine []byte var ( textNil = textLine("nil") textEllipsis = textLine("...") ) func (s textLine) Len() int { return len(s) } func (s1 textLine) Equal(s2 textNode) bool { if s2, ok := s2.(textLine); ok { return bytes.Equal([]byte(s1), []byte(s2)) } return false } func (s textLine) String() string { return string(s) } func (s textLine) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) { return append(b, s...), s } func (s textLine) formatExpandedTo(b []byte, _ diffMode, _ indentMode) []byte { return append(b, s...) } type diffStats struct { Name string NumIgnored int NumIdentical int NumRemoved int NumInserted int NumModified int } func (s diffStats) NumDiff() int { return s.NumRemoved + s.NumInserted + s.NumModified } func (s diffStats) Append(ds diffStats) diffStats { assert(s.Name == ds.Name) s.NumIgnored += ds.NumIgnored s.NumIdentical += ds.NumIdentical s.NumRemoved += ds.NumRemoved s.NumInserted += ds.NumInserted s.NumModified += ds.NumModified return s } // String prints a humanly-readable summary of coalesced records. // // Example: // diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields" func (s diffStats) String() string { var ss []string var sum int labels := [...]string{"ignored", "identical", "removed", "inserted", "modified"} counts := [...]int{s.NumIgnored, s.NumIdentical, s.NumRemoved, s.NumInserted, s.NumModified} for i, n := range counts { if n > 0 { ss = append(ss, fmt.Sprintf("%d %v", n, labels[i])) } sum += n } // Pluralize the name (adjusting for some obscure English grammar rules). name := s.Name if sum > 1 { name += "s" if strings.HasSuffix(name, "ys") { name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries" } } // Format the list according to English grammar (with Oxford comma). switch n := len(ss); n { case 0: return "" case 1, 2: return strings.Join(ss, " and ") + " " + name default: return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1] + " " + name } } type commentString string func (s commentString) String() string { return string(s) } go-cmp-0.3.1/cmp/report_value.go000066400000000000000000000070601352211333400165120ustar00rootroot00000000000000// Copyright 2019, 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 import "reflect" // valueNode represents a single node within a report, which is a // structured representation of the value tree, containing information // regarding which nodes are equal or not. type valueNode struct { parent *valueNode Type reflect.Type ValueX reflect.Value ValueY reflect.Value // NumSame is the number of leaf nodes that are equal. // All descendants are equal only if NumDiff is 0. NumSame int // NumDiff is the number of leaf nodes that are not equal. NumDiff int // NumIgnored is the number of leaf nodes that are ignored. NumIgnored int // NumCompared is the number of leaf nodes that were compared // using an Equal method or Comparer function. NumCompared int // NumTransformed is the number of non-leaf nodes that were transformed. NumTransformed int // NumChildren is the number of transitive descendants of this node. // This counts from zero; thus, leaf nodes have no descendants. NumChildren int // MaxDepth is the maximum depth of the tree. This counts from zero; // thus, leaf nodes have a depth of zero. MaxDepth int // Records is a list of struct fields, slice elements, or map entries. Records []reportRecord // If populated, implies Value is not populated // Value is the result of a transformation, pointer indirect, of // type assertion. Value *valueNode // If populated, implies Records is not populated // TransformerName is the name of the transformer. TransformerName string // If non-empty, implies Value is populated } type reportRecord struct { Key reflect.Value // Invalid for slice element Value *valueNode } func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) { vx, vy := ps.Values() child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy} switch s := ps.(type) { case StructField: assert(parent.Value == nil) parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child}) case SliceIndex: assert(parent.Value == nil) parent.Records = append(parent.Records, reportRecord{Value: child}) case MapIndex: assert(parent.Value == nil) parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child}) case Indirect: assert(parent.Value == nil && parent.Records == nil) parent.Value = child case TypeAssertion: assert(parent.Value == nil && parent.Records == nil) parent.Value = child case Transform: assert(parent.Value == nil && parent.Records == nil) parent.Value = child parent.TransformerName = s.Name() parent.NumTransformed++ default: assert(parent == nil) // Must be the root step } return child } func (r *valueNode) Report(rs Result) { assert(r.MaxDepth == 0) // May only be called on leaf nodes if rs.ByIgnore() { r.NumIgnored++ } else { if rs.Equal() { r.NumSame++ } else { r.NumDiff++ } } assert(r.NumSame+r.NumDiff+r.NumIgnored == 1) if rs.ByMethod() { r.NumCompared++ } if rs.ByFunc() { r.NumCompared++ } assert(r.NumCompared <= 1) } func (child *valueNode) PopStep() (parent *valueNode) { if child.parent == nil { return nil } parent = child.parent parent.NumSame += child.NumSame parent.NumDiff += child.NumDiff parent.NumIgnored += child.NumIgnored parent.NumCompared += child.NumCompared parent.NumTransformed += child.NumTransformed parent.NumChildren += child.NumChildren + 1 if parent.MaxDepth < child.MaxDepth+1 { parent.MaxDepth = child.MaxDepth + 1 } return parent } go-cmp-0.3.1/go.mod000066400000000000000000000000501352211333400137730ustar00rootroot00000000000000module github.com/google/go-cmp go 1.8