|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- // Copyright 2017, The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE.md file.
-
- package cmpopts
-
- import (
- "fmt"
- "reflect"
- "unicode"
- "unicode/utf8"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/internal/function"
- )
-
- // IgnoreFields returns an Option that ignores exported fields of the
- // given names on a single struct type.
- // The struct type is specified by passing in a value of that type.
- //
- // The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
- // specific sub-field that is embedded or nested within the parent struct.
- //
- // This does not handle unexported fields; use IgnoreUnexported instead.
- func IgnoreFields(typ interface{}, names ...string) cmp.Option {
- sf := newStructFilter(typ, names...)
- return cmp.FilterPath(sf.filter, cmp.Ignore())
- }
-
- // IgnoreTypes returns an Option that ignores all values assignable to
- // certain types, which are specified by passing in a value of each type.
- func IgnoreTypes(typs ...interface{}) cmp.Option {
- tf := newTypeFilter(typs...)
- return cmp.FilterPath(tf.filter, cmp.Ignore())
- }
-
- type typeFilter []reflect.Type
-
- func newTypeFilter(typs ...interface{}) (tf typeFilter) {
- for _, typ := range typs {
- t := reflect.TypeOf(typ)
- if t == nil {
- // This occurs if someone tries to pass in sync.Locker(nil)
- panic("cannot determine type; consider using IgnoreInterfaces")
- }
- tf = append(tf, t)
- }
- return tf
- }
- func (tf typeFilter) filter(p cmp.Path) bool {
- if len(p) < 1 {
- return false
- }
- t := p.Last().Type()
- for _, ti := range tf {
- if t.AssignableTo(ti) {
- return true
- }
- }
- return false
- }
-
- // IgnoreInterfaces returns an Option that ignores all values or references of
- // values assignable to certain interface types. These interfaces are specified
- // by passing in an anonymous struct with the interface types embedded in it.
- // For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
- func IgnoreInterfaces(ifaces interface{}) cmp.Option {
- tf := newIfaceFilter(ifaces)
- return cmp.FilterPath(tf.filter, cmp.Ignore())
- }
-
- type ifaceFilter []reflect.Type
-
- func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) {
- t := reflect.TypeOf(ifaces)
- if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct {
- panic("input must be an anonymous struct")
- }
- for i := 0; i < t.NumField(); i++ {
- fi := t.Field(i)
- switch {
- case !fi.Anonymous:
- panic("struct cannot have named fields")
- case fi.Type.Kind() != reflect.Interface:
- panic("embedded field must be an interface type")
- case fi.Type.NumMethod() == 0:
- // This matches everything; why would you ever want this?
- panic("cannot ignore empty interface")
- default:
- tf = append(tf, fi.Type)
- }
- }
- return tf
- }
- func (tf ifaceFilter) filter(p cmp.Path) bool {
- if len(p) < 1 {
- return false
- }
- t := p.Last().Type()
- for _, ti := range tf {
- if t.AssignableTo(ti) {
- return true
- }
- if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) {
- return true
- }
- }
- return false
- }
-
- // IgnoreUnexported returns an Option that only ignores the immediate unexported
- // fields of a struct, including anonymous fields of unexported types.
- // In particular, unexported fields within the struct's exported fields
- // of struct types, including anonymous fields, will not be ignored unless the
- // type of the field itself is also passed to IgnoreUnexported.
- //
- // Avoid ignoring unexported fields of a type which you do not control (i.e. a
- // type from another repository), as changes to the implementation of such types
- // may change how the comparison behaves. Prefer a custom Comparer instead.
- func IgnoreUnexported(typs ...interface{}) cmp.Option {
- ux := newUnexportedFilter(typs...)
- return cmp.FilterPath(ux.filter, cmp.Ignore())
- }
-
- type unexportedFilter struct{ m map[reflect.Type]bool }
-
- func newUnexportedFilter(typs ...interface{}) unexportedFilter {
- ux := unexportedFilter{m: make(map[reflect.Type]bool)}
- for _, typ := range typs {
- t := reflect.TypeOf(typ)
- if t == nil || t.Kind() != reflect.Struct {
- panic(fmt.Sprintf("invalid struct type: %T", typ))
- }
- ux.m[t] = true
- }
- return ux
- }
- func (xf unexportedFilter) filter(p cmp.Path) bool {
- sf, ok := p.Index(-1).(cmp.StructField)
- if !ok {
- return false
- }
- return xf.m[p.Index(-2).Type()] && !isExported(sf.Name())
- }
-
- // isExported reports whether the identifier is exported.
- func isExported(id string) bool {
- r, _ := utf8.DecodeRuneInString(id)
- return unicode.IsUpper(r)
- }
-
- // IgnoreSliceElements returns an Option that ignores elements of []V.
- // The discard function must be of the form "func(T) bool" which is used to
- // ignore slice elements of type V, where V is assignable to T.
- // Elements are ignored if the function reports true.
- func IgnoreSliceElements(discardFunc interface{}) cmp.Option {
- vf := reflect.ValueOf(discardFunc)
- if !function.IsType(vf.Type(), function.ValuePredicate) || vf.IsNil() {
- panic(fmt.Sprintf("invalid discard function: %T", discardFunc))
- }
- return cmp.FilterPath(func(p cmp.Path) bool {
- si, ok := p.Index(-1).(cmp.SliceIndex)
- if !ok {
- return false
- }
- if !si.Type().AssignableTo(vf.Type().In(0)) {
- return false
- }
- vx, vy := si.Values()
- if vx.IsValid() && vf.Call([]reflect.Value{vx})[0].Bool() {
- return true
- }
- if vy.IsValid() && vf.Call([]reflect.Value{vy})[0].Bool() {
- return true
- }
- return false
- }, cmp.Ignore())
- }
-
- // IgnoreMapEntries returns an Option that ignores entries of map[K]V.
- // The discard function must be of the form "func(T, R) bool" which is used to
- // ignore map entries of type K and V, where K and V are assignable to T and R.
- // Entries are ignored if the function reports true.
- func IgnoreMapEntries(discardFunc interface{}) cmp.Option {
- vf := reflect.ValueOf(discardFunc)
- if !function.IsType(vf.Type(), function.KeyValuePredicate) || vf.IsNil() {
- panic(fmt.Sprintf("invalid discard function: %T", discardFunc))
- }
- return cmp.FilterPath(func(p cmp.Path) bool {
- mi, ok := p.Index(-1).(cmp.MapIndex)
- if !ok {
- return false
- }
- if !mi.Key().Type().AssignableTo(vf.Type().In(0)) || !mi.Type().AssignableTo(vf.Type().In(1)) {
- return false
- }
- k := mi.Key()
- vx, vy := mi.Values()
- if vx.IsValid() && vf.Call([]reflect.Value{k, vx})[0].Bool() {
- return true
- }
- if vy.IsValid() && vf.Call([]reflect.Value{k, vy})[0].Bool() {
- return true
- }
- return false
- }, cmp.Ignore())
- }
|