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