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 5package cmp 6 7import ( 8 "fmt" 9 "reflect" 10 "runtime" 11 "strings" 12 13 "github.com/google/go-cmp/cmp/internal/function" 14) 15 16// Option configures for specific behavior of Equal and Diff. In particular, 17// the fundamental Option functions (Ignore, Transformer, and Comparer), 18// configure how equality is determined. 19// 20// The fundamental options may be composed with filters (FilterPath and 21// FilterValues) to control the scope over which they are applied. 22// 23// The cmp/cmpopts package provides helper functions for creating options that 24// may be used with Equal and Diff. 25type Option interface { 26 // filter applies all filters and returns the option that remains. 27 // Each option may only read s.curPath and call s.callTTBFunc. 28 // 29 // An Options is returned only if multiple comparers or transformers 30 // can apply simultaneously and will only contain values of those types 31 // or sub-Options containing values of those types. 32 filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption 33} 34 35// applicableOption represents the following types: 36// Fundamental: ignore | invalid | *comparer | *transformer 37// Grouping: Options 38type applicableOption interface { 39 Option 40 41 // apply executes the option and reports whether the option was applied. 42 // Each option may mutate s. 43 apply(s *state, vx, vy reflect.Value) bool 44} 45 46// coreOption represents the following types: 47// Fundamental: ignore | invalid | *comparer | *transformer 48// Filters: *pathFilter | *valuesFilter 49type coreOption interface { 50 Option 51 isCore() 52} 53 54type core struct{} 55 56func (core) isCore() {} 57 58// Options is a list of Option values that also satisfies the Option interface. 59// Helper comparison packages may return an Options value when packing multiple 60// Option values into a single Option. When this package processes an Options, 61// it will be implicitly expanded into a flat list. 62// 63// Applying a filter on an Options is equivalent to applying that same filter 64// on all individual options held within. 65type Options []Option 66 67func (opts Options) filter(s *state, vx, vy reflect.Value, t reflect.Type) (out applicableOption) { 68 for _, opt := range opts { 69 switch opt := opt.filter(s, vx, vy, t); opt.(type) { 70 case ignore: 71 return ignore{} // Only ignore can short-circuit evaluation 72 case invalid: 73 out = invalid{} // Takes precedence over comparer or transformer 74 case *comparer, *transformer, Options: 75 switch out.(type) { 76 case nil: 77 out = opt 78 case invalid: 79 // Keep invalid 80 case *comparer, *transformer, Options: 81 out = Options{out, opt} // Conflicting comparers or transformers 82 } 83 } 84 } 85 return out 86} 87 88func (opts Options) apply(s *state, _, _ reflect.Value) bool { 89 const warning = "ambiguous set of applicable options" 90 const help = "consider using filters to ensure at most one Comparer or Transformer may apply" 91 var ss []string 92 for _, opt := range flattenOptions(nil, opts) { 93 ss = append(ss, fmt.Sprint(opt)) 94 } 95 set := strings.Join(ss, "\n\t") 96 panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help)) 97} 98 99func (opts Options) String() string { 100 var ss []string 101 for _, opt := range opts { 102 ss = append(ss, fmt.Sprint(opt)) 103 } 104 return fmt.Sprintf("Options{%s}", strings.Join(ss, ", ")) 105} 106 107// FilterPath returns a new Option where opt is only evaluated if filter f 108// returns true for the current Path in the value tree. 109// 110// The option passed in may be an Ignore, Transformer, Comparer, Options, or 111// a previously filtered Option. 112func FilterPath(f func(Path) bool, opt Option) Option { 113 if f == nil { 114 panic("invalid path filter function") 115 } 116 if opt := normalizeOption(opt); opt != nil { 117 return &pathFilter{fnc: f, opt: opt} 118 } 119 return nil 120} 121 122type pathFilter struct { 123 core 124 fnc func(Path) bool 125 opt Option 126} 127 128func (f pathFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption { 129 if f.fnc(s.curPath) { 130 return f.opt.filter(s, vx, vy, t) 131 } 132 return nil 133} 134 135func (f pathFilter) String() string { 136 fn := getFuncName(reflect.ValueOf(f.fnc).Pointer()) 137 return fmt.Sprintf("FilterPath(%s, %v)", fn, f.opt) 138} 139 140// FilterValues returns a new Option where opt is only evaluated if filter f, 141// which is a function of the form "func(T, T) bool", returns true for the 142// current pair of values being compared. If the type of the values is not 143// assignable to T, then this filter implicitly returns false. 144// 145// The filter function must be 146// symmetric (i.e., agnostic to the order of the inputs) and 147// deterministic (i.e., produces the same result when given the same inputs). 148// If T is an interface, it is possible that f is called with two values with 149// different concrete types that both implement T. 150// 151// The option passed in may be an Ignore, Transformer, Comparer, Options, or 152// a previously filtered Option. 153func FilterValues(f interface{}, opt Option) Option { 154 v := reflect.ValueOf(f) 155 if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() { 156 panic(fmt.Sprintf("invalid values filter function: %T", f)) 157 } 158 if opt := normalizeOption(opt); opt != nil { 159 vf := &valuesFilter{fnc: v, opt: opt} 160 if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { 161 vf.typ = ti 162 } 163 return vf 164 } 165 return nil 166} 167 168type valuesFilter struct { 169 core 170 typ reflect.Type // T 171 fnc reflect.Value // func(T, T) bool 172 opt Option 173} 174 175func (f valuesFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption { 176 if !vx.IsValid() || !vy.IsValid() { 177 return invalid{} 178 } 179 if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) { 180 return f.opt.filter(s, vx, vy, t) 181 } 182 return nil 183} 184 185func (f valuesFilter) String() string { 186 fn := getFuncName(f.fnc.Pointer()) 187 return fmt.Sprintf("FilterValues(%s, %v)", fn, f.opt) 188} 189 190// Ignore is an Option that causes all comparisons to be ignored. 191// This value is intended to be combined with FilterPath or FilterValues. 192// It is an error to pass an unfiltered Ignore option to Equal. 193func Ignore() Option { return ignore{} } 194 195type ignore struct{ core } 196 197func (ignore) isFiltered() bool { return false } 198func (ignore) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return ignore{} } 199func (ignore) apply(_ *state, _, _ reflect.Value) bool { return true } 200func (ignore) String() string { return "Ignore()" } 201 202// invalid is a sentinel Option type to indicate that some options could not 203// be evaluated due to unexported fields. 204type invalid struct{ core } 205 206func (invalid) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return invalid{} } 207func (invalid) apply(s *state, _, _ reflect.Value) bool { 208 const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported" 209 panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help)) 210} 211 212// Transformer returns an Option that applies a transformation function that 213// converts values of a certain type into that of another. 214// 215// The transformer f must be a function "func(T) R" that converts values of 216// type T to those of type R and is implicitly filtered to input values 217// assignable to T. The transformer must not mutate T in any way. 218// If T and R are the same type, an additional filter must be applied to 219// act as the base case to prevent an infinite recursion applying the same 220// transform to itself (see the SortedSlice example). 221// 222// The name is a user provided label that is used as the Transform.Name in the 223// transformation PathStep. If empty, an arbitrary name is used. 224func Transformer(name string, f interface{}) Option { 225 v := reflect.ValueOf(f) 226 if !function.IsType(v.Type(), function.Transformer) || v.IsNil() { 227 panic(fmt.Sprintf("invalid transformer function: %T", f)) 228 } 229 if name == "" { 230 name = "λ" // Lambda-symbol as place-holder for anonymous transformer 231 } 232 if !isValid(name) { 233 panic(fmt.Sprintf("invalid name: %q", name)) 234 } 235 tr := &transformer{name: name, fnc: reflect.ValueOf(f)} 236 if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { 237 tr.typ = ti 238 } 239 return tr 240} 241 242type transformer struct { 243 core 244 name string 245 typ reflect.Type // T 246 fnc reflect.Value // func(T) R 247} 248 249func (tr *transformer) isFiltered() bool { return tr.typ != nil } 250 251func (tr *transformer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption { 252 if tr.typ == nil || t.AssignableTo(tr.typ) { 253 return tr 254 } 255 return nil 256} 257 258func (tr *transformer) apply(s *state, vx, vy reflect.Value) bool { 259 // Update path before calling the Transformer so that dynamic checks 260 // will use the updated path. 261 s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr}) 262 defer s.curPath.pop() 263 264 vx = s.callTRFunc(tr.fnc, vx) 265 vy = s.callTRFunc(tr.fnc, vy) 266 s.compareAny(vx, vy) 267 return true 268} 269 270func (tr transformer) String() string { 271 return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer())) 272} 273 274// Comparer returns an Option that determines whether two values are equal 275// to each other. 276// 277// The comparer f must be a function "func(T, T) bool" and is implicitly 278// filtered to input values assignable to T. If T is an interface, it is 279// possible that f is called with two values of different concrete types that 280// both implement T. 281// 282// The equality function must be: 283// • Symmetric: equal(x, y) == equal(y, x) 284// • Deterministic: equal(x, y) == equal(x, y) 285// • Pure: equal(x, y) does not modify x or y 286func Comparer(f interface{}) Option { 287 v := reflect.ValueOf(f) 288 if !function.IsType(v.Type(), function.Equal) || v.IsNil() { 289 panic(fmt.Sprintf("invalid comparer function: %T", f)) 290 } 291 cm := &comparer{fnc: v} 292 if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { 293 cm.typ = ti 294 } 295 return cm 296} 297 298type comparer struct { 299 core 300 typ reflect.Type // T 301 fnc reflect.Value // func(T, T) bool 302} 303 304func (cm *comparer) isFiltered() bool { return cm.typ != nil } 305 306func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption { 307 if cm.typ == nil || t.AssignableTo(cm.typ) { 308 return cm 309 } 310 return nil 311} 312 313func (cm *comparer) apply(s *state, vx, vy reflect.Value) bool { 314 eq := s.callTTBFunc(cm.fnc, vx, vy) 315 s.report(eq, vx, vy) 316 return true 317} 318 319func (cm comparer) String() string { 320 return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer())) 321} 322 323// AllowUnexported returns an Option that forcibly allows operations on 324// unexported fields in certain structs, which are specified by passing in a 325// value of each struct type. 326// 327// Users of this option must understand that comparing on unexported fields 328// from external packages is not safe since changes in the internal 329// implementation of some external package may cause the result of Equal 330// to unexpectedly change. However, it may be valid to use this option on types 331// defined in an internal package where the semantic meaning of an unexported 332// field is in the control of the user. 333// 334// For some cases, a custom Comparer should be used instead that defines 335// equality as a function of the public API of a type rather than the underlying 336// unexported implementation. 337// 338// For example, the reflect.Type documentation defines equality to be determined 339// by the == operator on the interface (essentially performing a shallow pointer 340// comparison) and most attempts to compare *regexp.Regexp types are interested 341// in only checking that the regular expression strings are equal. 342// Both of these are accomplished using Comparers: 343// 344// Comparer(func(x, y reflect.Type) bool { return x == y }) 345// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() }) 346// 347// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore 348// all unexported fields on specified struct types. 349func AllowUnexported(types ...interface{}) Option { 350 if !supportAllowUnexported { 351 panic("AllowUnexported is not supported on App Engine Classic or GopherJS") 352 } 353 m := make(map[reflect.Type]bool) 354 for _, typ := range types { 355 t := reflect.TypeOf(typ) 356 if t.Kind() != reflect.Struct { 357 panic(fmt.Sprintf("invalid struct type: %T", typ)) 358 } 359 m[t] = true 360 } 361 return visibleStructs(m) 362} 363 364type visibleStructs map[reflect.Type]bool 365 366func (visibleStructs) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { 367 panic("not implemented") 368} 369 370// reporter is an Option that configures how differences are reported. 371type reporter interface { 372 // TODO: Not exported yet. 373 // 374 // Perhaps add PushStep and PopStep and change Report to only accept 375 // a PathStep instead of the full-path? Adding a PushStep and PopStep makes 376 // it clear that we are traversing the value tree in a depth-first-search 377 // manner, which has an effect on how values are printed. 378 379 Option 380 381 // Report is called for every comparison made and will be provided with 382 // the two values being compared, the equality result, and the 383 // current path in the value tree. It is possible for x or y to be an 384 // invalid reflect.Value if one of the values is non-existent; 385 // which is possible with maps and slices. 386 Report(x, y reflect.Value, eq bool, p Path) 387} 388 389// normalizeOption normalizes the input options such that all Options groups 390// are flattened and groups with a single element are reduced to that element. 391// Only coreOptions and Options containing coreOptions are allowed. 392func normalizeOption(src Option) Option { 393 switch opts := flattenOptions(nil, Options{src}); len(opts) { 394 case 0: 395 return nil 396 case 1: 397 return opts[0] 398 default: 399 return opts 400 } 401} 402 403// flattenOptions copies all options in src to dst as a flat list. 404// Only coreOptions and Options containing coreOptions are allowed. 405func flattenOptions(dst, src Options) Options { 406 for _, opt := range src { 407 switch opt := opt.(type) { 408 case nil: 409 continue 410 case Options: 411 dst = flattenOptions(dst, opt) 412 case coreOption: 413 dst = append(dst, opt) 414 default: 415 panic(fmt.Sprintf("invalid option type: %T", opt)) 416 } 417 } 418 return dst 419} 420 421// getFuncName returns a short function name from the pointer. 422// The string parsing logic works up until Go1.9. 423func getFuncName(p uintptr) string { 424 fnc := runtime.FuncForPC(p) 425 if fnc == nil { 426 return "<unknown>" 427 } 428 name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm" 429 if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") { 430 // Strip the package name from method name. 431 name = strings.TrimSuffix(name, ")-fm") 432 name = strings.TrimSuffix(name, ")·fm") 433 if i := strings.LastIndexByte(name, '('); i >= 0 { 434 methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc" 435 if j := strings.LastIndexByte(methodName, '.'); j >= 0 { 436 methodName = methodName[j+1:] // E.g., "myfunc" 437 } 438 name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc" 439 } 440 } 441 if i := strings.LastIndexByte(name, '/'); i >= 0 { 442 // Strip the package name. 443 name = name[i+1:] // E.g., "mypkg.(mytype).myfunc" 444 } 445 return name 446} 447