1*88d15eacSSasha Smundak// Copyright 2017, The Go Authors. All rights reserved. 2*88d15eacSSasha Smundak// Use of this source code is governed by a BSD-style 3*88d15eacSSasha Smundak// license that can be found in the LICENSE file. 4*88d15eacSSasha Smundak 5*88d15eacSSasha Smundakpackage cmpopts 6*88d15eacSSasha Smundak 7*88d15eacSSasha Smundakimport ( 8*88d15eacSSasha Smundak "fmt" 9*88d15eacSSasha Smundak "reflect" 10*88d15eacSSasha Smundak "unicode" 11*88d15eacSSasha Smundak "unicode/utf8" 12*88d15eacSSasha Smundak 13*88d15eacSSasha Smundak "github.com/google/go-cmp/cmp" 14*88d15eacSSasha Smundak "github.com/google/go-cmp/cmp/internal/function" 15*88d15eacSSasha Smundak) 16*88d15eacSSasha Smundak 17*88d15eacSSasha Smundak// IgnoreFields returns an Option that ignores fields of the 18*88d15eacSSasha Smundak// given names on a single struct type. It respects the names of exported fields 19*88d15eacSSasha Smundak// that are forwarded due to struct embedding. 20*88d15eacSSasha Smundak// The struct type is specified by passing in a value of that type. 21*88d15eacSSasha Smundak// 22*88d15eacSSasha Smundak// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a 23*88d15eacSSasha Smundak// specific sub-field that is embedded or nested within the parent struct. 24*88d15eacSSasha Smundakfunc IgnoreFields(typ interface{}, names ...string) cmp.Option { 25*88d15eacSSasha Smundak sf := newStructFilter(typ, names...) 26*88d15eacSSasha Smundak return cmp.FilterPath(sf.filter, cmp.Ignore()) 27*88d15eacSSasha Smundak} 28*88d15eacSSasha Smundak 29*88d15eacSSasha Smundak// IgnoreTypes returns an Option that ignores all values assignable to 30*88d15eacSSasha Smundak// certain types, which are specified by passing in a value of each type. 31*88d15eacSSasha Smundakfunc IgnoreTypes(typs ...interface{}) cmp.Option { 32*88d15eacSSasha Smundak tf := newTypeFilter(typs...) 33*88d15eacSSasha Smundak return cmp.FilterPath(tf.filter, cmp.Ignore()) 34*88d15eacSSasha Smundak} 35*88d15eacSSasha Smundak 36*88d15eacSSasha Smundaktype typeFilter []reflect.Type 37*88d15eacSSasha Smundak 38*88d15eacSSasha Smundakfunc newTypeFilter(typs ...interface{}) (tf typeFilter) { 39*88d15eacSSasha Smundak for _, typ := range typs { 40*88d15eacSSasha Smundak t := reflect.TypeOf(typ) 41*88d15eacSSasha Smundak if t == nil { 42*88d15eacSSasha Smundak // This occurs if someone tries to pass in sync.Locker(nil) 43*88d15eacSSasha Smundak panic("cannot determine type; consider using IgnoreInterfaces") 44*88d15eacSSasha Smundak } 45*88d15eacSSasha Smundak tf = append(tf, t) 46*88d15eacSSasha Smundak } 47*88d15eacSSasha Smundak return tf 48*88d15eacSSasha Smundak} 49*88d15eacSSasha Smundakfunc (tf typeFilter) filter(p cmp.Path) bool { 50*88d15eacSSasha Smundak if len(p) < 1 { 51*88d15eacSSasha Smundak return false 52*88d15eacSSasha Smundak } 53*88d15eacSSasha Smundak t := p.Last().Type() 54*88d15eacSSasha Smundak for _, ti := range tf { 55*88d15eacSSasha Smundak if t.AssignableTo(ti) { 56*88d15eacSSasha Smundak return true 57*88d15eacSSasha Smundak } 58*88d15eacSSasha Smundak } 59*88d15eacSSasha Smundak return false 60*88d15eacSSasha Smundak} 61*88d15eacSSasha Smundak 62*88d15eacSSasha Smundak// IgnoreInterfaces returns an Option that ignores all values or references of 63*88d15eacSSasha Smundak// values assignable to certain interface types. These interfaces are specified 64*88d15eacSSasha Smundak// by passing in an anonymous struct with the interface types embedded in it. 65*88d15eacSSasha Smundak// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}. 66*88d15eacSSasha Smundakfunc IgnoreInterfaces(ifaces interface{}) cmp.Option { 67*88d15eacSSasha Smundak tf := newIfaceFilter(ifaces) 68*88d15eacSSasha Smundak return cmp.FilterPath(tf.filter, cmp.Ignore()) 69*88d15eacSSasha Smundak} 70*88d15eacSSasha Smundak 71*88d15eacSSasha Smundaktype ifaceFilter []reflect.Type 72*88d15eacSSasha Smundak 73*88d15eacSSasha Smundakfunc newIfaceFilter(ifaces interface{}) (tf ifaceFilter) { 74*88d15eacSSasha Smundak t := reflect.TypeOf(ifaces) 75*88d15eacSSasha Smundak if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct { 76*88d15eacSSasha Smundak panic("input must be an anonymous struct") 77*88d15eacSSasha Smundak } 78*88d15eacSSasha Smundak for i := 0; i < t.NumField(); i++ { 79*88d15eacSSasha Smundak fi := t.Field(i) 80*88d15eacSSasha Smundak switch { 81*88d15eacSSasha Smundak case !fi.Anonymous: 82*88d15eacSSasha Smundak panic("struct cannot have named fields") 83*88d15eacSSasha Smundak case fi.Type.Kind() != reflect.Interface: 84*88d15eacSSasha Smundak panic("embedded field must be an interface type") 85*88d15eacSSasha Smundak case fi.Type.NumMethod() == 0: 86*88d15eacSSasha Smundak // This matches everything; why would you ever want this? 87*88d15eacSSasha Smundak panic("cannot ignore empty interface") 88*88d15eacSSasha Smundak default: 89*88d15eacSSasha Smundak tf = append(tf, fi.Type) 90*88d15eacSSasha Smundak } 91*88d15eacSSasha Smundak } 92*88d15eacSSasha Smundak return tf 93*88d15eacSSasha Smundak} 94*88d15eacSSasha Smundakfunc (tf ifaceFilter) filter(p cmp.Path) bool { 95*88d15eacSSasha Smundak if len(p) < 1 { 96*88d15eacSSasha Smundak return false 97*88d15eacSSasha Smundak } 98*88d15eacSSasha Smundak t := p.Last().Type() 99*88d15eacSSasha Smundak for _, ti := range tf { 100*88d15eacSSasha Smundak if t.AssignableTo(ti) { 101*88d15eacSSasha Smundak return true 102*88d15eacSSasha Smundak } 103*88d15eacSSasha Smundak if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) { 104*88d15eacSSasha Smundak return true 105*88d15eacSSasha Smundak } 106*88d15eacSSasha Smundak } 107*88d15eacSSasha Smundak return false 108*88d15eacSSasha Smundak} 109*88d15eacSSasha Smundak 110*88d15eacSSasha Smundak// IgnoreUnexported returns an Option that only ignores the immediate unexported 111*88d15eacSSasha Smundak// fields of a struct, including anonymous fields of unexported types. 112*88d15eacSSasha Smundak// In particular, unexported fields within the struct's exported fields 113*88d15eacSSasha Smundak// of struct types, including anonymous fields, will not be ignored unless the 114*88d15eacSSasha Smundak// type of the field itself is also passed to IgnoreUnexported. 115*88d15eacSSasha Smundak// 116*88d15eacSSasha Smundak// Avoid ignoring unexported fields of a type which you do not control (i.e. a 117*88d15eacSSasha Smundak// type from another repository), as changes to the implementation of such types 118*88d15eacSSasha Smundak// may change how the comparison behaves. Prefer a custom Comparer instead. 119*88d15eacSSasha Smundakfunc IgnoreUnexported(typs ...interface{}) cmp.Option { 120*88d15eacSSasha Smundak ux := newUnexportedFilter(typs...) 121*88d15eacSSasha Smundak return cmp.FilterPath(ux.filter, cmp.Ignore()) 122*88d15eacSSasha Smundak} 123*88d15eacSSasha Smundak 124*88d15eacSSasha Smundaktype unexportedFilter struct{ m map[reflect.Type]bool } 125*88d15eacSSasha Smundak 126*88d15eacSSasha Smundakfunc newUnexportedFilter(typs ...interface{}) unexportedFilter { 127*88d15eacSSasha Smundak ux := unexportedFilter{m: make(map[reflect.Type]bool)} 128*88d15eacSSasha Smundak for _, typ := range typs { 129*88d15eacSSasha Smundak t := reflect.TypeOf(typ) 130*88d15eacSSasha Smundak if t == nil || t.Kind() != reflect.Struct { 131*88d15eacSSasha Smundak panic(fmt.Sprintf("%T must be a non-pointer struct", typ)) 132*88d15eacSSasha Smundak } 133*88d15eacSSasha Smundak ux.m[t] = true 134*88d15eacSSasha Smundak } 135*88d15eacSSasha Smundak return ux 136*88d15eacSSasha Smundak} 137*88d15eacSSasha Smundakfunc (xf unexportedFilter) filter(p cmp.Path) bool { 138*88d15eacSSasha Smundak sf, ok := p.Index(-1).(cmp.StructField) 139*88d15eacSSasha Smundak if !ok { 140*88d15eacSSasha Smundak return false 141*88d15eacSSasha Smundak } 142*88d15eacSSasha Smundak return xf.m[p.Index(-2).Type()] && !isExported(sf.Name()) 143*88d15eacSSasha Smundak} 144*88d15eacSSasha Smundak 145*88d15eacSSasha Smundak// isExported reports whether the identifier is exported. 146*88d15eacSSasha Smundakfunc isExported(id string) bool { 147*88d15eacSSasha Smundak r, _ := utf8.DecodeRuneInString(id) 148*88d15eacSSasha Smundak return unicode.IsUpper(r) 149*88d15eacSSasha Smundak} 150*88d15eacSSasha Smundak 151*88d15eacSSasha Smundak// IgnoreSliceElements returns an Option that ignores elements of []V. 152*88d15eacSSasha Smundak// The discard function must be of the form "func(T) bool" which is used to 153*88d15eacSSasha Smundak// ignore slice elements of type V, where V is assignable to T. 154*88d15eacSSasha Smundak// Elements are ignored if the function reports true. 155*88d15eacSSasha Smundakfunc IgnoreSliceElements(discardFunc interface{}) cmp.Option { 156*88d15eacSSasha Smundak vf := reflect.ValueOf(discardFunc) 157*88d15eacSSasha Smundak if !function.IsType(vf.Type(), function.ValuePredicate) || vf.IsNil() { 158*88d15eacSSasha Smundak panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) 159*88d15eacSSasha Smundak } 160*88d15eacSSasha Smundak return cmp.FilterPath(func(p cmp.Path) bool { 161*88d15eacSSasha Smundak si, ok := p.Index(-1).(cmp.SliceIndex) 162*88d15eacSSasha Smundak if !ok { 163*88d15eacSSasha Smundak return false 164*88d15eacSSasha Smundak } 165*88d15eacSSasha Smundak if !si.Type().AssignableTo(vf.Type().In(0)) { 166*88d15eacSSasha Smundak return false 167*88d15eacSSasha Smundak } 168*88d15eacSSasha Smundak vx, vy := si.Values() 169*88d15eacSSasha Smundak if vx.IsValid() && vf.Call([]reflect.Value{vx})[0].Bool() { 170*88d15eacSSasha Smundak return true 171*88d15eacSSasha Smundak } 172*88d15eacSSasha Smundak if vy.IsValid() && vf.Call([]reflect.Value{vy})[0].Bool() { 173*88d15eacSSasha Smundak return true 174*88d15eacSSasha Smundak } 175*88d15eacSSasha Smundak return false 176*88d15eacSSasha Smundak }, cmp.Ignore()) 177*88d15eacSSasha Smundak} 178*88d15eacSSasha Smundak 179*88d15eacSSasha Smundak// IgnoreMapEntries returns an Option that ignores entries of map[K]V. 180*88d15eacSSasha Smundak// The discard function must be of the form "func(T, R) bool" which is used to 181*88d15eacSSasha Smundak// ignore map entries of type K and V, where K and V are assignable to T and R. 182*88d15eacSSasha Smundak// Entries are ignored if the function reports true. 183*88d15eacSSasha Smundakfunc IgnoreMapEntries(discardFunc interface{}) cmp.Option { 184*88d15eacSSasha Smundak vf := reflect.ValueOf(discardFunc) 185*88d15eacSSasha Smundak if !function.IsType(vf.Type(), function.KeyValuePredicate) || vf.IsNil() { 186*88d15eacSSasha Smundak panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) 187*88d15eacSSasha Smundak } 188*88d15eacSSasha Smundak return cmp.FilterPath(func(p cmp.Path) bool { 189*88d15eacSSasha Smundak mi, ok := p.Index(-1).(cmp.MapIndex) 190*88d15eacSSasha Smundak if !ok { 191*88d15eacSSasha Smundak return false 192*88d15eacSSasha Smundak } 193*88d15eacSSasha Smundak if !mi.Key().Type().AssignableTo(vf.Type().In(0)) || !mi.Type().AssignableTo(vf.Type().In(1)) { 194*88d15eacSSasha Smundak return false 195*88d15eacSSasha Smundak } 196*88d15eacSSasha Smundak k := mi.Key() 197*88d15eacSSasha Smundak vx, vy := mi.Values() 198*88d15eacSSasha Smundak if vx.IsValid() && vf.Call([]reflect.Value{k, vx})[0].Bool() { 199*88d15eacSSasha Smundak return true 200*88d15eacSSasha Smundak } 201*88d15eacSSasha Smundak if vy.IsValid() && vf.Call([]reflect.Value{k, vy})[0].Bool() { 202*88d15eacSSasha Smundak return true 203*88d15eacSSasha Smundak } 204*88d15eacSSasha Smundak return false 205*88d15eacSSasha Smundak }, cmp.Ignore()) 206*88d15eacSSasha Smundak} 207