]>
Commit | Line | Data |
---|---|---|
107c1cdb ND |
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, which may mutate s or panic. | |
42 | apply(s *state, vx, vy 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 | // | |
224 | // The name is a user provided label that is used as the Transform.Name in the | |
225 | // transformation PathStep. If empty, an arbitrary name is used. | |
226 | func Transformer(name string, f interface{}) Option { | |
227 | v := reflect.ValueOf(f) | |
228 | if !function.IsType(v.Type(), function.Transformer) || v.IsNil() { | |
229 | panic(fmt.Sprintf("invalid transformer function: %T", f)) | |
230 | } | |
231 | if name == "" { | |
232 | name = "λ" // Lambda-symbol as place-holder for anonymous transformer | |
233 | } | |
234 | if !isValid(name) { | |
235 | panic(fmt.Sprintf("invalid name: %q", name)) | |
236 | } | |
237 | tr := &transformer{name: name, fnc: reflect.ValueOf(f)} | |
238 | if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { | |
239 | tr.typ = ti | |
240 | } | |
241 | return tr | |
242 | } | |
243 | ||
244 | type transformer struct { | |
245 | core | |
246 | name string | |
247 | typ reflect.Type // T | |
248 | fnc reflect.Value // func(T) R | |
249 | } | |
250 | ||
251 | func (tr *transformer) isFiltered() bool { return tr.typ != nil } | |
252 | ||
253 | func (tr *transformer) filter(s *state, _, _ reflect.Value, t reflect.Type) applicableOption { | |
254 | for i := len(s.curPath) - 1; i >= 0; i-- { | |
255 | if t, ok := s.curPath[i].(*transform); !ok { | |
256 | break // Hit most recent non-Transform step | |
257 | } else if tr == t.trans { | |
258 | return nil // Cannot directly use same Transform | |
259 | } | |
260 | } | |
261 | if tr.typ == nil || t.AssignableTo(tr.typ) { | |
262 | return tr | |
263 | } | |
264 | return nil | |
265 | } | |
266 | ||
267 | func (tr *transformer) apply(s *state, vx, vy reflect.Value) { | |
268 | // Update path before calling the Transformer so that dynamic checks | |
269 | // will use the updated path. | |
270 | s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr}) | |
271 | defer s.curPath.pop() | |
272 | ||
273 | vx = s.callTRFunc(tr.fnc, vx) | |
274 | vy = s.callTRFunc(tr.fnc, vy) | |
275 | s.compareAny(vx, vy) | |
276 | } | |
277 | ||
278 | func (tr transformer) String() string { | |
279 | return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer())) | |
280 | } | |
281 | ||
282 | // Comparer returns an Option that determines whether two values are equal | |
283 | // to each other. | |
284 | // | |
285 | // The comparer f must be a function "func(T, T) bool" and is implicitly | |
286 | // filtered to input values assignable to T. If T is an interface, it is | |
287 | // possible that f is called with two values of different concrete types that | |
288 | // both implement T. | |
289 | // | |
290 | // The equality function must be: | |
291 | // • Symmetric: equal(x, y) == equal(y, x) | |
292 | // • Deterministic: equal(x, y) == equal(x, y) | |
293 | // • Pure: equal(x, y) does not modify x or y | |
294 | func Comparer(f interface{}) Option { | |
295 | v := reflect.ValueOf(f) | |
296 | if !function.IsType(v.Type(), function.Equal) || v.IsNil() { | |
297 | panic(fmt.Sprintf("invalid comparer function: %T", f)) | |
298 | } | |
299 | cm := &comparer{fnc: v} | |
300 | if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { | |
301 | cm.typ = ti | |
302 | } | |
303 | return cm | |
304 | } | |
305 | ||
306 | type comparer struct { | |
307 | core | |
308 | typ reflect.Type // T | |
309 | fnc reflect.Value // func(T, T) bool | |
310 | } | |
311 | ||
312 | func (cm *comparer) isFiltered() bool { return cm.typ != nil } | |
313 | ||
314 | func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption { | |
315 | if cm.typ == nil || t.AssignableTo(cm.typ) { | |
316 | return cm | |
317 | } | |
318 | return nil | |
319 | } | |
320 | ||
321 | func (cm *comparer) apply(s *state, vx, vy reflect.Value) { | |
322 | eq := s.callTTBFunc(cm.fnc, vx, vy) | |
323 | s.report(eq, vx, vy) | |
324 | } | |
325 | ||
326 | func (cm comparer) String() string { | |
327 | return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer())) | |
328 | } | |
329 | ||
330 | // AllowUnexported returns an Option that forcibly allows operations on | |
331 | // unexported fields in certain structs, which are specified by passing in a | |
332 | // value of each struct type. | |
333 | // | |
334 | // Users of this option must understand that comparing on unexported fields | |
335 | // from external packages is not safe since changes in the internal | |
336 | // implementation of some external package may cause the result of Equal | |
337 | // to unexpectedly change. However, it may be valid to use this option on types | |
338 | // defined in an internal package where the semantic meaning of an unexported | |
339 | // field is in the control of the user. | |
340 | // | |
341 | // For some cases, a custom Comparer should be used instead that defines | |
342 | // equality as a function of the public API of a type rather than the underlying | |
343 | // unexported implementation. | |
344 | // | |
345 | // For example, the reflect.Type documentation defines equality to be determined | |
346 | // by the == operator on the interface (essentially performing a shallow pointer | |
347 | // comparison) and most attempts to compare *regexp.Regexp types are interested | |
348 | // in only checking that the regular expression strings are equal. | |
349 | // Both of these are accomplished using Comparers: | |
350 | // | |
351 | // Comparer(func(x, y reflect.Type) bool { return x == y }) | |
352 | // Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() }) | |
353 | // | |
354 | // In other cases, the cmpopts.IgnoreUnexported option can be used to ignore | |
355 | // all unexported fields on specified struct types. | |
356 | func AllowUnexported(types ...interface{}) Option { | |
357 | if !supportAllowUnexported { | |
358 | panic("AllowUnexported is not supported on purego builds, Google App Engine Standard, or GopherJS") | |
359 | } | |
360 | m := make(map[reflect.Type]bool) | |
361 | for _, typ := range types { | |
362 | t := reflect.TypeOf(typ) | |
363 | if t.Kind() != reflect.Struct { | |
364 | panic(fmt.Sprintf("invalid struct type: %T", typ)) | |
365 | } | |
366 | m[t] = true | |
367 | } | |
368 | return visibleStructs(m) | |
369 | } | |
370 | ||
371 | type visibleStructs map[reflect.Type]bool | |
372 | ||
373 | func (visibleStructs) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { | |
374 | panic("not implemented") | |
375 | } | |
376 | ||
377 | // reporter is an Option that configures how differences are reported. | |
378 | type reporter interface { | |
379 | // TODO: Not exported yet. | |
380 | // | |
381 | // Perhaps add PushStep and PopStep and change Report to only accept | |
382 | // a PathStep instead of the full-path? Adding a PushStep and PopStep makes | |
383 | // it clear that we are traversing the value tree in a depth-first-search | |
384 | // manner, which has an effect on how values are printed. | |
385 | ||
386 | Option | |
387 | ||
388 | // Report is called for every comparison made and will be provided with | |
389 | // the two values being compared, the equality result, and the | |
390 | // current path in the value tree. It is possible for x or y to be an | |
391 | // invalid reflect.Value if one of the values is non-existent; | |
392 | // which is possible with maps and slices. | |
393 | Report(x, y reflect.Value, eq bool, p Path) | |
394 | } | |
395 | ||
396 | // normalizeOption normalizes the input options such that all Options groups | |
397 | // are flattened and groups with a single element are reduced to that element. | |
398 | // Only coreOptions and Options containing coreOptions are allowed. | |
399 | func normalizeOption(src Option) Option { | |
400 | switch opts := flattenOptions(nil, Options{src}); len(opts) { | |
401 | case 0: | |
402 | return nil | |
403 | case 1: | |
404 | return opts[0] | |
405 | default: | |
406 | return opts | |
407 | } | |
408 | } | |
409 | ||
410 | // flattenOptions copies all options in src to dst as a flat list. | |
411 | // Only coreOptions and Options containing coreOptions are allowed. | |
412 | func flattenOptions(dst, src Options) Options { | |
413 | for _, opt := range src { | |
414 | switch opt := opt.(type) { | |
415 | case nil: | |
416 | continue | |
417 | case Options: | |
418 | dst = flattenOptions(dst, opt) | |
419 | case coreOption: | |
420 | dst = append(dst, opt) | |
421 | default: | |
422 | panic(fmt.Sprintf("invalid option type: %T", opt)) | |
423 | } | |
424 | } | |
425 | return dst | |
426 | } | |
427 | ||
428 | // getFuncName returns a short function name from the pointer. | |
429 | // The string parsing logic works up until Go1.9. | |
430 | func getFuncName(p uintptr) string { | |
431 | fnc := runtime.FuncForPC(p) | |
432 | if fnc == nil { | |
433 | return "<unknown>" | |
434 | } | |
435 | name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm" | |
436 | if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") { | |
437 | // Strip the package name from method name. | |
438 | name = strings.TrimSuffix(name, ")-fm") | |
439 | name = strings.TrimSuffix(name, ")·fm") | |
440 | if i := strings.LastIndexByte(name, '('); i >= 0 { | |
441 | methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc" | |
442 | if j := strings.LastIndexByte(methodName, '.'); j >= 0 { | |
443 | methodName = methodName[j+1:] // E.g., "myfunc" | |
444 | } | |
445 | name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc" | |
446 | } | |
447 | } | |
448 | if i := strings.LastIndexByte(name, '/'); i >= 0 { | |
449 | // Strip the package name. | |
450 | name = name[i+1:] // E.g., "mypkg.(mytype).myfunc" | |
451 | } | |
452 | return name | |
453 | } |