You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

208 lines
6.3 KiB

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