...

Source file src/github.com/google/go-cmp/cmp/cmpopts/ignore.go

Documentation: github.com/google/go-cmp/cmp/cmpopts

     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  
     5  package cmpopts
     6  
     7  import (
     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 [cmp.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.
    24  func 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 [cmp.Option] that ignores all values assignable to
    30  // certain types, which are specified by passing in a value of each type.
    31  func IgnoreTypes(typs ...interface{}) cmp.Option {
    32  	tf := newTypeFilter(typs...)
    33  	return cmp.FilterPath(tf.filter, cmp.Ignore())
    34  }
    35  
    36  type typeFilter []reflect.Type
    37  
    38  func 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  }
    49  func (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 [cmp.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}{}.
    66  func IgnoreInterfaces(ifaces interface{}) cmp.Option {
    67  	tf := newIfaceFilter(ifaces)
    68  	return cmp.FilterPath(tf.filter, cmp.Ignore())
    69  }
    70  
    71  type ifaceFilter []reflect.Type
    72  
    73  func 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  }
    94  func (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 [cmp.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 [cmp.Comparer] instead.
   119  func IgnoreUnexported(typs ...interface{}) cmp.Option {
   120  	ux := newUnexportedFilter(typs...)
   121  	return cmp.FilterPath(ux.filter, cmp.Ignore())
   122  }
   123  
   124  type unexportedFilter struct{ m map[reflect.Type]bool }
   125  
   126  func 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  }
   137  func (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.
   146  func isExported(id string) bool {
   147  	r, _ := utf8.DecodeRuneInString(id)
   148  	return unicode.IsUpper(r)
   149  }
   150  
   151  // IgnoreSliceElements returns an [cmp.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.
   155  func 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 [cmp.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.
   183  func 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  

View as plain text