// 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 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 [cmp.Option] that ignores fields of the // given names on a single struct type. It respects the names of exported fields // that are forwarded due to struct embedding. // 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. func IgnoreFields(typ interface{}, names ...string) cmp.Option { sf := newStructFilter(typ, names...) return cmp.FilterPath(sf.filter, cmp.Ignore()) } // IgnoreTypes returns an [cmp.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 [cmp.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 [cmp.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 [cmp.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("%T must be a non-pointer struct", 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 [cmp.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 [cmp.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()) }