github.com/integration-system/go-cmp@v0.0.0-20190131081942-ac5582987a2f/cmp/options.go (about)

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