From 4c00efd4bf844d1774932c212359bcc498458802 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 23 Dec 2017 13:02:03 +0100 Subject: [PATCH] Vendor go-cmp --- Gopkg.lock | 6 + vendor/github.com/google/go-cmp/.travis.yml | 18 + .../github.com/google/go-cmp/CONTRIBUTING.md | 23 + vendor/github.com/google/go-cmp/LICENSE | 27 + vendor/github.com/google/go-cmp/README.md | 44 + .../google/go-cmp/cmp/cmpopts/equate.go | 89 + .../google/go-cmp/cmp/cmpopts/ignore.go | 148 ++ .../google/go-cmp/cmp/cmpopts/sort.go | 146 ++ .../google/go-cmp/cmp/cmpopts/sort_go17.go | 46 + .../google/go-cmp/cmp/cmpopts/sort_go18.go | 31 + .../go-cmp/cmp/cmpopts/struct_filter.go | 182 ++ .../google/go-cmp/cmp/cmpopts/util_test.go | 996 +++++++++ .../github.com/google/go-cmp/cmp/compare.go | 529 +++++ .../google/go-cmp/cmp/compare_test.go | 1795 +++++++++++++++++ .../google/go-cmp/cmp/example_test.go | 374 ++++ .../go-cmp/cmp/internal/diff/debug_disable.go | 17 + .../go-cmp/cmp/internal/diff/debug_enable.go | 122 ++ .../google/go-cmp/cmp/internal/diff/diff.go | 373 ++++ .../go-cmp/cmp/internal/diff/diff_test.go | 467 +++++ .../go-cmp/cmp/internal/function/func.go | 49 + .../go-cmp/cmp/internal/testprotos/protos.go | 116 ++ .../cmp/internal/teststructs/project1.go | 267 +++ .../cmp/internal/teststructs/project2.go | 74 + .../cmp/internal/teststructs/project3.go | 77 + .../cmp/internal/teststructs/project4.go | 142 ++ .../cmp/internal/teststructs/structs.go | 197 ++ .../go-cmp/cmp/internal/value/format.go | 259 +++ .../go-cmp/cmp/internal/value/format_test.go | 91 + .../google/go-cmp/cmp/internal/value/sort.go | 111 + .../go-cmp/cmp/internal/value/sort_test.go | 152 ++ .../github.com/google/go-cmp/cmp/options.go | 446 ++++ .../google/go-cmp/cmp/options_test.go | 231 +++ vendor/github.com/google/go-cmp/cmp/path.go | 293 +++ .../github.com/google/go-cmp/cmp/reporter.go | 53 + .../google/go-cmp/cmp/unsafe_panic.go | 15 + .../google/go-cmp/cmp/unsafe_reflect.go | 23 + 36 files changed, 8029 insertions(+) create mode 100644 vendor/github.com/google/go-cmp/.travis.yml create mode 100644 vendor/github.com/google/go-cmp/CONTRIBUTING.md create mode 100644 vendor/github.com/google/go-cmp/LICENSE create mode 100644 vendor/github.com/google/go-cmp/README.md create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/util_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/compare.go create mode 100644 vendor/github.com/google/go-cmp/cmp/compare_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/example_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/diff/diff_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/function/func.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/testprotos/protos.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/teststructs/project1.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/teststructs/project2.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/teststructs/project3.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/teststructs/project4.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/teststructs/structs.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/format.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/format_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/sort.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/sort_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/options.go create mode 100644 vendor/github.com/google/go-cmp/cmp/options_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/path.go create mode 100644 vendor/github.com/google/go-cmp/cmp/reporter.go create mode 100644 vendor/github.com/google/go-cmp/cmp/unsafe_panic.go create mode 100644 vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go diff --git a/Gopkg.lock b/Gopkg.lock index 49e3a8518..ccbe4f7c8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -67,6 +67,12 @@ revision = "925541529c1fa6821df4e44ce2723319eb2be768" version = "v1.0.0" +[[projects]] + name = "github.com/google/go-cmp" + packages = ["cmp","cmp/internal/diff","cmp/internal/function","cmp/internal/value"] + revision = "8099a9787ce5dc5984ed879a3bda47dc730a8e97" + version = "v0.1.0" + [[projects]] name = "github.com/inconshreveable/mousetrap" packages = ["."] diff --git a/vendor/github.com/google/go-cmp/.travis.yml b/vendor/github.com/google/go-cmp/.travis.yml new file mode 100644 index 000000000..9d9b7f916 --- /dev/null +++ b/vendor/github.com/google/go-cmp/.travis.yml @@ -0,0 +1,18 @@ +sudo: false +language: go +go: + - 1.x + - master +matrix: + include: + - go: 1.6.x + script: go test -v -race ./... + allow_failures: + - go: master + fast_finish: true +install: + - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (it is intended for this package to have no dependencies other than the standard library). +script: + - diff -u <(echo -n) <(gofmt -d -s .) + - go tool vet . + - go test -v -race ./... diff --git a/vendor/github.com/google/go-cmp/CONTRIBUTING.md b/vendor/github.com/google/go-cmp/CONTRIBUTING.md new file mode 100644 index 000000000..ae319c70a --- /dev/null +++ b/vendor/github.com/google/go-cmp/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# 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. diff --git a/vendor/github.com/google/go-cmp/LICENSE b/vendor/github.com/google/go-cmp/LICENSE new file mode 100644 index 000000000..32017f8fa --- /dev/null +++ b/vendor/github.com/google/go-cmp/LICENSE @@ -0,0 +1,27 @@ +Copyright (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. diff --git a/vendor/github.com/google/go-cmp/README.md b/vendor/github.com/google/go-cmp/README.md new file mode 100644 index 000000000..d82f10bfc --- /dev/null +++ b/vendor/github.com/google/go-cmp/README.md @@ -0,0 +1,44 @@ +# 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 explictly + 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 diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go new file mode 100644 index 000000000..cc39492cf --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go @@ -0,0 +1,89 @@ +// 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 conjuction 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 conjuction 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 conjuction 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)) +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go new file mode 100644 index 000000000..016891da3 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go @@ -0,0 +1,148 @@ +// 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" +) + +// 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[len(p)-1].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[len(p)-1].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. +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 { + if len(p) < 2 { + return false + } + sf, ok := p[len(p)-1].(cmp.StructField) + if !ok { + return false + } + return xf.m[p[len(p)-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) +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go new file mode 100644 index 000000000..a566d240b --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go @@ -0,0 +1,146 @@ +// 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" + + "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 conjuction with EquateEmpty. +func SortSlices(less interface{}) cmp.Option { + vf := reflect.ValueOf(less) + if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { + panic(fmt.Sprintf("invalid less function: %T", less)) + } + ss := sliceSorter{vf.Type().In(0), vf} + return cmp.FilterValues(ss.filter, cmp.Transformer("Sort", 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 := sliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) }) + ok2 := 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)) + } + sortSliceStable(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 conjuction with EquateEmpty. +func SortMaps(less interface{}) cmp.Option { + vf := reflect.ValueOf(less) + if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { + panic(fmt.Sprintf("invalid less function: %T", less)) + } + ms := mapSorter{vf.Type().In(0), vf} + return cmp.FilterValues(ms.filter, cmp.Transformer("Sort", 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 := mapEntryType(src.Type()) + 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) + } + sortSlice(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) + if !hasReflectStructOf { + vx, vy = vx.Elem(), vy.Elem() + } + return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool() +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go new file mode 100644 index 000000000..839b88ca4 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go @@ -0,0 +1,46 @@ +// 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 !go1.8 + +package cmpopts + +import ( + "reflect" + "sort" +) + +const hasReflectStructOf = false + +func mapEntryType(reflect.Type) reflect.Type { + return reflect.TypeOf(struct{ K, V interface{} }{}) +} + +func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool { + return sort.IsSorted(reflectSliceSorter{reflect.ValueOf(slice), less}) +} +func sortSlice(slice interface{}, less func(i, j int) bool) { + sort.Sort(reflectSliceSorter{reflect.ValueOf(slice), less}) +} +func sortSliceStable(slice interface{}, less func(i, j int) bool) { + sort.Stable(reflectSliceSorter{reflect.ValueOf(slice), less}) +} + +type reflectSliceSorter struct { + slice reflect.Value + less func(i, j int) bool +} + +func (ss reflectSliceSorter) Len() int { + return ss.slice.Len() +} +func (ss reflectSliceSorter) Less(i, j int) bool { + return ss.less(i, j) +} +func (ss reflectSliceSorter) Swap(i, j int) { + vi := ss.slice.Index(i).Interface() + vj := ss.slice.Index(j).Interface() + ss.slice.Index(i).Set(reflect.ValueOf(vj)) + ss.slice.Index(j).Set(reflect.ValueOf(vi)) +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go new file mode 100644 index 000000000..8a59c0d38 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go @@ -0,0 +1,31 @@ +// 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 go1.8 + +package cmpopts + +import ( + "reflect" + "sort" +) + +const hasReflectStructOf = true + +func mapEntryType(t reflect.Type) reflect.Type { + return reflect.StructOf([]reflect.StructField{ + {Name: "K", Type: t.Key()}, + {Name: "V", Type: t.Elem()}, + }) +} + +func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool { + return sort.SliceIsSorted(slice, less) +} +func sortSlice(slice interface{}, less func(i, j int) bool) { + sort.Slice(slice, less) +} +func sortSliceStable(slice interface{}, less func(i, j int) bool) { + sort.SliceStable(slice, less) +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go new file mode 100644 index 000000000..97f707983 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go @@ -0,0 +1,182 @@ +// 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 +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/util_test.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/util_test.go new file mode 100644 index 000000000..f53278990 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/util_test.go @@ -0,0 +1,996 @@ +// 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 + MyFloat float32 + 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", + }} + + for _, tt := range tests { + tRun(t, 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 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", + }} + + for _, tt := range tests { + tRun(t, 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.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) + } + }) + } +} + +// TODO: Delete this hack when we drop Go1.6 support. +func tRun(t *testing.T, name string, f func(t *testing.T)) { + type runner interface { + Run(string, func(t *testing.T)) bool + } + var ti interface{} = t + if r, ok := ti.(runner); ok { + r.Run(name, f) + } else { + t.Logf("Test: %s", name) + f(t) + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/compare.go b/vendor/github.com/google/go-cmp/cmp/compare.go new file mode 100644 index 000000000..5527f0149 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/compare.go @@ -0,0 +1,529 @@ +// 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 explictly compared +// using the AllowUnexported option. +package cmp + +import ( + "fmt" + "reflect" + + "github.com/google/go-cmp/cmp/internal/diff" + "github.com/google/go-cmp/cmp/internal/function" + "github.com/google/go-cmp/cmp/internal/value" +) + +// BUG: Maps with keys containing NaN values cannot be properly compared due to +// the reflection package's inability to retrieve such entries. Equal will panic +// anytime it comes across a NaN key, but this behavior may change. +// +// See https://golang.org/issue/11104 for more details. + +var nothing = reflect.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: +// +// • If two values are not of the same type, then they are never equal +// and the overall result is false. +// +// • 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). 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. +// Pointers are equal if the underlying values they point to are also equal. +// Interfaces are equal if their underlying concrete values are also equal. +// +// Structs are equal if all of their fields are equal. If a struct contains +// unexported fields, Equal panics unless the AllowUnexported option is used or +// an Ignore option (e.g., cmpopts.IgnoreUnexported) ignores that field. +// +// Arrays, slices, and maps are equal if they are both nil or both non-nil +// with the same length and the elements at each index or key are equal. +// Note that a non-nil empty slice and a nil slice are not equal. +// To equate empty slices and maps, consider using cmpopts.EquateEmpty. +// Map keys are equal according to the == operator. +// To use custom comparisons for map keys, consider using cmpopts.SortMaps. +func Equal(x, y interface{}, opts ...Option) bool { + s := newState(opts) + s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y)) + 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 string will use the "-" symbol to +// indicate elements removed from x, and the "+" symbol to indicate elements +// added to y. +// +// Do not depend on this output being stable. +func Diff(x, y interface{}, opts ...Option) string { + r := new(defaultReporter) + opts = Options{Options(opts), r} + eq := Equal(x, y, opts...) + 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 + reporter reporter // Optional reporter used for difference formatting + + // 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 { + s := new(state) + for _, opt := range opts { + s.processOption(opt) + } + 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: + if s.reporter != nil { + panic("difference reporter already registered") + } + s.reporter = 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(vx, vy reflect.Value) 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, oldReporter := s.result, s.reporter + s.result = diff.Result{} // Reset result + s.reporter = nil // Remove reporter to avoid spurious printouts + s.compareAny(vx, vy) + res := s.result + s.result, s.reporter = oldResult, oldReporter + return res +} + +func (s *state) compareAny(vx, vy reflect.Value) { + // TODO: Support cyclic data structures. + + // Rule 0: Differing types are never equal. + if !vx.IsValid() || !vy.IsValid() { + s.report(vx.IsValid() == vy.IsValid(), vx, vy) + return + } + if vx.Type() != vy.Type() { + s.report(false, vx, vy) // Possible for path to be empty + return + } + t := vx.Type() + if len(s.curPath) == 0 { + s.curPath.push(&pathStep{typ: t}) + defer s.curPath.pop() + } + vx, vy = s.tryExporting(vx, vy) + + // Rule 1: Check whether an option applies on this node in the value tree. + if s.tryOptions(vx, vy, t) { + return + } + + // Rule 2: Check whether the type has a valid Equal method. + if s.tryMethod(vx, vy, t) { + return + } + + // Rule 3: Recursively descend into each value's underlying kind. + switch t.Kind() { + case reflect.Bool: + s.report(vx.Bool() == vy.Bool(), vx, vy) + return + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + s.report(vx.Int() == vy.Int(), vx, vy) + return + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + s.report(vx.Uint() == vy.Uint(), vx, vy) + return + case reflect.Float32, reflect.Float64: + s.report(vx.Float() == vy.Float(), vx, vy) + return + case reflect.Complex64, reflect.Complex128: + s.report(vx.Complex() == vy.Complex(), vx, vy) + return + case reflect.String: + s.report(vx.String() == vy.String(), vx, vy) + return + case reflect.Chan, reflect.UnsafePointer: + s.report(vx.Pointer() == vy.Pointer(), vx, vy) + return + case reflect.Func: + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + case reflect.Ptr: + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + } + s.curPath.push(&indirect{pathStep{t.Elem()}}) + defer s.curPath.pop() + s.compareAny(vx.Elem(), vy.Elem()) + return + case reflect.Interface: + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + } + if vx.Elem().Type() != vy.Elem().Type() { + s.report(false, vx.Elem(), vy.Elem()) + return + } + s.curPath.push(&typeAssertion{pathStep{vx.Elem().Type()}}) + defer s.curPath.pop() + s.compareAny(vx.Elem(), vy.Elem()) + return + case reflect.Slice: + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + } + fallthrough + case reflect.Array: + s.compareArray(vx, vy, t) + return + case reflect.Map: + s.compareMap(vx, vy, t) + return + case reflect.Struct: + s.compareStruct(vx, vy, t) + return + default: + panic(fmt.Sprintf("%v kind not handled", t.Kind())) + } +} + +func (s *state) tryExporting(vx, vy reflect.Value) (reflect.Value, reflect.Value) { + if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported { + if sf.force { + // Use unsafe pointer arithmetic to get read-write access to an + // unexported field in the struct. + vx = unsafeRetrieveField(sf.pvx, sf.field) + vy = unsafeRetrieveField(sf.pvy, sf.field) + } else { + // We are not allowed to export the value, so invalidate them + // so that tryOptions can panic later if not explicitly ignored. + vx = nothing + vy = nothing + } + } + return vx, vy +} + +func (s *state) tryOptions(vx, vy reflect.Value, t reflect.Type) bool { + // If there were no FilterValues, we will not detect invalid inputs, + // so manually check for them and append invalid if necessary. + // We still evaluate the options since an ignore can override invalid. + opts := s.opts + if !vx.IsValid() || !vy.IsValid() { + opts = Options{opts, invalid{}} + } + + // Evaluate all filters and apply the remaining options. + if opt := opts.filter(s, vx, vy, t); opt != nil { + return opt.apply(s, vx, vy) + } + return false +} + +func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) 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, vx, vy) + return true +} + +func (s *state) callTRFunc(f, v reflect.Value) reflect.Value { + 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) + want := f.Call([]reflect.Value{v})[0] + if got := <-c; !s.statelessCompare(got, want).Equal() { + // To avoid false-positives with non-reflexive equality operations, + // we sanity check whether a value is equal to itself. + if !s.statelessCompare(want, want).Equal() { + return want + } + fn := getFuncName(f.Pointer()) + panic(fmt.Sprintf("non-deterministic function detected: %s", fn)) + } + return want +} + +func (s *state) callTTBFunc(f, x, y reflect.Value) bool { + 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) + want := f.Call([]reflect.Value{x, y})[0].Bool() + if got := <-c; !got.IsValid() || got.Bool() != want { + fn := getFuncName(f.Pointer()) + panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn)) + } + 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] +} + +func (s *state) compareArray(vx, vy reflect.Value, t reflect.Type) { + step := &sliceIndex{pathStep{t.Elem()}, 0, 0} + s.curPath.push(step) + + // Compute an edit-script for slices vx and vy. + eq, es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result { + step.xkey, step.ykey = ix, iy + return s.statelessCompare(vx.Index(ix), vy.Index(iy)) + }) + + // Equal or no edit-script, so report entire slices as is. + if eq || es == nil { + s.curPath.pop() // Pop first since we are reporting the whole slice + s.report(eq, vx, vy) + return + } + + // Replay the edit-script. + var ix, iy int + for _, e := range es { + switch e { + case diff.UniqueX: + step.xkey, step.ykey = ix, -1 + s.report(false, vx.Index(ix), nothing) + ix++ + case diff.UniqueY: + step.xkey, step.ykey = -1, iy + s.report(false, nothing, vy.Index(iy)) + iy++ + default: + step.xkey, step.ykey = ix, iy + if e == diff.Identity { + s.report(true, vx.Index(ix), vy.Index(iy)) + } else { + s.compareAny(vx.Index(ix), vy.Index(iy)) + } + ix++ + iy++ + } + } + s.curPath.pop() + return +} + +func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) { + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + } + + // We combine and sort the two map keys so that we can perform the + // comparisons in a deterministic order. + step := &mapIndex{pathStep: pathStep{t.Elem()}} + s.curPath.push(step) + defer s.curPath.pop() + for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) { + step.key = k + vvx := vx.MapIndex(k) + vvy := vy.MapIndex(k) + switch { + case vvx.IsValid() && vvy.IsValid(): + s.compareAny(vvx, vvy) + case vvx.IsValid() && !vvy.IsValid(): + s.report(false, vvx, nothing) + case !vvx.IsValid() && vvy.IsValid(): + s.report(false, nothing, vvy) + default: + // It is possible for both vvx and vvy to be invalid if the + // key contained a NaN value in it. There is no way in + // reflection to be able to retrieve these values. + // See https://golang.org/issue/11104 + panic(fmt.Sprintf("%#v has map key with NaNs", s.curPath)) + } + } +} + +func (s *state) compareStruct(vx, vy reflect.Value, t reflect.Type) { + var vax, vay reflect.Value // Addressable versions of vx and vy + + step := &structField{} + s.curPath.push(step) + defer s.curPath.pop() + for i := 0; i < t.NumField(); i++ { + vvx := vx.Field(i) + vvy := vy.Field(i) + step.typ = t.Field(i).Type + step.name = t.Field(i).Name + step.idx = i + step.unexported = !isExported(step.name) + if step.unexported { + // Defer checking of unexported fields until later to give an + // Ignore a chance to ignore the field. + if !vax.IsValid() || !vay.IsValid() { + // For unsafeRetrieveField 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.force = s.exporters[t] + step.pvx = vax + step.pvy = vay + step.field = t.Field(i) + } + s.compareAny(vvx, vvy) + } +} + +// report records the result of a single comparison. +// It also calls Report if any reporter is registered. +func (s *state) report(eq bool, vx, vy reflect.Value) { + if eq { + s.result.NSame++ + } else { + s.result.NDiff++ + } + if s.reporter != nil { + s.reporter.Report(vx, vy, eq, s.curPath) + } +} + +// 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 +} diff --git a/vendor/github.com/google/go-cmp/cmp/compare_test.go b/vendor/github.com/google/go-cmp/cmp/compare_test.go new file mode 100644 index 000000000..36a4ecf7d --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/compare_test.go @@ -0,0 +1,1795 @@ +// 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" + "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" + pb "github.com/google/go-cmp/cmp/internal/testprotos" + ts "github.com/google/go-cmp/cmp/internal/teststructs" +) + +var now = time.Now() + +func intPtr(n int) *int { return &n } + +type test struct { + label string // Test description + 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 +} + +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 + tRunParallel(t, tt.label, func(t *testing.T) { + 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...) + }() + if tt.wantPanic == "" { + if gotPanic != "" { + t.Fatalf("unexpected panic message: %s", gotPanic) + } + if got, want := strings.TrimSpace(gotDiff), strings.TrimSpace(tt.wantDiff); got != want { + t.Fatalf("difference message:\ngot:\n%s\n\nwant:\n%s", got, want) + } + } else { + if !strings.Contains(gotPanic, tt.wantPanic) { + t.Fatalf("panic message:\ngot: %s\nwant: %s", gotPanic, tt.wantPanic) + } + } + }) + } +} + +func comparerTests() []test { + const label = "Comparer" + + return []test{{ + 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: "root.C:\n\t-: 3\n\t+: 4\n", + }, { + 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: "*root.A:\n\t-: 4\n\t+: 5\n", + }, { + 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: "root.R:\n\t-: \"\"\n\t+: \n", + }, { + 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}[1]: + -: "a*b*c*" + +: "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: ` +root: + -: "hello" + +: "hello2"`, + }, { + label: label, + x: md5.Sum([]byte{'a'}), + y: md5.Sum([]byte{'b'}), + wantDiff: ` +{[16]uint8}: + -: [16]uint8{0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61} + +: [16]uint8{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: ` +: + -: & + +: `, + }, { + 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}: + -: []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +: []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}`, + }} +} + +func transformerTests() []test { + const label = "Transformer/" + + 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}))): + -: 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}[1]): + -: -5 + +: 3 +λ({[]int}[3]): + -: -1 + +: -5`, + }, { + label: label, + x: 0, + y: 1, + opts: []cmp.Option{ + cmp.Transformer("", func(in int) interface{} { + if in == 0 { + return "string" + } + return float64(in) + }), + }, + wantDiff: ` +λ({int}): + -: "string" + +: 1`, + }} +} + +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 + } + + 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.Public: + -: 1 + +: 2 +{teststructs.ParentStructA}.privateStruct.private: + -: 2 + +: 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.Public: + -: 1 + +: 2 +{teststructs.ParentStructB}.PublicStruct.private: + -: 2 + +: 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.Public: + -: 1 + +: 2 +{teststructs.ParentStructC}.privateStruct.private: + -: 2 + +: 3 +{teststructs.ParentStructC}.Public: + -: 3 + +: 4 +{teststructs.ParentStructC}.private: + -: 4 + +: 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.Public: + -: 1 + +: 2 +{teststructs.ParentStructD}.PublicStruct.private: + -: 2 + +: 3 +{teststructs.ParentStructD}.Public: + -: 3 + +: 4 +{teststructs.ParentStructD}.private: + -: 4 + +: 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.Public: + -: 1 + +: 2 +{teststructs.ParentStructE}.privateStruct.private: + -: 2 + +: 3 +{teststructs.ParentStructE}.PublicStruct.Public: + -: 3 + +: 4 +{teststructs.ParentStructE}.PublicStruct.private: + -: 4 + +: 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.Public: + -: 1 + +: 2 +{teststructs.ParentStructF}.privateStruct.private: + -: 2 + +: 3 +{teststructs.ParentStructF}.PublicStruct.Public: + -: 3 + +: 4 +{teststructs.ParentStructF}.PublicStruct.private: + -: 4 + +: 5 +{teststructs.ParentStructF}.Public: + -: 5 + +: 6 +{teststructs.ParentStructF}.private: + -: 6 + +: 7`, + }, { + label: label + "ParentStructG", + x: ts.ParentStructG{}, + y: ts.ParentStructG{}, + wantPanic: "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.Public: + -: 1 + +: 2 +{*teststructs.ParentStructG}.privateStruct.private: + -: 2 + +: 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.Public: + -: 1 + +: 2 +{*teststructs.ParentStructH}.PublicStruct.private: + -: 2 + +: 3`, + }, { + label: label + "ParentStructI", + x: ts.ParentStructI{}, + y: ts.ParentStructI{}, + wantPanic: "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.Public: + -: 1 + +: 2 +{*teststructs.ParentStructI}.privateStruct.private: + -: 2 + +: 3 +{*teststructs.ParentStructI}.PublicStruct.Public: + -: 3 + +: 4 +{*teststructs.ParentStructI}.PublicStruct.private: + -: 4 + +: 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.Public: + -: 1 + +: 2 +{*teststructs.ParentStructJ}.privateStruct.private: + -: 2 + +: 3 +{*teststructs.ParentStructJ}.PublicStruct.Public: + -: 3 + +: 4 +{*teststructs.ParentStructJ}.PublicStruct.private: + -: 4 + +: 5 +{*teststructs.ParentStructJ}.Public.Public: + -: 7 + +: 8 +{*teststructs.ParentStructJ}.Public.private: + -: 8 + +: 9 +{*teststructs.ParentStructJ}.private.Public: + -: 5 + +: 6 +{*teststructs.ParentStructJ}.private.private: + -: 6 + +: 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{"NotEqual"}, + y: ts.StructA{"not_equal"}, + }, { + label: label + "StructA", + x: &ts.StructA{"NotEqual"}, + y: &ts.StructA{"not_equal"}, + }, { + label: label + "StructB", + x: ts.StructB{"NotEqual"}, + y: ts.StructB{"not_equal"}, + wantDiff: ` +{teststructs.StructB}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructB", + x: ts.StructB{"NotEqual"}, + y: ts.StructB{"not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructB", + x: &ts.StructB{"NotEqual"}, + y: &ts.StructB{"not_equal"}, + }, { + label: label + "StructC", + x: ts.StructC{"NotEqual"}, + y: ts.StructC{"not_equal"}, + }, { + label: label + "StructC", + x: &ts.StructC{"NotEqual"}, + y: &ts.StructC{"not_equal"}, + }, { + label: label + "StructD", + x: ts.StructD{"NotEqual"}, + y: ts.StructD{"not_equal"}, + wantDiff: ` +{teststructs.StructD}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructD", + x: ts.StructD{"NotEqual"}, + y: ts.StructD{"not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructD", + x: &ts.StructD{"NotEqual"}, + y: &ts.StructD{"not_equal"}, + }, { + label: label + "StructE", + x: ts.StructE{"NotEqual"}, + y: ts.StructE{"not_equal"}, + wantDiff: ` +{teststructs.StructE}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructE", + x: ts.StructE{"NotEqual"}, + y: ts.StructE{"not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructE", + x: &ts.StructE{"NotEqual"}, + y: &ts.StructE{"not_equal"}, + }, { + label: label + "StructF", + x: ts.StructF{"NotEqual"}, + y: ts.StructF{"not_equal"}, + wantDiff: ` +{teststructs.StructF}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructF", + x: &ts.StructF{"NotEqual"}, + y: &ts.StructF{"not_equal"}, + }, { + label: label + "StructA1", + x: ts.StructA1{ts.StructA{"NotEqual"}, "equal"}, + y: ts.StructA1{ts.StructA{"not_equal"}, "equal"}, + }, { + label: label + "StructA1", + x: ts.StructA1{ts.StructA{"NotEqual"}, "NotEqual"}, + y: ts.StructA1{ts.StructA{"not_equal"}, "not_equal"}, + wantDiff: "{teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructA1", + x: &ts.StructA1{ts.StructA{"NotEqual"}, "equal"}, + y: &ts.StructA1{ts.StructA{"not_equal"}, "equal"}, + }, { + label: label + "StructA1", + x: &ts.StructA1{ts.StructA{"NotEqual"}, "NotEqual"}, + y: &ts.StructA1{ts.StructA{"not_equal"}, "not_equal"}, + wantDiff: "{*teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructB1", + x: ts.StructB1{ts.StructB{"NotEqual"}, "equal"}, + y: ts.StructB1{ts.StructB{"not_equal"}, "equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructB1", + x: ts.StructB1{ts.StructB{"NotEqual"}, "NotEqual"}, + y: ts.StructB1{ts.StructB{"not_equal"}, "not_equal"}, + opts: []cmp.Option{derefTransform}, + wantDiff: "{teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructB1", + x: &ts.StructB1{ts.StructB{"NotEqual"}, "equal"}, + y: &ts.StructB1{ts.StructB{"not_equal"}, "equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructB1", + x: &ts.StructB1{ts.StructB{"NotEqual"}, "NotEqual"}, + y: &ts.StructB1{ts.StructB{"not_equal"}, "not_equal"}, + opts: []cmp.Option{derefTransform}, + wantDiff: "{*teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructC1", + x: ts.StructC1{ts.StructC{"NotEqual"}, "NotEqual"}, + y: ts.StructC1{ts.StructC{"not_equal"}, "not_equal"}, + }, { + label: label + "StructC1", + x: &ts.StructC1{ts.StructC{"NotEqual"}, "NotEqual"}, + y: &ts.StructC1{ts.StructC{"not_equal"}, "not_equal"}, + }, { + label: label + "StructD1", + x: ts.StructD1{ts.StructD{"NotEqual"}, "NotEqual"}, + y: ts.StructD1{ts.StructD{"not_equal"}, "not_equal"}, + wantDiff: ` +{teststructs.StructD1}.StructD.X: + -: "NotEqual" + +: "not_equal" +{teststructs.StructD1}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructD1", + x: ts.StructD1{ts.StructD{"NotEqual"}, "NotEqual"}, + y: ts.StructD1{ts.StructD{"not_equal"}, "not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructD1", + x: &ts.StructD1{ts.StructD{"NotEqual"}, "NotEqual"}, + y: &ts.StructD1{ts.StructD{"not_equal"}, "not_equal"}, + }, { + label: label + "StructE1", + x: ts.StructE1{ts.StructE{"NotEqual"}, "NotEqual"}, + y: ts.StructE1{ts.StructE{"not_equal"}, "not_equal"}, + wantDiff: ` +{teststructs.StructE1}.StructE.X: + -: "NotEqual" + +: "not_equal" +{teststructs.StructE1}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructE1", + x: ts.StructE1{ts.StructE{"NotEqual"}, "NotEqual"}, + y: ts.StructE1{ts.StructE{"not_equal"}, "not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructE1", + x: &ts.StructE1{ts.StructE{"NotEqual"}, "NotEqual"}, + y: &ts.StructE1{ts.StructE{"not_equal"}, "not_equal"}, + }, { + label: label + "StructF1", + x: ts.StructF1{ts.StructF{"NotEqual"}, "NotEqual"}, + y: ts.StructF1{ts.StructF{"not_equal"}, "not_equal"}, + wantDiff: ` +{teststructs.StructF1}.StructF.X: + -: "NotEqual" + +: "not_equal" +{teststructs.StructF1}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructF1", + x: &ts.StructF1{ts.StructF{"NotEqual"}, "NotEqual"}, + y: &ts.StructF1{ts.StructF{"not_equal"}, "not_equal"}, + }, { + label: label + "StructA2", + x: ts.StructA2{&ts.StructA{"NotEqual"}, "equal"}, + y: ts.StructA2{&ts.StructA{"not_equal"}, "equal"}, + }, { + label: label + "StructA2", + x: ts.StructA2{&ts.StructA{"NotEqual"}, "NotEqual"}, + y: ts.StructA2{&ts.StructA{"not_equal"}, "not_equal"}, + wantDiff: "{teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructA2", + x: &ts.StructA2{&ts.StructA{"NotEqual"}, "equal"}, + y: &ts.StructA2{&ts.StructA{"not_equal"}, "equal"}, + }, { + label: label + "StructA2", + x: &ts.StructA2{&ts.StructA{"NotEqual"}, "NotEqual"}, + y: &ts.StructA2{&ts.StructA{"not_equal"}, "not_equal"}, + wantDiff: "{*teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructB2", + x: ts.StructB2{&ts.StructB{"NotEqual"}, "equal"}, + y: ts.StructB2{&ts.StructB{"not_equal"}, "equal"}, + }, { + label: label + "StructB2", + x: ts.StructB2{&ts.StructB{"NotEqual"}, "NotEqual"}, + y: ts.StructB2{&ts.StructB{"not_equal"}, "not_equal"}, + wantDiff: "{teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructB2", + x: &ts.StructB2{&ts.StructB{"NotEqual"}, "equal"}, + y: &ts.StructB2{&ts.StructB{"not_equal"}, "equal"}, + }, { + label: label + "StructB2", + x: &ts.StructB2{&ts.StructB{"NotEqual"}, "NotEqual"}, + y: &ts.StructB2{&ts.StructB{"not_equal"}, "not_equal"}, + wantDiff: "{*teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructC2", + x: ts.StructC2{&ts.StructC{"NotEqual"}, "NotEqual"}, + y: ts.StructC2{&ts.StructC{"not_equal"}, "not_equal"}, + }, { + label: label + "StructC2", + x: &ts.StructC2{&ts.StructC{"NotEqual"}, "NotEqual"}, + y: &ts.StructC2{&ts.StructC{"not_equal"}, "not_equal"}, + }, { + label: label + "StructD2", + x: ts.StructD2{&ts.StructD{"NotEqual"}, "NotEqual"}, + y: ts.StructD2{&ts.StructD{"not_equal"}, "not_equal"}, + }, { + label: label + "StructD2", + x: &ts.StructD2{&ts.StructD{"NotEqual"}, "NotEqual"}, + y: &ts.StructD2{&ts.StructD{"not_equal"}, "not_equal"}, + }, { + label: label + "StructE2", + x: ts.StructE2{&ts.StructE{"NotEqual"}, "NotEqual"}, + y: ts.StructE2{&ts.StructE{"not_equal"}, "not_equal"}, + }, { + label: label + "StructE2", + x: &ts.StructE2{&ts.StructE{"NotEqual"}, "NotEqual"}, + y: &ts.StructE2{&ts.StructE{"not_equal"}, "not_equal"}, + }, { + label: label + "StructF2", + x: ts.StructF2{&ts.StructF{"NotEqual"}, "NotEqual"}, + y: ts.StructF2{&ts.StructF{"not_equal"}, "not_equal"}, + }, { + label: label + "StructF2", + x: &ts.StructF2{&ts.StructF{"NotEqual"}, "NotEqual"}, + y: &ts.StructF2{&ts.StructF{"not_equal"}, "not_equal"}, + }, { + label: label + "StructNo", + x: ts.StructNo{"NotEqual"}, + y: ts.StructNo{"not_equal"}, + wantDiff: "{teststructs.StructNo}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + 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{"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{"metadata"}}, + }}}, + y: ts.Eagle{Slaps: []ts.Slap{{ + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + }}}, + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: ts.Eagle{Slaps: []ts.Slap{{ + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + }}}, + y: ts.Eagle{Slaps: []ts.Slap{{ + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + }}}, + opts: []cmp.Option{cmp.Comparer(pb.Equal)}, + }, { + label: label, + x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, { + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + }}}, + y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, { + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata2"}}, + }}}, + opts: []cmp.Option{cmp.Comparer(pb.Equal)}, + wantDiff: "{teststructs.Eagle}.Slaps[4].Args:\n\t-: \"metadata\"\n\t+: \"metadata2\"\n", + }, { + 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}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.ID: + -: "southbay2" + +: "southbay" +*{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.State: + -: testprotos.Goat_States(6) + +: testprotos.Goat_States(5) +{teststructs.Eagle}.Slaps[0].Immutable.MildSlap: + -: false + +: true +{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[1->?]: + -: "bar" + +: +{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[2->?]: + -: "baz" + +: `, + }} +} + +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.FilterValues(func(x, y []*pb.Germ) bool { + ok1 := sort.IsSorted(germSorter(x)) + ok2 := sort.IsSorted(germSorter(y)) + return !ok1 || !ok2 + }, 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{"germ1"}}, + }, + 18: { + {Stringer: pb.Stringer{"germ2"}}, + {Stringer: pb.Stringer{"germ3"}}, + {Stringer: pb.Stringer{"germ4"}}, + }, + }, + GermMap: map[int32]*pb.Germ{ + 13: {Stringer: pb.Stringer{"germ13"}}, + 21: {Stringer: pb.Stringer{"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{"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[18][0->?]: + -: "germ2" + +: +{teststructs.GermBatch}.DirtyGerms[18][?->2]: + -: + +: "germ2"`, + }, { + 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[17]: + -: + +: []*testprotos.Germ{"germ1"} +{teststructs.GermBatch}.DirtyGerms[18][2->?]: + -: "germ4" + +: +{teststructs.GermBatch}.DishMap[1]: + -: (*teststructs.Dish)(nil) + +: &teststructs.Dish{err: &errors.errorString{s: "unexpected EOF"}} +{teststructs.GermBatch}.GermStrain: + -: 421 + +: 22`, + }} +} + +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{"proto"}} + d.SetWizard(map[string]*pb.Wizard{ + "harry": {Stringer: pb.Stringer{"potter"}}, + "albus": {Stringer: pb.Stringer{"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{"blah"}} + return d + }(), + y: func() ts.Dirt { + d := createDirt() + d.Discord = 500 + d.SetWizard(map[string]*pb.Wizard{ + "harry": {Stringer: pb.Stringer{"otter"}}, + }) + return d + }(), + opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, + wantDiff: ` +{teststructs.Dirt}.table: + -: &teststructs.MockTable{state: []string{"a", "c"}} + +: &teststructs.MockTable{state: []string{"a", "b", "c"}} +{teststructs.Dirt}.Discord: + -: teststructs.DiscordState(554) + +: teststructs.DiscordState(500) +λ({teststructs.Dirt}.Proto): + -: "blah" + +: "proto" +{teststructs.Dirt}.wizard["albus"]: + -: "dumbledore" + +: +{teststructs.Dirt}.wizard["harry"]: + -: "potter" + +: "otter"`, + }} +} + +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.SetManufactuer("acme") + + var hq ts.Headquarter + hq.SetID(5) + hq.SetLocation("moon") + hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"}) + hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{"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.SetManufactuer("acme") + p2.SetPoisonType(2) + p2.SetManufactuer("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.subDivisions[0->?]: + -: "alpha" + +: +{teststructs.Cartel}.Headquarter.publicMessage[2]: + -: 0x03 + +: 0x04 +{teststructs.Cartel}.Headquarter.publicMessage[3]: + -: 0x04 + +: 0x03 +{teststructs.Cartel}.poisons[0].poisonType: + -: testprotos.PoisonType(1) + +: testprotos.PoisonType(5) +{teststructs.Cartel}.poisons[1->?]: + -: &teststructs.Poison{poisonType: testprotos.PoisonType(2), manufactuer: "acme2"} + +: `, + }} +} + +// TODO: Delete this hack when we drop Go1.6 support. +func tRunParallel(t *testing.T, name string, f func(t *testing.T)) { + type runner interface { + Run(string, func(t *testing.T)) bool + } + var ti interface{} = t + if r, ok := ti.(runner); ok { + r.Run(name, func(t *testing.T) { + t.Parallel() + f(t) + }) + } else { + // Cannot run sub-tests in parallel in Go1.6. + t.Logf("Test: %s", name) + f(t) + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/example_test.go b/vendor/github.com/google/go-cmp/cmp/example_test.go new file mode 100644 index 000000000..8729db398 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/example_test.go @@ -0,0 +1,374 @@ +// 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" + "reflect" + "sort" + "strings" + + "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 for printing out human-readable errors for test cases comparing +// nested or structured data. +func ExampleDiff_testing() { + // Code under test: + type ShipManifest struct { + Name string + Crew map[string]string + Androids int + Stolen bool + } + + // AddCrew tries to add the given crewmember to the manifest. + AddCrew := func(m *ShipManifest, name, title string) { + if m.Crew == nil { + m.Crew = make(map[string]string) + } + m.Crew[title] = name + } + + // Test function: + tests := []struct { + desc string + before *ShipManifest + name, title string + after *ShipManifest + }{ + { + desc: "add to empty", + before: &ShipManifest{}, + name: "Zaphod Beeblebrox", + title: "Galactic President", + after: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + }, + }, + }, + { + desc: "add another", + before: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + }, + }, + name: "Trillian", + title: "Human", + after: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + "Trillian": "Human", + }, + }, + }, + { + desc: "overwrite", + before: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Galactic President", + }, + }, + name: "Zaphod Beeblebrox", + title: "Just this guy, you know?", + after: &ShipManifest{ + Crew: map[string]string{ + "Zaphod Beeblebrox": "Just this guy, you know?", + }, + }, + }, + } + + var t fakeT + for _, test := range tests { + AddCrew(test.before, test.name, test.title) + if diff := cmp.Diff(test.before, test.after); diff != "" { + t.Errorf("%s: after AddCrew, manifest differs: (-got +want)\n%s", test.desc, diff) + } + } + + // Output: + // add to empty: after AddCrew, manifest differs: (-got +want) + // {*cmp_test.ShipManifest}.Crew["Galactic President"]: + // -: "Zaphod Beeblebrox" + // +: + // {*cmp_test.ShipManifest}.Crew["Zaphod Beeblebrox"]: + // -: + // +: "Galactic President" + // + // add another: after AddCrew, manifest differs: (-got +want) + // {*cmp_test.ShipManifest}.Crew["Human"]: + // -: "Trillian" + // +: + // {*cmp_test.ShipManifest}.Crew["Trillian"]: + // -: + // +: "Human" + // + // overwrite: after AddCrew, manifest differs: (-got +want) + // {*cmp_test.ShipManifest}.Crew["Just this guy, you know?"]: + // -: "Zaphod Beeblebrox" + // +: + // {*cmp_test.ShipManifest}.Crew["Zaphod Beeblebrox"]: + // -: "Galactic President" + // +: "Just this guy, you know?" +} + +// 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. + // Since the transformer transforms []int into []int, there is problem where + // this is recursively applied forever. To prevent this, use a FilterValues + // to first check for the condition upon which the transformer ought to apply. + trans := cmp.FilterValues(func(x, y []int) bool { + return !sort.IntsAreSorted(x) || !sort.IntsAreSorted(y) + }, 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 fakeT struct{} + +func (t fakeT) Errorf(format string, args ...interface{}) { fmt.Printf(format+"\n", args...) } diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go new file mode 100644 index 000000000..42afa4960 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go @@ -0,0 +1,17 @@ +// 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 !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() {} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go new file mode 100644 index 000000000..ba46c62e1 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go @@ -0,0 +1,122 @@ +// 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 debug + +package diff + +import ( + "fmt" + "strings" + "sync" + "time" +) + +// The algorithm can be seen running in real-time by enabling debugging: +// go test -tags=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, +// seperated 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) +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go new file mode 100644 index 000000000..baa41fd23 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go @@ -0,0 +1,373 @@ +// 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. +// NSame is the number of sub-elements that are equal. +// NDiff is the number of sub-elements that are not equal. +type Result struct{ NSame, NDiff int } + +// Equal indicates whether the symbols are equal. Two symbols are equal +// if and only if NDiff == 0. If Equal, then they are also Similar. +func (r Result) Equal() bool { return r.NDiff == 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 NSame to NDiff to determine similarity may change. +func (r Result) Similar() bool { + // Use NSame+1 to offset NSame so that binary comparisons are similar. + return r.NSame+1 >= r.NDiff +} + +// Difference reports whether two lists of lengths nx and ny are equal +// given the definition of equality provided as f. +// +// This function may return a edit-script, which is a sequence of operations +// needed to convert one list into the other. If non-nil, 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) (eq bool, es EditScript) { + es = searchGraph(nx, ny, f) + st := es.stats() + eq = len(es) == st.NI + if !eq && st.NI < (nx+ny)/4 { + return eq, nil // Edit-script more distracting than helpful + } + return eq, es +} + +func searchGraph(nx, ny int, f EqualFunc) 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 "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 +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff_test.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff_test.go new file mode 100644 index 000000000..5996ea2a9 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff_test.go @@ -0,0 +1,467 @@ +// 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: "0123456789", + y: "9876543210", + }, { + x: "0123456789", + y: "6725819034", + }, { + x: "FBQMOIGTLN72X90E4SP651HKRJUDA83CVZW", + y: "5WHXO10R9IVKZLCTAJ8P3NSEQM472G6UBDF", + }} + + for _, tt := range tests { + tRun(t, "", 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 { + tRun(t, fmt.Sprintf("P%d", i), func(t *testing.T) { + // Sweep from 1B to 1KiB. + for n := 1; n <= 1024; n <<= 1 { + tRun(t, 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, n int) { + // TODO: Use testing.B.Run when we drop Go1.6 support. + 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 BenchmarkDifference1K(b *testing.B) { benchmarkDifference(b, 1<<10) } +func BenchmarkDifference4K(b *testing.B) { benchmarkDifference(b, 1<<12) } +func BenchmarkDifference16K(b *testing.B) { benchmarkDifference(b, 1<<14) } +func BenchmarkDifference64K(b *testing.B) { benchmarkDifference(b, 1<<16) } +func BenchmarkDifference256K(b *testing.B) { benchmarkDifference(b, 1<<18) } +func BenchmarkDifference1M(b *testing.B) { benchmarkDifference(b, 1<<20) } + +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 { + wantEq := x == y + eq, es := Difference(len(x), len(y), func(ix, iy int) Result { + return compareByte(x[ix], y[iy]) + }) + if eq != wantEq { + t.Errorf("equality mismatch: got %v, want %v", eq, wantEq) + } + if es != nil { + 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 got := (es.Dist() == 0); got != wantEq { + t.Errorf("violation of equality invariant: got %v, want %v", got, wantEq) + } + 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{NDiff: 0} + similarResult = Result{NDiff: 1} + differentResult = Result{NDiff: 2} +) + +func TestResult(t *testing.T) { + tests := []struct { + result Result + wantEqual bool + wantSimilar bool + }{ + // equalResult is equal since NDiff == 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 NSame==1 or NDiff==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{NSame: 0, NDiff: 0}, true, true}, + + // Binary comparisons (where NSame+NDiff == 1) are always similar. + {Result{NSame: 1, NDiff: 0}, true, true}, + {Result{NSame: 0, NDiff: 1}, false, true}, + + // More complex ratios. The exact ratio for similarity may change, + // and may require updates to these test cases. + {Result{NSame: 1, NDiff: 1}, false, true}, + {Result{NSame: 1, NDiff: 2}, false, true}, + {Result{NSame: 1, NDiff: 3}, false, false}, + {Result{NSame: 2, NDiff: 1}, false, true}, + {Result{NSame: 2, NDiff: 2}, false, true}, + {Result{NSame: 2, NDiff: 3}, false, true}, + {Result{NSame: 3, NDiff: 1}, false, true}, + {Result{NSame: 3, NDiff: 2}, false, true}, + {Result{NSame: 3, NDiff: 3}, false, true}, + {Result{NSame: 1000, NDiff: 0}, true, true}, + {Result{NSame: 1000, NDiff: 1}, false, true}, + {Result{NSame: 1000, NDiff: 2}, false, true}, + {Result{NSame: 0, NDiff: 1000}, false, false}, + {Result{NSame: 1, NDiff: 1000}, false, false}, + {Result{NSame: 2, NDiff: 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) + } + } +} + +// TODO: Delete this hack when we drop Go1.6 support. +func tRun(t *testing.T, name string, f func(t *testing.T)) { + type runner interface { + Run(string, func(t *testing.T)) bool + } + var ti interface{} = t + if r, ok := ti.(runner); ok { + r.Run(name, f) + } else { + t.Logf("Test: %s", name) + f(t) + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/function/func.go b/vendor/github.com/google/go-cmp/cmp/internal/function/func.go new file mode 100644 index 000000000..4c35ff11e --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/function/func.go @@ -0,0 +1,49 @@ +// 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 identifies function types. +package function + +import "reflect" + +type funcType int + +const ( + _ funcType = iota + + ttbFunc // func(T, T) 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 +) + +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 ttbFunc: // func(T, T) bool + if ni == 2 && no == 1 && t.In(0) == t.In(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 +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/testprotos/protos.go b/vendor/github.com/google/go-cmp/cmp/internal/testprotos/protos.go new file mode 100644 index 000000000..120c8b0e8 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/testprotos/protos.go @@ -0,0 +1,116 @@ +// 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 + } +) diff --git a/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project1.go b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project1.go new file mode 100644 index 000000000..1999e38fd --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project1.go @@ -0,0 +1,267 @@ +// 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 } diff --git a/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project2.go b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project2.go new file mode 100644 index 000000000..536592bbe --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project2.go @@ -0,0 +1,74 @@ +// 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 +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project3.go b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project3.go new file mode 100644 index 000000000..00c252e5e --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project3.go @@ -0,0 +1,77 @@ +// 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 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 sync.Mutex +} + +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 } diff --git a/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project4.go b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project4.go new file mode 100644 index 000000000..9b50d738e --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/project4.go @@ -0,0 +1,142 @@ +// 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.Manufactuer() == y.Manufactuer() && + 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 + manufactuer string + potency int +} + +func (p Poison) PoisonType() pb.PoisonType { return p.poisonType } +func (p Poison) Expiration() time.Time { return p.expiration } +func (p Poison) Manufactuer() string { return p.manufactuer } +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) SetManufactuer(x string) { p.manufactuer = x } +func (p *Poison) SetPotency(x int) { p.potency = x } diff --git a/vendor/github.com/google/go-cmp/cmp/internal/teststructs/structs.go b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/structs.go new file mode 100644 index 000000000..6b4d2a725 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/teststructs/structs.go @@ -0,0 +1,197 @@ +// 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 } diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/format.go b/vendor/github.com/google/go-cmp/cmp/internal/value/format.go new file mode 100644 index 000000000..abaeca89e --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/format.go @@ -0,0 +1,259 @@ +// 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 provides functionality for reflect.Value types. +package value + +import ( + "fmt" + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +// formatFakePointers controls whether to substitute pointer addresses with nil. +// This is used for deterministic testing. +var formatFakePointers = false + +var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() + +// Format formats the value v as a string. +// +// This is similar to fmt.Sprintf("%+v", v) except this: +// * Prints the type unless it can be elided +// * Avoids printing struct fields that are zero +// * Prints a nil-slice as being nil, not empty +// * Prints map entries in deterministic order +func Format(v reflect.Value, useStringer bool) string { + return formatAny(v, formatConfig{useStringer, true, true, !formatFakePointers}, nil) +} + +type formatConfig struct { + useStringer bool // Should the String method be used if available? + printType bool // Should we print the type before the value? + followPointers bool // Should we recursively follow pointers? + realPointers bool // Should we print the real address of pointers? +} + +func formatAny(v reflect.Value, conf formatConfig, visited map[uintptr]bool) string { + // TODO: Should this be a multi-line printout in certain situations? + + if !v.IsValid() { + return "" + } + if conf.useStringer && v.Type().Implements(stringerIface) && v.CanInterface() { + if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() { + return "" + } + return fmt.Sprintf("%q", v.Interface().(fmt.Stringer).String()) + } + + switch v.Kind() { + case reflect.Bool: + return formatPrimitive(v.Type(), v.Bool(), conf) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return formatPrimitive(v.Type(), v.Int(), conf) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr { + // Unnamed uints are usually bytes or words, so use hexadecimal. + return formatPrimitive(v.Type(), formatHex(v.Uint()), conf) + } + return formatPrimitive(v.Type(), v.Uint(), conf) + case reflect.Float32, reflect.Float64: + return formatPrimitive(v.Type(), v.Float(), conf) + case reflect.Complex64, reflect.Complex128: + return formatPrimitive(v.Type(), v.Complex(), conf) + case reflect.String: + return formatPrimitive(v.Type(), fmt.Sprintf("%q", v), conf) + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + return formatPointer(v, conf) + case reflect.Ptr: + if v.IsNil() { + if conf.printType { + return fmt.Sprintf("(%v)(nil)", v.Type()) + } + return "" + } + if visited[v.Pointer()] || !conf.followPointers { + return formatPointer(v, conf) + } + visited = insertPointer(visited, v.Pointer()) + return "&" + formatAny(v.Elem(), conf, visited) + case reflect.Interface: + if v.IsNil() { + if conf.printType { + return fmt.Sprintf("%v(nil)", v.Type()) + } + return "" + } + return formatAny(v.Elem(), conf, visited) + case reflect.Slice: + if v.IsNil() { + if conf.printType { + return fmt.Sprintf("%v(nil)", v.Type()) + } + return "" + } + if visited[v.Pointer()] { + return formatPointer(v, conf) + } + visited = insertPointer(visited, v.Pointer()) + fallthrough + case reflect.Array: + var ss []string + subConf := conf + subConf.printType = v.Type().Elem().Kind() == reflect.Interface + for i := 0; i < v.Len(); i++ { + s := formatAny(v.Index(i), subConf, visited) + ss = append(ss, s) + } + s := fmt.Sprintf("{%s}", strings.Join(ss, ", ")) + if conf.printType { + return v.Type().String() + s + } + return s + case reflect.Map: + if v.IsNil() { + if conf.printType { + return fmt.Sprintf("%v(nil)", v.Type()) + } + return "" + } + if visited[v.Pointer()] { + return formatPointer(v, conf) + } + visited = insertPointer(visited, v.Pointer()) + + var ss []string + subConf := conf + subConf.printType = v.Type().Elem().Kind() == reflect.Interface + for _, k := range SortKeys(v.MapKeys()) { + sk := formatAny(k, formatConfig{realPointers: conf.realPointers}, visited) + sv := formatAny(v.MapIndex(k), subConf, visited) + ss = append(ss, fmt.Sprintf("%s: %s", sk, sv)) + } + s := fmt.Sprintf("{%s}", strings.Join(ss, ", ")) + if conf.printType { + return v.Type().String() + s + } + return s + case reflect.Struct: + var ss []string + subConf := conf + subConf.printType = true + for i := 0; i < v.NumField(); i++ { + vv := v.Field(i) + if isZero(vv) { + continue // Elide zero value fields + } + name := v.Type().Field(i).Name + subConf.useStringer = conf.useStringer && isExported(name) + s := formatAny(vv, subConf, visited) + ss = append(ss, fmt.Sprintf("%s: %s", name, s)) + } + s := fmt.Sprintf("{%s}", strings.Join(ss, ", ")) + if conf.printType { + return v.Type().String() + s + } + return s + default: + panic(fmt.Sprintf("%v kind not handled", v.Kind())) + } +} + +func formatPrimitive(t reflect.Type, v interface{}, conf formatConfig) string { + if conf.printType && t.PkgPath() != "" { + return fmt.Sprintf("%v(%v)", t, v) + } + return fmt.Sprintf("%v", v) +} + +func formatPointer(v reflect.Value, conf formatConfig) string { + p := v.Pointer() + if !conf.realPointers { + p = 0 // For deterministic printing purposes + } + s := formatHex(uint64(p)) + if conf.printType { + return fmt.Sprintf("(%v)(%s)", v.Type(), s) + } + return s +} + +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) +} + +// insertPointer insert p into m, allocating m if necessary. +func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool { + if m == nil { + m = make(map[uintptr]bool) + } + m[p] = true + return m +} + +// 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 v.Float() == 0 + case reflect.Complex64, reflect.Complex128: + return 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 +} + +// isExported reports whether the identifier is exported. +func isExported(id string) bool { + r, _ := utf8.DecodeRuneInString(id) + return unicode.IsUpper(r) +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/format_test.go b/vendor/github.com/google/go-cmp/cmp/internal/value/format_test.go new file mode 100644 index 000000000..6498854f2 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/format_test.go @@ -0,0 +1,91 @@ +// 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 ( + "bytes" + "io" + "reflect" + "testing" +) + +func TestFormat(t *testing.T) { + type key struct { + a int + b string + c chan bool + } + + tests := []struct { + in interface{} + want string + }{{ + in: []int{}, + want: "[]int{}", + }, { + in: []int(nil), + want: "[]int(nil)", + }, { + in: []int{1, 2, 3, 4, 5}, + want: "[]int{1, 2, 3, 4, 5}", + }, { + in: []interface{}{1, true, "hello", struct{ A, B int }{1, 2}}, + want: "[]interface {}{1, true, \"hello\", struct { A int; B int }{A: 1, B: 2}}", + }, { + in: []struct{ A, B int }{{1, 2}, {0, 4}, {}}, + want: "[]struct { A int; B int }{{A: 1, B: 2}, {B: 4}, {}}", + }, { + in: map[*int]string{new(int): "hello"}, + want: "map[*int]string{0x00: \"hello\"}", + }, { + in: map[key]string{{}: "hello"}, + want: "map[value.key]string{{}: \"hello\"}", + }, { + in: map[key]string{{a: 5, b: "key", c: make(chan bool)}: "hello"}, + want: "map[value.key]string{{a: 5, b: \"key\", c: (chan bool)(0x00)}: \"hello\"}", + }, { + in: map[io.Reader]string{new(bytes.Reader): "hello"}, + want: "map[io.Reader]string{0x00: \"hello\"}", + }, { + in: func() interface{} { + var a = []interface{}{nil} + a[0] = a + return a + }(), + want: "[]interface {}{([]interface {})(0x00)}", + }, { + in: func() interface{} { + type A *A + var a A + a = &a + return a + }(), + want: "&(value.A)(0x00)", + }, { + in: func() interface{} { + type A map[*A]A + a := make(A) + a[&a] = a + return a + }(), + want: "value.A{0x00: 0x00}", + }, { + in: func() interface{} { + var a [2]interface{} + a[0] = &a + return a + }(), + want: "[2]interface {}{&[2]interface {}{(*[2]interface {})(0x00), interface {}(nil)}, interface {}(nil)}", + }} + + formatFakePointers = true + defer func() { formatFakePointers = false }() + for i, tt := range tests { + got := Format(reflect.ValueOf(tt.in), true) + if got != tt.want { + t.Errorf("test %d, Format():\ngot %q\nwant %q", i, got, tt.want) + } + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go b/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go new file mode 100644 index 000000000..ea73cf143 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go @@ -0,0 +1,111 @@ +// 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.Sort(valueSorter(vs)) + + // Deduplicate keys (fails for NaNs). + vs2 := vs[:1] + for _, v := range vs[1:] { + if v.Interface() != vs2[len(vs2)-1].Interface() { + vs2 = append(vs2, v) + } + } + return vs2 +} + +// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above. +type valueSorter []reflect.Value + +func (vs valueSorter) Len() int { return len(vs) } +func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) } +func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } + +// 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: + 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())) + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/sort_test.go b/vendor/github.com/google/go-cmp/cmp/internal/value/sort_test.go new file mode 100644 index 000000000..c5a6bbb12 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/sort_test.go @@ -0,0 +1,152 @@ +// 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(), math.NaN(), math.NaN(), math.NaN(), + complex(math.NaN(), math.NaN()), complex(math.NaN(), math.NaN()), + complex(math.NaN(), 0), complex(math.NaN(), 0), complex(math.NaN(), 0), complex(math.NaN(), 0), + complex(0, math.NaN()), complex(0, math.NaN()), complex(0, math.NaN()), complex(0, math.NaN()), + }, + }} + + for i, tt := range tests { + keys := append(reflect.ValueOf(tt.in).MapKeys(), reflect.ValueOf(tt.in).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) + } + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/options.go b/vendor/github.com/google/go-cmp/cmp/options.go new file mode 100644 index 000000000..a4e159ac4 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/options.go @@ -0,0 +1,446 @@ +// 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" + "runtime" + "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, vx, vy reflect.Value, t reflect.Type) applicableOption +} + +// applicableOption represents the following types: +// Fundamental: ignore | invalid | *comparer | *transformer +// Grouping: Options +type applicableOption interface { + Option + + // apply executes the option and reports whether the option was applied. + // Each option may mutate s. + apply(s *state, vx, vy reflect.Value) bool +} + +// coreOption represents the following types: +// Fundamental: ignore | invalid | *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, vx, vy reflect.Value, t reflect.Type) (out applicableOption) { + for _, opt := range opts { + switch opt := opt.filter(s, vx, vy, t); opt.(type) { + case ignore: + return ignore{} // Only ignore can short-circuit evaluation + case invalid: + out = invalid{} // Takes precedence over comparer or transformer + case *comparer, *transformer, Options: + switch out.(type) { + case nil: + out = opt + case invalid: + // Keep invalid + case *comparer, *transformer, Options: + out = Options{out, opt} // Conflicting comparers or transformers + } + } + } + return out +} + +func (opts Options) apply(s *state, _, _ reflect.Value) bool { + 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. +// +// 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, vx, vy reflect.Value, t reflect.Type) applicableOption { + if f.fnc(s.curPath) { + return f.opt.filter(s, vx, vy, t) + } + return nil +} + +func (f pathFilter) String() string { + fn := getFuncName(reflect.ValueOf(f.fnc).Pointer()) + return fmt.Sprintf("FilterPath(%s, %v)", fn, 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 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, vx, vy reflect.Value, t reflect.Type) applicableOption { + if !vx.IsValid() || !vy.IsValid() { + return invalid{} + } + if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) { + return f.opt.filter(s, vx, vy, t) + } + return nil +} + +func (f valuesFilter) String() string { + fn := getFuncName(f.fnc.Pointer()) + return fmt.Sprintf("FilterValues(%s, %v)", fn, 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.Value, _ reflect.Type) applicableOption { return ignore{} } +func (ignore) apply(_ *state, _, _ reflect.Value) bool { return true } +func (ignore) String() string { return "Ignore()" } + +// invalid is a sentinel Option type to indicate that some options could not +// be evaluated due to unexported fields. +type invalid struct{ core } + +func (invalid) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return invalid{} } +func (invalid) apply(s *state, _, _ reflect.Value) bool { + const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported" + panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help)) +} + +// 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. +// If T and R are the same type, an additional filter must be applied to +// act as the base case to prevent an infinite recursion applying the same +// transform to itself (see the SortedSlice example). +// +// The name is a user provided label that is used as the Transform.Name in the +// transformation PathStep. 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 = "λ" // Lambda-symbol as place-holder for anonymous transformer + } + if !isValid(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(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption { + if tr.typ == nil || t.AssignableTo(tr.typ) { + return tr + } + return nil +} + +func (tr *transformer) apply(s *state, vx, vy reflect.Value) bool { + // Update path before calling the Transformer so that dynamic checks + // will use the updated path. + s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr}) + defer s.curPath.pop() + + vx = s.callTRFunc(tr.fnc, vx) + vy = s.callTRFunc(tr.fnc, vy) + s.compareAny(vx, vy) + return true +} + +func (tr transformer) String() string { + return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer())) +} + +// 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, _, _ reflect.Value, t reflect.Type) applicableOption { + if cm.typ == nil || t.AssignableTo(cm.typ) { + return cm + } + return nil +} + +func (cm *comparer) apply(s *state, vx, vy reflect.Value) bool { + eq := s.callTTBFunc(cm.fnc, vx, vy) + s.report(eq, vx, vy) + return true +} + +func (cm comparer) String() string { + return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer())) +} + +// 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. +// +// For some 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 App Engine Classic 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.Value, _ reflect.Type) applicableOption { + panic("not implemented") +} + +// reporter is an Option that configures how differences are reported. +type reporter interface { + // TODO: Not exported yet. + // + // Perhaps add PushStep and PopStep and change Report to only accept + // a PathStep instead of the full-path? Adding a PushStep and PopStep makes + // it clear that we are traversing the value tree in a depth-first-search + // manner, which has an effect on how values are printed. + + Option + + // Report is called for every comparison made and will be provided with + // the two values being compared, the equality result, and the + // current path in the value tree. It is possible for x or y to be an + // invalid reflect.Value if one of the values is non-existent; + // which is possible with maps and slices. + Report(x, y reflect.Value, eq bool, p Path) +} + +// 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 +} + +// getFuncName returns a short function name from the pointer. +// The string parsing logic works up until Go1.9. +func getFuncName(p uintptr) string { + fnc := runtime.FuncForPC(p) + if fnc == nil { + return "" + } + name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm" + if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") { + // Strip the package name from method name. + name = strings.TrimSuffix(name, ")-fm") + name = strings.TrimSuffix(name, ")·fm") + if i := strings.LastIndexByte(name, '('); i >= 0 { + methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc" + if j := strings.LastIndexByte(methodName, '.'); j >= 0 { + methodName = methodName[j+1:] // E.g., "myfunc" + } + name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc" + } + } + if i := strings.LastIndexByte(name, '/'); i >= 0 { + // Strip the package name. + name = name[i+1:] // E.g., "mypkg.(mytype).myfunc" + } + return name +} diff --git a/vendor/github.com/google/go-cmp/cmp/options_test.go b/vendor/github.com/google/go-cmp/cmp/options_test.go new file mode 100644 index 000000000..009b524af --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/options_test.go @@ -0,0 +1,231 @@ +// 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 }}, + wantPanic: "invalid name", + }, { + 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 }, &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(), &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 }, &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(), &defaultReporter{}}}, + wantPanic: "invalid option type", + }} + + for _, tt := range tests { + tRun(t, 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) + } + } + }) + } +} + +// TODO: Delete this hack when we drop Go1.6 support. +func tRun(t *testing.T, name string, f func(t *testing.T)) { + type runner interface { + Run(string, func(t *testing.T)) bool + } + var ti interface{} = t + if r, ok := ti.(runner); ok { + r.Run(name, f) + } else { + t.Logf("Test: %s", name) + f(t) + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/path.go b/vendor/github.com/google/go-cmp/cmp/path.go new file mode 100644 index 000000000..0c2eb333f --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/path.go @@ -0,0 +1,293 @@ +// 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" +) + +type ( + // 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. + 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. + PathStep interface { + String() string + Type() reflect.Type // Resulting type after performing the path step + isPathStep() + } + + // SliceIndex is an index operation on a slice or array at some index Key. + SliceIndex interface { + PathStep + Key() int // May return -1 if in a split state + + // SplitKeys returns 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. + SplitKeys() (x int, y int) + + isSliceIndex() + } + // MapIndex is an index operation on a map at some index Key. + MapIndex interface { + PathStep + Key() reflect.Value + isMapIndex() + } + // TypeAssertion represents a type assertion on an interface. + TypeAssertion interface { + PathStep + isTypeAssertion() + } + // StructField represents a struct field access on a field called Name. + StructField interface { + PathStep + Name() string + Index() int + isStructField() + } + // Indirect represents pointer indirection on the parent type. + Indirect interface { + PathStep + isIndirect() + } + // Transform is a transformation from the parent type to the current type. + Transform interface { + PathStep + Name() string + Func() reflect.Value + isTransform() + } +) + +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 { + if len(pa) > 0 { + return pa[len(pa)-1] + } + return pathStep{} +} + +// 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 + case *typeAssertion: + // Elide type assertions immediately following a transform to + // prevent overly verbose path printouts. + // Some transforms return interface{} because of Go's lack of + // generics, but typically take in and return the exact same + // concrete type. Other times, the transform creates an anonymous + // struct, which will be very verbose to print. + if _, ok := nextStep.(*transform); ok { + 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 + } + + sliceIndex struct { + pathStep + xkey, ykey int + } + mapIndex struct { + pathStep + key reflect.Value + } + typeAssertion struct { + pathStep + } + 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 + force bool // Forcibly allow visibility + pvx, pvy reflect.Value // Parent values + field reflect.StructField // Field information + } + indirect struct { + pathStep + } + transform struct { + pathStep + trans *transformer + } +) + +func (ps pathStep) Type() reflect.Type { return ps.typ } +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) +} + +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) + } +} +func (mi mapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) } +func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) } +func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) } +func (in indirect) String() string { return "*" } +func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) } + +func (si sliceIndex) Key() int { + if si.xkey != si.ykey { + return -1 + } + return si.xkey +} +func (si sliceIndex) SplitKeys() (x, y int) { return si.xkey, si.ykey } +func (mi mapIndex) Key() reflect.Value { return mi.key } +func (sf structField) Name() string { return sf.name } +func (sf structField) Index() int { return sf.idx } +func (tf transform) Name() string { return tf.trans.name } +func (tf transform) Func() reflect.Value { return tf.trans.fnc } + +func (pathStep) isPathStep() {} +func (sliceIndex) isSliceIndex() {} +func (mapIndex) isMapIndex() {} +func (typeAssertion) isTypeAssertion() {} +func (structField) isStructField() {} +func (indirect) isIndirect() {} +func (transform) isTransform() {} + +var ( + _ SliceIndex = sliceIndex{} + _ MapIndex = mapIndex{} + _ TypeAssertion = typeAssertion{} + _ StructField = structField{} + _ Indirect = indirect{} + _ Transform = transform{} + + _ PathStep = sliceIndex{} + _ PathStep = mapIndex{} + _ PathStep = typeAssertion{} + _ PathStep = structField{} + _ PathStep = indirect{} + _ PathStep = transform{} +) + +// isExported reports whether the identifier is exported. +func isExported(id string) bool { + r, _ := utf8.DecodeRuneInString(id) + return unicode.IsUpper(r) +} + +// isValid reports whether the identifier is valid. +// Empty and underscore-only strings are not valid. +func isValid(id string) bool { + ok := id != "" && id != "_" + for j, c := range id { + ok = ok && (j > 0 || !unicode.IsDigit(c)) + ok = ok && (c == '_' || unicode.IsLetter(c) || unicode.IsDigit(c)) + } + return ok +} diff --git a/vendor/github.com/google/go-cmp/cmp/reporter.go b/vendor/github.com/google/go-cmp/cmp/reporter.go new file mode 100644 index 000000000..a21d0cded --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/reporter.go @@ -0,0 +1,53 @@ +// 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" + + "github.com/google/go-cmp/cmp/internal/value" +) + +type defaultReporter struct { + Option + diffs []string // List of differences, possibly truncated + ndiffs int // Total number of differences + nbytes int // Number of bytes in diffs + nlines int // Number of lines in diffs +} + +var _ reporter = (*defaultReporter)(nil) + +func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) { + if eq { + return // Ignore equal results + } + const maxBytes = 4096 + const maxLines = 256 + r.ndiffs++ + if r.nbytes < maxBytes && r.nlines < maxLines { + sx := value.Format(x, true) + sy := value.Format(y, true) + if sx == sy { + // Stringer is not helpful, so rely on more exact formatting. + sx = value.Format(x, false) + sy = value.Format(y, false) + } + s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy) + r.diffs = append(r.diffs, s) + r.nbytes += len(s) + r.nlines += strings.Count(s, "\n") + } +} + +func (r *defaultReporter) String() string { + s := strings.Join(r.diffs, "") + if r.ndiffs == len(r.diffs) { + return s + } + return fmt.Sprintf("%s... %d more differences ...", s, len(r.diffs)-r.ndiffs) +} diff --git a/vendor/github.com/google/go-cmp/cmp/unsafe_panic.go b/vendor/github.com/google/go-cmp/cmp/unsafe_panic.go new file mode 100644 index 000000000..0d44987f5 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/unsafe_panic.go @@ -0,0 +1,15 @@ +// 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 appengine js + +package cmp + +import "reflect" + +const supportAllowUnexported = false + +func unsafeRetrieveField(reflect.Value, reflect.StructField) reflect.Value { + panic("unsafeRetrieveField is not implemented") +} diff --git a/vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go b/vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go new file mode 100644 index 000000000..81fb82632 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go @@ -0,0 +1,23 @@ +// 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 !appengine,!js + +package cmp + +import ( + "reflect" + "unsafe" +) + +const supportAllowUnexported = true + +// unsafeRetrieveField 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 unsafeRetrieveField(v reflect.Value, f reflect.StructField) reflect.Value { + return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem() +}