Browse code

Merge pull request #36034 from dnephin/update-gty

update gotestyourself vendor

Yong Tang authored on 2018/01/17 12:42:54
Showing 25 changed files
... ...
@@ -20,7 +20,8 @@ github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d
20 20
 golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
21 21
 github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987
22 22
 github.com/pmezard/go-difflib v1.0.0
23
-github.com/gotestyourself/gotestyourself v1.1.0
23
+github.com/gotestyourself/gotestyourself 511344eed30e4384f010579a593dfb442033a692
24
+github.com/google/go-cmp v0.1.0
24 25
 
25 26
 github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5
26 27
 github.com/imdario/mergo 0.2.1
27 28
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+Copyright (c) 2017 The Go Authors. All rights reserved.
1
+
2
+Redistribution and use in source and binary forms, with or without
3
+modification, are permitted provided that the following conditions are
4
+met:
5
+
6
+   * Redistributions of source code must retain the above copyright
7
+notice, this list of conditions and the following disclaimer.
8
+   * Redistributions in binary form must reproduce the above
9
+copyright notice, this list of conditions and the following disclaimer
10
+in the documentation and/or other materials provided with the
11
+distribution.
12
+   * Neither the name of Google Inc. nor the names of its
13
+contributors may be used to endorse or promote products derived from
14
+this software without specific prior written permission.
15
+
16
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 27
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+# Package for equality of Go values
1
+
2
+[![GoDoc](https://godoc.org/github.com/google/go-cmp/cmp?status.svg)][godoc]
3
+[![Build Status](https://travis-ci.org/google/go-cmp.svg?branch=master)][travis]
4
+
5
+This package is intended to be a more powerful and safer alternative to
6
+`reflect.DeepEqual` for comparing whether two values are semantically equal.
7
+
8
+The primary features of `cmp` are:
9
+
10
+* When the default behavior of equality does not suit the needs of the test,
11
+  custom equality functions can override the equality operation.
12
+  For example, an equality function may report floats as equal so long as they
13
+  are within some tolerance of each other.
14
+
15
+* Types that have an `Equal` method may use that method to determine equality.
16
+  This allows package authors to determine the equality operation for the types
17
+  that they define.
18
+
19
+* If no custom equality functions are used and no `Equal` method is defined,
20
+  equality is determined by recursively comparing the primitive kinds on both
21
+  values, much like `reflect.DeepEqual`. Unlike `reflect.DeepEqual`, unexported
22
+  fields are not compared by default; they result in panics unless suppressed
23
+  by using an `Ignore` option (see `cmpopts.IgnoreUnexported`) or explictly
24
+  compared using the `AllowUnexported` option.
25
+
26
+See the [GoDoc documentation][godoc] for more information.
27
+
28
+This is not an official Google product.
29
+
30
+[godoc]: https://godoc.org/github.com/google/go-cmp/cmp
31
+[travis]: https://travis-ci.org/google/go-cmp
32
+
33
+## Install
34
+
35
+```
36
+go get -u github.com/google/go-cmp/cmp
37
+```
38
+
39
+## License
40
+
41
+BSD - See [LICENSE][license] file
42
+
43
+[license]: https://github.com/google/go-cmp/blob/master/LICENSE
0 44
new file mode 100644
... ...
@@ -0,0 +1,529 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+// Package cmp determines equality of values.
5
+//
6
+// This package is intended to be a more powerful and safer alternative to
7
+// reflect.DeepEqual for comparing whether two values are semantically equal.
8
+//
9
+// The primary features of cmp are:
10
+//
11
+// • When the default behavior of equality does not suit the needs of the test,
12
+// custom equality functions can override the equality operation.
13
+// For example, an equality function may report floats as equal so long as they
14
+// are within some tolerance of each other.
15
+//
16
+// • Types that have an Equal method may use that method to determine equality.
17
+// This allows package authors to determine the equality operation for the types
18
+// that they define.
19
+//
20
+// • If no custom equality functions are used and no Equal method is defined,
21
+// equality is determined by recursively comparing the primitive kinds on both
22
+// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
23
+// fields are not compared by default; they result in panics unless suppressed
24
+// by using an Ignore option (see cmpopts.IgnoreUnexported) or explictly compared
25
+// using the AllowUnexported option.
26
+package cmp
27
+
28
+import (
29
+	"fmt"
30
+	"reflect"
31
+
32
+	"github.com/google/go-cmp/cmp/internal/diff"
33
+	"github.com/google/go-cmp/cmp/internal/function"
34
+	"github.com/google/go-cmp/cmp/internal/value"
35
+)
36
+
37
+// BUG: Maps with keys containing NaN values cannot be properly compared due to
38
+// the reflection package's inability to retrieve such entries. Equal will panic
39
+// anytime it comes across a NaN key, but this behavior may change.
40
+//
41
+// See https://golang.org/issue/11104 for more details.
42
+
43
+var nothing = reflect.Value{}
44
+
45
+// Equal reports whether x and y are equal by recursively applying the
46
+// following rules in the given order to x and y and all of their sub-values:
47
+//
48
+// • If two values are not of the same type, then they are never equal
49
+// and the overall result is false.
50
+//
51
+// • Let S be the set of all Ignore, Transformer, and Comparer options that
52
+// remain after applying all path filters, value filters, and type filters.
53
+// If at least one Ignore exists in S, then the comparison is ignored.
54
+// If the number of Transformer and Comparer options in S is greater than one,
55
+// then Equal panics because it is ambiguous which option to use.
56
+// If S contains a single Transformer, then use that to transform the current
57
+// values and recursively call Equal on the output values.
58
+// If S contains a single Comparer, then use that to compare the current values.
59
+// Otherwise, evaluation proceeds to the next rule.
60
+//
61
+// • If the values have an Equal method of the form "(T) Equal(T) bool" or
62
+// "(T) Equal(I) bool" where T is assignable to I, then use the result of
63
+// x.Equal(y). Otherwise, no such method exists and evaluation proceeds to
64
+// the next rule.
65
+//
66
+// • Lastly, try to compare x and y based on their basic kinds.
67
+// Simple kinds like booleans, integers, floats, complex numbers, strings, and
68
+// channels are compared using the equivalent of the == operator in Go.
69
+// Functions are only equal if they are both nil, otherwise they are unequal.
70
+// Pointers are equal if the underlying values they point to are also equal.
71
+// Interfaces are equal if their underlying concrete values are also equal.
72
+//
73
+// Structs are equal if all of their fields are equal. If a struct contains
74
+// unexported fields, Equal panics unless the AllowUnexported option is used or
75
+// an Ignore option (e.g., cmpopts.IgnoreUnexported) ignores that field.
76
+//
77
+// Arrays, slices, and maps are equal if they are both nil or both non-nil
78
+// with the same length and the elements at each index or key are equal.
79
+// Note that a non-nil empty slice and a nil slice are not equal.
80
+// To equate empty slices and maps, consider using cmpopts.EquateEmpty.
81
+// Map keys are equal according to the == operator.
82
+// To use custom comparisons for map keys, consider using cmpopts.SortMaps.
83
+func Equal(x, y interface{}, opts ...Option) bool {
84
+	s := newState(opts)
85
+	s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y))
86
+	return s.result.Equal()
87
+}
88
+
89
+// Diff returns a human-readable report of the differences between two values.
90
+// It returns an empty string if and only if Equal returns true for the same
91
+// input values and options. The output string will use the "-" symbol to
92
+// indicate elements removed from x, and the "+" symbol to indicate elements
93
+// added to y.
94
+//
95
+// Do not depend on this output being stable.
96
+func Diff(x, y interface{}, opts ...Option) string {
97
+	r := new(defaultReporter)
98
+	opts = Options{Options(opts), r}
99
+	eq := Equal(x, y, opts...)
100
+	d := r.String()
101
+	if (d == "") != eq {
102
+		panic("inconsistent difference and equality results")
103
+	}
104
+	return d
105
+}
106
+
107
+type state struct {
108
+	// These fields represent the "comparison state".
109
+	// Calling statelessCompare must not result in observable changes to these.
110
+	result   diff.Result // The current result of comparison
111
+	curPath  Path        // The current path in the value tree
112
+	reporter reporter    // Optional reporter used for difference formatting
113
+
114
+	// dynChecker triggers pseudo-random checks for option correctness.
115
+	// It is safe for statelessCompare to mutate this value.
116
+	dynChecker dynChecker
117
+
118
+	// These fields, once set by processOption, will not change.
119
+	exporters map[reflect.Type]bool // Set of structs with unexported field visibility
120
+	opts      Options               // List of all fundamental and filter options
121
+}
122
+
123
+func newState(opts []Option) *state {
124
+	s := new(state)
125
+	for _, opt := range opts {
126
+		s.processOption(opt)
127
+	}
128
+	return s
129
+}
130
+
131
+func (s *state) processOption(opt Option) {
132
+	switch opt := opt.(type) {
133
+	case nil:
134
+	case Options:
135
+		for _, o := range opt {
136
+			s.processOption(o)
137
+		}
138
+	case coreOption:
139
+		type filtered interface {
140
+			isFiltered() bool
141
+		}
142
+		if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
143
+			panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
144
+		}
145
+		s.opts = append(s.opts, opt)
146
+	case visibleStructs:
147
+		if s.exporters == nil {
148
+			s.exporters = make(map[reflect.Type]bool)
149
+		}
150
+		for t := range opt {
151
+			s.exporters[t] = true
152
+		}
153
+	case reporter:
154
+		if s.reporter != nil {
155
+			panic("difference reporter already registered")
156
+		}
157
+		s.reporter = opt
158
+	default:
159
+		panic(fmt.Sprintf("unknown option %T", opt))
160
+	}
161
+}
162
+
163
+// statelessCompare compares two values and returns the result.
164
+// This function is stateless in that it does not alter the current result,
165
+// or output to any registered reporters.
166
+func (s *state) statelessCompare(vx, vy reflect.Value) diff.Result {
167
+	// We do not save and restore the curPath because all of the compareX
168
+	// methods should properly push and pop from the path.
169
+	// It is an implementation bug if the contents of curPath differs from
170
+	// when calling this function to when returning from it.
171
+
172
+	oldResult, oldReporter := s.result, s.reporter
173
+	s.result = diff.Result{} // Reset result
174
+	s.reporter = nil         // Remove reporter to avoid spurious printouts
175
+	s.compareAny(vx, vy)
176
+	res := s.result
177
+	s.result, s.reporter = oldResult, oldReporter
178
+	return res
179
+}
180
+
181
+func (s *state) compareAny(vx, vy reflect.Value) {
182
+	// TODO: Support cyclic data structures.
183
+
184
+	// Rule 0: Differing types are never equal.
185
+	if !vx.IsValid() || !vy.IsValid() {
186
+		s.report(vx.IsValid() == vy.IsValid(), vx, vy)
187
+		return
188
+	}
189
+	if vx.Type() != vy.Type() {
190
+		s.report(false, vx, vy) // Possible for path to be empty
191
+		return
192
+	}
193
+	t := vx.Type()
194
+	if len(s.curPath) == 0 {
195
+		s.curPath.push(&pathStep{typ: t})
196
+		defer s.curPath.pop()
197
+	}
198
+	vx, vy = s.tryExporting(vx, vy)
199
+
200
+	// Rule 1: Check whether an option applies on this node in the value tree.
201
+	if s.tryOptions(vx, vy, t) {
202
+		return
203
+	}
204
+
205
+	// Rule 2: Check whether the type has a valid Equal method.
206
+	if s.tryMethod(vx, vy, t) {
207
+		return
208
+	}
209
+
210
+	// Rule 3: Recursively descend into each value's underlying kind.
211
+	switch t.Kind() {
212
+	case reflect.Bool:
213
+		s.report(vx.Bool() == vy.Bool(), vx, vy)
214
+		return
215
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
216
+		s.report(vx.Int() == vy.Int(), vx, vy)
217
+		return
218
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
219
+		s.report(vx.Uint() == vy.Uint(), vx, vy)
220
+		return
221
+	case reflect.Float32, reflect.Float64:
222
+		s.report(vx.Float() == vy.Float(), vx, vy)
223
+		return
224
+	case reflect.Complex64, reflect.Complex128:
225
+		s.report(vx.Complex() == vy.Complex(), vx, vy)
226
+		return
227
+	case reflect.String:
228
+		s.report(vx.String() == vy.String(), vx, vy)
229
+		return
230
+	case reflect.Chan, reflect.UnsafePointer:
231
+		s.report(vx.Pointer() == vy.Pointer(), vx, vy)
232
+		return
233
+	case reflect.Func:
234
+		s.report(vx.IsNil() && vy.IsNil(), vx, vy)
235
+		return
236
+	case reflect.Ptr:
237
+		if vx.IsNil() || vy.IsNil() {
238
+			s.report(vx.IsNil() && vy.IsNil(), vx, vy)
239
+			return
240
+		}
241
+		s.curPath.push(&indirect{pathStep{t.Elem()}})
242
+		defer s.curPath.pop()
243
+		s.compareAny(vx.Elem(), vy.Elem())
244
+		return
245
+	case reflect.Interface:
246
+		if vx.IsNil() || vy.IsNil() {
247
+			s.report(vx.IsNil() && vy.IsNil(), vx, vy)
248
+			return
249
+		}
250
+		if vx.Elem().Type() != vy.Elem().Type() {
251
+			s.report(false, vx.Elem(), vy.Elem())
252
+			return
253
+		}
254
+		s.curPath.push(&typeAssertion{pathStep{vx.Elem().Type()}})
255
+		defer s.curPath.pop()
256
+		s.compareAny(vx.Elem(), vy.Elem())
257
+		return
258
+	case reflect.Slice:
259
+		if vx.IsNil() || vy.IsNil() {
260
+			s.report(vx.IsNil() && vy.IsNil(), vx, vy)
261
+			return
262
+		}
263
+		fallthrough
264
+	case reflect.Array:
265
+		s.compareArray(vx, vy, t)
266
+		return
267
+	case reflect.Map:
268
+		s.compareMap(vx, vy, t)
269
+		return
270
+	case reflect.Struct:
271
+		s.compareStruct(vx, vy, t)
272
+		return
273
+	default:
274
+		panic(fmt.Sprintf("%v kind not handled", t.Kind()))
275
+	}
276
+}
277
+
278
+func (s *state) tryExporting(vx, vy reflect.Value) (reflect.Value, reflect.Value) {
279
+	if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported {
280
+		if sf.force {
281
+			// Use unsafe pointer arithmetic to get read-write access to an
282
+			// unexported field in the struct.
283
+			vx = unsafeRetrieveField(sf.pvx, sf.field)
284
+			vy = unsafeRetrieveField(sf.pvy, sf.field)
285
+		} else {
286
+			// We are not allowed to export the value, so invalidate them
287
+			// so that tryOptions can panic later if not explicitly ignored.
288
+			vx = nothing
289
+			vy = nothing
290
+		}
291
+	}
292
+	return vx, vy
293
+}
294
+
295
+func (s *state) tryOptions(vx, vy reflect.Value, t reflect.Type) bool {
296
+	// If there were no FilterValues, we will not detect invalid inputs,
297
+	// so manually check for them and append invalid if necessary.
298
+	// We still evaluate the options since an ignore can override invalid.
299
+	opts := s.opts
300
+	if !vx.IsValid() || !vy.IsValid() {
301
+		opts = Options{opts, invalid{}}
302
+	}
303
+
304
+	// Evaluate all filters and apply the remaining options.
305
+	if opt := opts.filter(s, vx, vy, t); opt != nil {
306
+		return opt.apply(s, vx, vy)
307
+	}
308
+	return false
309
+}
310
+
311
+func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool {
312
+	// Check if this type even has an Equal method.
313
+	m, ok := t.MethodByName("Equal")
314
+	if !ok || !function.IsType(m.Type, function.EqualAssignable) {
315
+		return false
316
+	}
317
+
318
+	eq := s.callTTBFunc(m.Func, vx, vy)
319
+	s.report(eq, vx, vy)
320
+	return true
321
+}
322
+
323
+func (s *state) callTRFunc(f, v reflect.Value) reflect.Value {
324
+	if !s.dynChecker.Next() {
325
+		return f.Call([]reflect.Value{v})[0]
326
+	}
327
+
328
+	// Run the function twice and ensure that we get the same results back.
329
+	// We run in goroutines so that the race detector (if enabled) can detect
330
+	// unsafe mutations to the input.
331
+	c := make(chan reflect.Value)
332
+	go detectRaces(c, f, v)
333
+	want := f.Call([]reflect.Value{v})[0]
334
+	if got := <-c; !s.statelessCompare(got, want).Equal() {
335
+		// To avoid false-positives with non-reflexive equality operations,
336
+		// we sanity check whether a value is equal to itself.
337
+		if !s.statelessCompare(want, want).Equal() {
338
+			return want
339
+		}
340
+		fn := getFuncName(f.Pointer())
341
+		panic(fmt.Sprintf("non-deterministic function detected: %s", fn))
342
+	}
343
+	return want
344
+}
345
+
346
+func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
347
+	if !s.dynChecker.Next() {
348
+		return f.Call([]reflect.Value{x, y})[0].Bool()
349
+	}
350
+
351
+	// Swapping the input arguments is sufficient to check that
352
+	// f is symmetric and deterministic.
353
+	// We run in goroutines so that the race detector (if enabled) can detect
354
+	// unsafe mutations to the input.
355
+	c := make(chan reflect.Value)
356
+	go detectRaces(c, f, y, x)
357
+	want := f.Call([]reflect.Value{x, y})[0].Bool()
358
+	if got := <-c; !got.IsValid() || got.Bool() != want {
359
+		fn := getFuncName(f.Pointer())
360
+		panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn))
361
+	}
362
+	return want
363
+}
364
+
365
+func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
366
+	var ret reflect.Value
367
+	defer func() {
368
+		recover() // Ignore panics, let the other call to f panic instead
369
+		c <- ret
370
+	}()
371
+	ret = f.Call(vs)[0]
372
+}
373
+
374
+func (s *state) compareArray(vx, vy reflect.Value, t reflect.Type) {
375
+	step := &sliceIndex{pathStep{t.Elem()}, 0, 0}
376
+	s.curPath.push(step)
377
+
378
+	// Compute an edit-script for slices vx and vy.
379
+	eq, es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result {
380
+		step.xkey, step.ykey = ix, iy
381
+		return s.statelessCompare(vx.Index(ix), vy.Index(iy))
382
+	})
383
+
384
+	// Equal or no edit-script, so report entire slices as is.
385
+	if eq || es == nil {
386
+		s.curPath.pop() // Pop first since we are reporting the whole slice
387
+		s.report(eq, vx, vy)
388
+		return
389
+	}
390
+
391
+	// Replay the edit-script.
392
+	var ix, iy int
393
+	for _, e := range es {
394
+		switch e {
395
+		case diff.UniqueX:
396
+			step.xkey, step.ykey = ix, -1
397
+			s.report(false, vx.Index(ix), nothing)
398
+			ix++
399
+		case diff.UniqueY:
400
+			step.xkey, step.ykey = -1, iy
401
+			s.report(false, nothing, vy.Index(iy))
402
+			iy++
403
+		default:
404
+			step.xkey, step.ykey = ix, iy
405
+			if e == diff.Identity {
406
+				s.report(true, vx.Index(ix), vy.Index(iy))
407
+			} else {
408
+				s.compareAny(vx.Index(ix), vy.Index(iy))
409
+			}
410
+			ix++
411
+			iy++
412
+		}
413
+	}
414
+	s.curPath.pop()
415
+	return
416
+}
417
+
418
+func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) {
419
+	if vx.IsNil() || vy.IsNil() {
420
+		s.report(vx.IsNil() && vy.IsNil(), vx, vy)
421
+		return
422
+	}
423
+
424
+	// We combine and sort the two map keys so that we can perform the
425
+	// comparisons in a deterministic order.
426
+	step := &mapIndex{pathStep: pathStep{t.Elem()}}
427
+	s.curPath.push(step)
428
+	defer s.curPath.pop()
429
+	for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
430
+		step.key = k
431
+		vvx := vx.MapIndex(k)
432
+		vvy := vy.MapIndex(k)
433
+		switch {
434
+		case vvx.IsValid() && vvy.IsValid():
435
+			s.compareAny(vvx, vvy)
436
+		case vvx.IsValid() && !vvy.IsValid():
437
+			s.report(false, vvx, nothing)
438
+		case !vvx.IsValid() && vvy.IsValid():
439
+			s.report(false, nothing, vvy)
440
+		default:
441
+			// It is possible for both vvx and vvy to be invalid if the
442
+			// key contained a NaN value in it. There is no way in
443
+			// reflection to be able to retrieve these values.
444
+			// See https://golang.org/issue/11104
445
+			panic(fmt.Sprintf("%#v has map key with NaNs", s.curPath))
446
+		}
447
+	}
448
+}
449
+
450
+func (s *state) compareStruct(vx, vy reflect.Value, t reflect.Type) {
451
+	var vax, vay reflect.Value // Addressable versions of vx and vy
452
+
453
+	step := &structField{}
454
+	s.curPath.push(step)
455
+	defer s.curPath.pop()
456
+	for i := 0; i < t.NumField(); i++ {
457
+		vvx := vx.Field(i)
458
+		vvy := vy.Field(i)
459
+		step.typ = t.Field(i).Type
460
+		step.name = t.Field(i).Name
461
+		step.idx = i
462
+		step.unexported = !isExported(step.name)
463
+		if step.unexported {
464
+			// Defer checking of unexported fields until later to give an
465
+			// Ignore a chance to ignore the field.
466
+			if !vax.IsValid() || !vay.IsValid() {
467
+				// For unsafeRetrieveField to work, the parent struct must
468
+				// be addressable. Create a new copy of the values if
469
+				// necessary to make them addressable.
470
+				vax = makeAddressable(vx)
471
+				vay = makeAddressable(vy)
472
+			}
473
+			step.force = s.exporters[t]
474
+			step.pvx = vax
475
+			step.pvy = vay
476
+			step.field = t.Field(i)
477
+		}
478
+		s.compareAny(vvx, vvy)
479
+	}
480
+}
481
+
482
+// report records the result of a single comparison.
483
+// It also calls Report if any reporter is registered.
484
+func (s *state) report(eq bool, vx, vy reflect.Value) {
485
+	if eq {
486
+		s.result.NSame++
487
+	} else {
488
+		s.result.NDiff++
489
+	}
490
+	if s.reporter != nil {
491
+		s.reporter.Report(vx, vy, eq, s.curPath)
492
+	}
493
+}
494
+
495
+// dynChecker tracks the state needed to periodically perform checks that
496
+// user provided functions are symmetric and deterministic.
497
+// The zero value is safe for immediate use.
498
+type dynChecker struct{ curr, next int }
499
+
500
+// Next increments the state and reports whether a check should be performed.
501
+//
502
+// Checks occur every Nth function call, where N is a triangular number:
503
+//	0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
504
+// See https://en.wikipedia.org/wiki/Triangular_number
505
+//
506
+// This sequence ensures that the cost of checks drops significantly as
507
+// the number of functions calls grows larger.
508
+func (dc *dynChecker) Next() bool {
509
+	ok := dc.curr == dc.next
510
+	if ok {
511
+		dc.curr = 0
512
+		dc.next++
513
+	}
514
+	dc.curr++
515
+	return ok
516
+}
517
+
518
+// makeAddressable returns a value that is always addressable.
519
+// It returns the input verbatim if it is already addressable,
520
+// otherwise it creates a new value and returns an addressable copy.
521
+func makeAddressable(v reflect.Value) reflect.Value {
522
+	if v.CanAddr() {
523
+		return v
524
+	}
525
+	vc := reflect.New(v.Type()).Elem()
526
+	vc.Set(v)
527
+	return vc
528
+}
0 529
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+// +build !debug
5
+
6
+package diff
7
+
8
+var debug debugger
9
+
10
+type debugger struct{}
11
+
12
+func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
13
+	return f
14
+}
15
+func (debugger) Update() {}
16
+func (debugger) Finish() {}
0 17
new file mode 100644
... ...
@@ -0,0 +1,122 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+// +build debug
5
+
6
+package diff
7
+
8
+import (
9
+	"fmt"
10
+	"strings"
11
+	"sync"
12
+	"time"
13
+)
14
+
15
+// The algorithm can be seen running in real-time by enabling debugging:
16
+//	go test -tags=debug -v
17
+//
18
+// Example output:
19
+//	=== RUN   TestDifference/#34
20
+//	┌───────────────────────────────┐
21
+//	│ \ · · · · · · · · · · · · · · │
22
+//	│ · # · · · · · · · · · · · · · │
23
+//	│ · \ · · · · · · · · · · · · · │
24
+//	│ · · \ · · · · · · · · · · · · │
25
+//	│ · · · X # · · · · · · · · · · │
26
+//	│ · · · # \ · · · · · · · · · · │
27
+//	│ · · · · · # # · · · · · · · · │
28
+//	│ · · · · · # \ · · · · · · · · │
29
+//	│ · · · · · · · \ · · · · · · · │
30
+//	│ · · · · · · · · \ · · · · · · │
31
+//	│ · · · · · · · · · \ · · · · · │
32
+//	│ · · · · · · · · · · \ · · # · │
33
+//	│ · · · · · · · · · · · \ # # · │
34
+//	│ · · · · · · · · · · · # # # · │
35
+//	│ · · · · · · · · · · # # # # · │
36
+//	│ · · · · · · · · · # # # # # · │
37
+//	│ · · · · · · · · · · · · · · \ │
38
+//	└───────────────────────────────┘
39
+//	[.Y..M.XY......YXYXY.|]
40
+//
41
+// The grid represents the edit-graph where the horizontal axis represents
42
+// list X and the vertical axis represents list Y. The start of the two lists
43
+// is the top-left, while the ends are the bottom-right. The '·' represents
44
+// an unexplored node in the graph. The '\' indicates that the two symbols
45
+// from list X and Y are equal. The 'X' indicates that two symbols are similar
46
+// (but not exactly equal) to each other. The '#' indicates that the two symbols
47
+// are different (and not similar). The algorithm traverses this graph trying to
48
+// make the paths starting in the top-left and the bottom-right connect.
49
+//
50
+// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
51
+// the currently established path from the forward and reverse searches,
52
+// seperated by a '|' character.
53
+
54
+const (
55
+	updateDelay  = 100 * time.Millisecond
56
+	finishDelay  = 500 * time.Millisecond
57
+	ansiTerminal = true // ANSI escape codes used to move terminal cursor
58
+)
59
+
60
+var debug debugger
61
+
62
+type debugger struct {
63
+	sync.Mutex
64
+	p1, p2           EditScript
65
+	fwdPath, revPath *EditScript
66
+	grid             []byte
67
+	lines            int
68
+}
69
+
70
+func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
71
+	dbg.Lock()
72
+	dbg.fwdPath, dbg.revPath = p1, p2
73
+	top := "┌─" + strings.Repeat("──", nx) + "┐\n"
74
+	row := "│ " + strings.Repeat("· ", nx) + "│\n"
75
+	btm := "└─" + strings.Repeat("──", nx) + "┘\n"
76
+	dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
77
+	dbg.lines = strings.Count(dbg.String(), "\n")
78
+	fmt.Print(dbg)
79
+
80
+	// Wrap the EqualFunc so that we can intercept each result.
81
+	return func(ix, iy int) (r Result) {
82
+		cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
83
+		for i := range cell {
84
+			cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
85
+		}
86
+		switch r = f(ix, iy); {
87
+		case r.Equal():
88
+			cell[0] = '\\'
89
+		case r.Similar():
90
+			cell[0] = 'X'
91
+		default:
92
+			cell[0] = '#'
93
+		}
94
+		return
95
+	}
96
+}
97
+
98
+func (dbg *debugger) Update() {
99
+	dbg.print(updateDelay)
100
+}
101
+
102
+func (dbg *debugger) Finish() {
103
+	dbg.print(finishDelay)
104
+	dbg.Unlock()
105
+}
106
+
107
+func (dbg *debugger) String() string {
108
+	dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
109
+	for i := len(*dbg.revPath) - 1; i >= 0; i-- {
110
+		dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
111
+	}
112
+	return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
113
+}
114
+
115
+func (dbg *debugger) print(d time.Duration) {
116
+	if ansiTerminal {
117
+		fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
118
+	}
119
+	fmt.Print(dbg)
120
+	time.Sleep(d)
121
+}
0 122
new file mode 100644
... ...
@@ -0,0 +1,373 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+// Package diff implements an algorithm for producing edit-scripts.
5
+// The edit-script is a sequence of operations needed to transform one list
6
+// of symbols into another (or vice-versa). The edits allowed are insertions,
7
+// deletions, and modifications. The summation of all edits is called the
8
+// Levenshtein distance as this problem is well-known in computer science.
9
+//
10
+// This package prioritizes performance over accuracy. That is, the run time
11
+// is more important than obtaining a minimal Levenshtein distance.
12
+package diff
13
+
14
+// EditType represents a single operation within an edit-script.
15
+type EditType uint8
16
+
17
+const (
18
+	// Identity indicates that a symbol pair is identical in both list X and Y.
19
+	Identity EditType = iota
20
+	// UniqueX indicates that a symbol only exists in X and not Y.
21
+	UniqueX
22
+	// UniqueY indicates that a symbol only exists in Y and not X.
23
+	UniqueY
24
+	// Modified indicates that a symbol pair is a modification of each other.
25
+	Modified
26
+)
27
+
28
+// EditScript represents the series of differences between two lists.
29
+type EditScript []EditType
30
+
31
+// String returns a human-readable string representing the edit-script where
32
+// Identity, UniqueX, UniqueY, and Modified are represented by the
33
+// '.', 'X', 'Y', and 'M' characters, respectively.
34
+func (es EditScript) String() string {
35
+	b := make([]byte, len(es))
36
+	for i, e := range es {
37
+		switch e {
38
+		case Identity:
39
+			b[i] = '.'
40
+		case UniqueX:
41
+			b[i] = 'X'
42
+		case UniqueY:
43
+			b[i] = 'Y'
44
+		case Modified:
45
+			b[i] = 'M'
46
+		default:
47
+			panic("invalid edit-type")
48
+		}
49
+	}
50
+	return string(b)
51
+}
52
+
53
+// stats returns a histogram of the number of each type of edit operation.
54
+func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
55
+	for _, e := range es {
56
+		switch e {
57
+		case Identity:
58
+			s.NI++
59
+		case UniqueX:
60
+			s.NX++
61
+		case UniqueY:
62
+			s.NY++
63
+		case Modified:
64
+			s.NM++
65
+		default:
66
+			panic("invalid edit-type")
67
+		}
68
+	}
69
+	return
70
+}
71
+
72
+// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
73
+// lists X and Y are equal.
74
+func (es EditScript) Dist() int { return len(es) - es.stats().NI }
75
+
76
+// LenX is the length of the X list.
77
+func (es EditScript) LenX() int { return len(es) - es.stats().NY }
78
+
79
+// LenY is the length of the Y list.
80
+func (es EditScript) LenY() int { return len(es) - es.stats().NX }
81
+
82
+// EqualFunc reports whether the symbols at indexes ix and iy are equal.
83
+// When called by Difference, the index is guaranteed to be within nx and ny.
84
+type EqualFunc func(ix int, iy int) Result
85
+
86
+// Result is the result of comparison.
87
+// NSame is the number of sub-elements that are equal.
88
+// NDiff is the number of sub-elements that are not equal.
89
+type Result struct{ NSame, NDiff int }
90
+
91
+// Equal indicates whether the symbols are equal. Two symbols are equal
92
+// if and only if NDiff == 0. If Equal, then they are also Similar.
93
+func (r Result) Equal() bool { return r.NDiff == 0 }
94
+
95
+// Similar indicates whether two symbols are similar and may be represented
96
+// by using the Modified type. As a special case, we consider binary comparisons
97
+// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
98
+//
99
+// The exact ratio of NSame to NDiff to determine similarity may change.
100
+func (r Result) Similar() bool {
101
+	// Use NSame+1 to offset NSame so that binary comparisons are similar.
102
+	return r.NSame+1 >= r.NDiff
103
+}
104
+
105
+// Difference reports whether two lists of lengths nx and ny are equal
106
+// given the definition of equality provided as f.
107
+//
108
+// This function may return a edit-script, which is a sequence of operations
109
+// needed to convert one list into the other. If non-nil, the following
110
+// invariants for the edit-script are maintained:
111
+//	• eq == (es.Dist()==0)
112
+//	• nx == es.LenX()
113
+//	• ny == es.LenY()
114
+//
115
+// This algorithm is not guaranteed to be an optimal solution (i.e., one that
116
+// produces an edit-script with a minimal Levenshtein distance). This algorithm
117
+// favors performance over optimality. The exact output is not guaranteed to
118
+// be stable and may change over time.
119
+func Difference(nx, ny int, f EqualFunc) (eq bool, es EditScript) {
120
+	es = searchGraph(nx, ny, f)
121
+	st := es.stats()
122
+	eq = len(es) == st.NI
123
+	if !eq && st.NI < (nx+ny)/4 {
124
+		return eq, nil // Edit-script more distracting than helpful
125
+	}
126
+	return eq, es
127
+}
128
+
129
+func searchGraph(nx, ny int, f EqualFunc) EditScript {
130
+	// This algorithm is based on traversing what is known as an "edit-graph".
131
+	// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
132
+	// by Eugene W. Myers. Since D can be as large as N itself, this is
133
+	// effectively O(N^2). Unlike the algorithm from that paper, we are not
134
+	// interested in the optimal path, but at least some "decent" path.
135
+	//
136
+	// For example, let X and Y be lists of symbols:
137
+	//	X = [A B C A B B A]
138
+	//	Y = [C B A B A C]
139
+	//
140
+	// The edit-graph can be drawn as the following:
141
+	//	   A B C A B B A
142
+	//	  ┌─────────────┐
143
+	//	C │_|_|\|_|_|_|_│ 0
144
+	//	B │_|\|_|_|\|\|_│ 1
145
+	//	A │\|_|_|\|_|_|\│ 2
146
+	//	B │_|\|_|_|\|\|_│ 3
147
+	//	A │\|_|_|\|_|_|\│ 4
148
+	//	C │ | |\| | | | │ 5
149
+	//	  └─────────────┘ 6
150
+	//	   0 1 2 3 4 5 6 7
151
+	//
152
+	// List X is written along the horizontal axis, while list Y is written
153
+	// along the vertical axis. At any point on this grid, if the symbol in
154
+	// list X matches the corresponding symbol in list Y, then a '\' is drawn.
155
+	// The goal of any minimal edit-script algorithm is to find a path from the
156
+	// top-left corner to the bottom-right corner, while traveling through the
157
+	// fewest horizontal or vertical edges.
158
+	// A horizontal edge is equivalent to inserting a symbol from list X.
159
+	// A vertical edge is equivalent to inserting a symbol from list Y.
160
+	// A diagonal edge is equivalent to a matching symbol between both X and Y.
161
+
162
+	// Invariants:
163
+	//	• 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
164
+	//	• 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
165
+	//
166
+	// In general:
167
+	//	• fwdFrontier.X < revFrontier.X
168
+	//	• fwdFrontier.Y < revFrontier.Y
169
+	// Unless, it is time for the algorithm to terminate.
170
+	fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
171
+	revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
172
+	fwdFrontier := fwdPath.point // Forward search frontier
173
+	revFrontier := revPath.point // Reverse search frontier
174
+
175
+	// Search budget bounds the cost of searching for better paths.
176
+	// The longest sequence of non-matching symbols that can be tolerated is
177
+	// approximately the square-root of the search budget.
178
+	searchBudget := 4 * (nx + ny) // O(n)
179
+
180
+	// The algorithm below is a greedy, meet-in-the-middle algorithm for
181
+	// computing sub-optimal edit-scripts between two lists.
182
+	//
183
+	// The algorithm is approximately as follows:
184
+	//	• Searching for differences switches back-and-forth between
185
+	//	a search that starts at the beginning (the top-left corner), and
186
+	//	a search that starts at the end (the bottom-right corner). The goal of
187
+	//	the search is connect with the search from the opposite corner.
188
+	//	• As we search, we build a path in a greedy manner, where the first
189
+	//	match seen is added to the path (this is sub-optimal, but provides a
190
+	//	decent result in practice). When matches are found, we try the next pair
191
+	//	of symbols in the lists and follow all matches as far as possible.
192
+	//	• When searching for matches, we search along a diagonal going through
193
+	//	through the "frontier" point. If no matches are found, we advance the
194
+	//	frontier towards the opposite corner.
195
+	//	• This algorithm terminates when either the X coordinates or the
196
+	//	Y coordinates of the forward and reverse frontier points ever intersect.
197
+	//
198
+	// This algorithm is correct even if searching only in the forward direction
199
+	// or in the reverse direction. We do both because it is commonly observed
200
+	// that two lists commonly differ because elements were added to the front
201
+	// or end of the other list.
202
+	//
203
+	// Running the tests with the "debug" build tag prints a visualization of
204
+	// the algorithm running in real-time. This is educational for understanding
205
+	// how the algorithm works. See debug_enable.go.
206
+	f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
207
+	for {
208
+		// Forward search from the beginning.
209
+		if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
210
+			break
211
+		}
212
+		for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
213
+			// Search in a diagonal pattern for a match.
214
+			z := zigzag(i)
215
+			p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
216
+			switch {
217
+			case p.X >= revPath.X || p.Y < fwdPath.Y:
218
+				stop1 = true // Hit top-right corner
219
+			case p.Y >= revPath.Y || p.X < fwdPath.X:
220
+				stop2 = true // Hit bottom-left corner
221
+			case f(p.X, p.Y).Equal():
222
+				// Match found, so connect the path to this point.
223
+				fwdPath.connect(p, f)
224
+				fwdPath.append(Identity)
225
+				// Follow sequence of matches as far as possible.
226
+				for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
227
+					if !f(fwdPath.X, fwdPath.Y).Equal() {
228
+						break
229
+					}
230
+					fwdPath.append(Identity)
231
+				}
232
+				fwdFrontier = fwdPath.point
233
+				stop1, stop2 = true, true
234
+			default:
235
+				searchBudget-- // Match not found
236
+			}
237
+			debug.Update()
238
+		}
239
+		// Advance the frontier towards reverse point.
240
+		if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
241
+			fwdFrontier.X++
242
+		} else {
243
+			fwdFrontier.Y++
244
+		}
245
+
246
+		// Reverse search from the end.
247
+		if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
248
+			break
249
+		}
250
+		for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
251
+			// Search in a diagonal pattern for a match.
252
+			z := zigzag(i)
253
+			p := point{revFrontier.X - z, revFrontier.Y + z}
254
+			switch {
255
+			case fwdPath.X >= p.X || revPath.Y < p.Y:
256
+				stop1 = true // Hit bottom-left corner
257
+			case fwdPath.Y >= p.Y || revPath.X < p.X:
258
+				stop2 = true // Hit top-right corner
259
+			case f(p.X-1, p.Y-1).Equal():
260
+				// Match found, so connect the path to this point.
261
+				revPath.connect(p, f)
262
+				revPath.append(Identity)
263
+				// Follow sequence of matches as far as possible.
264
+				for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
265
+					if !f(revPath.X-1, revPath.Y-1).Equal() {
266
+						break
267
+					}
268
+					revPath.append(Identity)
269
+				}
270
+				revFrontier = revPath.point
271
+				stop1, stop2 = true, true
272
+			default:
273
+				searchBudget-- // Match not found
274
+			}
275
+			debug.Update()
276
+		}
277
+		// Advance the frontier towards forward point.
278
+		if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
279
+			revFrontier.X--
280
+		} else {
281
+			revFrontier.Y--
282
+		}
283
+	}
284
+
285
+	// Join the forward and reverse paths and then append the reverse path.
286
+	fwdPath.connect(revPath.point, f)
287
+	for i := len(revPath.es) - 1; i >= 0; i-- {
288
+		t := revPath.es[i]
289
+		revPath.es = revPath.es[:i]
290
+		fwdPath.append(t)
291
+	}
292
+	debug.Finish()
293
+	return fwdPath.es
294
+}
295
+
296
+type path struct {
297
+	dir   int // +1 if forward, -1 if reverse
298
+	point     // Leading point of the EditScript path
299
+	es    EditScript
300
+}
301
+
302
+// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
303
+// to the edit-script to connect p.point to dst.
304
+func (p *path) connect(dst point, f EqualFunc) {
305
+	if p.dir > 0 {
306
+		// Connect in forward direction.
307
+		for dst.X > p.X && dst.Y > p.Y {
308
+			switch r := f(p.X, p.Y); {
309
+			case r.Equal():
310
+				p.append(Identity)
311
+			case r.Similar():
312
+				p.append(Modified)
313
+			case dst.X-p.X >= dst.Y-p.Y:
314
+				p.append(UniqueX)
315
+			default:
316
+				p.append(UniqueY)
317
+			}
318
+		}
319
+		for dst.X > p.X {
320
+			p.append(UniqueX)
321
+		}
322
+		for dst.Y > p.Y {
323
+			p.append(UniqueY)
324
+		}
325
+	} else {
326
+		// Connect in reverse direction.
327
+		for p.X > dst.X && p.Y > dst.Y {
328
+			switch r := f(p.X-1, p.Y-1); {
329
+			case r.Equal():
330
+				p.append(Identity)
331
+			case r.Similar():
332
+				p.append(Modified)
333
+			case p.Y-dst.Y >= p.X-dst.X:
334
+				p.append(UniqueY)
335
+			default:
336
+				p.append(UniqueX)
337
+			}
338
+		}
339
+		for p.X > dst.X {
340
+			p.append(UniqueX)
341
+		}
342
+		for p.Y > dst.Y {
343
+			p.append(UniqueY)
344
+		}
345
+	}
346
+}
347
+
348
+func (p *path) append(t EditType) {
349
+	p.es = append(p.es, t)
350
+	switch t {
351
+	case Identity, Modified:
352
+		p.add(p.dir, p.dir)
353
+	case UniqueX:
354
+		p.add(p.dir, 0)
355
+	case UniqueY:
356
+		p.add(0, p.dir)
357
+	}
358
+	debug.Update()
359
+}
360
+
361
+type point struct{ X, Y int }
362
+
363
+func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
364
+
365
+// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
366
+//	[0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
367
+func zigzag(x int) int {
368
+	if x&1 != 0 {
369
+		x = ^x
370
+	}
371
+	return x >> 1
372
+}
0 373
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+// Package function identifies function types.
5
+package function
6
+
7
+import "reflect"
8
+
9
+type funcType int
10
+
11
+const (
12
+	_ funcType = iota
13
+
14
+	ttbFunc // func(T, T) bool
15
+	tibFunc // func(T, I) bool
16
+	trFunc  // func(T) R
17
+
18
+	Equal           = ttbFunc // func(T, T) bool
19
+	EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
20
+	Transformer     = trFunc  // func(T) R
21
+	ValueFilter     = ttbFunc // func(T, T) bool
22
+	Less            = ttbFunc // func(T, T) bool
23
+)
24
+
25
+var boolType = reflect.TypeOf(true)
26
+
27
+// IsType reports whether the reflect.Type is of the specified function type.
28
+func IsType(t reflect.Type, ft funcType) bool {
29
+	if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
30
+		return false
31
+	}
32
+	ni, no := t.NumIn(), t.NumOut()
33
+	switch ft {
34
+	case ttbFunc: // func(T, T) bool
35
+		if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
36
+			return true
37
+		}
38
+	case tibFunc: // func(T, I) bool
39
+		if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
40
+			return true
41
+		}
42
+	case trFunc: // func(T) R
43
+		if ni == 1 && no == 1 {
44
+			return true
45
+		}
46
+	}
47
+	return false
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,259 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+// Package value provides functionality for reflect.Value types.
5
+package value
6
+
7
+import (
8
+	"fmt"
9
+	"reflect"
10
+	"strings"
11
+	"unicode"
12
+	"unicode/utf8"
13
+)
14
+
15
+// formatFakePointers controls whether to substitute pointer addresses with nil.
16
+// This is used for deterministic testing.
17
+var formatFakePointers = false
18
+
19
+var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
20
+
21
+// Format formats the value v as a string.
22
+//
23
+// This is similar to fmt.Sprintf("%+v", v) except this:
24
+//	* Prints the type unless it can be elided
25
+//	* Avoids printing struct fields that are zero
26
+//	* Prints a nil-slice as being nil, not empty
27
+//	* Prints map entries in deterministic order
28
+func Format(v reflect.Value, useStringer bool) string {
29
+	return formatAny(v, formatConfig{useStringer, true, true, !formatFakePointers}, nil)
30
+}
31
+
32
+type formatConfig struct {
33
+	useStringer    bool // Should the String method be used if available?
34
+	printType      bool // Should we print the type before the value?
35
+	followPointers bool // Should we recursively follow pointers?
36
+	realPointers   bool // Should we print the real address of pointers?
37
+}
38
+
39
+func formatAny(v reflect.Value, conf formatConfig, visited map[uintptr]bool) string {
40
+	// TODO: Should this be a multi-line printout in certain situations?
41
+
42
+	if !v.IsValid() {
43
+		return "<non-existent>"
44
+	}
45
+	if conf.useStringer && v.Type().Implements(stringerIface) && v.CanInterface() {
46
+		if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
47
+			return "<nil>"
48
+		}
49
+		return fmt.Sprintf("%q", v.Interface().(fmt.Stringer).String())
50
+	}
51
+
52
+	switch v.Kind() {
53
+	case reflect.Bool:
54
+		return formatPrimitive(v.Type(), v.Bool(), conf)
55
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
56
+		return formatPrimitive(v.Type(), v.Int(), conf)
57
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
58
+		if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
59
+			// Unnamed uints are usually bytes or words, so use hexadecimal.
60
+			return formatPrimitive(v.Type(), formatHex(v.Uint()), conf)
61
+		}
62
+		return formatPrimitive(v.Type(), v.Uint(), conf)
63
+	case reflect.Float32, reflect.Float64:
64
+		return formatPrimitive(v.Type(), v.Float(), conf)
65
+	case reflect.Complex64, reflect.Complex128:
66
+		return formatPrimitive(v.Type(), v.Complex(), conf)
67
+	case reflect.String:
68
+		return formatPrimitive(v.Type(), fmt.Sprintf("%q", v), conf)
69
+	case reflect.UnsafePointer, reflect.Chan, reflect.Func:
70
+		return formatPointer(v, conf)
71
+	case reflect.Ptr:
72
+		if v.IsNil() {
73
+			if conf.printType {
74
+				return fmt.Sprintf("(%v)(nil)", v.Type())
75
+			}
76
+			return "<nil>"
77
+		}
78
+		if visited[v.Pointer()] || !conf.followPointers {
79
+			return formatPointer(v, conf)
80
+		}
81
+		visited = insertPointer(visited, v.Pointer())
82
+		return "&" + formatAny(v.Elem(), conf, visited)
83
+	case reflect.Interface:
84
+		if v.IsNil() {
85
+			if conf.printType {
86
+				return fmt.Sprintf("%v(nil)", v.Type())
87
+			}
88
+			return "<nil>"
89
+		}
90
+		return formatAny(v.Elem(), conf, visited)
91
+	case reflect.Slice:
92
+		if v.IsNil() {
93
+			if conf.printType {
94
+				return fmt.Sprintf("%v(nil)", v.Type())
95
+			}
96
+			return "<nil>"
97
+		}
98
+		if visited[v.Pointer()] {
99
+			return formatPointer(v, conf)
100
+		}
101
+		visited = insertPointer(visited, v.Pointer())
102
+		fallthrough
103
+	case reflect.Array:
104
+		var ss []string
105
+		subConf := conf
106
+		subConf.printType = v.Type().Elem().Kind() == reflect.Interface
107
+		for i := 0; i < v.Len(); i++ {
108
+			s := formatAny(v.Index(i), subConf, visited)
109
+			ss = append(ss, s)
110
+		}
111
+		s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
112
+		if conf.printType {
113
+			return v.Type().String() + s
114
+		}
115
+		return s
116
+	case reflect.Map:
117
+		if v.IsNil() {
118
+			if conf.printType {
119
+				return fmt.Sprintf("%v(nil)", v.Type())
120
+			}
121
+			return "<nil>"
122
+		}
123
+		if visited[v.Pointer()] {
124
+			return formatPointer(v, conf)
125
+		}
126
+		visited = insertPointer(visited, v.Pointer())
127
+
128
+		var ss []string
129
+		subConf := conf
130
+		subConf.printType = v.Type().Elem().Kind() == reflect.Interface
131
+		for _, k := range SortKeys(v.MapKeys()) {
132
+			sk := formatAny(k, formatConfig{realPointers: conf.realPointers}, visited)
133
+			sv := formatAny(v.MapIndex(k), subConf, visited)
134
+			ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
135
+		}
136
+		s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
137
+		if conf.printType {
138
+			return v.Type().String() + s
139
+		}
140
+		return s
141
+	case reflect.Struct:
142
+		var ss []string
143
+		subConf := conf
144
+		subConf.printType = true
145
+		for i := 0; i < v.NumField(); i++ {
146
+			vv := v.Field(i)
147
+			if isZero(vv) {
148
+				continue // Elide zero value fields
149
+			}
150
+			name := v.Type().Field(i).Name
151
+			subConf.useStringer = conf.useStringer && isExported(name)
152
+			s := formatAny(vv, subConf, visited)
153
+			ss = append(ss, fmt.Sprintf("%s: %s", name, s))
154
+		}
155
+		s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
156
+		if conf.printType {
157
+			return v.Type().String() + s
158
+		}
159
+		return s
160
+	default:
161
+		panic(fmt.Sprintf("%v kind not handled", v.Kind()))
162
+	}
163
+}
164
+
165
+func formatPrimitive(t reflect.Type, v interface{}, conf formatConfig) string {
166
+	if conf.printType && t.PkgPath() != "" {
167
+		return fmt.Sprintf("%v(%v)", t, v)
168
+	}
169
+	return fmt.Sprintf("%v", v)
170
+}
171
+
172
+func formatPointer(v reflect.Value, conf formatConfig) string {
173
+	p := v.Pointer()
174
+	if !conf.realPointers {
175
+		p = 0 // For deterministic printing purposes
176
+	}
177
+	s := formatHex(uint64(p))
178
+	if conf.printType {
179
+		return fmt.Sprintf("(%v)(%s)", v.Type(), s)
180
+	}
181
+	return s
182
+}
183
+
184
+func formatHex(u uint64) string {
185
+	var f string
186
+	switch {
187
+	case u <= 0xff:
188
+		f = "0x%02x"
189
+	case u <= 0xffff:
190
+		f = "0x%04x"
191
+	case u <= 0xffffff:
192
+		f = "0x%06x"
193
+	case u <= 0xffffffff:
194
+		f = "0x%08x"
195
+	case u <= 0xffffffffff:
196
+		f = "0x%010x"
197
+	case u <= 0xffffffffffff:
198
+		f = "0x%012x"
199
+	case u <= 0xffffffffffffff:
200
+		f = "0x%014x"
201
+	case u <= 0xffffffffffffffff:
202
+		f = "0x%016x"
203
+	}
204
+	return fmt.Sprintf(f, u)
205
+}
206
+
207
+// insertPointer insert p into m, allocating m if necessary.
208
+func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool {
209
+	if m == nil {
210
+		m = make(map[uintptr]bool)
211
+	}
212
+	m[p] = true
213
+	return m
214
+}
215
+
216
+// isZero reports whether v is the zero value.
217
+// This does not rely on Interface and so can be used on unexported fields.
218
+func isZero(v reflect.Value) bool {
219
+	switch v.Kind() {
220
+	case reflect.Bool:
221
+		return v.Bool() == false
222
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
223
+		return v.Int() == 0
224
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
225
+		return v.Uint() == 0
226
+	case reflect.Float32, reflect.Float64:
227
+		return v.Float() == 0
228
+	case reflect.Complex64, reflect.Complex128:
229
+		return v.Complex() == 0
230
+	case reflect.String:
231
+		return v.String() == ""
232
+	case reflect.UnsafePointer:
233
+		return v.Pointer() == 0
234
+	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
235
+		return v.IsNil()
236
+	case reflect.Array:
237
+		for i := 0; i < v.Len(); i++ {
238
+			if !isZero(v.Index(i)) {
239
+				return false
240
+			}
241
+		}
242
+		return true
243
+	case reflect.Struct:
244
+		for i := 0; i < v.NumField(); i++ {
245
+			if !isZero(v.Field(i)) {
246
+				return false
247
+			}
248
+		}
249
+		return true
250
+	}
251
+	return false
252
+}
253
+
254
+// isExported reports whether the identifier is exported.
255
+func isExported(id string) bool {
256
+	r, _ := utf8.DecodeRuneInString(id)
257
+	return unicode.IsUpper(r)
258
+}
0 259
new file mode 100644
... ...
@@ -0,0 +1,111 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+package value
5
+
6
+import (
7
+	"fmt"
8
+	"math"
9
+	"reflect"
10
+	"sort"
11
+)
12
+
13
+// SortKeys sorts a list of map keys, deduplicating keys if necessary.
14
+// The type of each value must be comparable.
15
+func SortKeys(vs []reflect.Value) []reflect.Value {
16
+	if len(vs) == 0 {
17
+		return vs
18
+	}
19
+
20
+	// Sort the map keys.
21
+	sort.Sort(valueSorter(vs))
22
+
23
+	// Deduplicate keys (fails for NaNs).
24
+	vs2 := vs[:1]
25
+	for _, v := range vs[1:] {
26
+		if v.Interface() != vs2[len(vs2)-1].Interface() {
27
+			vs2 = append(vs2, v)
28
+		}
29
+	}
30
+	return vs2
31
+}
32
+
33
+// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above.
34
+type valueSorter []reflect.Value
35
+
36
+func (vs valueSorter) Len() int           { return len(vs) }
37
+func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) }
38
+func (vs valueSorter) Swap(i, j int)      { vs[i], vs[j] = vs[j], vs[i] }
39
+
40
+// isLess is a generic function for sorting arbitrary map keys.
41
+// The inputs must be of the same type and must be comparable.
42
+func isLess(x, y reflect.Value) bool {
43
+	switch x.Type().Kind() {
44
+	case reflect.Bool:
45
+		return !x.Bool() && y.Bool()
46
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
47
+		return x.Int() < y.Int()
48
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
49
+		return x.Uint() < y.Uint()
50
+	case reflect.Float32, reflect.Float64:
51
+		fx, fy := x.Float(), y.Float()
52
+		return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
53
+	case reflect.Complex64, reflect.Complex128:
54
+		cx, cy := x.Complex(), y.Complex()
55
+		rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
56
+		if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
57
+			return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
58
+		}
59
+		return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
60
+	case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
61
+		return x.Pointer() < y.Pointer()
62
+	case reflect.String:
63
+		return x.String() < y.String()
64
+	case reflect.Array:
65
+		for i := 0; i < x.Len(); i++ {
66
+			if isLess(x.Index(i), y.Index(i)) {
67
+				return true
68
+			}
69
+			if isLess(y.Index(i), x.Index(i)) {
70
+				return false
71
+			}
72
+		}
73
+		return false
74
+	case reflect.Struct:
75
+		for i := 0; i < x.NumField(); i++ {
76
+			if isLess(x.Field(i), y.Field(i)) {
77
+				return true
78
+			}
79
+			if isLess(y.Field(i), x.Field(i)) {
80
+				return false
81
+			}
82
+		}
83
+		return false
84
+	case reflect.Interface:
85
+		vx, vy := x.Elem(), y.Elem()
86
+		if !vx.IsValid() || !vy.IsValid() {
87
+			return !vx.IsValid() && vy.IsValid()
88
+		}
89
+		tx, ty := vx.Type(), vy.Type()
90
+		if tx == ty {
91
+			return isLess(x.Elem(), y.Elem())
92
+		}
93
+		if tx.Kind() != ty.Kind() {
94
+			return vx.Kind() < vy.Kind()
95
+		}
96
+		if tx.String() != ty.String() {
97
+			return tx.String() < ty.String()
98
+		}
99
+		if tx.PkgPath() != ty.PkgPath() {
100
+			return tx.PkgPath() < ty.PkgPath()
101
+		}
102
+		// This can happen in rare situations, so we fallback to just comparing
103
+		// the unique pointer for a reflect.Type. This guarantees deterministic
104
+		// ordering within a program, but it is obviously not stable.
105
+		return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
106
+	default:
107
+		// Must be Func, Map, or Slice; which are not comparable.
108
+		panic(fmt.Sprintf("%T is not comparable", x.Type()))
109
+	}
110
+}
0 111
new file mode 100644
... ...
@@ -0,0 +1,446 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+package cmp
5
+
6
+import (
7
+	"fmt"
8
+	"reflect"
9
+	"runtime"
10
+	"strings"
11
+
12
+	"github.com/google/go-cmp/cmp/internal/function"
13
+)
14
+
15
+// Option configures for specific behavior of Equal and Diff. In particular,
16
+// the fundamental Option functions (Ignore, Transformer, and Comparer),
17
+// configure how equality is determined.
18
+//
19
+// The fundamental options may be composed with filters (FilterPath and
20
+// FilterValues) to control the scope over which they are applied.
21
+//
22
+// The cmp/cmpopts package provides helper functions for creating options that
23
+// may be used with Equal and Diff.
24
+type Option interface {
25
+	// filter applies all filters and returns the option that remains.
26
+	// Each option may only read s.curPath and call s.callTTBFunc.
27
+	//
28
+	// An Options is returned only if multiple comparers or transformers
29
+	// can apply simultaneously and will only contain values of those types
30
+	// or sub-Options containing values of those types.
31
+	filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption
32
+}
33
+
34
+// applicableOption represents the following types:
35
+//	Fundamental: ignore | invalid | *comparer | *transformer
36
+//	Grouping:    Options
37
+type applicableOption interface {
38
+	Option
39
+
40
+	// apply executes the option and reports whether the option was applied.
41
+	// Each option may mutate s.
42
+	apply(s *state, vx, vy reflect.Value) bool
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) bool {
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) bool                              { return true }
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) bool {
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
+// If T and R are the same type, an additional filter must be applied to
218
+// act as the base case to prevent an infinite recursion applying the same
219
+// transform to itself (see the SortedSlice example).
220
+//
221
+// The name is a user provided label that is used as the Transform.Name in the
222
+// transformation PathStep. If empty, an arbitrary name is used.
223
+func Transformer(name string, f interface{}) Option {
224
+	v := reflect.ValueOf(f)
225
+	if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
226
+		panic(fmt.Sprintf("invalid transformer function: %T", f))
227
+	}
228
+	if name == "" {
229
+		name = "λ" // Lambda-symbol as place-holder for anonymous transformer
230
+	}
231
+	if !isValid(name) {
232
+		panic(fmt.Sprintf("invalid name: %q", name))
233
+	}
234
+	tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
235
+	if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
236
+		tr.typ = ti
237
+	}
238
+	return tr
239
+}
240
+
241
+type transformer struct {
242
+	core
243
+	name string
244
+	typ  reflect.Type  // T
245
+	fnc  reflect.Value // func(T) R
246
+}
247
+
248
+func (tr *transformer) isFiltered() bool { return tr.typ != nil }
249
+
250
+func (tr *transformer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption {
251
+	if tr.typ == nil || t.AssignableTo(tr.typ) {
252
+		return tr
253
+	}
254
+	return nil
255
+}
256
+
257
+func (tr *transformer) apply(s *state, vx, vy reflect.Value) bool {
258
+	// Update path before calling the Transformer so that dynamic checks
259
+	// will use the updated path.
260
+	s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr})
261
+	defer s.curPath.pop()
262
+
263
+	vx = s.callTRFunc(tr.fnc, vx)
264
+	vy = s.callTRFunc(tr.fnc, vy)
265
+	s.compareAny(vx, vy)
266
+	return true
267
+}
268
+
269
+func (tr transformer) String() string {
270
+	return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer()))
271
+}
272
+
273
+// Comparer returns an Option that determines whether two values are equal
274
+// to each other.
275
+//
276
+// The comparer f must be a function "func(T, T) bool" and is implicitly
277
+// filtered to input values assignable to T. If T is an interface, it is
278
+// possible that f is called with two values of different concrete types that
279
+// both implement T.
280
+//
281
+// The equality function must be:
282
+//	• Symmetric: equal(x, y) == equal(y, x)
283
+//	• Deterministic: equal(x, y) == equal(x, y)
284
+//	• Pure: equal(x, y) does not modify x or y
285
+func Comparer(f interface{}) Option {
286
+	v := reflect.ValueOf(f)
287
+	if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
288
+		panic(fmt.Sprintf("invalid comparer function: %T", f))
289
+	}
290
+	cm := &comparer{fnc: v}
291
+	if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
292
+		cm.typ = ti
293
+	}
294
+	return cm
295
+}
296
+
297
+type comparer struct {
298
+	core
299
+	typ reflect.Type  // T
300
+	fnc reflect.Value // func(T, T) bool
301
+}
302
+
303
+func (cm *comparer) isFiltered() bool { return cm.typ != nil }
304
+
305
+func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption {
306
+	if cm.typ == nil || t.AssignableTo(cm.typ) {
307
+		return cm
308
+	}
309
+	return nil
310
+}
311
+
312
+func (cm *comparer) apply(s *state, vx, vy reflect.Value) bool {
313
+	eq := s.callTTBFunc(cm.fnc, vx, vy)
314
+	s.report(eq, vx, vy)
315
+	return true
316
+}
317
+
318
+func (cm comparer) String() string {
319
+	return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer()))
320
+}
321
+
322
+// AllowUnexported returns an Option that forcibly allows operations on
323
+// unexported fields in certain structs, which are specified by passing in a
324
+// value of each struct type.
325
+//
326
+// Users of this option must understand that comparing on unexported fields
327
+// from external packages is not safe since changes in the internal
328
+// implementation of some external package may cause the result of Equal
329
+// to unexpectedly change. However, it may be valid to use this option on types
330
+// defined in an internal package where the semantic meaning of an unexported
331
+// field is in the control of the user.
332
+//
333
+// For some cases, a custom Comparer should be used instead that defines
334
+// equality as a function of the public API of a type rather than the underlying
335
+// unexported implementation.
336
+//
337
+// For example, the reflect.Type documentation defines equality to be determined
338
+// by the == operator on the interface (essentially performing a shallow pointer
339
+// comparison) and most attempts to compare *regexp.Regexp types are interested
340
+// in only checking that the regular expression strings are equal.
341
+// Both of these are accomplished using Comparers:
342
+//
343
+//	Comparer(func(x, y reflect.Type) bool { return x == y })
344
+//	Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
345
+//
346
+// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
347
+// all unexported fields on specified struct types.
348
+func AllowUnexported(types ...interface{}) Option {
349
+	if !supportAllowUnexported {
350
+		panic("AllowUnexported is not supported on App Engine Classic or GopherJS")
351
+	}
352
+	m := make(map[reflect.Type]bool)
353
+	for _, typ := range types {
354
+		t := reflect.TypeOf(typ)
355
+		if t.Kind() != reflect.Struct {
356
+			panic(fmt.Sprintf("invalid struct type: %T", typ))
357
+		}
358
+		m[t] = true
359
+	}
360
+	return visibleStructs(m)
361
+}
362
+
363
+type visibleStructs map[reflect.Type]bool
364
+
365
+func (visibleStructs) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption {
366
+	panic("not implemented")
367
+}
368
+
369
+// reporter is an Option that configures how differences are reported.
370
+type reporter interface {
371
+	// TODO: Not exported yet.
372
+	//
373
+	// Perhaps add PushStep and PopStep and change Report to only accept
374
+	// a PathStep instead of the full-path? Adding a PushStep and PopStep makes
375
+	// it clear that we are traversing the value tree in a depth-first-search
376
+	// manner, which has an effect on how values are printed.
377
+
378
+	Option
379
+
380
+	// Report is called for every comparison made and will be provided with
381
+	// the two values being compared, the equality result, and the
382
+	// current path in the value tree. It is possible for x or y to be an
383
+	// invalid reflect.Value if one of the values is non-existent;
384
+	// which is possible with maps and slices.
385
+	Report(x, y reflect.Value, eq bool, p Path)
386
+}
387
+
388
+// normalizeOption normalizes the input options such that all Options groups
389
+// are flattened and groups with a single element are reduced to that element.
390
+// Only coreOptions and Options containing coreOptions are allowed.
391
+func normalizeOption(src Option) Option {
392
+	switch opts := flattenOptions(nil, Options{src}); len(opts) {
393
+	case 0:
394
+		return nil
395
+	case 1:
396
+		return opts[0]
397
+	default:
398
+		return opts
399
+	}
400
+}
401
+
402
+// flattenOptions copies all options in src to dst as a flat list.
403
+// Only coreOptions and Options containing coreOptions are allowed.
404
+func flattenOptions(dst, src Options) Options {
405
+	for _, opt := range src {
406
+		switch opt := opt.(type) {
407
+		case nil:
408
+			continue
409
+		case Options:
410
+			dst = flattenOptions(dst, opt)
411
+		case coreOption:
412
+			dst = append(dst, opt)
413
+		default:
414
+			panic(fmt.Sprintf("invalid option type: %T", opt))
415
+		}
416
+	}
417
+	return dst
418
+}
419
+
420
+// getFuncName returns a short function name from the pointer.
421
+// The string parsing logic works up until Go1.9.
422
+func getFuncName(p uintptr) string {
423
+	fnc := runtime.FuncForPC(p)
424
+	if fnc == nil {
425
+		return "<unknown>"
426
+	}
427
+	name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
428
+	if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
429
+		// Strip the package name from method name.
430
+		name = strings.TrimSuffix(name, ")-fm")
431
+		name = strings.TrimSuffix(name, ")·fm")
432
+		if i := strings.LastIndexByte(name, '('); i >= 0 {
433
+			methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
434
+			if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
435
+				methodName = methodName[j+1:] // E.g., "myfunc"
436
+			}
437
+			name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
438
+		}
439
+	}
440
+	if i := strings.LastIndexByte(name, '/'); i >= 0 {
441
+		// Strip the package name.
442
+		name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
443
+	}
444
+	return name
445
+}
0 446
new file mode 100644
... ...
@@ -0,0 +1,293 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+package cmp
5
+
6
+import (
7
+	"fmt"
8
+	"reflect"
9
+	"strings"
10
+	"unicode"
11
+	"unicode/utf8"
12
+)
13
+
14
+type (
15
+	// Path is a list of PathSteps describing the sequence of operations to get
16
+	// from some root type to the current position in the value tree.
17
+	// The first Path element is always an operation-less PathStep that exists
18
+	// simply to identify the initial type.
19
+	//
20
+	// When traversing structs with embedded structs, the embedded struct will
21
+	// always be accessed as a field before traversing the fields of the
22
+	// embedded struct themselves. That is, an exported field from the
23
+	// embedded struct will never be accessed directly from the parent struct.
24
+	Path []PathStep
25
+
26
+	// PathStep is a union-type for specific operations to traverse
27
+	// a value's tree structure. Users of this package never need to implement
28
+	// these types as values of this type will be returned by this package.
29
+	PathStep interface {
30
+		String() string
31
+		Type() reflect.Type // Resulting type after performing the path step
32
+		isPathStep()
33
+	}
34
+
35
+	// SliceIndex is an index operation on a slice or array at some index Key.
36
+	SliceIndex interface {
37
+		PathStep
38
+		Key() int // May return -1 if in a split state
39
+
40
+		// SplitKeys returns the indexes for indexing into slices in the
41
+		// x and y values, respectively. These indexes may differ due to the
42
+		// insertion or removal of an element in one of the slices, causing
43
+		// all of the indexes to be shifted. If an index is -1, then that
44
+		// indicates that the element does not exist in the associated slice.
45
+		//
46
+		// Key is guaranteed to return -1 if and only if the indexes returned
47
+		// by SplitKeys are not the same. SplitKeys will never return -1 for
48
+		// both indexes.
49
+		SplitKeys() (x int, y int)
50
+
51
+		isSliceIndex()
52
+	}
53
+	// MapIndex is an index operation on a map at some index Key.
54
+	MapIndex interface {
55
+		PathStep
56
+		Key() reflect.Value
57
+		isMapIndex()
58
+	}
59
+	// TypeAssertion represents a type assertion on an interface.
60
+	TypeAssertion interface {
61
+		PathStep
62
+		isTypeAssertion()
63
+	}
64
+	// StructField represents a struct field access on a field called Name.
65
+	StructField interface {
66
+		PathStep
67
+		Name() string
68
+		Index() int
69
+		isStructField()
70
+	}
71
+	// Indirect represents pointer indirection on the parent type.
72
+	Indirect interface {
73
+		PathStep
74
+		isIndirect()
75
+	}
76
+	// Transform is a transformation from the parent type to the current type.
77
+	Transform interface {
78
+		PathStep
79
+		Name() string
80
+		Func() reflect.Value
81
+		isTransform()
82
+	}
83
+)
84
+
85
+func (pa *Path) push(s PathStep) {
86
+	*pa = append(*pa, s)
87
+}
88
+
89
+func (pa *Path) pop() {
90
+	*pa = (*pa)[:len(*pa)-1]
91
+}
92
+
93
+// Last returns the last PathStep in the Path.
94
+// If the path is empty, this returns a non-nil PathStep that reports a nil Type.
95
+func (pa Path) Last() PathStep {
96
+	if len(pa) > 0 {
97
+		return pa[len(pa)-1]
98
+	}
99
+	return pathStep{}
100
+}
101
+
102
+// String returns the simplified path to a node.
103
+// The simplified path only contains struct field accesses.
104
+//
105
+// For example:
106
+//	MyMap.MySlices.MyField
107
+func (pa Path) String() string {
108
+	var ss []string
109
+	for _, s := range pa {
110
+		if _, ok := s.(*structField); ok {
111
+			ss = append(ss, s.String())
112
+		}
113
+	}
114
+	return strings.TrimPrefix(strings.Join(ss, ""), ".")
115
+}
116
+
117
+// GoString returns the path to a specific node using Go syntax.
118
+//
119
+// For example:
120
+//	(*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
121
+func (pa Path) GoString() string {
122
+	var ssPre, ssPost []string
123
+	var numIndirect int
124
+	for i, s := range pa {
125
+		var nextStep PathStep
126
+		if i+1 < len(pa) {
127
+			nextStep = pa[i+1]
128
+		}
129
+		switch s := s.(type) {
130
+		case *indirect:
131
+			numIndirect++
132
+			pPre, pPost := "(", ")"
133
+			switch nextStep.(type) {
134
+			case *indirect:
135
+				continue // Next step is indirection, so let them batch up
136
+			case *structField:
137
+				numIndirect-- // Automatic indirection on struct fields
138
+			case nil:
139
+				pPre, pPost = "", "" // Last step; no need for parenthesis
140
+			}
141
+			if numIndirect > 0 {
142
+				ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))
143
+				ssPost = append(ssPost, pPost)
144
+			}
145
+			numIndirect = 0
146
+			continue
147
+		case *transform:
148
+			ssPre = append(ssPre, s.trans.name+"(")
149
+			ssPost = append(ssPost, ")")
150
+			continue
151
+		case *typeAssertion:
152
+			// Elide type assertions immediately following a transform to
153
+			// prevent overly verbose path printouts.
154
+			// Some transforms return interface{} because of Go's lack of
155
+			// generics, but typically take in and return the exact same
156
+			// concrete type. Other times, the transform creates an anonymous
157
+			// struct, which will be very verbose to print.
158
+			if _, ok := nextStep.(*transform); ok {
159
+				continue
160
+			}
161
+		}
162
+		ssPost = append(ssPost, s.String())
163
+	}
164
+	for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
165
+		ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
166
+	}
167
+	return strings.Join(ssPre, "") + strings.Join(ssPost, "")
168
+}
169
+
170
+type (
171
+	pathStep struct {
172
+		typ reflect.Type
173
+	}
174
+
175
+	sliceIndex struct {
176
+		pathStep
177
+		xkey, ykey int
178
+	}
179
+	mapIndex struct {
180
+		pathStep
181
+		key reflect.Value
182
+	}
183
+	typeAssertion struct {
184
+		pathStep
185
+	}
186
+	structField struct {
187
+		pathStep
188
+		name string
189
+		idx  int
190
+
191
+		// These fields are used for forcibly accessing an unexported field.
192
+		// pvx, pvy, and field are only valid if unexported is true.
193
+		unexported bool
194
+		force      bool                // Forcibly allow visibility
195
+		pvx, pvy   reflect.Value       // Parent values
196
+		field      reflect.StructField // Field information
197
+	}
198
+	indirect struct {
199
+		pathStep
200
+	}
201
+	transform struct {
202
+		pathStep
203
+		trans *transformer
204
+	}
205
+)
206
+
207
+func (ps pathStep) Type() reflect.Type { return ps.typ }
208
+func (ps pathStep) String() string {
209
+	if ps.typ == nil {
210
+		return "<nil>"
211
+	}
212
+	s := ps.typ.String()
213
+	if s == "" || strings.ContainsAny(s, "{}\n") {
214
+		return "root" // Type too simple or complex to print
215
+	}
216
+	return fmt.Sprintf("{%s}", s)
217
+}
218
+
219
+func (si sliceIndex) String() string {
220
+	switch {
221
+	case si.xkey == si.ykey:
222
+		return fmt.Sprintf("[%d]", si.xkey)
223
+	case si.ykey == -1:
224
+		// [5->?] means "I don't know where X[5] went"
225
+		return fmt.Sprintf("[%d->?]", si.xkey)
226
+	case si.xkey == -1:
227
+		// [?->3] means "I don't know where Y[3] came from"
228
+		return fmt.Sprintf("[?->%d]", si.ykey)
229
+	default:
230
+		// [5->3] means "X[5] moved to Y[3]"
231
+		return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
232
+	}
233
+}
234
+func (mi mapIndex) String() string      { return fmt.Sprintf("[%#v]", mi.key) }
235
+func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
236
+func (sf structField) String() string   { return fmt.Sprintf(".%s", sf.name) }
237
+func (in indirect) String() string      { return "*" }
238
+func (tf transform) String() string     { return fmt.Sprintf("%s()", tf.trans.name) }
239
+
240
+func (si sliceIndex) Key() int {
241
+	if si.xkey != si.ykey {
242
+		return -1
243
+	}
244
+	return si.xkey
245
+}
246
+func (si sliceIndex) SplitKeys() (x, y int) { return si.xkey, si.ykey }
247
+func (mi mapIndex) Key() reflect.Value      { return mi.key }
248
+func (sf structField) Name() string         { return sf.name }
249
+func (sf structField) Index() int           { return sf.idx }
250
+func (tf transform) Name() string           { return tf.trans.name }
251
+func (tf transform) Func() reflect.Value    { return tf.trans.fnc }
252
+
253
+func (pathStep) isPathStep()           {}
254
+func (sliceIndex) isSliceIndex()       {}
255
+func (mapIndex) isMapIndex()           {}
256
+func (typeAssertion) isTypeAssertion() {}
257
+func (structField) isStructField()     {}
258
+func (indirect) isIndirect()           {}
259
+func (transform) isTransform()         {}
260
+
261
+var (
262
+	_ SliceIndex    = sliceIndex{}
263
+	_ MapIndex      = mapIndex{}
264
+	_ TypeAssertion = typeAssertion{}
265
+	_ StructField   = structField{}
266
+	_ Indirect      = indirect{}
267
+	_ Transform     = transform{}
268
+
269
+	_ PathStep = sliceIndex{}
270
+	_ PathStep = mapIndex{}
271
+	_ PathStep = typeAssertion{}
272
+	_ PathStep = structField{}
273
+	_ PathStep = indirect{}
274
+	_ PathStep = transform{}
275
+)
276
+
277
+// isExported reports whether the identifier is exported.
278
+func isExported(id string) bool {
279
+	r, _ := utf8.DecodeRuneInString(id)
280
+	return unicode.IsUpper(r)
281
+}
282
+
283
+// isValid reports whether the identifier is valid.
284
+// Empty and underscore-only strings are not valid.
285
+func isValid(id string) bool {
286
+	ok := id != "" && id != "_"
287
+	for j, c := range id {
288
+		ok = ok && (j > 0 || !unicode.IsDigit(c))
289
+		ok = ok && (c == '_' || unicode.IsLetter(c) || unicode.IsDigit(c))
290
+	}
291
+	return ok
292
+}
0 293
new file mode 100644
... ...
@@ -0,0 +1,53 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+package cmp
5
+
6
+import (
7
+	"fmt"
8
+	"reflect"
9
+	"strings"
10
+
11
+	"github.com/google/go-cmp/cmp/internal/value"
12
+)
13
+
14
+type defaultReporter struct {
15
+	Option
16
+	diffs  []string // List of differences, possibly truncated
17
+	ndiffs int      // Total number of differences
18
+	nbytes int      // Number of bytes in diffs
19
+	nlines int      // Number of lines in diffs
20
+}
21
+
22
+var _ reporter = (*defaultReporter)(nil)
23
+
24
+func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) {
25
+	if eq {
26
+		return // Ignore equal results
27
+	}
28
+	const maxBytes = 4096
29
+	const maxLines = 256
30
+	r.ndiffs++
31
+	if r.nbytes < maxBytes && r.nlines < maxLines {
32
+		sx := value.Format(x, true)
33
+		sy := value.Format(y, true)
34
+		if sx == sy {
35
+			// Stringer is not helpful, so rely on more exact formatting.
36
+			sx = value.Format(x, false)
37
+			sy = value.Format(y, false)
38
+		}
39
+		s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
40
+		r.diffs = append(r.diffs, s)
41
+		r.nbytes += len(s)
42
+		r.nlines += strings.Count(s, "\n")
43
+	}
44
+}
45
+
46
+func (r *defaultReporter) String() string {
47
+	s := strings.Join(r.diffs, "")
48
+	if r.ndiffs == len(r.diffs) {
49
+		return s
50
+	}
51
+	return fmt.Sprintf("%s... %d more differences ...", s, len(r.diffs)-r.ndiffs)
52
+}
0 53
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+// +build appengine js
5
+
6
+package cmp
7
+
8
+import "reflect"
9
+
10
+const supportAllowUnexported = false
11
+
12
+func unsafeRetrieveField(reflect.Value, reflect.StructField) reflect.Value {
13
+	panic("unsafeRetrieveField is not implemented")
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+// Copyright 2017, The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE.md file.
3
+
4
+// +build !appengine,!js
5
+
6
+package cmp
7
+
8
+import (
9
+	"reflect"
10
+	"unsafe"
11
+)
12
+
13
+const supportAllowUnexported = true
14
+
15
+// unsafeRetrieveField uses unsafe to forcibly retrieve any field from a struct
16
+// such that the value has read-write permissions.
17
+//
18
+// The parent struct, v, must be addressable, while f must be a StructField
19
+// describing the field to retrieve.
20
+func unsafeRetrieveField(v reflect.Value, f reflect.StructField) reflect.Value {
21
+	return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem()
22
+}
... ...
@@ -10,24 +10,23 @@ patterns.
10 10
 
11 11
 ## Packages
12 12
 
13
+* [assert](http://godoc.org/github.com/gotestyourself/gotestyourself/assert) -
14
+  compare values and fail the test when the comparison fails
15
+* [env](http://godoc.org/github.com/gotestyourself/gotestyourself/env) -
16
+  test code that uses environment variables
13 17
 * [fs](http://godoc.org/github.com/gotestyourself/gotestyourself/fs) -
14 18
   create test files and directories
15 19
 * [golden](http://godoc.org/github.com/gotestyourself/gotestyourself/golden) -
16 20
   compare large multi-line strings
17
-* [testsum](http://godoc.org/github.com/gotestyourself/gotestyourself/testsum) -
18
-  a program to summarize `go test` output and test failures
19 21
 * [icmd](http://godoc.org/github.com/gotestyourself/gotestyourself/icmd) -
20 22
   execute binaries and test the output
21 23
 * [poll](http://godoc.org/github.com/gotestyourself/gotestyourself/poll) -
22 24
   test asynchronous code by polling until a desired state is reached
23 25
 * [skip](http://godoc.org/github.com/gotestyourself/gotestyourself/skip) -
24 26
   skip tests based on conditions
27
+* [testsum](http://godoc.org/github.com/gotestyourself/gotestyourself/testsum) -
28
+  a program to summarize `go test` output and test failures
25 29
 
26 30
 ## Related
27 31
 
28
-* [testify/assert](https://godoc.org/github.com/stretchr/testify/assert) and 
29
-  [testify/require](https://godoc.org/github.com/stretchr/testify/require) -
30
-  assertion libraries with common assertions
31
-* [golang/mock](https://github.com/golang/mock) - generate mocks for interfaces
32
-* [testify/suite](https://godoc.org/github.com/stretchr/testify/suite) - 
33
-  group test into suites to share common setup/teardown logic
32
+* [maxbrunsfeld/counterfeiter](https://github.com/maxbrunsfeld/counterfeiter) - generate fakes for interfaces
34 33
new file mode 100644
... ...
@@ -0,0 +1,186 @@
0
+/*Package assert provides assertions and checks for comparing expected values to
1
+actual values. When an assertion or check fails a helpful error message is
2
+printed.
3
+
4
+Assert and Check
5
+
6
+Assert() and Check() both accept a Comparison, and fail the test when the
7
+comparison fails. The one difference is that Assert() will end the test execution
8
+immediately (using t.FailNow()) whereas Check() will fail the test (using t.Fail()),
9
+return the value of the comparison, then proceed with the rest of the test case.
10
+
11
+Example Usage
12
+
13
+The example below shows assert used with some common types.
14
+
15
+
16
+	import (
17
+	    "testing"
18
+
19
+	    "github.com/gotestyourself/gotestyourself/assert"
20
+	    is "github.com/gotestyourself/gotestyourself/assert/cmp"
21
+	)
22
+
23
+	func TestEverything(t *testing.T) {
24
+	    // booleans
25
+	    assert.Assert(t, isOk)
26
+	    assert.Assert(t, !missing)
27
+
28
+	    // primitives
29
+	    assert.Equal(t, count, 1)
30
+	    assert.Equal(t, msg, "the message")
31
+	    assert.Assert(t, total != 10) // NotEqual
32
+
33
+	    // errors
34
+	    assert.NilError(t, closer.Close())
35
+	    assert.Assert(t, is.Error(err, "the exact error message"))
36
+	    assert.Assert(t, is.ErrorContains(err, "includes this"))
37
+
38
+	    // complex types
39
+	    assert.Assert(t, is.Len(items, 3))
40
+	    assert.Assert(t, len(sequence) != 0) // NotEmpty
41
+	    assert.Assert(t, is.Contains(mapping, "key"))
42
+	    assert.Assert(t, is.Compare(result, myStruct{Name: "title"}))
43
+
44
+	    // pointers and interface
45
+	    assert.Assert(t, is.Nil(ref))
46
+	    assert.Assert(t, ref != nil) // NotNil
47
+	}
48
+
49
+Comparisons
50
+
51
+https://godoc.org/github.com/gotestyourself/gotestyourself/assert/cmp provides
52
+many common comparisons. Additional comparisons can be written to compare
53
+values in other ways.
54
+
55
+Below is an example of a custom comparison using a regex pattern:
56
+
57
+	func RegexP(value string, pattern string) func() (bool, string) {
58
+	    return func() (bool, string) {
59
+	        re := regexp.MustCompile(pattern)
60
+	        msg := fmt.Sprintf("%q did not match pattern %q", value, pattern)
61
+	        return re.MatchString(value), msg
62
+	    }
63
+	}
64
+
65
+*/
66
+package assert
67
+
68
+import (
69
+	"fmt"
70
+
71
+	"github.com/gotestyourself/gotestyourself/assert/cmp"
72
+	"github.com/gotestyourself/gotestyourself/internal/format"
73
+	"github.com/gotestyourself/gotestyourself/internal/source"
74
+)
75
+
76
+// BoolOrComparison can be a bool, or Comparison. Other types will panic.
77
+type BoolOrComparison interface{}
78
+
79
+// Comparison is a function which compares values and returns true if the actual
80
+// value matches the expected value. If the values do not match it returns a message
81
+// with details about why it failed.
82
+//
83
+// https://godoc.org/github.com/gotestyourself/gotestyourself/assert/cmp
84
+// provides many general purpose Comparisons.
85
+type Comparison func() (success bool, message string)
86
+
87
+// TestingT is the subset of testing.T used by the assert package.
88
+type TestingT interface {
89
+	FailNow()
90
+	Fail()
91
+	Log(args ...interface{})
92
+}
93
+
94
+type helperT interface {
95
+	Helper()
96
+}
97
+
98
+// stackIndex = Assert()/Check(), assert()
99
+const stackIndex = 2
100
+const comparisonArgPos = 1
101
+
102
+const failureMessage = "assertion failed: "
103
+
104
+func assert(
105
+	t TestingT,
106
+	failer func(),
107
+	comparison BoolOrComparison,
108
+	msgAndArgs ...interface{},
109
+) bool {
110
+	if ht, ok := t.(helperT); ok {
111
+		ht.Helper()
112
+	}
113
+	switch check := comparison.(type) {
114
+	case bool:
115
+		if check {
116
+			return true
117
+		}
118
+		source, err := source.GetCondition(stackIndex, comparisonArgPos)
119
+		if err != nil {
120
+			t.Log(err.Error())
121
+		}
122
+
123
+		msg := " is false"
124
+		t.Log(format.WithCustomMessage(failureMessage+source+msg, msgAndArgs...))
125
+		failer()
126
+		return false
127
+
128
+	case Comparison:
129
+		return runCompareFunc(failer, t, check, msgAndArgs...)
130
+
131
+	case func() (success bool, message string):
132
+		return runCompareFunc(failer, t, check, msgAndArgs...)
133
+
134
+	default:
135
+		panic(fmt.Sprintf("comparison arg must be bool or Comparison, not %T", comparison))
136
+	}
137
+}
138
+
139
+func runCompareFunc(failer func(), t TestingT, f Comparison, msgAndArgs ...interface{}) bool {
140
+	if ht, ok := t.(helperT); ok {
141
+		ht.Helper()
142
+	}
143
+	if success, message := f(); !success {
144
+		t.Log(format.WithCustomMessage(failureMessage+message, msgAndArgs...))
145
+		failer()
146
+		return false
147
+	}
148
+	return true
149
+}
150
+
151
+// Assert performs a comparison, marks the test as having failed if the comparison
152
+// returns false, and stops execution immediately.
153
+func Assert(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) {
154
+	if ht, ok := t.(helperT); ok {
155
+		ht.Helper()
156
+	}
157
+	assert(t, t.FailNow, comparison, msgAndArgs...)
158
+}
159
+
160
+// Check performs a comparison and marks the test as having failed if the comparison
161
+// returns false. Returns the result of the comparison.
162
+func Check(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) bool {
163
+	if ht, ok := t.(helperT); ok {
164
+		ht.Helper()
165
+	}
166
+	return assert(t, t.Fail, comparison, msgAndArgs...)
167
+}
168
+
169
+// NilError fails the test immediately if the last arg is a non-nil error.
170
+// This is equivalent to Assert(t, cmp.NilError(err)).
171
+func NilError(t TestingT, err error, msgAndArgs ...interface{}) {
172
+	if ht, ok := t.(helperT); ok {
173
+		ht.Helper()
174
+	}
175
+	assert(t, t.FailNow, cmp.NilError(err), msgAndArgs...)
176
+}
177
+
178
+// Equal uses the == operator to assert two values are equal and fails the test
179
+// if they are not equal. This is equivalent to Assert(t, cmp.Equal(x, y)).
180
+func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) {
181
+	if ht, ok := t.(helperT); ok {
182
+		ht.Helper()
183
+	}
184
+	assert(t, t.FailNow, cmp.Equal(x, y), msgAndArgs...)
185
+}
0 186
new file mode 100644
... ...
@@ -0,0 +1,201 @@
0
+/*Package cmp provides Comparisons for Assert and Check*/
1
+package cmp
2
+
3
+import (
4
+	"fmt"
5
+	"reflect"
6
+	"strings"
7
+
8
+	"github.com/google/go-cmp/cmp"
9
+	"github.com/pmezard/go-difflib/difflib"
10
+)
11
+
12
+// Compare two complex values using https://godoc.org/github.com/google/go-cmp/cmp
13
+// and succeeds if the values are equal.
14
+//
15
+// The comparison can be customized using comparison Options.
16
+func Compare(x, y interface{}, opts ...cmp.Option) func() (bool, string) {
17
+	return func() (bool, string) {
18
+		diff := cmp.Diff(x, y, opts...)
19
+		return diff == "", "\n" + diff
20
+	}
21
+}
22
+
23
+// Equal succeeds if x == y.
24
+func Equal(x, y interface{}) func() (success bool, message string) {
25
+	return func() (bool, string) {
26
+		return x == y, fmt.Sprintf("%v (%T) != %v (%T)", x, x, y, y)
27
+	}
28
+}
29
+
30
+// Len succeeds if the sequence has the expected length.
31
+func Len(seq interface{}, expected int) func() (bool, string) {
32
+	return func() (success bool, message string) {
33
+		defer func() {
34
+			if e := recover(); e != nil {
35
+				success = false
36
+				message = fmt.Sprintf("type %T does not have a length", seq)
37
+			}
38
+		}()
39
+		value := reflect.ValueOf(seq)
40
+		length := value.Len()
41
+		if length == expected {
42
+			return true, ""
43
+		}
44
+		msg := fmt.Sprintf("expected %s (length %d) to have length %d", seq, length, expected)
45
+		return false, msg
46
+	}
47
+}
48
+
49
+// NilError succeeds if the last argument is a nil error.
50
+func NilError(arg interface{}, args ...interface{}) func() (bool, string) {
51
+	return func() (bool, string) {
52
+		msgFunc := func(value reflect.Value) string {
53
+			return fmt.Sprintf("error is not nil: %s", value.Interface().(error).Error())
54
+		}
55
+		if len(args) == 0 {
56
+			return isNil(arg, msgFunc)()
57
+		}
58
+		return isNil(args[len(args)-1], msgFunc)()
59
+	}
60
+}
61
+
62
+// Contains succeeds if item is in collection. Collection may be a string, map,
63
+// slice, or array.
64
+//
65
+// If collection is a string, item must also be a string, and is compared using
66
+// strings.Contains().
67
+// If collection is a Map, contains will succeed if item is a key in the map.
68
+// If collection is a slice or array, item is compared to each item in the
69
+// sequence using reflect.DeepEqual().
70
+func Contains(collection interface{}, item interface{}) func() (bool, string) {
71
+	return func() (bool, string) {
72
+		colValue := reflect.ValueOf(collection)
73
+		if !colValue.IsValid() {
74
+			return false, fmt.Sprintf("nil does not contain items")
75
+		}
76
+		msg := fmt.Sprintf("%v does not contain %v", collection, item)
77
+
78
+		itemValue := reflect.ValueOf(item)
79
+		switch colValue.Type().Kind() {
80
+		case reflect.String:
81
+			if itemValue.Type().Kind() != reflect.String {
82
+				return false, "string may only contain strings"
83
+			}
84
+			success := strings.Contains(colValue.String(), itemValue.String())
85
+			return success, fmt.Sprintf("string %q does not contain %q", collection, item)
86
+
87
+		case reflect.Map:
88
+			if itemValue.Type() != colValue.Type().Key() {
89
+				return false, fmt.Sprintf(
90
+					"%v can not contain a %v key", colValue.Type(), itemValue.Type())
91
+			}
92
+			index := colValue.MapIndex(itemValue)
93
+			return index.IsValid(), msg
94
+
95
+		case reflect.Slice, reflect.Array:
96
+			for i := 0; i < colValue.Len(); i++ {
97
+				if reflect.DeepEqual(colValue.Index(i).Interface(), item) {
98
+					return true, ""
99
+				}
100
+			}
101
+			return false, msg
102
+		default:
103
+			return false, fmt.Sprintf("type %T does not contain items", collection)
104
+		}
105
+	}
106
+}
107
+
108
+// Panics succeeds if f() panics.
109
+func Panics(f func()) func() (bool, string) {
110
+	return func() (success bool, message string) {
111
+		defer func() {
112
+			if err := recover(); err != nil {
113
+				success = true
114
+			}
115
+		}()
116
+		f()
117
+		return false, "did not panic"
118
+	}
119
+}
120
+
121
+// EqualMultiLine succeeds if the two strings are equal. If they are not equal
122
+// the failure message will be the difference between the two strings.
123
+func EqualMultiLine(x, y string) func() (bool, string) {
124
+	return func() (bool, string) {
125
+		if x == y {
126
+			return true, ""
127
+		}
128
+
129
+		diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
130
+			A:        difflib.SplitLines(x),
131
+			B:        difflib.SplitLines(y),
132
+			FromFile: "left",
133
+			ToFile:   "right",
134
+			Context:  3,
135
+		})
136
+		if err != nil {
137
+			return false, fmt.Sprintf("failed to produce diff: %s", err)
138
+		}
139
+		return false, "\n" + diff
140
+	}
141
+}
142
+
143
+// Error succeeds if err is a non-nil error, and the error message equals the
144
+// expected message.
145
+func Error(err error, message string) func() (bool, string) {
146
+	return func() (bool, string) {
147
+		switch {
148
+		case err == nil:
149
+			return false, "expected an error, got nil"
150
+		case err.Error() != message:
151
+			return false, fmt.Sprintf(
152
+				"expected error message %q, got %q", message, err.Error())
153
+		}
154
+		return true, ""
155
+	}
156
+}
157
+
158
+// ErrorContains succeeds if err is a non-nil error, and the error message contains
159
+// the expected substring.
160
+func ErrorContains(err error, substring string) func() (bool, string) {
161
+	return func() (bool, string) {
162
+		switch {
163
+		case err == nil:
164
+			return false, "expected an error, got nil"
165
+		case !strings.Contains(err.Error(), substring):
166
+			return false, fmt.Sprintf(
167
+				"expected error message to contain %q, got %q", substring, err.Error())
168
+		}
169
+		return true, ""
170
+	}
171
+}
172
+
173
+// Nil succeeds if obj is a nil interface, pointer, or function.
174
+//
175
+// Use NilError() for comparing errors. Use Len(obj, 0) for comparing slices,
176
+// maps, and channels.
177
+func Nil(obj interface{}) func() (bool, string) {
178
+	msgFunc := func(value reflect.Value) string {
179
+		return fmt.Sprintf("%v (type %s) is not nil", reflect.Indirect(value), value.Type())
180
+	}
181
+	return isNil(obj, msgFunc)
182
+}
183
+
184
+func isNil(obj interface{}, msgFunc func(reflect.Value) string) func() (bool, string) {
185
+	return func() (bool, string) {
186
+		if obj == nil {
187
+			return true, ""
188
+		}
189
+		value := reflect.ValueOf(obj)
190
+		kind := value.Type().Kind()
191
+		if kind >= reflect.Chan && kind <= reflect.Slice {
192
+			if value.IsNil() {
193
+				return true, ""
194
+			}
195
+			return false, msgFunc(value)
196
+		}
197
+
198
+		return false, fmt.Sprintf("%v (type %s) can not be nil", value, value.Type())
199
+	}
200
+}
... ...
@@ -8,29 +8,42 @@ import (
8 8
 	"os"
9 9
 	"path/filepath"
10 10
 
11
-	"github.com/stretchr/testify/require"
11
+	"github.com/gotestyourself/gotestyourself/assert"
12 12
 )
13 13
 
14 14
 // Path objects return their filesystem path. Both File and Dir implement Path.
15 15
 type Path interface {
16 16
 	Path() string
17
+	Remove()
17 18
 }
18 19
 
20
+var (
21
+	_ Path = &Dir{}
22
+	_ Path = &File{}
23
+)
24
+
19 25
 // File is a temporary file on the filesystem
20 26
 type File struct {
21 27
 	path string
22 28
 }
23 29
 
30
+type helperT interface {
31
+	Helper()
32
+}
33
+
24 34
 // NewFile creates a new file in a temporary directory using prefix as part of
25 35
 // the filename. The PathOps are applied to the before returning the File.
26
-func NewFile(t require.TestingT, prefix string, ops ...PathOp) *File {
36
+func NewFile(t assert.TestingT, prefix string, ops ...PathOp) *File {
37
+	if ht, ok := t.(helperT); ok {
38
+		ht.Helper()
39
+	}
27 40
 	tempfile, err := ioutil.TempFile("", prefix+"-")
28
-	require.NoError(t, err)
41
+	assert.NilError(t, err)
29 42
 	file := &File{path: tempfile.Name()}
30
-	require.NoError(t, tempfile.Close())
43
+	assert.NilError(t, tempfile.Close())
31 44
 
32 45
 	for _, op := range ops {
33
-		require.NoError(t, op(file))
46
+		assert.NilError(t, op(file))
34 47
 	}
35 48
 	return file
36 49
 }
... ...
@@ -53,13 +66,16 @@ type Dir struct {
53 53
 
54 54
 // NewDir returns a new temporary directory using prefix as part of the directory
55 55
 // name. The PathOps are applied before returning the Dir.
56
-func NewDir(t require.TestingT, prefix string, ops ...PathOp) *Dir {
56
+func NewDir(t assert.TestingT, prefix string, ops ...PathOp) *Dir {
57
+	if ht, ok := t.(helperT); ok {
58
+		ht.Helper()
59
+	}
57 60
 	path, err := ioutil.TempDir("", prefix+"-")
58
-	require.NoError(t, err)
61
+	assert.NilError(t, err)
59 62
 	dir := &Dir{path: path}
60 63
 
61 64
 	for _, op := range ops {
62
-		require.NoError(t, op(dir))
65
+		assert.NilError(t, op(dir))
63 66
 	}
64 67
 	return dir
65 68
 }
... ...
@@ -31,14 +31,17 @@ func AsUser(uid, gid int) PathOp {
31 31
 }
32 32
 
33 33
 // WithFile creates a file in the directory at path with content
34
-func WithFile(filename, content string) PathOp {
34
+func WithFile(filename, content string, ops ...PathOp) PathOp {
35 35
 	return func(path Path) error {
36
-		return createFile(path.Path(), filename, content)
36
+		fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename))
37
+		if err := createFile(fullpath, content); err != nil {
38
+			return err
39
+		}
40
+		return applyPathOps(&File{path: fullpath}, ops)
37 41
 	}
38 42
 }
39 43
 
40
-func createFile(dir, filename, content string) error {
41
-	fullpath := filepath.Join(dir, filepath.FromSlash(filename))
44
+func createFile(fullpath string, content string) error {
42 45
 	return ioutil.WriteFile(fullpath, []byte(content), 0644)
43 46
 }
44 47
 
... ...
@@ -46,7 +49,8 @@ func createFile(dir, filename, content string) error {
46 46
 func WithFiles(files map[string]string) PathOp {
47 47
 	return func(path Path) error {
48 48
 		for filename, content := range files {
49
-			if err := createFile(path.Path(), filename, content); err != nil {
49
+			fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename))
50
+			if err := createFile(fullpath, content); err != nil {
50 51
 				return err
51 52
 			}
52 53
 		}
... ...
@@ -61,6 +65,35 @@ func FromDir(source string) PathOp {
61 61
 	}
62 62
 }
63 63
 
64
+// WithDir creates a subdirectory in the directory at path. Additional PathOp
65
+// can be used to modify the subdirectory
66
+func WithDir(name string, ops ...PathOp) PathOp {
67
+	return func(path Path) error {
68
+		fullpath := filepath.Join(path.Path(), filepath.FromSlash(name))
69
+		err := os.MkdirAll(fullpath, 0755)
70
+		if err != nil {
71
+			return err
72
+		}
73
+		return applyPathOps(&Dir{path: fullpath}, ops)
74
+	}
75
+}
76
+
77
+func applyPathOps(path Path, ops []PathOp) error {
78
+	for _, op := range ops {
79
+		if err := op(path); err != nil {
80
+			return err
81
+		}
82
+	}
83
+	return nil
84
+}
85
+
86
+// WithMode sets the file mode on the directory or file at path
87
+func WithMode(mode os.FileMode) PathOp {
88
+	return func(path Path) error {
89
+		return os.Chmod(path.Path(), mode)
90
+	}
91
+}
92
+
64 93
 func copyDirectory(source, dest string) error {
65 94
 	entries, err := ioutil.ReadDir(source)
66 95
 	if err != nil {
... ...
@@ -7,8 +7,6 @@ import (
7 7
 	"fmt"
8 8
 	"io"
9 9
 	"os/exec"
10
-	"path/filepath"
11
-	"runtime"
12 10
 	"strings"
13 11
 	"sync"
14 12
 	"time"
... ...
@@ -18,8 +16,12 @@ type testingT interface {
18 18
 	Fatalf(string, ...interface{})
19 19
 }
20 20
 
21
+type helperT interface {
22
+	Helper()
23
+}
24
+
21 25
 // None is a token to inform Result.Assert that the output should be empty
22
-const None string = "[NOTHING]"
26
+const None = "[NOTHING]"
23 27
 
24 28
 type lockedBuffer struct {
25 29
 	m   sync.RWMutex
... ...
@@ -51,17 +53,16 @@ type Result struct {
51 51
 
52 52
 // Assert compares the Result against the Expected struct, and fails the test if
53 53
 // any of the expectations are not met.
54
+// TODO: deprecate and replace with assert.CompareFunc
54 55
 func (r *Result) Assert(t testingT, exp Expected) *Result {
56
+	if ht, ok := t.(helperT); ok {
57
+		ht.Helper()
58
+	}
55 59
 	err := r.Compare(exp)
56 60
 	if err == nil {
57 61
 		return r
58 62
 	}
59
-	_, file, line, ok := runtime.Caller(1)
60
-	if ok {
61
-		t.Fatalf("at %s:%d - %s\n", filepath.Base(file), line, err.Error())
62
-	} else {
63
-		t.Fatalf("(no file/line info) - %s", err.Error())
64
-	}
63
+	t.Fatalf(err.Error() + "\n")
65 64
 	return nil
66 65
 }
67 66
 
68 67
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package format
1
+
2
+import "fmt"
3
+
4
+// Message accepts a msgAndArgs varargs and formats it using fmt.Sprintf
5
+func Message(msgAndArgs ...interface{}) string {
6
+	switch len(msgAndArgs) {
7
+	case 0:
8
+		return ""
9
+	case 1:
10
+		return fmt.Sprintf("%v", msgAndArgs[0])
11
+	default:
12
+		return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
13
+	}
14
+}
15
+
16
+// WithCustomMessage accepts one or two messages and formats them appropriately
17
+func WithCustomMessage(source string, msgAndArgs ...interface{}) string {
18
+	custom := Message(msgAndArgs...)
19
+	switch {
20
+	case custom == "":
21
+		return source
22
+	case source == "":
23
+		return custom
24
+	}
25
+	return fmt.Sprintf("%s: %s", source, custom)
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,109 @@
0
+package source
1
+
2
+import (
3
+	"bytes"
4
+	"go/ast"
5
+	"go/format"
6
+	"go/parser"
7
+	"go/token"
8
+	"runtime"
9
+
10
+	"github.com/pkg/errors"
11
+)
12
+
13
+const baseStackIndex = 1
14
+
15
+// GetCondition returns the condition string by reading it from the file
16
+// identified in the callstack. In golang 1.9 the line number changed from
17
+// being the line where the statement ended to the line where the statement began.
18
+func GetCondition(stackIndex int, argPos int) (string, error) {
19
+	_, filename, lineNum, ok := runtime.Caller(baseStackIndex + stackIndex)
20
+	if !ok {
21
+		return "", errors.New("failed to get caller info")
22
+	}
23
+
24
+	node, err := getNodeAtLine(filename, lineNum)
25
+	if err != nil {
26
+		return "", err
27
+	}
28
+	return getArgSourceFromAST(node, argPos)
29
+}
30
+
31
+func getNodeAtLine(filename string, lineNum int) (ast.Node, error) {
32
+	fileset := token.NewFileSet()
33
+	astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors)
34
+	if err != nil {
35
+		return nil, errors.Wrapf(err, "failed to parse source file: %s", filename)
36
+	}
37
+
38
+	node := scanToLine(fileset, astFile, lineNum)
39
+	if node == nil {
40
+		return nil, errors.Wrapf(err,
41
+			"failed to find an expression on line %d in %s", lineNum, filename)
42
+	}
43
+	return node, nil
44
+}
45
+
46
+func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node {
47
+	v := &scanToLineVisitor{lineNum: lineNum, fileset: fileset}
48
+	ast.Walk(v, node)
49
+	return v.matchedNode
50
+}
51
+
52
+type scanToLineVisitor struct {
53
+	lineNum     int
54
+	matchedNode ast.Node
55
+	fileset     *token.FileSet
56
+}
57
+
58
+func (v *scanToLineVisitor) Visit(node ast.Node) ast.Visitor {
59
+	if node == nil || v.matchedNode != nil {
60
+		return nil
61
+	}
62
+
63
+	var position token.Position
64
+	switch {
65
+	case runtime.Version() < "go1.9":
66
+		position = v.fileset.Position(node.End())
67
+	default:
68
+		position = v.fileset.Position(node.Pos())
69
+	}
70
+
71
+	if position.Line == v.lineNum {
72
+		v.matchedNode = node
73
+		return nil
74
+	}
75
+	return v
76
+}
77
+
78
+func getArgSourceFromAST(node ast.Node, argPos int) (string, error) {
79
+	visitor := &callExprVisitor{}
80
+	ast.Walk(visitor, node)
81
+	if visitor.expr == nil {
82
+		return "", errors.Errorf("unexpected ast")
83
+	}
84
+
85
+	buf := new(bytes.Buffer)
86
+	err := format.Node(buf, token.NewFileSet(), visitor.expr.Args[argPos])
87
+	return buf.String(), err
88
+}
89
+
90
+type callExprVisitor struct {
91
+	expr *ast.CallExpr
92
+}
93
+
94
+func (v *callExprVisitor) Visit(node ast.Node) ast.Visitor {
95
+	switch typed := node.(type) {
96
+	case nil:
97
+		return nil
98
+	case *ast.IfStmt:
99
+		ast.Walk(v, typed.Cond)
100
+	case *ast.CallExpr:
101
+		v.expr = typed
102
+	}
103
+
104
+	if v.expr != nil {
105
+		return nil
106
+	}
107
+	return v
108
+}
... ...
@@ -19,6 +19,10 @@ type LogT interface {
19 19
 	Logf(format string, args ...interface{})
20 20
 }
21 21
 
22
+type helperT interface {
23
+	Helper()
24
+}
25
+
22 26
 // Settings are used to configure the behaviour of WaitOn
23 27
 type Settings struct {
24 28
 	// Timeout is the maximum time to wait for the condition. Defaults to 10s
... ...
@@ -101,6 +105,9 @@ func Error(err error) Result {
101 101
 // check returns a done Result. To fail a test and exit polling with an error
102 102
 // return a error result.
103 103
 func WaitOn(t TestingT, check func(t LogT) Result, pollOps ...SettingOp) {
104
+	if ht, ok := t.(helperT); ok {
105
+		ht.Helper()
106
+	}
104 107
 	config := defaultConfig()
105 108
 	for _, pollOp := range pollOps {
106 109
 		pollOp(config)
... ...
@@ -3,19 +3,14 @@
3 3
 package skip
4 4
 
5 5
 import (
6
-	"bytes"
7 6
 	"fmt"
8
-	"go/ast"
9
-	"go/format"
10
-	"go/parser"
11
-	"go/token"
12
-	"io/ioutil"
13 7
 	"path"
14 8
 	"reflect"
15 9
 	"runtime"
16 10
 	"strings"
17 11
 
18
-	"github.com/pkg/errors"
12
+	"github.com/gotestyourself/gotestyourself/internal/format"
13
+	"github.com/gotestyourself/gotestyourself/internal/source"
19 14
 )
20 15
 
21 16
 type skipT interface {
... ...
@@ -23,14 +18,29 @@ type skipT interface {
23 23
 	Log(args ...interface{})
24 24
 }
25 25
 
26
+type helperT interface {
27
+	Helper()
28
+}
29
+
30
+// BoolOrCheckFunc can be a bool or func() bool, other types will panic
31
+type BoolOrCheckFunc interface{}
32
+
26 33
 // If skips the test if the check function returns true. The skip message will
27 34
 // contain the name of the check function. Extra message text can be passed as a
28 35
 // format string with args
29
-func If(t skipT, check func() bool, msgAndArgs ...interface{}) {
30
-	if check() {
31
-		t.Skip(formatWithCustomMessage(
32
-			getFunctionName(check),
33
-			formatMessage(msgAndArgs...)))
36
+func If(t skipT, condition BoolOrCheckFunc, msgAndArgs ...interface{}) {
37
+	if ht, ok := t.(helperT); ok {
38
+		ht.Helper()
39
+	}
40
+	switch check := condition.(type) {
41
+	case bool:
42
+		ifCondition(t, check, msgAndArgs...)
43
+	case func() bool:
44
+		if check() {
45
+			t.Skip(format.WithCustomMessage(getFunctionName(check), msgAndArgs...))
46
+		}
47
+	default:
48
+		panic(fmt.Sprintf("invalid type for condition arg: %T", check))
34 49
 	}
35 50
 }
36 51
 
... ...
@@ -42,92 +52,30 @@ func getFunctionName(function func() bool) string {
42 42
 // IfCondition skips the test if the condition is true. The skip message will
43 43
 // contain the source of the expression passed as the condition. Extra message
44 44
 // text can be passed as a format string with args.
45
+//
46
+// Deprecated: Use If() which now accepts bool arguments
45 47
 func IfCondition(t skipT, condition bool, msgAndArgs ...interface{}) {
46
-	if !condition {
47
-		return
48
-	}
49
-	source, err := getConditionSource()
50
-	if err != nil {
51
-		t.Log(err.Error())
52
-		t.Skip(formatMessage(msgAndArgs...))
48
+	if ht, ok := t.(helperT); ok {
49
+		ht.Helper()
53 50
 	}
54
-	t.Skip(formatWithCustomMessage(source, formatMessage(msgAndArgs...)))
51
+	ifCondition(t, condition, msgAndArgs...)
55 52
 }
56 53
 
57
-func getConditionSource() (string, error) {
58
-	const callstackIndex = 3
59
-	lines, err := getSourceLine(callstackIndex)
60
-	if err != nil {
61
-		return "", err
54
+func ifCondition(t skipT, condition bool, msgAndArgs ...interface{}) {
55
+	if ht, ok := t.(helperT); ok {
56
+		ht.Helper()
62 57
 	}
63
-
64
-	for i := range lines {
65
-		source := strings.Join(lines[len(lines)-i-1:], "\n")
66
-		node, err := parser.ParseExpr(source)
67
-		if err == nil {
68
-			return getConditionArgFromAST(node)
69
-		}
70
-	}
71
-	return "", errors.Wrapf(err, "failed to parse source")
72
-}
73
-
74
-// maxContextLines is the maximum number of lines to scan for a complete
75
-// skip.If() statement
76
-const maxContextLines = 10
77
-
78
-// getSourceLines returns the source line which called skip.If() along with a
79
-// few preceding lines. To properly parse the AST a complete statement is
80
-// required, and that statement may be split across multiple lines, so include
81
-// up to maxContextLines.
82
-func getSourceLine(stackIndex int) ([]string, error) {
83
-	_, filename, line, ok := runtime.Caller(stackIndex)
84
-	if !ok {
85
-		return nil, errors.New("failed to get caller info")
58
+	if !condition {
59
+		return
86 60
 	}
87
-
88
-	raw, err := ioutil.ReadFile(filename)
61
+	const (
62
+		stackIndex = 2
63
+		argPos     = 1
64
+	)
65
+	source, err := source.GetCondition(stackIndex, argPos)
89 66
 	if err != nil {
90
-		return nil, errors.Wrapf(err, "failed to read source file: %s", filename)
91
-	}
92
-
93
-	lines := strings.Split(string(raw), "\n")
94
-	if len(lines) < line {
95
-		return nil, errors.Errorf("file %s does not have line %d", filename, line)
96
-	}
97
-	firstLine := line - maxContextLines
98
-	if firstLine < 0 {
99
-		firstLine = 0
100
-	}
101
-	return lines[firstLine:line], nil
102
-}
103
-
104
-func getConditionArgFromAST(node ast.Expr) (string, error) {
105
-	switch expr := node.(type) {
106
-	case *ast.CallExpr:
107
-		buf := new(bytes.Buffer)
108
-		err := format.Node(buf, token.NewFileSet(), expr.Args[1])
109
-		return buf.String(), err
110
-	}
111
-	return "", errors.New("unexpected ast")
112
-}
113
-
114
-func formatMessage(msgAndArgs ...interface{}) string {
115
-	switch len(msgAndArgs) {
116
-	case 0:
117
-		return ""
118
-	case 1:
119
-		return msgAndArgs[0].(string)
120
-	default:
121
-		return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
122
-	}
123
-}
124
-
125
-func formatWithCustomMessage(source, custom string) string {
126
-	switch {
127
-	case custom == "":
128
-		return source
129
-	case source == "":
130
-		return custom
67
+		t.Log(err.Error())
68
+		t.Skip(format.Message(msgAndArgs...))
131 69
 	}
132
-	return fmt.Sprintf("%s: %s", source, custom)
70
+	t.Skip(format.WithCustomMessage(source, msgAndArgs...))
133 71
 }