Vendor go-cmp

This commit is contained in:
Alexander Neumann 2017-12-23 13:02:03 +01:00
parent b6f98bdb02
commit 4c00efd4bf
36 changed files with 8029 additions and 0 deletions

89
vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go generated vendored Normal file
View file

@ -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))
}

148
vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go generated vendored Normal file
View file

@ -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)
}

146
vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go generated vendored Normal file
View file

@ -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()
}

View file

@ -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))
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}
}