Home | History | Annotate | Download | only in cmp
      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 
      5 package cmp
      6 
      7 import (
      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.
     25 type 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
     38 type 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
     49 type coreOption interface {
     50 	Option
     51 	isCore()
     52 }
     53 
     54 type core struct{}
     55 
     56 func (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.
     65 type Options []Option
     66 
     67 func (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 
     88 func (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 
     99 func (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.
    112 func 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 
    122 type pathFilter struct {
    123 	core
    124 	fnc func(Path) bool
    125 	opt Option
    126 }
    127 
    128 func (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 
    135 func (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.
    153 func 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 
    168 type valuesFilter struct {
    169 	core
    170 	typ reflect.Type  // T
    171 	fnc reflect.Value // func(T, T) bool
    172 	opt Option
    173 }
    174 
    175 func (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 
    185 func (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.
    193 func Ignore() Option { return ignore{} }
    194 
    195 type ignore struct{ core }
    196 
    197 func (ignore) isFiltered() bool                                                     { return false }
    198 func (ignore) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return ignore{} }
    199 func (ignore) apply(_ *state, _, _ reflect.Value) bool                              { return true }
    200 func (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.
    204 type invalid struct{ core }
    205 
    206 func (invalid) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return invalid{} }
    207 func (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.
    224 func 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 
    242 type transformer struct {
    243 	core
    244 	name string
    245 	typ  reflect.Type  // T
    246 	fnc  reflect.Value // func(T) R
    247 }
    248 
    249 func (tr *transformer) isFiltered() bool { return tr.typ != nil }
    250 
    251 func (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 
    258 func (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 
    270 func (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
    286 func 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 
    298 type comparer struct {
    299 	core
    300 	typ reflect.Type  // T
    301 	fnc reflect.Value // func(T, T) bool
    302 }
    303 
    304 func (cm *comparer) isFiltered() bool { return cm.typ != nil }
    305 
    306 func (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 
    313 func (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 
    319 func (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.
    349 func 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 
    364 type visibleStructs map[reflect.Type]bool
    365 
    366 func (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.
    371 type 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.
    392 func 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.
    405 func 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.
    423 func 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