├── .github
└── workflows
│ └── test.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cmp
├── cmpopts
│ ├── equate.go
│ ├── example_test.go
│ ├── ignore.go
│ ├── sort.go
│ ├── struct_filter.go
│ ├── util_test.go
│ └── xform.go
├── compare.go
├── compare_test.go
├── example_reporter_test.go
├── example_test.go
├── export.go
├── internal
│ ├── diff
│ │ ├── debug_disable.go
│ │ ├── debug_enable.go
│ │ ├── diff.go
│ │ └── diff_test.go
│ ├── flags
│ │ └── flags.go
│ ├── function
│ │ ├── func.go
│ │ └── func_test.go
│ ├── testprotos
│ │ └── protos.go
│ ├── teststructs
│ │ ├── foo1
│ │ │ └── foo.go
│ │ ├── foo2
│ │ │ └── foo.go
│ │ ├── project1.go
│ │ ├── project2.go
│ │ ├── project3.go
│ │ ├── project4.go
│ │ └── structs.go
│ └── value
│ │ ├── name.go
│ │ ├── name_test.go
│ │ ├── pointer.go
│ │ ├── sort.go
│ │ └── sort_test.go
├── options.go
├── options_test.go
├── path.go
├── report.go
├── report_compare.go
├── report_references.go
├── report_reflect.go
├── report_slices.go
├── report_text.go
├── report_value.go
└── testdata
│ └── diffs
└── go.mod
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | name: Test
3 | permissions:
4 | contents: read
5 | jobs:
6 | test:
7 | strategy:
8 | matrix:
9 | go-version: [1.21.x]
10 | os: [ubuntu-latest, macos-latest]
11 | runs-on: ${{ matrix.os }}
12 | steps:
13 | - name: Install Go
14 | uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2.2.0
15 | with:
16 | go-version: ${{ matrix.go-version }}
17 | - name: Checkout code
18 | uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
19 | - name: Test
20 | run: go test -v -race ./...
21 | - name: Format
22 | if: matrix.go-version == '1.21.x'
23 | run: diff -u <(echo -n) <(gofmt -d .)
24 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Package for equality of Go values
2 |
3 | [][godev]
4 | [][actions]
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 explicitly
25 | compared using the `AllowUnexported` option.
26 |
27 | See the [documentation][godev] for more information.
28 |
29 | This is not an official Google product.
30 |
31 | [godev]: https://pkg.go.dev/github.com/google/go-cmp/cmp
32 | [actions]: https://github.com/google/go-cmp/actions
33 |
34 | ## Install
35 |
36 | ```
37 | go get -u github.com/google/go-cmp/cmp
38 | ```
39 |
40 | ## License
41 |
42 | BSD - See [LICENSE][license] file
43 |
44 | [license]: https://github.com/google/go-cmp/blob/master/LICENSE
45 |
--------------------------------------------------------------------------------
/cmp/cmpopts/equate.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package cmpopts provides common options for the cmp package.
6 | package cmpopts
7 |
8 | import (
9 | "errors"
10 | "fmt"
11 | "math"
12 | "reflect"
13 | "time"
14 |
15 | "github.com/google/go-cmp/cmp"
16 | )
17 |
18 | func equateAlways(_, _ interface{}) bool { return true }
19 |
20 | // EquateEmpty returns a [cmp.Comparer] option that determines all maps and slices
21 | // with a length of zero to be equal, regardless of whether they are nil.
22 | //
23 | // EquateEmpty can be used in conjunction with [SortSlices] and [SortMaps].
24 | func EquateEmpty() cmp.Option {
25 | return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways))
26 | }
27 |
28 | func isEmpty(x, y interface{}) bool {
29 | vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
30 | return (x != nil && y != nil && vx.Type() == vy.Type()) &&
31 | (vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
32 | (vx.Len() == 0 && vy.Len() == 0)
33 | }
34 |
35 | // EquateApprox returns a [cmp.Comparer] option that determines float32 or float64
36 | // values to be equal if they are within a relative fraction or absolute margin.
37 | // This option is not used when either x or y is NaN or infinite.
38 | //
39 | // The fraction determines that the difference of two values must be within the
40 | // smaller fraction of the two values, while the margin determines that the two
41 | // values must be within some absolute margin.
42 | // To express only a fraction or only a margin, use 0 for the other parameter.
43 | // The fraction and margin must be non-negative.
44 | //
45 | // The mathematical expression used is equivalent to:
46 | //
47 | // |x-y| ≤ max(fraction*min(|x|, |y|), margin)
48 | //
49 | // EquateApprox can be used in conjunction with [EquateNaNs].
50 | func EquateApprox(fraction, margin float64) cmp.Option {
51 | if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) {
52 | panic("margin or fraction must be a non-negative number")
53 | }
54 | a := approximator{fraction, margin}
55 | return cmp.Options{
56 | cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)),
57 | cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)),
58 | }
59 | }
60 |
61 | type approximator struct{ frac, marg float64 }
62 |
63 | func areRealF64s(x, y float64) bool {
64 | return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0)
65 | }
66 | func areRealF32s(x, y float32) bool {
67 | return areRealF64s(float64(x), float64(y))
68 | }
69 | func (a approximator) compareF64(x, y float64) bool {
70 | relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y))
71 | return math.Abs(x-y) <= math.Max(a.marg, relMarg)
72 | }
73 | func (a approximator) compareF32(x, y float32) bool {
74 | return a.compareF64(float64(x), float64(y))
75 | }
76 |
77 | // EquateNaNs returns a [cmp.Comparer] option that determines float32 and float64
78 | // NaN values to be equal.
79 | //
80 | // EquateNaNs can be used in conjunction with [EquateApprox].
81 | func EquateNaNs() cmp.Option {
82 | return cmp.Options{
83 | cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)),
84 | cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)),
85 | }
86 | }
87 |
88 | func areNaNsF64s(x, y float64) bool {
89 | return math.IsNaN(x) && math.IsNaN(y)
90 | }
91 | func areNaNsF32s(x, y float32) bool {
92 | return areNaNsF64s(float64(x), float64(y))
93 | }
94 |
95 | // EquateApproxTime returns a [cmp.Comparer] option that determines two non-zero
96 | // [time.Time] values to be equal if they are within some margin of one another.
97 | // If both times have a monotonic clock reading, then the monotonic time
98 | // difference will be used. The margin must be non-negative.
99 | func EquateApproxTime(margin time.Duration) cmp.Option {
100 | if margin < 0 {
101 | panic("margin must be a non-negative number")
102 | }
103 | a := timeApproximator{margin}
104 | return cmp.FilterValues(areNonZeroTimes, cmp.Comparer(a.compare))
105 | }
106 |
107 | func areNonZeroTimes(x, y time.Time) bool {
108 | return !x.IsZero() && !y.IsZero()
109 | }
110 |
111 | type timeApproximator struct {
112 | margin time.Duration
113 | }
114 |
115 | func (a timeApproximator) compare(x, y time.Time) bool {
116 | // Avoid subtracting times to avoid overflow when the
117 | // difference is larger than the largest representable duration.
118 | if x.After(y) {
119 | // Ensure x is always before y
120 | x, y = y, x
121 | }
122 | // We're within the margin if x+margin >= y.
123 | // Note: time.Time doesn't have AfterOrEqual method hence the negation.
124 | return !x.Add(a.margin).Before(y)
125 | }
126 |
127 | // AnyError is an error that matches any non-nil error.
128 | var AnyError anyError
129 |
130 | type anyError struct{}
131 |
132 | func (anyError) Error() string { return "any error" }
133 | func (anyError) Is(err error) bool { return err != nil }
134 |
135 | // EquateErrors returns a [cmp.Comparer] option that determines errors to be equal
136 | // if [errors.Is] reports them to match. The [AnyError] error can be used to
137 | // match any non-nil error.
138 | func EquateErrors() cmp.Option {
139 | return cmp.FilterValues(areConcreteErrors, cmp.Comparer(compareErrors))
140 | }
141 |
142 | // areConcreteErrors reports whether x and y are types that implement error.
143 | // The input types are deliberately of the interface{} type rather than the
144 | // error type so that we can handle situations where the current type is an
145 | // interface{}, but the underlying concrete types both happen to implement
146 | // the error interface.
147 | func areConcreteErrors(x, y interface{}) bool {
148 | _, ok1 := x.(error)
149 | _, ok2 := y.(error)
150 | return ok1 && ok2
151 | }
152 |
153 | func compareErrors(x, y interface{}) bool {
154 | xe := x.(error)
155 | ye := y.(error)
156 | return errors.Is(xe, ye) || errors.Is(ye, xe)
157 | }
158 |
159 | // EquateComparable returns a [cmp.Option] that determines equality
160 | // of comparable types by directly comparing them using the == operator in Go.
161 | // The types to compare are specified by passing a value of that type.
162 | // This option should only be used on types that are documented as being
163 | // safe for direct == comparison. For example, [net/netip.Addr] is documented
164 | // as being semantically safe to use with ==, while [time.Time] is documented
165 | // to discourage the use of == on time values.
166 | func EquateComparable(typs ...interface{}) cmp.Option {
167 | types := make(typesFilter)
168 | for _, typ := range typs {
169 | switch t := reflect.TypeOf(typ); {
170 | case !t.Comparable():
171 | panic(fmt.Sprintf("%T is not a comparable Go type", typ))
172 | case types[t]:
173 | panic(fmt.Sprintf("%T is already specified", typ))
174 | default:
175 | types[t] = true
176 | }
177 | }
178 | return cmp.FilterPath(types.filter, cmp.Comparer(equateAny))
179 | }
180 |
181 | type typesFilter map[reflect.Type]bool
182 |
183 | func (tf typesFilter) filter(p cmp.Path) bool { return tf[p.Last().Type()] }
184 |
185 | func equateAny(x, y interface{}) bool { return x == y }
186 |
--------------------------------------------------------------------------------
/cmp/cmpopts/example_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmpopts_test
6 |
7 | import (
8 | "fmt"
9 | "net"
10 | "time"
11 |
12 | "github.com/google/go-cmp/cmp"
13 | "github.com/google/go-cmp/cmp/cmpopts"
14 | "github.com/google/go-cmp/cmp/internal/flags"
15 | )
16 |
17 | func init() {
18 | flags.Deterministic = true
19 | }
20 |
21 | // Use IgnoreFields to ignore fields on a struct type when comparing
22 | // by providing a value of the type and the field names to ignore.
23 | // Typically, a zero value of the type is used (e.g., foo.MyStruct{}).
24 | func ExampleIgnoreFields_testing() {
25 | // Let got be the hypothetical value obtained from some logic under test
26 | // and want be the expected golden data.
27 | got, want := MakeGatewayInfo()
28 |
29 | // While the specified fields will be semantically ignored for the comparison,
30 | // the fields may be printed in the diff when displaying entire values
31 | // that are already determined to be different.
32 | if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(Client{}, "IPAddress")); diff != "" {
33 | t.Errorf("MakeGatewayInfo() mismatch (-want +got):\n%s", diff)
34 | }
35 |
36 | // Output:
37 | // MakeGatewayInfo() mismatch (-want +got):
38 | // cmpopts_test.Gateway{
39 | // SSID: "CoffeeShopWiFi",
40 | // - IPAddress: s"192.168.0.2",
41 | // + IPAddress: s"192.168.0.1",
42 | // NetMask: s"ffff0000",
43 | // Clients: []cmpopts_test.Client{
44 | // ... // 3 identical elements
45 | // {Hostname: "espresso", ...},
46 | // {Hostname: "latte", LastSeen: s"2009-11-10 23:00:23 +0000 UTC", ...},
47 | // + {
48 | // + Hostname: "americano",
49 | // + IPAddress: s"192.168.0.188",
50 | // + LastSeen: s"2009-11-10 23:03:05 +0000 UTC",
51 | // + },
52 | // },
53 | // }
54 | }
55 |
56 | type (
57 | Gateway struct {
58 | SSID string
59 | IPAddress net.IP
60 | NetMask net.IPMask
61 | Clients []Client
62 | }
63 | Client struct {
64 | Hostname string
65 | IPAddress net.IP
66 | LastSeen time.Time
67 | }
68 | )
69 |
70 | func MakeGatewayInfo() (x, y Gateway) {
71 | x = Gateway{
72 | SSID: "CoffeeShopWiFi",
73 | IPAddress: net.IPv4(192, 168, 0, 1),
74 | NetMask: net.IPv4Mask(255, 255, 0, 0),
75 | Clients: []Client{{
76 | Hostname: "ristretto",
77 | IPAddress: net.IPv4(192, 168, 0, 116),
78 | }, {
79 | Hostname: "arabica",
80 | IPAddress: net.IPv4(192, 168, 0, 104),
81 | LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC),
82 | }, {
83 | Hostname: "macchiato",
84 | IPAddress: net.IPv4(192, 168, 0, 153),
85 | LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC),
86 | }, {
87 | Hostname: "espresso",
88 | IPAddress: net.IPv4(192, 168, 0, 121),
89 | }, {
90 | Hostname: "latte",
91 | IPAddress: net.IPv4(192, 168, 0, 219),
92 | LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC),
93 | }, {
94 | Hostname: "americano",
95 | IPAddress: net.IPv4(192, 168, 0, 188),
96 | LastSeen: time.Date(2009, time.November, 10, 23, 3, 5, 0, time.UTC),
97 | }},
98 | }
99 | y = Gateway{
100 | SSID: "CoffeeShopWiFi",
101 | IPAddress: net.IPv4(192, 168, 0, 2),
102 | NetMask: net.IPv4Mask(255, 255, 0, 0),
103 | Clients: []Client{{
104 | Hostname: "ristretto",
105 | IPAddress: net.IPv4(192, 168, 0, 116),
106 | }, {
107 | Hostname: "arabica",
108 | IPAddress: net.IPv4(192, 168, 0, 104),
109 | LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC),
110 | }, {
111 | Hostname: "macchiato",
112 | IPAddress: net.IPv4(192, 168, 0, 153),
113 | LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC),
114 | }, {
115 | Hostname: "espresso",
116 | IPAddress: net.IPv4(192, 168, 0, 121),
117 | }, {
118 | Hostname: "latte",
119 | IPAddress: net.IPv4(192, 168, 0, 221),
120 | LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC),
121 | }},
122 | }
123 | return x, y
124 | }
125 |
126 | var t fakeT
127 |
128 | type fakeT struct{}
129 |
130 | func (t fakeT) Errorf(format string, args ...interface{}) { fmt.Printf(format+"\n", args...) }
131 |
--------------------------------------------------------------------------------
/cmp/cmpopts/ignore.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmpopts
6 |
7 | import (
8 | "fmt"
9 | "reflect"
10 | "unicode"
11 | "unicode/utf8"
12 |
13 | "github.com/google/go-cmp/cmp"
14 | "github.com/google/go-cmp/cmp/internal/function"
15 | )
16 |
17 | // IgnoreFields returns an [cmp.Option] that ignores fields of the
18 | // given names on a single struct type. It respects the names of exported fields
19 | // that are forwarded due to struct embedding.
20 | // The struct type is specified by passing in a value of that type.
21 | //
22 | // The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
23 | // specific sub-field that is embedded or nested within the parent struct.
24 | func IgnoreFields(typ interface{}, names ...string) cmp.Option {
25 | sf := newStructFilter(typ, names...)
26 | return cmp.FilterPath(sf.filter, cmp.Ignore())
27 | }
28 |
29 | // IgnoreTypes returns an [cmp.Option] that ignores all values assignable to
30 | // certain types, which are specified by passing in a value of each type.
31 | func IgnoreTypes(typs ...interface{}) cmp.Option {
32 | tf := newTypeFilter(typs...)
33 | return cmp.FilterPath(tf.filter, cmp.Ignore())
34 | }
35 |
36 | type typeFilter []reflect.Type
37 |
38 | func newTypeFilter(typs ...interface{}) (tf typeFilter) {
39 | for _, typ := range typs {
40 | t := reflect.TypeOf(typ)
41 | if t == nil {
42 | // This occurs if someone tries to pass in sync.Locker(nil)
43 | panic("cannot determine type; consider using IgnoreInterfaces")
44 | }
45 | tf = append(tf, t)
46 | }
47 | return tf
48 | }
49 | func (tf typeFilter) filter(p cmp.Path) bool {
50 | if len(p) < 1 {
51 | return false
52 | }
53 | t := p.Last().Type()
54 | for _, ti := range tf {
55 | if t.AssignableTo(ti) {
56 | return true
57 | }
58 | }
59 | return false
60 | }
61 |
62 | // IgnoreInterfaces returns an [cmp.Option] that ignores all values or references of
63 | // values assignable to certain interface types. These interfaces are specified
64 | // by passing in an anonymous struct with the interface types embedded in it.
65 | // For example, to ignore [sync.Locker], pass in struct{sync.Locker}{}.
66 | func IgnoreInterfaces(ifaces interface{}) cmp.Option {
67 | tf := newIfaceFilter(ifaces)
68 | return cmp.FilterPath(tf.filter, cmp.Ignore())
69 | }
70 |
71 | type ifaceFilter []reflect.Type
72 |
73 | func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) {
74 | t := reflect.TypeOf(ifaces)
75 | if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct {
76 | panic("input must be an anonymous struct")
77 | }
78 | for i := 0; i < t.NumField(); i++ {
79 | fi := t.Field(i)
80 | switch {
81 | case !fi.Anonymous:
82 | panic("struct cannot have named fields")
83 | case fi.Type.Kind() != reflect.Interface:
84 | panic("embedded field must be an interface type")
85 | case fi.Type.NumMethod() == 0:
86 | // This matches everything; why would you ever want this?
87 | panic("cannot ignore empty interface")
88 | default:
89 | tf = append(tf, fi.Type)
90 | }
91 | }
92 | return tf
93 | }
94 | func (tf ifaceFilter) filter(p cmp.Path) bool {
95 | if len(p) < 1 {
96 | return false
97 | }
98 | t := p.Last().Type()
99 | for _, ti := range tf {
100 | if t.AssignableTo(ti) {
101 | return true
102 | }
103 | if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) {
104 | return true
105 | }
106 | }
107 | return false
108 | }
109 |
110 | // IgnoreUnexported returns an [cmp.Option] that only ignores the immediate unexported
111 | // fields of a struct, including anonymous fields of unexported types.
112 | // In particular, unexported fields within the struct's exported fields
113 | // of struct types, including anonymous fields, will not be ignored unless the
114 | // type of the field itself is also passed to IgnoreUnexported.
115 | //
116 | // Avoid ignoring unexported fields of a type which you do not control (i.e. a
117 | // type from another repository), as changes to the implementation of such types
118 | // may change how the comparison behaves. Prefer a custom [cmp.Comparer] instead.
119 | func IgnoreUnexported(typs ...interface{}) cmp.Option {
120 | ux := newUnexportedFilter(typs...)
121 | return cmp.FilterPath(ux.filter, cmp.Ignore())
122 | }
123 |
124 | type unexportedFilter struct{ m map[reflect.Type]bool }
125 |
126 | func newUnexportedFilter(typs ...interface{}) unexportedFilter {
127 | ux := unexportedFilter{m: make(map[reflect.Type]bool)}
128 | for _, typ := range typs {
129 | t := reflect.TypeOf(typ)
130 | if t == nil || t.Kind() != reflect.Struct {
131 | panic(fmt.Sprintf("%T must be a non-pointer struct", typ))
132 | }
133 | ux.m[t] = true
134 | }
135 | return ux
136 | }
137 | func (xf unexportedFilter) filter(p cmp.Path) bool {
138 | sf, ok := p.Index(-1).(cmp.StructField)
139 | if !ok {
140 | return false
141 | }
142 | return xf.m[p.Index(-2).Type()] && !isExported(sf.Name())
143 | }
144 |
145 | // isExported reports whether the identifier is exported.
146 | func isExported(id string) bool {
147 | r, _ := utf8.DecodeRuneInString(id)
148 | return unicode.IsUpper(r)
149 | }
150 |
151 | // IgnoreSliceElements returns an [cmp.Option] that ignores elements of []V.
152 | // The discard function must be of the form "func(T) bool" which is used to
153 | // ignore slice elements of type V, where V is assignable to T.
154 | // Elements are ignored if the function reports true.
155 | func IgnoreSliceElements(discardFunc interface{}) cmp.Option {
156 | vf := reflect.ValueOf(discardFunc)
157 | if !function.IsType(vf.Type(), function.ValuePredicate) || vf.IsNil() {
158 | panic(fmt.Sprintf("invalid discard function: %T", discardFunc))
159 | }
160 | return cmp.FilterPath(func(p cmp.Path) bool {
161 | si, ok := p.Index(-1).(cmp.SliceIndex)
162 | if !ok {
163 | return false
164 | }
165 | if !si.Type().AssignableTo(vf.Type().In(0)) {
166 | return false
167 | }
168 | vx, vy := si.Values()
169 | if vx.IsValid() && vf.Call([]reflect.Value{vx})[0].Bool() {
170 | return true
171 | }
172 | if vy.IsValid() && vf.Call([]reflect.Value{vy})[0].Bool() {
173 | return true
174 | }
175 | return false
176 | }, cmp.Ignore())
177 | }
178 |
179 | // IgnoreMapEntries returns an [cmp.Option] that ignores entries of map[K]V.
180 | // The discard function must be of the form "func(T, R) bool" which is used to
181 | // ignore map entries of type K and V, where K and V are assignable to T and R.
182 | // Entries are ignored if the function reports true.
183 | func IgnoreMapEntries(discardFunc interface{}) cmp.Option {
184 | vf := reflect.ValueOf(discardFunc)
185 | if !function.IsType(vf.Type(), function.KeyValuePredicate) || vf.IsNil() {
186 | panic(fmt.Sprintf("invalid discard function: %T", discardFunc))
187 | }
188 | return cmp.FilterPath(func(p cmp.Path) bool {
189 | mi, ok := p.Index(-1).(cmp.MapIndex)
190 | if !ok {
191 | return false
192 | }
193 | if !mi.Key().Type().AssignableTo(vf.Type().In(0)) || !mi.Type().AssignableTo(vf.Type().In(1)) {
194 | return false
195 | }
196 | k := mi.Key()
197 | vx, vy := mi.Values()
198 | if vx.IsValid() && vf.Call([]reflect.Value{k, vx})[0].Bool() {
199 | return true
200 | }
201 | if vy.IsValid() && vf.Call([]reflect.Value{k, vy})[0].Bool() {
202 | return true
203 | }
204 | return false
205 | }, cmp.Ignore())
206 | }
207 |
--------------------------------------------------------------------------------
/cmp/cmpopts/sort.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmpopts
6 |
7 | import (
8 | "fmt"
9 | "reflect"
10 | "sort"
11 |
12 | "github.com/google/go-cmp/cmp"
13 | "github.com/google/go-cmp/cmp/internal/function"
14 | )
15 |
16 | // SortSlices returns a [cmp.Transformer] option that sorts all []V.
17 | // The lessOrCompareFunc function must be either
18 | // a less function of the form "func(T, T) bool" or
19 | // a compare function of the format "func(T, T) int"
20 | // which is used to sort any slice with element type V that is assignable to T.
21 | //
22 | // A less function must be:
23 | // - Deterministic: less(x, y) == less(x, y)
24 | // - Irreflexive: !less(x, x)
25 | // - Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
26 | //
27 | // A compare function must be:
28 | // - Deterministic: compare(x, y) == compare(x, y)
29 | // - Irreflexive: compare(x, x) == 0
30 | // - Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
31 | //
32 | // The function does not have to be "total". That is, if x != y, but
33 | // less or compare report inequality, their relative order is maintained.
34 | //
35 | // SortSlices can be used in conjunction with [EquateEmpty].
36 | func SortSlices(lessOrCompareFunc interface{}) cmp.Option {
37 | vf := reflect.ValueOf(lessOrCompareFunc)
38 | if (!function.IsType(vf.Type(), function.Less) && !function.IsType(vf.Type(), function.Compare)) || vf.IsNil() {
39 | panic(fmt.Sprintf("invalid less or compare function: %T", lessOrCompareFunc))
40 | }
41 | ss := sliceSorter{vf.Type().In(0), vf}
42 | return cmp.FilterValues(ss.filter, cmp.Transformer("cmpopts.SortSlices", ss.sort))
43 | }
44 |
45 | type sliceSorter struct {
46 | in reflect.Type // T
47 | fnc reflect.Value // func(T, T) bool
48 | }
49 |
50 | func (ss sliceSorter) filter(x, y interface{}) bool {
51 | vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
52 | if !(x != nil && y != nil && vx.Type() == vy.Type()) ||
53 | !(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) ||
54 | (vx.Len() <= 1 && vy.Len() <= 1) {
55 | return false
56 | }
57 | // Check whether the slices are already sorted to avoid an infinite
58 | // recursion cycle applying the same transform to itself.
59 | ok1 := sort.SliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) })
60 | ok2 := sort.SliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) })
61 | return !ok1 || !ok2
62 | }
63 | func (ss sliceSorter) sort(x interface{}) interface{} {
64 | src := reflect.ValueOf(x)
65 | dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len())
66 | for i := 0; i < src.Len(); i++ {
67 | dst.Index(i).Set(src.Index(i))
68 | }
69 | sort.SliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) })
70 | ss.checkSort(dst)
71 | return dst.Interface()
72 | }
73 | func (ss sliceSorter) checkSort(v reflect.Value) {
74 | start := -1 // Start of a sequence of equal elements.
75 | for i := 1; i < v.Len(); i++ {
76 | if ss.less(v, i-1, i) {
77 | // Check that first and last elements in v[start:i] are equal.
78 | if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) {
79 | panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i)))
80 | }
81 | start = -1
82 | } else if start == -1 {
83 | start = i
84 | }
85 | }
86 | }
87 | func (ss sliceSorter) less(v reflect.Value, i, j int) bool {
88 | vx, vy := v.Index(i), v.Index(j)
89 | vo := ss.fnc.Call([]reflect.Value{vx, vy})[0]
90 | if vo.Kind() == reflect.Bool {
91 | return vo.Bool()
92 | } else {
93 | return vo.Int() < 0
94 | }
95 | }
96 |
97 | // SortMaps returns a [cmp.Transformer] option that flattens map[K]V types to be
98 | // a sorted []struct{K, V}. The lessOrCompareFunc function must be either
99 | // a less function of the form "func(T, T) bool" or
100 | // a compare function of the format "func(T, T) int"
101 | // which is used to sort any map with key K that is assignable to T.
102 | //
103 | // Flattening the map into a slice has the property that [cmp.Equal] is able to
104 | // use [cmp.Comparer] options on K or the K.Equal method if it exists.
105 | //
106 | // A less function must be:
107 | // - Deterministic: less(x, y) == less(x, y)
108 | // - Irreflexive: !less(x, x)
109 | // - Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
110 | // - Total: if x != y, then either less(x, y) or less(y, x)
111 | //
112 | // A compare function must be:
113 | // - Deterministic: compare(x, y) == compare(x, y)
114 | // - Irreflexive: compare(x, x) == 0
115 | // - Transitive: if compare(x, y) < 0 and compare(y, z) < 0, then compare(x, z) < 0
116 | // - Total: if x != y, then compare(x, y) != 0
117 | //
118 | // SortMaps can be used in conjunction with [EquateEmpty].
119 | func SortMaps(lessOrCompareFunc interface{}) cmp.Option {
120 | vf := reflect.ValueOf(lessOrCompareFunc)
121 | if (!function.IsType(vf.Type(), function.Less) && !function.IsType(vf.Type(), function.Compare)) || vf.IsNil() {
122 | panic(fmt.Sprintf("invalid less or compare function: %T", lessOrCompareFunc))
123 | }
124 | ms := mapSorter{vf.Type().In(0), vf}
125 | return cmp.FilterValues(ms.filter, cmp.Transformer("cmpopts.SortMaps", ms.sort))
126 | }
127 |
128 | type mapSorter struct {
129 | in reflect.Type // T
130 | fnc reflect.Value // func(T, T) bool
131 | }
132 |
133 | func (ms mapSorter) filter(x, y interface{}) bool {
134 | vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
135 | return (x != nil && y != nil && vx.Type() == vy.Type()) &&
136 | (vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) &&
137 | (vx.Len() != 0 || vy.Len() != 0)
138 | }
139 | func (ms mapSorter) sort(x interface{}) interface{} {
140 | src := reflect.ValueOf(x)
141 | outType := reflect.StructOf([]reflect.StructField{
142 | {Name: "K", Type: src.Type().Key()},
143 | {Name: "V", Type: src.Type().Elem()},
144 | })
145 | dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len())
146 | for i, k := range src.MapKeys() {
147 | v := reflect.New(outType).Elem()
148 | v.Field(0).Set(k)
149 | v.Field(1).Set(src.MapIndex(k))
150 | dst.Index(i).Set(v)
151 | }
152 | sort.Slice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) })
153 | ms.checkSort(dst)
154 | return dst.Interface()
155 | }
156 | func (ms mapSorter) checkSort(v reflect.Value) {
157 | for i := 1; i < v.Len(); i++ {
158 | if !ms.less(v, i-1, i) {
159 | panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i)))
160 | }
161 | }
162 | }
163 | func (ms mapSorter) less(v reflect.Value, i, j int) bool {
164 | vx, vy := v.Index(i).Field(0), v.Index(j).Field(0)
165 | vo := ms.fnc.Call([]reflect.Value{vx, vy})[0]
166 | if vo.Kind() == reflect.Bool {
167 | return vo.Bool()
168 | } else {
169 | return vo.Int() < 0
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/cmp/cmpopts/struct_filter.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmpopts
6 |
7 | import (
8 | "fmt"
9 | "reflect"
10 | "strings"
11 |
12 | "github.com/google/go-cmp/cmp"
13 | )
14 |
15 | // filterField returns a new Option where opt is only evaluated on paths that
16 | // include a specific exported field on a single struct type.
17 | // The struct type is specified by passing in a value of that type.
18 | //
19 | // The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
20 | // specific sub-field that is embedded or nested within the parent struct.
21 | func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
22 | // TODO: This is currently unexported over concerns of how helper filters
23 | // can be composed together easily.
24 | // TODO: Add tests for FilterField.
25 |
26 | sf := newStructFilter(typ, name)
27 | return cmp.FilterPath(sf.filter, opt)
28 | }
29 |
30 | type structFilter struct {
31 | t reflect.Type // The root struct type to match on
32 | ft fieldTree // Tree of fields to match on
33 | }
34 |
35 | func newStructFilter(typ interface{}, names ...string) structFilter {
36 | // TODO: Perhaps allow * as a special identifier to allow ignoring any
37 | // number of path steps until the next field match?
38 | // This could be useful when a concrete struct gets transformed into
39 | // an anonymous struct where it is not possible to specify that by type,
40 | // but the transformer happens to provide guarantees about the names of
41 | // the transformed fields.
42 |
43 | t := reflect.TypeOf(typ)
44 | if t == nil || t.Kind() != reflect.Struct {
45 | panic(fmt.Sprintf("%T must be a non-pointer struct", typ))
46 | }
47 | var ft fieldTree
48 | for _, name := range names {
49 | cname, err := canonicalName(t, name)
50 | if err != nil {
51 | panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
52 | }
53 | ft.insert(cname)
54 | }
55 | return structFilter{t, ft}
56 | }
57 |
58 | func (sf structFilter) filter(p cmp.Path) bool {
59 | for i, ps := range p {
60 | if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
61 | return true
62 | }
63 | }
64 | return false
65 | }
66 |
67 | // fieldTree represents a set of dot-separated identifiers.
68 | //
69 | // For example, inserting the following selectors:
70 | //
71 | // Foo
72 | // Foo.Bar.Baz
73 | // Foo.Buzz
74 | // Nuka.Cola.Quantum
75 | //
76 | // Results in a tree of the form:
77 | //
78 | // {sub: {
79 | // "Foo": {ok: true, sub: {
80 | // "Bar": {sub: {
81 | // "Baz": {ok: true},
82 | // }},
83 | // "Buzz": {ok: true},
84 | // }},
85 | // "Nuka": {sub: {
86 | // "Cola": {sub: {
87 | // "Quantum": {ok: true},
88 | // }},
89 | // }},
90 | // }}
91 | type fieldTree struct {
92 | ok bool // Whether this is a specified node
93 | sub map[string]fieldTree // The sub-tree of fields under this node
94 | }
95 |
96 | // insert inserts a sequence of field accesses into the tree.
97 | func (ft *fieldTree) insert(cname []string) {
98 | if ft.sub == nil {
99 | ft.sub = make(map[string]fieldTree)
100 | }
101 | if len(cname) == 0 {
102 | ft.ok = true
103 | return
104 | }
105 | sub := ft.sub[cname[0]]
106 | sub.insert(cname[1:])
107 | ft.sub[cname[0]] = sub
108 | }
109 |
110 | // matchPrefix reports whether any selector in the fieldTree matches
111 | // the start of path p.
112 | func (ft fieldTree) matchPrefix(p cmp.Path) bool {
113 | for _, ps := range p {
114 | switch ps := ps.(type) {
115 | case cmp.StructField:
116 | ft = ft.sub[ps.Name()]
117 | if ft.ok {
118 | return true
119 | }
120 | if len(ft.sub) == 0 {
121 | return false
122 | }
123 | case cmp.Indirect:
124 | default:
125 | return false
126 | }
127 | }
128 | return false
129 | }
130 |
131 | // canonicalName returns a list of identifiers where any struct field access
132 | // through an embedded field is expanded to include the names of the embedded
133 | // types themselves.
134 | //
135 | // For example, suppose field "Foo" is not directly in the parent struct,
136 | // but actually from an embedded struct of type "Bar". Then, the canonical name
137 | // of "Foo" is actually "Bar.Foo".
138 | //
139 | // Suppose field "Foo" is not directly in the parent struct, but actually
140 | // a field in two different embedded structs of types "Bar" and "Baz".
141 | // Then the selector "Foo" causes a panic since it is ambiguous which one it
142 | // refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
143 | func canonicalName(t reflect.Type, sel string) ([]string, error) {
144 | var name string
145 | sel = strings.TrimPrefix(sel, ".")
146 | if sel == "" {
147 | return nil, fmt.Errorf("name must not be empty")
148 | }
149 | if i := strings.IndexByte(sel, '.'); i < 0 {
150 | name, sel = sel, ""
151 | } else {
152 | name, sel = sel[:i], sel[i:]
153 | }
154 |
155 | // Type must be a struct or pointer to struct.
156 | if t.Kind() == reflect.Ptr {
157 | t = t.Elem()
158 | }
159 | if t.Kind() != reflect.Struct {
160 | return nil, fmt.Errorf("%v must be a struct", t)
161 | }
162 |
163 | // Find the canonical name for this current field name.
164 | // If the field exists in an embedded struct, then it will be expanded.
165 | sf, _ := t.FieldByName(name)
166 | if !isExported(name) {
167 | // Avoid using reflect.Type.FieldByName for unexported fields due to
168 | // buggy behavior with regard to embeddeding and unexported fields.
169 | // See https://golang.org/issue/4876 for details.
170 | sf = reflect.StructField{}
171 | for i := 0; i < t.NumField() && sf.Name == ""; i++ {
172 | if t.Field(i).Name == name {
173 | sf = t.Field(i)
174 | }
175 | }
176 | }
177 | if sf.Name == "" {
178 | return []string{name}, fmt.Errorf("does not exist")
179 | }
180 | var ss []string
181 | for i := range sf.Index {
182 | ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
183 | }
184 | if sel == "" {
185 | return ss, nil
186 | }
187 | ssPost, err := canonicalName(sf.Type, sel)
188 | return append(ss, ssPost...), err
189 | }
190 |
--------------------------------------------------------------------------------
/cmp/cmpopts/xform.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmpopts
6 |
7 | import (
8 | "github.com/google/go-cmp/cmp"
9 | )
10 |
11 | type xformFilter struct{ xform cmp.Option }
12 |
13 | func (xf xformFilter) filter(p cmp.Path) bool {
14 | for _, ps := range p {
15 | if t, ok := ps.(cmp.Transform); ok && t.Option() == xf.xform {
16 | return false
17 | }
18 | }
19 | return true
20 | }
21 |
22 | // AcyclicTransformer returns a [cmp.Transformer] with a filter applied that ensures
23 | // that the transformer cannot be recursively applied upon its own output.
24 | //
25 | // An example use case is a transformer that splits a string by lines:
26 | //
27 | // AcyclicTransformer("SplitLines", func(s string) []string{
28 | // return strings.Split(s, "\n")
29 | // })
30 | //
31 | // Had this been an unfiltered [cmp.Transformer] instead, this would result in an
32 | // infinite cycle converting a string to []string to [][]string and so on.
33 | func AcyclicTransformer(name string, xformFunc interface{}) cmp.Option {
34 | xf := xformFilter{cmp.Transformer(name, xformFunc)}
35 | return cmp.FilterPath(xf.filter, xf.xform)
36 | }
37 |
--------------------------------------------------------------------------------
/cmp/example_reporter_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp_test
6 |
7 | import (
8 | "fmt"
9 | "strings"
10 |
11 | "github.com/google/go-cmp/cmp"
12 | )
13 |
14 | // DiffReporter is a simple custom reporter that only records differences
15 | // detected during comparison.
16 | type DiffReporter struct {
17 | path cmp.Path
18 | diffs []string
19 | }
20 |
21 | func (r *DiffReporter) PushStep(ps cmp.PathStep) {
22 | r.path = append(r.path, ps)
23 | }
24 |
25 | func (r *DiffReporter) Report(rs cmp.Result) {
26 | if !rs.Equal() {
27 | vx, vy := r.path.Last().Values()
28 | r.diffs = append(r.diffs, fmt.Sprintf("%#v:\n\t-: %+v\n\t+: %+v\n", r.path, vx, vy))
29 | }
30 | }
31 |
32 | func (r *DiffReporter) PopStep() {
33 | r.path = r.path[:len(r.path)-1]
34 | }
35 |
36 | func (r *DiffReporter) String() string {
37 | return strings.Join(r.diffs, "\n")
38 | }
39 |
40 | func ExampleReporter() {
41 | x, y := MakeGatewayInfo()
42 |
43 | var r DiffReporter
44 | cmp.Equal(x, y, cmp.Reporter(&r))
45 | fmt.Print(r.String())
46 |
47 | // Output:
48 | // {cmp_test.Gateway}.IPAddress:
49 | // -: 192.168.0.1
50 | // +: 192.168.0.2
51 | //
52 | // {cmp_test.Gateway}.Clients[4].IPAddress:
53 | // -: 192.168.0.219
54 | // +: 192.168.0.221
55 | //
56 | // {cmp_test.Gateway}.Clients[5->?]:
57 | // -: {Hostname:americano IPAddress:192.168.0.188 LastSeen:2009-11-10 23:03:05 +0000 UTC}
58 | // +:
59 | }
60 |
--------------------------------------------------------------------------------
/cmp/example_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp_test
6 |
7 | import (
8 | "fmt"
9 | "math"
10 | "net"
11 | "reflect"
12 | "sort"
13 | "strings"
14 | "time"
15 |
16 | "github.com/google/go-cmp/cmp"
17 | )
18 |
19 | // TODO: Re-write these examples in terms of how you actually use the
20 | // fundamental options and filters and not in terms of what cool things you can
21 | // do with them since that overlaps with cmp/cmpopts.
22 |
23 | // Use Diff to print out a human-readable report of differences for tests
24 | // comparing nested or structured data.
25 | func ExampleDiff_testing() {
26 | // Let got be the hypothetical value obtained from some logic under test
27 | // and want be the expected golden data.
28 | got, want := MakeGatewayInfo()
29 |
30 | if diff := cmp.Diff(want, got); diff != "" {
31 | t.Errorf("MakeGatewayInfo() mismatch (-want +got):\n%s", diff)
32 | }
33 |
34 | // Output:
35 | // MakeGatewayInfo() mismatch (-want +got):
36 | // cmp_test.Gateway{
37 | // SSID: "CoffeeShopWiFi",
38 | // - IPAddress: s"192.168.0.2",
39 | // + IPAddress: s"192.168.0.1",
40 | // NetMask: s"ffff0000",
41 | // Clients: []cmp_test.Client{
42 | // ... // 2 identical elements
43 | // {Hostname: "macchiato", IPAddress: s"192.168.0.153", LastSeen: s"2009-11-10 23:39:43 +0000 UTC"},
44 | // {Hostname: "espresso", IPAddress: s"192.168.0.121"},
45 | // {
46 | // Hostname: "latte",
47 | // - IPAddress: s"192.168.0.221",
48 | // + IPAddress: s"192.168.0.219",
49 | // LastSeen: s"2009-11-10 23:00:23 +0000 UTC",
50 | // },
51 | // + {
52 | // + Hostname: "americano",
53 | // + IPAddress: s"192.168.0.188",
54 | // + LastSeen: s"2009-11-10 23:03:05 +0000 UTC",
55 | // + },
56 | // },
57 | // }
58 | }
59 |
60 | // Approximate equality for floats can be handled by defining a custom
61 | // comparer on floats that determines two values to be equal if they are within
62 | // some range of each other.
63 | //
64 | // This example is for demonstrative purposes;
65 | // use [github.com/google/go-cmp/cmp/cmpopts.EquateApprox] instead.
66 | func ExampleOption_approximateFloats() {
67 | // This Comparer only operates on float64.
68 | // To handle float32s, either define a similar function for that type
69 | // or use a Transformer to convert float32s into float64s.
70 | opt := cmp.Comparer(func(x, y float64) bool {
71 | delta := math.Abs(x - y)
72 | mean := math.Abs(x+y) / 2.0
73 | return delta/mean < 0.00001
74 | })
75 |
76 | x := []float64{1.0, 1.1, 1.2, math.Pi}
77 | y := []float64{1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi
78 | z := []float64{1.0, 1.1, 1.2, 3.1415} // Diverges too far from Pi
79 |
80 | fmt.Println(cmp.Equal(x, y, opt))
81 | fmt.Println(cmp.Equal(y, z, opt))
82 | fmt.Println(cmp.Equal(z, x, opt))
83 |
84 | // Output:
85 | // true
86 | // false
87 | // false
88 | }
89 |
90 | // Normal floating-point arithmetic defines == to be false when comparing
91 | // NaN with itself. In certain cases, this is not the desired property.
92 | //
93 | // This example is for demonstrative purposes;
94 | // use [github.com/google/go-cmp/cmp/cmpopts.EquateNaNs] instead.
95 | func ExampleOption_equalNaNs() {
96 | // This Comparer only operates on float64.
97 | // To handle float32s, either define a similar function for that type
98 | // or use a Transformer to convert float32s into float64s.
99 | opt := cmp.Comparer(func(x, y float64) bool {
100 | return (math.IsNaN(x) && math.IsNaN(y)) || x == y
101 | })
102 |
103 | x := []float64{1.0, math.NaN(), math.E, 0.0}
104 | y := []float64{1.0, math.NaN(), math.E, 0.0}
105 | z := []float64{1.0, math.NaN(), math.Pi, 0.0} // Pi constant instead of E
106 |
107 | fmt.Println(cmp.Equal(x, y, opt))
108 | fmt.Println(cmp.Equal(y, z, opt))
109 | fmt.Println(cmp.Equal(z, x, opt))
110 |
111 | // Output:
112 | // true
113 | // false
114 | // false
115 | }
116 |
117 | // To have floating-point comparisons combine both properties of NaN being
118 | // equal to itself and also approximate equality of values, filters are needed
119 | // to restrict the scope of the comparison so that they are composable.
120 | //
121 | // This example is for demonstrative purposes;
122 | // use [github.com/google/go-cmp/cmp/cmpopts.EquateApprox] instead.
123 | func ExampleOption_equalNaNsAndApproximateFloats() {
124 | alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
125 |
126 | opts := cmp.Options{
127 | // This option declares that a float64 comparison is equal only if
128 | // both inputs are NaN.
129 | cmp.FilterValues(func(x, y float64) bool {
130 | return math.IsNaN(x) && math.IsNaN(y)
131 | }, alwaysEqual),
132 |
133 | // This option declares approximate equality on float64s only if
134 | // both inputs are not NaN.
135 | cmp.FilterValues(func(x, y float64) bool {
136 | return !math.IsNaN(x) && !math.IsNaN(y)
137 | }, cmp.Comparer(func(x, y float64) bool {
138 | delta := math.Abs(x - y)
139 | mean := math.Abs(x+y) / 2.0
140 | return delta/mean < 0.00001
141 | })),
142 | }
143 |
144 | x := []float64{math.NaN(), 1.0, 1.1, 1.2, math.Pi}
145 | y := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi
146 | z := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.1415} // Diverges too far from Pi
147 |
148 | fmt.Println(cmp.Equal(x, y, opts))
149 | fmt.Println(cmp.Equal(y, z, opts))
150 | fmt.Println(cmp.Equal(z, x, opts))
151 |
152 | // Output:
153 | // true
154 | // false
155 | // false
156 | }
157 |
158 | // Sometimes, an empty map or slice is considered equal to an allocated one
159 | // of zero length.
160 | //
161 | // This example is for demonstrative purposes;
162 | // use [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty] instead.
163 | func ExampleOption_equalEmpty() {
164 | alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
165 |
166 | // This option handles slices and maps of any type.
167 | opt := cmp.FilterValues(func(x, y interface{}) bool {
168 | vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
169 | return (vx.IsValid() && vy.IsValid() && vx.Type() == vy.Type()) &&
170 | (vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
171 | (vx.Len() == 0 && vy.Len() == 0)
172 | }, alwaysEqual)
173 |
174 | type S struct {
175 | A []int
176 | B map[string]bool
177 | }
178 | x := S{nil, make(map[string]bool, 100)}
179 | y := S{make([]int, 0, 200), nil}
180 | z := S{[]int{0}, nil} // []int has a single element (i.e., not empty)
181 |
182 | fmt.Println(cmp.Equal(x, y, opt))
183 | fmt.Println(cmp.Equal(y, z, opt))
184 | fmt.Println(cmp.Equal(z, x, opt))
185 |
186 | // Output:
187 | // true
188 | // false
189 | // false
190 | }
191 |
192 | // Two slices may be considered equal if they have the same elements,
193 | // regardless of the order that they appear in. Transformations can be used
194 | // to sort the slice.
195 | //
196 | // This example is for demonstrative purposes;
197 | // use [github.com/google/go-cmp/cmp/cmpopts.SortSlices] instead.
198 | func ExampleOption_sortedSlice() {
199 | // This Transformer sorts a []int.
200 | trans := cmp.Transformer("Sort", func(in []int) []int {
201 | out := append([]int(nil), in...) // Copy input to avoid mutating it
202 | sort.Ints(out)
203 | return out
204 | })
205 |
206 | x := struct{ Ints []int }{[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}
207 | y := struct{ Ints []int }{[]int{2, 8, 0, 9, 6, 1, 4, 7, 3, 5}}
208 | z := struct{ Ints []int }{[]int{0, 0, 1, 2, 3, 4, 5, 6, 7, 8}}
209 |
210 | fmt.Println(cmp.Equal(x, y, trans))
211 | fmt.Println(cmp.Equal(y, z, trans))
212 | fmt.Println(cmp.Equal(z, x, trans))
213 |
214 | // Output:
215 | // true
216 | // false
217 | // false
218 | }
219 |
220 | type otherString string
221 |
222 | func (x otherString) Equal(y otherString) bool {
223 | return strings.EqualFold(string(x), string(y))
224 | }
225 |
226 | // If the Equal method defined on a type is not suitable, the type can be
227 | // dynamically transformed to be stripped of the Equal method (or any method
228 | // for that matter).
229 | func ExampleOption_avoidEqualMethod() {
230 | // Suppose otherString.Equal performs a case-insensitive equality,
231 | // which is too loose for our needs.
232 | // We can avoid the methods of otherString by declaring a new type.
233 | type myString otherString
234 |
235 | // This transformer converts otherString to myString, allowing Equal to use
236 | // other Options to determine equality.
237 | trans := cmp.Transformer("", func(in otherString) myString {
238 | return myString(in)
239 | })
240 |
241 | x := []otherString{"foo", "bar", "baz"}
242 | y := []otherString{"fOO", "bAr", "Baz"} // Same as before, but with different case
243 |
244 | fmt.Println(cmp.Equal(x, y)) // Equal because of case-insensitivity
245 | fmt.Println(cmp.Equal(x, y, trans)) // Not equal because of more exact equality
246 |
247 | // Output:
248 | // true
249 | // false
250 | }
251 |
252 | func roundF64(z float64) float64 {
253 | if z < 0 {
254 | return math.Ceil(z - 0.5)
255 | }
256 | return math.Floor(z + 0.5)
257 | }
258 |
259 | // The complex numbers complex64 and complex128 can really just be decomposed
260 | // into a pair of float32 or float64 values. It would be convenient to be able
261 | // define only a single comparator on float64 and have float32, complex64, and
262 | // complex128 all be able to use that comparator. Transformations can be used
263 | // to handle this.
264 | func ExampleOption_transformComplex() {
265 | opts := []cmp.Option{
266 | // This transformer decomposes complex128 into a pair of float64s.
267 | cmp.Transformer("T1", func(in complex128) (out struct{ Real, Imag float64 }) {
268 | out.Real, out.Imag = real(in), imag(in)
269 | return out
270 | }),
271 | // This transformer converts complex64 to complex128 to allow the
272 | // above transform to take effect.
273 | cmp.Transformer("T2", func(in complex64) complex128 {
274 | return complex128(in)
275 | }),
276 | // This transformer converts float32 to float64.
277 | cmp.Transformer("T3", func(in float32) float64 {
278 | return float64(in)
279 | }),
280 | // This equality function compares float64s as rounded integers.
281 | cmp.Comparer(func(x, y float64) bool {
282 | return roundF64(x) == roundF64(y)
283 | }),
284 | }
285 |
286 | x := []interface{}{
287 | complex128(3.0), complex64(5.1 + 2.9i), float32(-1.2), float64(12.3),
288 | }
289 | y := []interface{}{
290 | complex128(3.1), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7),
291 | }
292 | z := []interface{}{
293 | complex128(3.8), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7),
294 | }
295 |
296 | fmt.Println(cmp.Equal(x, y, opts...))
297 | fmt.Println(cmp.Equal(y, z, opts...))
298 | fmt.Println(cmp.Equal(z, x, opts...))
299 |
300 | // Output:
301 | // true
302 | // false
303 | // false
304 | }
305 |
306 | type (
307 | Gateway struct {
308 | SSID string
309 | IPAddress net.IP
310 | NetMask net.IPMask
311 | Clients []Client
312 | }
313 | Client struct {
314 | Hostname string
315 | IPAddress net.IP
316 | LastSeen time.Time
317 | }
318 | )
319 |
320 | func MakeGatewayInfo() (x, y Gateway) {
321 | x = Gateway{
322 | SSID: "CoffeeShopWiFi",
323 | IPAddress: net.IPv4(192, 168, 0, 1),
324 | NetMask: net.IPv4Mask(255, 255, 0, 0),
325 | Clients: []Client{{
326 | Hostname: "ristretto",
327 | IPAddress: net.IPv4(192, 168, 0, 116),
328 | }, {
329 | Hostname: "arabica",
330 | IPAddress: net.IPv4(192, 168, 0, 104),
331 | LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC),
332 | }, {
333 | Hostname: "macchiato",
334 | IPAddress: net.IPv4(192, 168, 0, 153),
335 | LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC),
336 | }, {
337 | Hostname: "espresso",
338 | IPAddress: net.IPv4(192, 168, 0, 121),
339 | }, {
340 | Hostname: "latte",
341 | IPAddress: net.IPv4(192, 168, 0, 219),
342 | LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC),
343 | }, {
344 | Hostname: "americano",
345 | IPAddress: net.IPv4(192, 168, 0, 188),
346 | LastSeen: time.Date(2009, time.November, 10, 23, 3, 5, 0, time.UTC),
347 | }},
348 | }
349 | y = Gateway{
350 | SSID: "CoffeeShopWiFi",
351 | IPAddress: net.IPv4(192, 168, 0, 2),
352 | NetMask: net.IPv4Mask(255, 255, 0, 0),
353 | Clients: []Client{{
354 | Hostname: "ristretto",
355 | IPAddress: net.IPv4(192, 168, 0, 116),
356 | }, {
357 | Hostname: "arabica",
358 | IPAddress: net.IPv4(192, 168, 0, 104),
359 | LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC),
360 | }, {
361 | Hostname: "macchiato",
362 | IPAddress: net.IPv4(192, 168, 0, 153),
363 | LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC),
364 | }, {
365 | Hostname: "espresso",
366 | IPAddress: net.IPv4(192, 168, 0, 121),
367 | }, {
368 | Hostname: "latte",
369 | IPAddress: net.IPv4(192, 168, 0, 221),
370 | LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC),
371 | }},
372 | }
373 | return x, y
374 | }
375 |
376 | var t fakeT
377 |
378 | type fakeT struct{}
379 |
380 | func (t fakeT) Errorf(format string, args ...interface{}) { fmt.Printf(format+"\n", args...) }
381 |
--------------------------------------------------------------------------------
/cmp/export.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp
6 |
7 | import (
8 | "reflect"
9 | "unsafe"
10 | )
11 |
12 | // retrieveUnexportedField uses unsafe to forcibly retrieve any field from
13 | // a struct such that the value has read-write permissions.
14 | //
15 | // The parent struct, v, must be addressable, while f must be a StructField
16 | // describing the field to retrieve. If addr is false,
17 | // then the returned value will be shallowed copied to be non-addressable.
18 | func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value {
19 | ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
20 | if !addr {
21 | // A field is addressable if and only if the struct is addressable.
22 | // If the original parent value was not addressable, shallow copy the
23 | // value to make it non-addressable to avoid leaking an implementation
24 | // detail of how forcibly exporting a field works.
25 | if ve.Kind() == reflect.Interface && ve.IsNil() {
26 | return reflect.Zero(f.Type)
27 | }
28 | return reflect.ValueOf(ve.Interface()).Convert(f.Type)
29 | }
30 | return ve
31 | }
32 |
--------------------------------------------------------------------------------
/cmp/internal/diff/debug_disable.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | //go:build !cmp_debug
6 | // +build !cmp_debug
7 |
8 | package diff
9 |
10 | var debug debugger
11 |
12 | type debugger struct{}
13 |
14 | func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
15 | return f
16 | }
17 | func (debugger) Update() {}
18 | func (debugger) Finish() {}
19 |
--------------------------------------------------------------------------------
/cmp/internal/diff/debug_enable.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | //go:build cmp_debug
6 | // +build cmp_debug
7 |
8 | package diff
9 |
10 | import (
11 | "fmt"
12 | "strings"
13 | "sync"
14 | "time"
15 | )
16 |
17 | // The algorithm can be seen running in real-time by enabling debugging:
18 | // go test -tags=cmp_debug -v
19 | //
20 | // Example output:
21 | // === RUN TestDifference/#34
22 | // ┌───────────────────────────────┐
23 | // │ \ · · · · · · · · · · · · · · │
24 | // │ · # · · · · · · · · · · · · · │
25 | // │ · \ · · · · · · · · · · · · · │
26 | // │ · · \ · · · · · · · · · · · · │
27 | // │ · · · X # · · · · · · · · · · │
28 | // │ · · · # \ · · · · · · · · · · │
29 | // │ · · · · · # # · · · · · · · · │
30 | // │ · · · · · # \ · · · · · · · · │
31 | // │ · · · · · · · \ · · · · · · · │
32 | // │ · · · · · · · · \ · · · · · · │
33 | // │ · · · · · · · · · \ · · · · · │
34 | // │ · · · · · · · · · · \ · · # · │
35 | // │ · · · · · · · · · · · \ # # · │
36 | // │ · · · · · · · · · · · # # # · │
37 | // │ · · · · · · · · · · # # # # · │
38 | // │ · · · · · · · · · # # # # # · │
39 | // │ · · · · · · · · · · · · · · \ │
40 | // └───────────────────────────────┘
41 | // [.Y..M.XY......YXYXY.|]
42 | //
43 | // The grid represents the edit-graph where the horizontal axis represents
44 | // list X and the vertical axis represents list Y. The start of the two lists
45 | // is the top-left, while the ends are the bottom-right. The '·' represents
46 | // an unexplored node in the graph. The '\' indicates that the two symbols
47 | // from list X and Y are equal. The 'X' indicates that two symbols are similar
48 | // (but not exactly equal) to each other. The '#' indicates that the two symbols
49 | // are different (and not similar). The algorithm traverses this graph trying to
50 | // make the paths starting in the top-left and the bottom-right connect.
51 | //
52 | // The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
53 | // the currently established path from the forward and reverse searches,
54 | // separated by a '|' character.
55 |
56 | const (
57 | updateDelay = 100 * time.Millisecond
58 | finishDelay = 500 * time.Millisecond
59 | ansiTerminal = true // ANSI escape codes used to move terminal cursor
60 | )
61 |
62 | var debug debugger
63 |
64 | type debugger struct {
65 | sync.Mutex
66 | p1, p2 EditScript
67 | fwdPath, revPath *EditScript
68 | grid []byte
69 | lines int
70 | }
71 |
72 | func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
73 | dbg.Lock()
74 | dbg.fwdPath, dbg.revPath = p1, p2
75 | top := "┌─" + strings.Repeat("──", nx) + "┐\n"
76 | row := "│ " + strings.Repeat("· ", nx) + "│\n"
77 | btm := "└─" + strings.Repeat("──", nx) + "┘\n"
78 | dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
79 | dbg.lines = strings.Count(dbg.String(), "\n")
80 | fmt.Print(dbg)
81 |
82 | // Wrap the EqualFunc so that we can intercept each result.
83 | return func(ix, iy int) (r Result) {
84 | cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
85 | for i := range cell {
86 | cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
87 | }
88 | switch r = f(ix, iy); {
89 | case r.Equal():
90 | cell[0] = '\\'
91 | case r.Similar():
92 | cell[0] = 'X'
93 | default:
94 | cell[0] = '#'
95 | }
96 | return
97 | }
98 | }
99 |
100 | func (dbg *debugger) Update() {
101 | dbg.print(updateDelay)
102 | }
103 |
104 | func (dbg *debugger) Finish() {
105 | dbg.print(finishDelay)
106 | dbg.Unlock()
107 | }
108 |
109 | func (dbg *debugger) String() string {
110 | dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
111 | for i := len(*dbg.revPath) - 1; i >= 0; i-- {
112 | dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
113 | }
114 | return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
115 | }
116 |
117 | func (dbg *debugger) print(d time.Duration) {
118 | if ansiTerminal {
119 | fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
120 | }
121 | fmt.Print(dbg)
122 | time.Sleep(d)
123 | }
124 |
--------------------------------------------------------------------------------
/cmp/internal/diff/diff.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package diff implements an algorithm for producing edit-scripts.
6 | // The edit-script is a sequence of operations needed to transform one list
7 | // of symbols into another (or vice-versa). The edits allowed are insertions,
8 | // deletions, and modifications. The summation of all edits is called the
9 | // Levenshtein distance as this problem is well-known in computer science.
10 | //
11 | // This package prioritizes performance over accuracy. That is, the run time
12 | // is more important than obtaining a minimal Levenshtein distance.
13 | package diff
14 |
15 | import (
16 | "math/rand"
17 | "time"
18 |
19 | "github.com/google/go-cmp/cmp/internal/flags"
20 | )
21 |
22 | // EditType represents a single operation within an edit-script.
23 | type EditType uint8
24 |
25 | const (
26 | // Identity indicates that a symbol pair is identical in both list X and Y.
27 | Identity EditType = iota
28 | // UniqueX indicates that a symbol only exists in X and not Y.
29 | UniqueX
30 | // UniqueY indicates that a symbol only exists in Y and not X.
31 | UniqueY
32 | // Modified indicates that a symbol pair is a modification of each other.
33 | Modified
34 | )
35 |
36 | // EditScript represents the series of differences between two lists.
37 | type EditScript []EditType
38 |
39 | // String returns a human-readable string representing the edit-script where
40 | // Identity, UniqueX, UniqueY, and Modified are represented by the
41 | // '.', 'X', 'Y', and 'M' characters, respectively.
42 | func (es EditScript) String() string {
43 | b := make([]byte, len(es))
44 | for i, e := range es {
45 | switch e {
46 | case Identity:
47 | b[i] = '.'
48 | case UniqueX:
49 | b[i] = 'X'
50 | case UniqueY:
51 | b[i] = 'Y'
52 | case Modified:
53 | b[i] = 'M'
54 | default:
55 | panic("invalid edit-type")
56 | }
57 | }
58 | return string(b)
59 | }
60 |
61 | // stats returns a histogram of the number of each type of edit operation.
62 | func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
63 | for _, e := range es {
64 | switch e {
65 | case Identity:
66 | s.NI++
67 | case UniqueX:
68 | s.NX++
69 | case UniqueY:
70 | s.NY++
71 | case Modified:
72 | s.NM++
73 | default:
74 | panic("invalid edit-type")
75 | }
76 | }
77 | return
78 | }
79 |
80 | // Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
81 | // lists X and Y are equal.
82 | func (es EditScript) Dist() int { return len(es) - es.stats().NI }
83 |
84 | // LenX is the length of the X list.
85 | func (es EditScript) LenX() int { return len(es) - es.stats().NY }
86 |
87 | // LenY is the length of the Y list.
88 | func (es EditScript) LenY() int { return len(es) - es.stats().NX }
89 |
90 | // EqualFunc reports whether the symbols at indexes ix and iy are equal.
91 | // When called by Difference, the index is guaranteed to be within nx and ny.
92 | type EqualFunc func(ix int, iy int) Result
93 |
94 | // Result is the result of comparison.
95 | // NumSame is the number of sub-elements that are equal.
96 | // NumDiff is the number of sub-elements that are not equal.
97 | type Result struct{ NumSame, NumDiff int }
98 |
99 | // BoolResult returns a Result that is either Equal or not Equal.
100 | func BoolResult(b bool) Result {
101 | if b {
102 | return Result{NumSame: 1} // Equal, Similar
103 | } else {
104 | return Result{NumDiff: 2} // Not Equal, not Similar
105 | }
106 | }
107 |
108 | // Equal indicates whether the symbols are equal. Two symbols are equal
109 | // if and only if NumDiff == 0. If Equal, then they are also Similar.
110 | func (r Result) Equal() bool { return r.NumDiff == 0 }
111 |
112 | // Similar indicates whether two symbols are similar and may be represented
113 | // by using the Modified type. As a special case, we consider binary comparisons
114 | // (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
115 | //
116 | // The exact ratio of NumSame to NumDiff to determine similarity may change.
117 | func (r Result) Similar() bool {
118 | // Use NumSame+1 to offset NumSame so that binary comparisons are similar.
119 | return r.NumSame+1 >= r.NumDiff
120 | }
121 |
122 | var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
123 |
124 | // Difference reports whether two lists of lengths nx and ny are equal
125 | // given the definition of equality provided as f.
126 | //
127 | // This function returns an edit-script, which is a sequence of operations
128 | // needed to convert one list into the other. The following invariants for
129 | // the edit-script are maintained:
130 | // - eq == (es.Dist()==0)
131 | // - nx == es.LenX()
132 | // - ny == es.LenY()
133 | //
134 | // This algorithm is not guaranteed to be an optimal solution (i.e., one that
135 | // produces an edit-script with a minimal Levenshtein distance). This algorithm
136 | // favors performance over optimality. The exact output is not guaranteed to
137 | // be stable and may change over time.
138 | func Difference(nx, ny int, f EqualFunc) (es EditScript) {
139 | // This algorithm is based on traversing what is known as an "edit-graph".
140 | // See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
141 | // by Eugene W. Myers. Since D can be as large as N itself, this is
142 | // effectively O(N^2). Unlike the algorithm from that paper, we are not
143 | // interested in the optimal path, but at least some "decent" path.
144 | //
145 | // For example, let X and Y be lists of symbols:
146 | // X = [A B C A B B A]
147 | // Y = [C B A B A C]
148 | //
149 | // The edit-graph can be drawn as the following:
150 | // A B C A B B A
151 | // ┌─────────────┐
152 | // C │_|_|\|_|_|_|_│ 0
153 | // B │_|\|_|_|\|\|_│ 1
154 | // A │\|_|_|\|_|_|\│ 2
155 | // B │_|\|_|_|\|\|_│ 3
156 | // A │\|_|_|\|_|_|\│ 4
157 | // C │ | |\| | | | │ 5
158 | // └─────────────┘ 6
159 | // 0 1 2 3 4 5 6 7
160 | //
161 | // List X is written along the horizontal axis, while list Y is written
162 | // along the vertical axis. At any point on this grid, if the symbol in
163 | // list X matches the corresponding symbol in list Y, then a '\' is drawn.
164 | // The goal of any minimal edit-script algorithm is to find a path from the
165 | // top-left corner to the bottom-right corner, while traveling through the
166 | // fewest horizontal or vertical edges.
167 | // A horizontal edge is equivalent to inserting a symbol from list X.
168 | // A vertical edge is equivalent to inserting a symbol from list Y.
169 | // A diagonal edge is equivalent to a matching symbol between both X and Y.
170 |
171 | // Invariants:
172 | // - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
173 | // - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
174 | //
175 | // In general:
176 | // - fwdFrontier.X < revFrontier.X
177 | // - fwdFrontier.Y < revFrontier.Y
178 | //
179 | // Unless, it is time for the algorithm to terminate.
180 | fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
181 | revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
182 | fwdFrontier := fwdPath.point // Forward search frontier
183 | revFrontier := revPath.point // Reverse search frontier
184 |
185 | // Search budget bounds the cost of searching for better paths.
186 | // The longest sequence of non-matching symbols that can be tolerated is
187 | // approximately the square-root of the search budget.
188 | searchBudget := 4 * (nx + ny) // O(n)
189 |
190 | // Running the tests with the "cmp_debug" build tag prints a visualization
191 | // of the algorithm running in real-time. This is educational for
192 | // understanding how the algorithm works. See debug_enable.go.
193 | f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
194 |
195 | // The algorithm below is a greedy, meet-in-the-middle algorithm for
196 | // computing sub-optimal edit-scripts between two lists.
197 | //
198 | // The algorithm is approximately as follows:
199 | // - Searching for differences switches back-and-forth between
200 | // a search that starts at the beginning (the top-left corner), and
201 | // a search that starts at the end (the bottom-right corner).
202 | // The goal of the search is connect with the search
203 | // from the opposite corner.
204 | // - As we search, we build a path in a greedy manner,
205 | // where the first match seen is added to the path (this is sub-optimal,
206 | // but provides a decent result in practice). When matches are found,
207 | // we try the next pair of symbols in the lists and follow all matches
208 | // as far as possible.
209 | // - When searching for matches, we search along a diagonal going through
210 | // through the "frontier" point. If no matches are found,
211 | // we advance the frontier towards the opposite corner.
212 | // - This algorithm terminates when either the X coordinates or the
213 | // Y coordinates of the forward and reverse frontier points ever intersect.
214 |
215 | // This algorithm is correct even if searching only in the forward direction
216 | // or in the reverse direction. We do both because it is commonly observed
217 | // that two lists commonly differ because elements were added to the front
218 | // or end of the other list.
219 | //
220 | // Non-deterministically start with either the forward or reverse direction
221 | // to introduce some deliberate instability so that we have the flexibility
222 | // to change this algorithm in the future.
223 | if flags.Deterministic || randBool {
224 | goto forwardSearch
225 | } else {
226 | goto reverseSearch
227 | }
228 |
229 | forwardSearch:
230 | {
231 | // Forward search from the beginning.
232 | if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
233 | goto finishSearch
234 | }
235 | for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
236 | // Search in a diagonal pattern for a match.
237 | z := zigzag(i)
238 | p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
239 | switch {
240 | case p.X >= revPath.X || p.Y < fwdPath.Y:
241 | stop1 = true // Hit top-right corner
242 | case p.Y >= revPath.Y || p.X < fwdPath.X:
243 | stop2 = true // Hit bottom-left corner
244 | case f(p.X, p.Y).Equal():
245 | // Match found, so connect the path to this point.
246 | fwdPath.connect(p, f)
247 | fwdPath.append(Identity)
248 | // Follow sequence of matches as far as possible.
249 | for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
250 | if !f(fwdPath.X, fwdPath.Y).Equal() {
251 | break
252 | }
253 | fwdPath.append(Identity)
254 | }
255 | fwdFrontier = fwdPath.point
256 | stop1, stop2 = true, true
257 | default:
258 | searchBudget-- // Match not found
259 | }
260 | debug.Update()
261 | }
262 | // Advance the frontier towards reverse point.
263 | if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
264 | fwdFrontier.X++
265 | } else {
266 | fwdFrontier.Y++
267 | }
268 | goto reverseSearch
269 | }
270 |
271 | reverseSearch:
272 | {
273 | // Reverse search from the end.
274 | if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
275 | goto finishSearch
276 | }
277 | for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
278 | // Search in a diagonal pattern for a match.
279 | z := zigzag(i)
280 | p := point{revFrontier.X - z, revFrontier.Y + z}
281 | switch {
282 | case fwdPath.X >= p.X || revPath.Y < p.Y:
283 | stop1 = true // Hit bottom-left corner
284 | case fwdPath.Y >= p.Y || revPath.X < p.X:
285 | stop2 = true // Hit top-right corner
286 | case f(p.X-1, p.Y-1).Equal():
287 | // Match found, so connect the path to this point.
288 | revPath.connect(p, f)
289 | revPath.append(Identity)
290 | // Follow sequence of matches as far as possible.
291 | for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
292 | if !f(revPath.X-1, revPath.Y-1).Equal() {
293 | break
294 | }
295 | revPath.append(Identity)
296 | }
297 | revFrontier = revPath.point
298 | stop1, stop2 = true, true
299 | default:
300 | searchBudget-- // Match not found
301 | }
302 | debug.Update()
303 | }
304 | // Advance the frontier towards forward point.
305 | if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
306 | revFrontier.X--
307 | } else {
308 | revFrontier.Y--
309 | }
310 | goto forwardSearch
311 | }
312 |
313 | finishSearch:
314 | // Join the forward and reverse paths and then append the reverse path.
315 | fwdPath.connect(revPath.point, f)
316 | for i := len(revPath.es) - 1; i >= 0; i-- {
317 | t := revPath.es[i]
318 | revPath.es = revPath.es[:i]
319 | fwdPath.append(t)
320 | }
321 | debug.Finish()
322 | return fwdPath.es
323 | }
324 |
325 | type path struct {
326 | dir int // +1 if forward, -1 if reverse
327 | point // Leading point of the EditScript path
328 | es EditScript
329 | }
330 |
331 | // connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
332 | // to the edit-script to connect p.point to dst.
333 | func (p *path) connect(dst point, f EqualFunc) {
334 | if p.dir > 0 {
335 | // Connect in forward direction.
336 | for dst.X > p.X && dst.Y > p.Y {
337 | switch r := f(p.X, p.Y); {
338 | case r.Equal():
339 | p.append(Identity)
340 | case r.Similar():
341 | p.append(Modified)
342 | case dst.X-p.X >= dst.Y-p.Y:
343 | p.append(UniqueX)
344 | default:
345 | p.append(UniqueY)
346 | }
347 | }
348 | for dst.X > p.X {
349 | p.append(UniqueX)
350 | }
351 | for dst.Y > p.Y {
352 | p.append(UniqueY)
353 | }
354 | } else {
355 | // Connect in reverse direction.
356 | for p.X > dst.X && p.Y > dst.Y {
357 | switch r := f(p.X-1, p.Y-1); {
358 | case r.Equal():
359 | p.append(Identity)
360 | case r.Similar():
361 | p.append(Modified)
362 | case p.Y-dst.Y >= p.X-dst.X:
363 | p.append(UniqueY)
364 | default:
365 | p.append(UniqueX)
366 | }
367 | }
368 | for p.X > dst.X {
369 | p.append(UniqueX)
370 | }
371 | for p.Y > dst.Y {
372 | p.append(UniqueY)
373 | }
374 | }
375 | }
376 |
377 | func (p *path) append(t EditType) {
378 | p.es = append(p.es, t)
379 | switch t {
380 | case Identity, Modified:
381 | p.add(p.dir, p.dir)
382 | case UniqueX:
383 | p.add(p.dir, 0)
384 | case UniqueY:
385 | p.add(0, p.dir)
386 | }
387 | debug.Update()
388 | }
389 |
390 | type point struct{ X, Y int }
391 |
392 | func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
393 |
394 | // zigzag maps a consecutive sequence of integers to a zig-zag sequence.
395 | //
396 | // [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
397 | func zigzag(x int) int {
398 | if x&1 != 0 {
399 | x = ^x
400 | }
401 | return x >> 1
402 | }
403 |
--------------------------------------------------------------------------------
/cmp/internal/diff/diff_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package diff
6 |
7 | import (
8 | "fmt"
9 | "math/rand"
10 | "strings"
11 | "testing"
12 | "unicode"
13 | )
14 |
15 | func TestDifference(t *testing.T) {
16 | tests := []struct {
17 | // Before passing x and y to Difference, we strip all spaces so that
18 | // they can be used by the test author to indicate a missing symbol
19 | // in one of the lists.
20 | x, y string
21 | want string // '|' separated list of possible outputs
22 | }{{
23 | x: "",
24 | y: "",
25 | want: "",
26 | }, {
27 | x: "#",
28 | y: "#",
29 | want: ".",
30 | }, {
31 | x: "##",
32 | y: "# ",
33 | want: ".X|X.",
34 | }, {
35 | x: "a#",
36 | y: "A ",
37 | want: "MX",
38 | }, {
39 | x: "#a",
40 | y: " A",
41 | want: "XM",
42 | }, {
43 | x: "# ",
44 | y: "##",
45 | want: ".Y|Y.",
46 | }, {
47 | x: " #",
48 | y: "@#",
49 | want: "Y.",
50 | }, {
51 | x: "@#",
52 | y: " #",
53 | want: "X.",
54 | }, {
55 | x: "##########0123456789",
56 | y: " 0123456789",
57 | want: "XXXXXXXXXX..........",
58 | }, {
59 | x: " 0123456789",
60 | y: "##########0123456789",
61 | want: "YYYYYYYYYY..........",
62 | }, {
63 | x: "#####0123456789#####",
64 | y: " 0123456789 ",
65 | want: "XXXXX..........XXXXX",
66 | }, {
67 | x: " 0123456789 ",
68 | y: "#####0123456789#####",
69 | want: "YYYYY..........YYYYY",
70 | }, {
71 | x: "01234##########56789",
72 | y: "01234 56789",
73 | want: ".....XXXXXXXXXX.....",
74 | }, {
75 | x: "01234 56789",
76 | y: "01234##########56789",
77 | want: ".....YYYYYYYYYY.....",
78 | }, {
79 | x: "0123456789##########",
80 | y: "0123456789 ",
81 | want: "..........XXXXXXXXXX",
82 | }, {
83 | x: "0123456789 ",
84 | y: "0123456789##########",
85 | want: "..........YYYYYYYYYY",
86 | }, {
87 | x: "abcdefghij0123456789",
88 | y: "ABCDEFGHIJ0123456789",
89 | want: "MMMMMMMMMM..........",
90 | }, {
91 | x: "ABCDEFGHIJ0123456789",
92 | y: "abcdefghij0123456789",
93 | want: "MMMMMMMMMM..........",
94 | }, {
95 | x: "01234abcdefghij56789",
96 | y: "01234ABCDEFGHIJ56789",
97 | want: ".....MMMMMMMMMM.....",
98 | }, {
99 | x: "01234ABCDEFGHIJ56789",
100 | y: "01234abcdefghij56789",
101 | want: ".....MMMMMMMMMM.....",
102 | }, {
103 | x: "0123456789abcdefghij",
104 | y: "0123456789ABCDEFGHIJ",
105 | want: "..........MMMMMMMMMM",
106 | }, {
107 | x: "0123456789ABCDEFGHIJ",
108 | y: "0123456789abcdefghij",
109 | want: "..........MMMMMMMMMM",
110 | }, {
111 | x: "ABCDEFGHIJ0123456789 ",
112 | y: " 0123456789abcdefghij",
113 | want: "XXXXXXXXXX..........YYYYYYYYYY",
114 | }, {
115 | x: " 0123456789abcdefghij",
116 | y: "ABCDEFGHIJ0123456789 ",
117 | want: "YYYYYYYYYY..........XXXXXXXXXX",
118 | }, {
119 | x: "ABCDE0123456789 FGHIJ",
120 | y: " 0123456789abcdefghij",
121 | want: "XXXXX..........YYYYYMMMMM",
122 | }, {
123 | x: " 0123456789abcdefghij",
124 | y: "ABCDE0123456789 FGHIJ",
125 | want: "YYYYY..........XXXXXMMMMM",
126 | }, {
127 | x: "ABCDE01234F G H I J 56789 ",
128 | y: " 01234 a b c d e56789fghij",
129 | want: "XXXXX.....XYXYXYXYXY.....YYYYY",
130 | }, {
131 | x: " 01234a b c d e 56789fghij",
132 | y: "ABCDE01234 F G H I J56789 ",
133 | want: "YYYYY.....XYXYXYXYXY.....XXXXX",
134 | }, {
135 | x: "FGHIJ01234ABCDE56789 ",
136 | y: " 01234abcde56789fghij",
137 | want: "XXXXX.....MMMMM.....YYYYY",
138 | }, {
139 | x: " 01234abcde56789fghij",
140 | y: "FGHIJ01234ABCDE56789 ",
141 | want: "YYYYY.....MMMMM.....XXXXX",
142 | }, {
143 | x: "ABCAB BA ",
144 | y: " C BABAC",
145 | want: "XX.X.Y..Y|XX.Y.X..Y",
146 | }, {
147 | x: "# #### ###",
148 | y: "#y####yy###",
149 | want: ".Y....YY...",
150 | }, {
151 | x: "# #### # ##x#x",
152 | y: "#y####y y## # ",
153 | want: ".Y....YXY..X.X",
154 | }, {
155 | x: "###z#z###### x #",
156 | y: "#y##Z#Z###### yy#",
157 | want: ".Y..M.M......XYY.",
158 | }, {
159 | x: "0 12z3x 456789 x x 0",
160 | y: "0y12Z3 y456789y y y0",
161 | want: ".Y..M.XY......YXYXY.|.Y..M.XY......XYXYY.",
162 | }, {
163 | x: "0 2 4 6 8 ..................abXXcdEXF.ghXi",
164 | y: " 1 3 5 7 9..................AB CDE F.GH I",
165 | want: "XYXYXYXYXY..................MMXXMM.X..MMXM",
166 | }, {
167 | x: "I HG.F EDC BA..................9 7 5 3 1 ",
168 | y: "iXhg.FXEdcXXba.................. 8 6 4 2 0",
169 | want: "MYMM..Y.MMYYMM..................XYXYXYXYXY",
170 | }, {
171 | x: "x1234",
172 | y: " 1234",
173 | want: "X....",
174 | }, {
175 | x: "x123x4",
176 | y: " 123 4",
177 | want: "X...X.",
178 | }, {
179 | x: "x1234x56",
180 | y: " 1234 ",
181 | want: "X....XXX",
182 | }, {
183 | x: "x1234xxx56",
184 | y: " 1234 56",
185 | want: "X....XXX..",
186 | }, {
187 | x: ".1234...ab",
188 | y: " 1234 AB",
189 | want: "X....XXXMM",
190 | }, {
191 | x: "x1234xxab.",
192 | y: " 1234 AB ",
193 | want: "X....XXMMX",
194 | }, {
195 | x: " 0123456789",
196 | y: "9012345678 ",
197 | want: "Y.........X",
198 | }, {
199 | x: " 0123456789",
200 | y: "8901234567 ",
201 | want: "YY........XX",
202 | }, {
203 | x: " 0123456789",
204 | y: "7890123456 ",
205 | want: "YYY.......XXX",
206 | }, {
207 | x: " 0123456789",
208 | y: "6789012345 ",
209 | want: "YYYY......XXXX",
210 | }, {
211 | x: "0123456789 ",
212 | y: " 5678901234",
213 | want: "XXXXX.....YYYYY|YYYYY.....XXXXX",
214 | }, {
215 | x: "0123456789 ",
216 | y: " 4567890123",
217 | want: "XXXX......YYYY",
218 | }, {
219 | x: "0123456789 ",
220 | y: " 3456789012",
221 | want: "XXX.......YYY",
222 | }, {
223 | x: "0123456789 ",
224 | y: " 2345678901",
225 | want: "XX........YY",
226 | }, {
227 | x: "0123456789 ",
228 | y: " 1234567890",
229 | want: "X.........Y",
230 | }, {
231 | x: "0 1 2 3 45 6 7 8 9 ",
232 | y: " 9 8 7 6 54 3 2 1 0",
233 | want: "XYXYXYXYX.YXYXYXYXY",
234 | }, {
235 | x: "0 1 2345678 9 ",
236 | y: " 6 72 5 819034",
237 | want: "XYXY.XX.XX.Y.YYY",
238 | }, {
239 | x: "F B Q M O I G T L N72X90 E 4S P 651HKRJU DA 83CVZW",
240 | y: " 5 W H XO10R9IV K ZLCTAJ8P3N SEQM4 7 2G6 UBD F ",
241 | want: "XYXYXYXY.YYYY.YXYXY.YYYYYYY.XXXXXY.YY.XYXYY.XXXXXX.Y.XYXXXXXX",
242 | }}
243 |
244 | for _, tt := range tests {
245 | t.Run("", func(t *testing.T) {
246 | x := strings.Replace(tt.x, " ", "", -1)
247 | y := strings.Replace(tt.y, " ", "", -1)
248 | es := testStrings(t, x, y)
249 | var want string
250 | got := es.String()
251 | for _, want = range strings.Split(tt.want, "|") {
252 | if got == want {
253 | return
254 | }
255 | }
256 | t.Errorf("Difference(%s, %s):\ngot %s\nwant %s", x, y, got, want)
257 | })
258 | }
259 | }
260 |
261 | func TestDifferenceFuzz(t *testing.T) {
262 | tests := []struct{ px, py, pm float32 }{
263 | {px: 0.0, py: 0.0, pm: 0.1},
264 | {px: 0.0, py: 0.1, pm: 0.0},
265 | {px: 0.1, py: 0.0, pm: 0.0},
266 | {px: 0.0, py: 0.1, pm: 0.1},
267 | {px: 0.1, py: 0.0, pm: 0.1},
268 | {px: 0.2, py: 0.2, pm: 0.2},
269 | {px: 0.3, py: 0.1, pm: 0.2},
270 | {px: 0.1, py: 0.3, pm: 0.2},
271 | {px: 0.2, py: 0.2, pm: 0.2},
272 | {px: 0.3, py: 0.3, pm: 0.3},
273 | {px: 0.1, py: 0.1, pm: 0.5},
274 | {px: 0.4, py: 0.1, pm: 0.5},
275 | {px: 0.3, py: 0.2, pm: 0.5},
276 | {px: 0.2, py: 0.3, pm: 0.5},
277 | {px: 0.1, py: 0.4, pm: 0.5},
278 | }
279 |
280 | for i, tt := range tests {
281 | t.Run(fmt.Sprintf("P%d", i), func(t *testing.T) {
282 | // Sweep from 1B to 1KiB.
283 | for n := 1; n <= 1024; n <<= 1 {
284 | t.Run(fmt.Sprintf("N%d", n), func(t *testing.T) {
285 | for j := 0; j < 10; j++ {
286 | x, y := generateStrings(n, tt.px, tt.py, tt.pm, int64(j))
287 | testStrings(t, x, y)
288 | }
289 | })
290 | }
291 | })
292 | }
293 | }
294 |
295 | func BenchmarkDifference(b *testing.B) {
296 | for n := 1 << 10; n <= 1<<20; n <<= 2 {
297 | b.Run(fmt.Sprintf("N%d", n), func(b *testing.B) {
298 | x, y := generateStrings(n, 0.05, 0.05, 0.10, 0)
299 | b.ReportAllocs()
300 | b.SetBytes(int64(len(x) + len(y)))
301 | for i := 0; i < b.N; i++ {
302 | Difference(len(x), len(y), func(ix, iy int) Result {
303 | return compareByte(x[ix], y[iy])
304 | })
305 | }
306 | })
307 | }
308 | }
309 |
310 | func generateStrings(n int, px, py, pm float32, seed int64) (string, string) {
311 | if px+py+pm > 1.0 {
312 | panic("invalid probabilities")
313 | }
314 | py += px
315 | pm += py
316 |
317 | b := make([]byte, n)
318 | r := rand.New(rand.NewSource(seed))
319 | r.Read(b)
320 |
321 | var x, y []byte
322 | for len(b) > 0 {
323 | switch p := r.Float32(); {
324 | case p < px: // UniqueX
325 | x = append(x, b[0])
326 | case p < py: // UniqueY
327 | y = append(y, b[0])
328 | case p < pm: // Modified
329 | x = append(x, 'A'+(b[0]%26))
330 | y = append(y, 'a'+(b[0]%26))
331 | default: // Identity
332 | x = append(x, b[0])
333 | y = append(y, b[0])
334 | }
335 | b = b[1:]
336 | }
337 | return string(x), string(y)
338 | }
339 |
340 | func testStrings(t *testing.T, x, y string) EditScript {
341 | es := Difference(len(x), len(y), func(ix, iy int) Result {
342 | return compareByte(x[ix], y[iy])
343 | })
344 | if es.LenX() != len(x) {
345 | t.Errorf("es.LenX = %d, want %d", es.LenX(), len(x))
346 | }
347 | if es.LenY() != len(y) {
348 | t.Errorf("es.LenY = %d, want %d", es.LenY(), len(y))
349 | }
350 | if !validateScript(x, y, es) {
351 | t.Errorf("invalid edit script: %v", es)
352 | }
353 | return es
354 | }
355 |
356 | func validateScript(x, y string, es EditScript) bool {
357 | var bx, by []byte
358 | for _, e := range es {
359 | switch e {
360 | case Identity:
361 | if !compareByte(x[len(bx)], y[len(by)]).Equal() {
362 | return false
363 | }
364 | bx = append(bx, x[len(bx)])
365 | by = append(by, y[len(by)])
366 | case UniqueX:
367 | bx = append(bx, x[len(bx)])
368 | case UniqueY:
369 | by = append(by, y[len(by)])
370 | case Modified:
371 | if !compareByte(x[len(bx)], y[len(by)]).Similar() {
372 | return false
373 | }
374 | bx = append(bx, x[len(bx)])
375 | by = append(by, y[len(by)])
376 | }
377 | }
378 | return string(bx) == x && string(by) == y
379 | }
380 |
381 | // compareByte returns a Result where the result is Equal if x == y,
382 | // similar if x and y differ only in casing, and different otherwise.
383 | func compareByte(x, y byte) (r Result) {
384 | switch {
385 | case x == y:
386 | return equalResult // Identity
387 | case unicode.ToUpper(rune(x)) == unicode.ToUpper(rune(y)):
388 | return similarResult // Modified
389 | default:
390 | return differentResult // UniqueX or UniqueY
391 | }
392 | }
393 |
394 | var (
395 | equalResult = Result{NumDiff: 0}
396 | similarResult = Result{NumDiff: 1}
397 | differentResult = Result{NumDiff: 2}
398 | )
399 |
400 | func TestResult(t *testing.T) {
401 | tests := []struct {
402 | result Result
403 | wantEqual bool
404 | wantSimilar bool
405 | }{
406 | // equalResult is equal since NumDiff == 0, by definition of Equal method.
407 | {equalResult, true, true},
408 | // similarResult is similar since it is a binary result where only one
409 | // element was compared (i.e., Either NumSame==1 or NumDiff==1).
410 | {similarResult, false, true},
411 | // differentResult is different since there are enough differences that
412 | // it isn't even considered similar.
413 | {differentResult, false, false},
414 |
415 | // Zero value is always equal.
416 | {Result{NumSame: 0, NumDiff: 0}, true, true},
417 |
418 | // Binary comparisons (where NumSame+NumDiff == 1) are always similar.
419 | {Result{NumSame: 1, NumDiff: 0}, true, true},
420 | {Result{NumSame: 0, NumDiff: 1}, false, true},
421 |
422 | // More complex ratios. The exact ratio for similarity may change,
423 | // and may require updates to these test cases.
424 | {Result{NumSame: 1, NumDiff: 1}, false, true},
425 | {Result{NumSame: 1, NumDiff: 2}, false, true},
426 | {Result{NumSame: 1, NumDiff: 3}, false, false},
427 | {Result{NumSame: 2, NumDiff: 1}, false, true},
428 | {Result{NumSame: 2, NumDiff: 2}, false, true},
429 | {Result{NumSame: 2, NumDiff: 3}, false, true},
430 | {Result{NumSame: 3, NumDiff: 1}, false, true},
431 | {Result{NumSame: 3, NumDiff: 2}, false, true},
432 | {Result{NumSame: 3, NumDiff: 3}, false, true},
433 | {Result{NumSame: 1000, NumDiff: 0}, true, true},
434 | {Result{NumSame: 1000, NumDiff: 1}, false, true},
435 | {Result{NumSame: 1000, NumDiff: 2}, false, true},
436 | {Result{NumSame: 0, NumDiff: 1000}, false, false},
437 | {Result{NumSame: 1, NumDiff: 1000}, false, false},
438 | {Result{NumSame: 2, NumDiff: 1000}, false, false},
439 | }
440 |
441 | for _, tt := range tests {
442 | if got := tt.result.Equal(); got != tt.wantEqual {
443 | t.Errorf("%#v.Equal() = %v, want %v", tt.result, got, tt.wantEqual)
444 | }
445 | if got := tt.result.Similar(); got != tt.wantSimilar {
446 | t.Errorf("%#v.Similar() = %v, want %v", tt.result, got, tt.wantSimilar)
447 | }
448 | }
449 | }
450 |
--------------------------------------------------------------------------------
/cmp/internal/flags/flags.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package flags
6 |
7 | // Deterministic controls whether the output of Diff should be deterministic.
8 | // This is only used for testing.
9 | var Deterministic bool
10 |
--------------------------------------------------------------------------------
/cmp/internal/function/func.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package function provides functionality for identifying function types.
6 | package function
7 |
8 | import (
9 | "reflect"
10 | "regexp"
11 | "runtime"
12 | "strings"
13 | )
14 |
15 | type funcType int
16 |
17 | const (
18 | _ funcType = iota
19 |
20 | tbFunc // func(T) bool
21 | ttbFunc // func(T, T) bool
22 | ttiFunc // func(T, T) int
23 | trbFunc // func(T, R) bool
24 | tibFunc // func(T, I) bool
25 | trFunc // func(T) R
26 |
27 | Equal = ttbFunc // func(T, T) bool
28 | EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
29 | Transformer = trFunc // func(T) R
30 | ValueFilter = ttbFunc // func(T, T) bool
31 | Less = ttbFunc // func(T, T) bool
32 | Compare = ttiFunc // func(T, T) int
33 | ValuePredicate = tbFunc // func(T) bool
34 | KeyValuePredicate = trbFunc // func(T, R) bool
35 | )
36 |
37 | var boolType = reflect.TypeOf(true)
38 | var intType = reflect.TypeOf(0)
39 |
40 | // IsType reports whether the reflect.Type is of the specified function type.
41 | func IsType(t reflect.Type, ft funcType) bool {
42 | if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
43 | return false
44 | }
45 | ni, no := t.NumIn(), t.NumOut()
46 | switch ft {
47 | case tbFunc: // func(T) bool
48 | if ni == 1 && no == 1 && t.Out(0) == boolType {
49 | return true
50 | }
51 | case ttbFunc: // func(T, T) bool
52 | if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
53 | return true
54 | }
55 | case ttiFunc: // func(T, T) int
56 | if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == intType {
57 | return true
58 | }
59 | case trbFunc: // func(T, R) bool
60 | if ni == 2 && no == 1 && t.Out(0) == boolType {
61 | return true
62 | }
63 | case tibFunc: // func(T, I) bool
64 | if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
65 | return true
66 | }
67 | case trFunc: // func(T) R
68 | if ni == 1 && no == 1 {
69 | return true
70 | }
71 | }
72 | return false
73 | }
74 |
75 | var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`)
76 |
77 | // NameOf returns the name of the function value.
78 | func NameOf(v reflect.Value) string {
79 | fnc := runtime.FuncForPC(v.Pointer())
80 | if fnc == nil {
81 | return ""
82 | }
83 | fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm"
84 |
85 | // Method closures have a "-fm" suffix.
86 | fullName = strings.TrimSuffix(fullName, "-fm")
87 |
88 | var name string
89 | for len(fullName) > 0 {
90 | inParen := strings.HasSuffix(fullName, ")")
91 | fullName = strings.TrimSuffix(fullName, ")")
92 |
93 | s := lastIdentRx.FindString(fullName)
94 | if s == "" {
95 | break
96 | }
97 | name = s + "." + name
98 | fullName = strings.TrimSuffix(fullName, s)
99 |
100 | if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 {
101 | fullName = fullName[:i]
102 | }
103 | fullName = strings.TrimSuffix(fullName, ".")
104 | }
105 | return strings.TrimSuffix(name, ".")
106 | }
107 |
--------------------------------------------------------------------------------
/cmp/internal/function/func_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package function
6 |
7 | import (
8 | "bytes"
9 | "reflect"
10 | "testing"
11 | )
12 |
13 | type myType struct{ bytes.Buffer }
14 |
15 | func (myType) valueMethod() {}
16 | func (myType) ValueMethod() {}
17 |
18 | func (*myType) pointerMethod() {}
19 | func (*myType) PointerMethod() {}
20 |
21 | func TestNameOf(t *testing.T) {
22 | tests := []struct {
23 | fnc interface{}
24 | want string
25 | }{
26 | {TestNameOf, "function.TestNameOf"},
27 | {func() {}, "function.TestNameOf.func1"},
28 | {(myType).valueMethod, "function.myType.valueMethod"},
29 | {(myType).ValueMethod, "function.myType.ValueMethod"},
30 | {(myType{}).valueMethod, "function.myType.valueMethod"},
31 | {(myType{}).ValueMethod, "function.myType.ValueMethod"},
32 | {(*myType).valueMethod, "function.myType.valueMethod"},
33 | {(*myType).ValueMethod, "function.myType.ValueMethod"},
34 | {(&myType{}).valueMethod, "function.myType.valueMethod"},
35 | {(&myType{}).ValueMethod, "function.myType.ValueMethod"},
36 | {(*myType).pointerMethod, "function.myType.pointerMethod"},
37 | {(*myType).PointerMethod, "function.myType.PointerMethod"},
38 | {(&myType{}).pointerMethod, "function.myType.pointerMethod"},
39 | {(&myType{}).PointerMethod, "function.myType.PointerMethod"},
40 | {(*myType).Write, "function.myType.Write"},
41 | {(&myType{}).Write, "bytes.Buffer.Write"},
42 | }
43 | for _, tt := range tests {
44 | t.Run("", func(t *testing.T) {
45 | got := NameOf(reflect.ValueOf(tt.fnc))
46 | if got != tt.want {
47 | t.Errorf("NameOf() = %v, want %v", got, tt.want)
48 | }
49 | })
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/cmp/internal/testprotos/protos.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package testprotos
6 |
7 | func Equal(x, y Message) bool {
8 | if x == nil || y == nil {
9 | return x == nil && y == nil
10 | }
11 | return x.String() == y.String()
12 | }
13 |
14 | type Message interface {
15 | Proto()
16 | String() string
17 | }
18 |
19 | type proto interface {
20 | Proto()
21 | }
22 |
23 | type notComparable struct {
24 | unexportedField func()
25 | }
26 |
27 | type Stringer struct{ X string }
28 |
29 | func (s *Stringer) String() string { return s.X }
30 |
31 | // Project1 protocol buffers
32 | type (
33 | Eagle_States int
34 | Eagle_MissingCalls int
35 | Dreamer_States int
36 | Dreamer_MissingCalls int
37 | Slap_States int
38 | Goat_States int
39 | Donkey_States int
40 | SummerType int
41 |
42 | Eagle struct {
43 | proto
44 | notComparable
45 | Stringer
46 | }
47 | Dreamer struct {
48 | proto
49 | notComparable
50 | Stringer
51 | }
52 | Slap struct {
53 | proto
54 | notComparable
55 | Stringer
56 | }
57 | Goat struct {
58 | proto
59 | notComparable
60 | Stringer
61 | }
62 | Donkey struct {
63 | proto
64 | notComparable
65 | Stringer
66 | }
67 | )
68 |
69 | // Project2 protocol buffers
70 | type (
71 | Germ struct {
72 | proto
73 | notComparable
74 | Stringer
75 | }
76 | Dish struct {
77 | proto
78 | notComparable
79 | Stringer
80 | }
81 | )
82 |
83 | // Project3 protocol buffers
84 | type (
85 | Dirt struct {
86 | proto
87 | notComparable
88 | Stringer
89 | }
90 | Wizard struct {
91 | proto
92 | notComparable
93 | Stringer
94 | }
95 | Sadistic struct {
96 | proto
97 | notComparable
98 | Stringer
99 | }
100 | )
101 |
102 | // Project4 protocol buffers
103 | type (
104 | HoneyStatus int
105 | PoisonType int
106 | MetaData struct {
107 | proto
108 | notComparable
109 | Stringer
110 | }
111 | Restrictions struct {
112 | proto
113 | notComparable
114 | Stringer
115 | }
116 | )
117 |
--------------------------------------------------------------------------------
/cmp/internal/teststructs/foo1/foo.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package foo is deliberately named differently than the parent directory.
6 | // It contain declarations that have ambiguity in their short names,
7 | // relative to a different package also called foo.
8 | package foo
9 |
10 | type Bar struct{ S string }
11 |
--------------------------------------------------------------------------------
/cmp/internal/teststructs/foo2/foo.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package foo is deliberately named differently than the parent directory.
6 | // It contain declarations that have ambiguity in their short names,
7 | // relative to a different package also called foo.
8 | package foo
9 |
10 | type Bar struct{ S string }
11 |
--------------------------------------------------------------------------------
/cmp/internal/teststructs/project1.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package teststructs
6 |
7 | import (
8 | "time"
9 |
10 | pb "github.com/google/go-cmp/cmp/internal/testprotos"
11 | )
12 |
13 | // This is an sanitized example of equality from a real use-case.
14 | // The original equality function was as follows:
15 | /*
16 | func equalEagle(x, y Eagle) bool {
17 | if x.Name != y.Name &&
18 | !reflect.DeepEqual(x.Hounds, y.Hounds) &&
19 | x.Desc != y.Desc &&
20 | x.DescLong != y.DescLong &&
21 | x.Prong != y.Prong &&
22 | x.StateGoverner != y.StateGoverner &&
23 | x.PrankRating != y.PrankRating &&
24 | x.FunnyPrank != y.FunnyPrank &&
25 | !pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) {
26 | return false
27 | }
28 |
29 | if len(x.Dreamers) != len(y.Dreamers) {
30 | return false
31 | }
32 | for i := range x.Dreamers {
33 | if !equalDreamer(x.Dreamers[i], y.Dreamers[i]) {
34 | return false
35 | }
36 | }
37 | if len(x.Slaps) != len(y.Slaps) {
38 | return false
39 | }
40 | for i := range x.Slaps {
41 | if !equalSlap(x.Slaps[i], y.Slaps[i]) {
42 | return false
43 | }
44 | }
45 | return true
46 | }
47 | func equalDreamer(x, y Dreamer) bool {
48 | if x.Name != y.Name ||
49 | x.Desc != y.Desc ||
50 | x.DescLong != y.DescLong ||
51 | x.ContSlapsInterval != y.ContSlapsInterval ||
52 | x.Ornamental != y.Ornamental ||
53 | x.Amoeba != y.Amoeba ||
54 | x.Heroes != y.Heroes ||
55 | x.FloppyDisk != y.FloppyDisk ||
56 | x.MightiestDuck != y.MightiestDuck ||
57 | x.FunnyPrank != y.FunnyPrank ||
58 | !pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) {
59 |
60 | return false
61 | }
62 | if len(x.Animal) != len(y.Animal) {
63 | return false
64 | }
65 | for i := range x.Animal {
66 | vx := x.Animal[i]
67 | vy := y.Animal[i]
68 | if reflect.TypeOf(x.Animal) != reflect.TypeOf(y.Animal) {
69 | return false
70 | }
71 | switch vx.(type) {
72 | case Goat:
73 | if !equalGoat(vx.(Goat), vy.(Goat)) {
74 | return false
75 | }
76 | case Donkey:
77 | if !equalDonkey(vx.(Donkey), vy.(Donkey)) {
78 | return false
79 | }
80 | default:
81 | panic(fmt.Sprintf("unknown type: %T", vx))
82 | }
83 | }
84 | if len(x.PreSlaps) != len(y.PreSlaps) {
85 | return false
86 | }
87 | for i := range x.PreSlaps {
88 | if !equalSlap(x.PreSlaps[i], y.PreSlaps[i]) {
89 | return false
90 | }
91 | }
92 | if len(x.ContSlaps) != len(y.ContSlaps) {
93 | return false
94 | }
95 | for i := range x.ContSlaps {
96 | if !equalSlap(x.ContSlaps[i], y.ContSlaps[i]) {
97 | return false
98 | }
99 | }
100 | return true
101 | }
102 | func equalSlap(x, y Slap) bool {
103 | return x.Name == y.Name &&
104 | x.Desc == y.Desc &&
105 | x.DescLong == y.DescLong &&
106 | pb.Equal(x.Args, y.Args) &&
107 | x.Tense == y.Tense &&
108 | x.Interval == y.Interval &&
109 | x.Homeland == y.Homeland &&
110 | x.FunnyPrank == y.FunnyPrank &&
111 | pb.Equal(x.Immutable.Proto(), y.Immutable.Proto())
112 | }
113 | func equalGoat(x, y Goat) bool {
114 | if x.Target != y.Target ||
115 | x.FunnyPrank != y.FunnyPrank ||
116 | !pb.Equal(x.Immutable.Proto(), y.Immutable.Proto()) {
117 | return false
118 | }
119 | if len(x.Slaps) != len(y.Slaps) {
120 | return false
121 | }
122 | for i := range x.Slaps {
123 | if !equalSlap(x.Slaps[i], y.Slaps[i]) {
124 | return false
125 | }
126 | }
127 | return true
128 | }
129 | func equalDonkey(x, y Donkey) bool {
130 | return x.Pause == y.Pause &&
131 | x.Sleep == y.Sleep &&
132 | x.FunnyPrank == y.FunnyPrank &&
133 | pb.Equal(x.Immutable.Proto(), y.Immutable.Proto())
134 | }
135 | */
136 |
137 | type Eagle struct {
138 | Name string
139 | Hounds []string
140 | Desc string
141 | DescLong string
142 | Dreamers []Dreamer
143 | Prong int64
144 | Slaps []Slap
145 | StateGoverner string
146 | PrankRating string
147 | FunnyPrank string
148 | Immutable *EagleImmutable
149 | }
150 |
151 | type EagleImmutable struct {
152 | ID string
153 | State *pb.Eagle_States
154 | MissingCall *pb.Eagle_MissingCalls
155 | Birthday time.Time
156 | Death time.Time
157 | Started time.Time
158 | LastUpdate time.Time
159 | Creator string
160 | empty bool
161 | }
162 |
163 | type Dreamer struct {
164 | Name string
165 | Desc string
166 | DescLong string
167 | PreSlaps []Slap
168 | ContSlaps []Slap
169 | ContSlapsInterval int32
170 | Animal []interface{} // Could be either Goat or Donkey
171 | Ornamental bool
172 | Amoeba int64
173 | Heroes int32
174 | FloppyDisk int32
175 | MightiestDuck bool
176 | FunnyPrank string
177 | Immutable *DreamerImmutable
178 | }
179 |
180 | type DreamerImmutable struct {
181 | ID string
182 | State *pb.Dreamer_States
183 | MissingCall *pb.Dreamer_MissingCalls
184 | Calls int32
185 | Started time.Time
186 | Stopped time.Time
187 | LastUpdate time.Time
188 | empty bool
189 | }
190 |
191 | type Slap struct {
192 | Name string
193 | Desc string
194 | DescLong string
195 | Args pb.Message
196 | Tense int32
197 | Interval int32
198 | Homeland uint32
199 | FunnyPrank string
200 | Immutable *SlapImmutable
201 | }
202 |
203 | type SlapImmutable struct {
204 | ID string
205 | Out pb.Message
206 | MildSlap bool
207 | PrettyPrint string
208 | State *pb.Slap_States
209 | Started time.Time
210 | Stopped time.Time
211 | LastUpdate time.Time
212 | LoveRadius *LoveRadius
213 | empty bool
214 | }
215 |
216 | type Goat struct {
217 | Target string
218 | Slaps []Slap
219 | FunnyPrank string
220 | Immutable *GoatImmutable
221 | }
222 |
223 | type GoatImmutable struct {
224 | ID string
225 | State *pb.Goat_States
226 | Started time.Time
227 | Stopped time.Time
228 | LastUpdate time.Time
229 | empty bool
230 | }
231 | type Donkey struct {
232 | Pause bool
233 | Sleep int32
234 | FunnyPrank string
235 | Immutable *DonkeyImmutable
236 | }
237 |
238 | type DonkeyImmutable struct {
239 | ID string
240 | State *pb.Donkey_States
241 | Started time.Time
242 | Stopped time.Time
243 | LastUpdate time.Time
244 | empty bool
245 | }
246 |
247 | type LoveRadius struct {
248 | Summer *SummerLove
249 | empty bool
250 | }
251 |
252 | type SummerLove struct {
253 | Summary *SummerLoveSummary
254 | empty bool
255 | }
256 |
257 | type SummerLoveSummary struct {
258 | Devices []string
259 | ChangeType []pb.SummerType
260 | empty bool
261 | }
262 |
263 | func (EagleImmutable) Proto() *pb.Eagle { return nil }
264 | func (DreamerImmutable) Proto() *pb.Dreamer { return nil }
265 | func (SlapImmutable) Proto() *pb.Slap { return nil }
266 | func (GoatImmutable) Proto() *pb.Goat { return nil }
267 | func (DonkeyImmutable) Proto() *pb.Donkey { return nil }
268 |
--------------------------------------------------------------------------------
/cmp/internal/teststructs/project2.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package teststructs
6 |
7 | import (
8 | "time"
9 |
10 | pb "github.com/google/go-cmp/cmp/internal/testprotos"
11 | )
12 |
13 | // This is an sanitized example of equality from a real use-case.
14 | // The original equality function was as follows:
15 | /*
16 | func equalBatch(b1, b2 *GermBatch) bool {
17 | for _, b := range []*GermBatch{b1, b2} {
18 | for _, l := range b.DirtyGerms {
19 | sort.Slice(l, func(i, j int) bool { return l[i].String() < l[j].String() })
20 | }
21 | for _, l := range b.CleanGerms {
22 | sort.Slice(l, func(i, j int) bool { return l[i].String() < l[j].String() })
23 | }
24 | }
25 | if !pb.DeepEqual(b1.DirtyGerms, b2.DirtyGerms) ||
26 | !pb.DeepEqual(b1.CleanGerms, b2.CleanGerms) ||
27 | !pb.DeepEqual(b1.GermMap, b2.GermMap) {
28 | return false
29 | }
30 | if len(b1.DishMap) != len(b2.DishMap) {
31 | return false
32 | }
33 | for id := range b1.DishMap {
34 | kpb1, err1 := b1.DishMap[id].Proto()
35 | kpb2, err2 := b2.DishMap[id].Proto()
36 | if !pb.Equal(kpb1, kpb2) || !reflect.DeepEqual(err1, err2) {
37 | return false
38 | }
39 | }
40 | return b1.HasPreviousResult == b2.HasPreviousResult &&
41 | b1.DirtyID == b2.DirtyID &&
42 | b1.CleanID == b2.CleanID &&
43 | b1.GermStrain == b2.GermStrain &&
44 | b1.TotalDirtyGerms == b2.TotalDirtyGerms &&
45 | b1.InfectedAt.Equal(b2.InfectedAt)
46 | }
47 | */
48 |
49 | type GermBatch struct {
50 | DirtyGerms, CleanGerms map[int32][]*pb.Germ
51 | GermMap map[int32]*pb.Germ
52 | DishMap map[int32]*Dish
53 | HasPreviousResult bool
54 | DirtyID, CleanID int32
55 | GermStrain int32
56 | TotalDirtyGerms int
57 | InfectedAt time.Time
58 | }
59 |
60 | type Dish struct {
61 | pb *pb.Dish
62 | err error
63 | }
64 |
65 | func CreateDish(m *pb.Dish, err error) *Dish {
66 | return &Dish{pb: m, err: err}
67 | }
68 |
69 | func (d *Dish) Proto() (*pb.Dish, error) {
70 | if d.err != nil {
71 | return nil, d.err
72 | }
73 | return d.pb, nil
74 | }
75 |
--------------------------------------------------------------------------------
/cmp/internal/teststructs/project3.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package teststructs
6 |
7 | import (
8 | "sync"
9 |
10 | pb "github.com/google/go-cmp/cmp/internal/testprotos"
11 | )
12 |
13 | // This is an sanitized example of equality from a real use-case.
14 | // The original equality function was as follows:
15 | /*
16 | func equalDirt(x, y *Dirt) bool {
17 | if !reflect.DeepEqual(x.table, y.table) ||
18 | !reflect.DeepEqual(x.ts, y.ts) ||
19 | x.Discord != y.Discord ||
20 | !pb.Equal(&x.Proto, &y.Proto) ||
21 | len(x.wizard) != len(y.wizard) ||
22 | len(x.sadistic) != len(y.sadistic) ||
23 | x.lastTime != y.lastTime {
24 | return false
25 | }
26 | for k, vx := range x.wizard {
27 | vy, ok := y.wizard[k]
28 | if !ok || !pb.Equal(vx, vy) {
29 | return false
30 | }
31 | }
32 | for k, vx := range x.sadistic {
33 | vy, ok := y.sadistic[k]
34 | if !ok || !pb.Equal(vx, vy) {
35 | return false
36 | }
37 | }
38 | return true
39 | }
40 | */
41 |
42 | type FakeMutex struct {
43 | sync.Locker
44 | x struct{}
45 | }
46 |
47 | type Dirt struct {
48 | table Table // Always concrete type of MockTable
49 | ts Timestamp
50 | Discord DiscordState
51 | Proto pb.Dirt
52 | wizard map[string]*pb.Wizard
53 | sadistic map[string]*pb.Sadistic
54 | lastTime int64
55 | mu FakeMutex
56 | }
57 |
58 | type DiscordState int
59 |
60 | type Timestamp int64
61 |
62 | func (d *Dirt) SetTable(t Table) { d.table = t }
63 | func (d *Dirt) SetTimestamp(t Timestamp) { d.ts = t }
64 | func (d *Dirt) SetWizard(m map[string]*pb.Wizard) { d.wizard = m }
65 | func (d *Dirt) SetSadistic(m map[string]*pb.Sadistic) { d.sadistic = m }
66 | func (d *Dirt) SetLastTime(t int64) { d.lastTime = t }
67 |
68 | type Table interface {
69 | Operation1() error
70 | Operation2() error
71 | Operation3() error
72 | }
73 |
74 | type MockTable struct {
75 | state []string
76 | }
77 |
78 | func CreateMockTable(s []string) *MockTable { return &MockTable{s} }
79 | func (mt *MockTable) Operation1() error { return nil }
80 | func (mt *MockTable) Operation2() error { return nil }
81 | func (mt *MockTable) Operation3() error { return nil }
82 | func (mt *MockTable) State() []string { return mt.state }
83 |
--------------------------------------------------------------------------------
/cmp/internal/teststructs/project4.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package teststructs
6 |
7 | import (
8 | "time"
9 |
10 | pb "github.com/google/go-cmp/cmp/internal/testprotos"
11 | )
12 |
13 | // This is an sanitized example of equality from a real use-case.
14 | // The original equality function was as follows:
15 | /*
16 | func equalCartel(x, y Cartel) bool {
17 | if !(equalHeadquarter(x.Headquarter, y.Headquarter) &&
18 | x.Source() == y.Source() &&
19 | x.CreationDate().Equal(y.CreationDate()) &&
20 | x.Boss() == y.Boss() &&
21 | x.LastCrimeDate().Equal(y.LastCrimeDate())) {
22 | return false
23 | }
24 | if len(x.Poisons()) != len(y.Poisons()) {
25 | return false
26 | }
27 | for i := range x.Poisons() {
28 | if !equalPoison(*x.Poisons()[i], *y.Poisons()[i]) {
29 | return false
30 | }
31 | }
32 | return true
33 | }
34 | func equalHeadquarter(x, y Headquarter) bool {
35 | xr, yr := x.Restrictions(), y.Restrictions()
36 | return x.ID() == y.ID() &&
37 | x.Location() == y.Location() &&
38 | reflect.DeepEqual(x.SubDivisions(), y.SubDivisions()) &&
39 | x.IncorporatedDate().Equal(y.IncorporatedDate()) &&
40 | pb.Equal(x.MetaData(), y.MetaData()) &&
41 | bytes.Equal(x.PrivateMessage(), y.PrivateMessage()) &&
42 | bytes.Equal(x.PublicMessage(), y.PublicMessage()) &&
43 | x.HorseBack() == y.HorseBack() &&
44 | x.Rattle() == y.Rattle() &&
45 | x.Convulsion() == y.Convulsion() &&
46 | x.Expansion() == y.Expansion() &&
47 | x.Status() == y.Status() &&
48 | pb.Equal(&xr, &yr) &&
49 | x.CreationTime().Equal(y.CreationTime())
50 | }
51 | func equalPoison(x, y Poison) bool {
52 | return x.PoisonType() == y.PoisonType() &&
53 | x.Expiration().Equal(y.Expiration()) &&
54 | x.Manufacturer() == y.Manufacturer() &&
55 | x.Potency() == y.Potency()
56 | }
57 | */
58 |
59 | type Cartel struct {
60 | Headquarter
61 | source string
62 | creationDate time.Time
63 | boss string
64 | lastCrimeDate time.Time
65 | poisons []*Poison
66 | }
67 |
68 | func (p Cartel) Source() string { return p.source }
69 | func (p Cartel) CreationDate() time.Time { return p.creationDate }
70 | func (p Cartel) Boss() string { return p.boss }
71 | func (p Cartel) LastCrimeDate() time.Time { return p.lastCrimeDate }
72 | func (p Cartel) Poisons() []*Poison { return p.poisons }
73 |
74 | func (p *Cartel) SetSource(x string) { p.source = x }
75 | func (p *Cartel) SetCreationDate(x time.Time) { p.creationDate = x }
76 | func (p *Cartel) SetBoss(x string) { p.boss = x }
77 | func (p *Cartel) SetLastCrimeDate(x time.Time) { p.lastCrimeDate = x }
78 | func (p *Cartel) SetPoisons(x []*Poison) { p.poisons = x }
79 |
80 | type Headquarter struct {
81 | id uint64
82 | location string
83 | subDivisions []string
84 | incorporatedDate time.Time
85 | metaData *pb.MetaData
86 | privateMessage []byte
87 | publicMessage []byte
88 | horseBack string
89 | rattle string
90 | convulsion bool
91 | expansion uint64
92 | status pb.HoneyStatus
93 | restrictions pb.Restrictions
94 | creationTime time.Time
95 | }
96 |
97 | func (hq Headquarter) ID() uint64 { return hq.id }
98 | func (hq Headquarter) Location() string { return hq.location }
99 | func (hq Headquarter) SubDivisions() []string { return hq.subDivisions }
100 | func (hq Headquarter) IncorporatedDate() time.Time { return hq.incorporatedDate }
101 | func (hq Headquarter) MetaData() *pb.MetaData { return hq.metaData }
102 | func (hq Headquarter) PrivateMessage() []byte { return hq.privateMessage }
103 | func (hq Headquarter) PublicMessage() []byte { return hq.publicMessage }
104 | func (hq Headquarter) HorseBack() string { return hq.horseBack }
105 | func (hq Headquarter) Rattle() string { return hq.rattle }
106 | func (hq Headquarter) Convulsion() bool { return hq.convulsion }
107 | func (hq Headquarter) Expansion() uint64 { return hq.expansion }
108 | func (hq Headquarter) Status() pb.HoneyStatus { return hq.status }
109 | func (hq Headquarter) Restrictions() pb.Restrictions { return hq.restrictions }
110 | func (hq Headquarter) CreationTime() time.Time { return hq.creationTime }
111 |
112 | func (hq *Headquarter) SetID(x uint64) { hq.id = x }
113 | func (hq *Headquarter) SetLocation(x string) { hq.location = x }
114 | func (hq *Headquarter) SetSubDivisions(x []string) { hq.subDivisions = x }
115 | func (hq *Headquarter) SetIncorporatedDate(x time.Time) { hq.incorporatedDate = x }
116 | func (hq *Headquarter) SetMetaData(x *pb.MetaData) { hq.metaData = x }
117 | func (hq *Headquarter) SetPrivateMessage(x []byte) { hq.privateMessage = x }
118 | func (hq *Headquarter) SetPublicMessage(x []byte) { hq.publicMessage = x }
119 | func (hq *Headquarter) SetHorseBack(x string) { hq.horseBack = x }
120 | func (hq *Headquarter) SetRattle(x string) { hq.rattle = x }
121 | func (hq *Headquarter) SetConvulsion(x bool) { hq.convulsion = x }
122 | func (hq *Headquarter) SetExpansion(x uint64) { hq.expansion = x }
123 | func (hq *Headquarter) SetStatus(x pb.HoneyStatus) { hq.status = x }
124 | func (hq *Headquarter) SetRestrictions(x pb.Restrictions) { hq.restrictions = x }
125 | func (hq *Headquarter) SetCreationTime(x time.Time) { hq.creationTime = x }
126 |
127 | type Poison struct {
128 | poisonType pb.PoisonType
129 | expiration time.Time
130 | manufacturer string
131 | potency int
132 | }
133 |
134 | func (p Poison) PoisonType() pb.PoisonType { return p.poisonType }
135 | func (p Poison) Expiration() time.Time { return p.expiration }
136 | func (p Poison) Manufacturer() string { return p.manufacturer }
137 | func (p Poison) Potency() int { return p.potency }
138 |
139 | func (p *Poison) SetPoisonType(x pb.PoisonType) { p.poisonType = x }
140 | func (p *Poison) SetExpiration(x time.Time) { p.expiration = x }
141 | func (p *Poison) SetManufacturer(x string) { p.manufacturer = x }
142 | func (p *Poison) SetPotency(x int) { p.potency = x }
143 |
--------------------------------------------------------------------------------
/cmp/internal/teststructs/structs.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package teststructs
6 |
7 | type InterfaceA interface {
8 | InterfaceA()
9 | }
10 |
11 | type (
12 | StructA struct{ X string } // Equal method on value receiver
13 | StructB struct{ X string } // Equal method on pointer receiver
14 | StructC struct{ X string } // Equal method (with interface argument) on value receiver
15 | StructD struct{ X string } // Equal method (with interface argument) on pointer receiver
16 | StructE struct{ X string } // Equal method (with interface argument on value receiver) on pointer receiver
17 | StructF struct{ X string } // Equal method (with interface argument on pointer receiver) on value receiver
18 |
19 | // These embed the above types as a value.
20 | StructA1 struct {
21 | StructA
22 | X string
23 | }
24 | StructB1 struct {
25 | StructB
26 | X string
27 | }
28 | StructC1 struct {
29 | StructC
30 | X string
31 | }
32 | StructD1 struct {
33 | StructD
34 | X string
35 | }
36 | StructE1 struct {
37 | StructE
38 | X string
39 | }
40 | StructF1 struct {
41 | StructF
42 | X string
43 | }
44 |
45 | // These embed the above types as a pointer.
46 | StructA2 struct {
47 | *StructA
48 | X string
49 | }
50 | StructB2 struct {
51 | *StructB
52 | X string
53 | }
54 | StructC2 struct {
55 | *StructC
56 | X string
57 | }
58 | StructD2 struct {
59 | *StructD
60 | X string
61 | }
62 | StructE2 struct {
63 | *StructE
64 | X string
65 | }
66 | StructF2 struct {
67 | *StructF
68 | X string
69 | }
70 |
71 | StructNo struct{ X string } // Equal method (with interface argument) on non-satisfying receiver
72 |
73 | AssignA func() int
74 | AssignB struct{ A int }
75 | AssignC chan bool
76 | AssignD <-chan bool
77 | )
78 |
79 | func (x StructA) Equal(y StructA) bool { return true }
80 | func (x *StructB) Equal(y *StructB) bool { return true }
81 | func (x StructC) Equal(y InterfaceA) bool { return true }
82 | func (x StructC) InterfaceA() {}
83 | func (x *StructD) Equal(y InterfaceA) bool { return true }
84 | func (x *StructD) InterfaceA() {}
85 | func (x *StructE) Equal(y InterfaceA) bool { return true }
86 | func (x StructE) InterfaceA() {}
87 | func (x StructF) Equal(y InterfaceA) bool { return true }
88 | func (x *StructF) InterfaceA() {}
89 | func (x StructNo) Equal(y InterfaceA) bool { return true }
90 |
91 | func (x AssignA) Equal(y func() int) bool { return true }
92 | func (x AssignB) Equal(y struct{ A int }) bool { return true }
93 | func (x AssignC) Equal(y chan bool) bool { return true }
94 | func (x AssignD) Equal(y <-chan bool) bool { return true }
95 |
96 | var _ = func(
97 | a StructA, b StructB, c StructC, d StructD, e StructE, f StructF,
98 | ap *StructA, bp *StructB, cp *StructC, dp *StructD, ep *StructE, fp *StructF,
99 | a1 StructA1, b1 StructB1, c1 StructC1, d1 StructD1, e1 StructE1, f1 StructF1,
100 | a2 StructA2, b2 StructB2, c2 StructC2, d2 StructD2, e2 StructE2, f2 StructF1,
101 | ) {
102 | a.Equal(a)
103 | b.Equal(&b)
104 | c.Equal(c)
105 | d.Equal(&d)
106 | e.Equal(e)
107 | f.Equal(&f)
108 |
109 | ap.Equal(*ap)
110 | bp.Equal(bp)
111 | cp.Equal(*cp)
112 | dp.Equal(dp)
113 | ep.Equal(*ep)
114 | fp.Equal(fp)
115 |
116 | a1.Equal(a1.StructA)
117 | b1.Equal(&b1.StructB)
118 | c1.Equal(c1)
119 | d1.Equal(&d1)
120 | e1.Equal(e1)
121 | f1.Equal(&f1)
122 |
123 | a2.Equal(*a2.StructA)
124 | b2.Equal(b2.StructB)
125 | c2.Equal(c2)
126 | d2.Equal(&d2)
127 | e2.Equal(e2)
128 | f2.Equal(&f2)
129 | }
130 |
131 | type (
132 | privateStruct struct{ Public, private int }
133 | PublicStruct struct{ Public, private int }
134 | ParentStructA struct{ privateStruct }
135 | ParentStructB struct{ PublicStruct }
136 | ParentStructC struct {
137 | privateStruct
138 | Public, private int
139 | }
140 | ParentStructD struct {
141 | PublicStruct
142 | Public, private int
143 | }
144 | ParentStructE struct {
145 | privateStruct
146 | PublicStruct
147 | }
148 | ParentStructF struct {
149 | privateStruct
150 | PublicStruct
151 | Public, private int
152 | }
153 | ParentStructG struct {
154 | *privateStruct
155 | }
156 | ParentStructH struct {
157 | *PublicStruct
158 | }
159 | ParentStructI struct {
160 | *privateStruct
161 | *PublicStruct
162 | }
163 | ParentStructJ struct {
164 | *privateStruct
165 | *PublicStruct
166 | Public PublicStruct
167 | private privateStruct
168 | }
169 | )
170 |
171 | func NewParentStructG() *ParentStructG {
172 | return &ParentStructG{new(privateStruct)}
173 | }
174 | func NewParentStructH() *ParentStructH {
175 | return &ParentStructH{new(PublicStruct)}
176 | }
177 | func NewParentStructI() *ParentStructI {
178 | return &ParentStructI{new(privateStruct), new(PublicStruct)}
179 | }
180 | func NewParentStructJ() *ParentStructJ {
181 | return &ParentStructJ{
182 | privateStruct: new(privateStruct), PublicStruct: new(PublicStruct),
183 | }
184 | }
185 | func (s *privateStruct) SetPrivate(i int) { s.private = i }
186 | func (s *PublicStruct) SetPrivate(i int) { s.private = i }
187 | func (s *ParentStructC) SetPrivate(i int) { s.private = i }
188 | func (s *ParentStructD) SetPrivate(i int) { s.private = i }
189 | func (s *ParentStructF) SetPrivate(i int) { s.private = i }
190 | func (s *ParentStructA) PrivateStruct() *privateStruct { return &s.privateStruct }
191 | func (s *ParentStructC) PrivateStruct() *privateStruct { return &s.privateStruct }
192 | func (s *ParentStructE) PrivateStruct() *privateStruct { return &s.privateStruct }
193 | func (s *ParentStructF) PrivateStruct() *privateStruct { return &s.privateStruct }
194 | func (s *ParentStructG) PrivateStruct() *privateStruct { return s.privateStruct }
195 | func (s *ParentStructI) PrivateStruct() *privateStruct { return s.privateStruct }
196 | func (s *ParentStructJ) PrivateStruct() *privateStruct { return s.privateStruct }
197 | func (s *ParentStructJ) Private() *privateStruct { return &s.private }
198 |
--------------------------------------------------------------------------------
/cmp/internal/value/name.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package value
6 |
7 | import (
8 | "reflect"
9 | "strconv"
10 | )
11 |
12 | var anyType = reflect.TypeOf((*interface{})(nil)).Elem()
13 |
14 | // TypeString is nearly identical to reflect.Type.String,
15 | // but has an additional option to specify that full type names be used.
16 | func TypeString(t reflect.Type, qualified bool) string {
17 | return string(appendTypeName(nil, t, qualified, false))
18 | }
19 |
20 | func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte {
21 | // BUG: Go reflection provides no way to disambiguate two named types
22 | // of the same name and within the same package,
23 | // but declared within the namespace of different functions.
24 |
25 | // Use the "any" alias instead of "interface{}" for better readability.
26 | if t == anyType {
27 | return append(b, "any"...)
28 | }
29 |
30 | // Named type.
31 | if t.Name() != "" {
32 | if qualified && t.PkgPath() != "" {
33 | b = append(b, '"')
34 | b = append(b, t.PkgPath()...)
35 | b = append(b, '"')
36 | b = append(b, '.')
37 | b = append(b, t.Name()...)
38 | } else {
39 | b = append(b, t.String()...)
40 | }
41 | return b
42 | }
43 |
44 | // Unnamed type.
45 | switch k := t.Kind(); k {
46 | case reflect.Bool, reflect.String, reflect.UnsafePointer,
47 | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
48 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
49 | reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
50 | b = append(b, k.String()...)
51 | case reflect.Chan:
52 | if t.ChanDir() == reflect.RecvDir {
53 | b = append(b, "<-"...)
54 | }
55 | b = append(b, "chan"...)
56 | if t.ChanDir() == reflect.SendDir {
57 | b = append(b, "<-"...)
58 | }
59 | b = append(b, ' ')
60 | b = appendTypeName(b, t.Elem(), qualified, false)
61 | case reflect.Func:
62 | if !elideFunc {
63 | b = append(b, "func"...)
64 | }
65 | b = append(b, '(')
66 | for i := 0; i < t.NumIn(); i++ {
67 | if i > 0 {
68 | b = append(b, ", "...)
69 | }
70 | if i == t.NumIn()-1 && t.IsVariadic() {
71 | b = append(b, "..."...)
72 | b = appendTypeName(b, t.In(i).Elem(), qualified, false)
73 | } else {
74 | b = appendTypeName(b, t.In(i), qualified, false)
75 | }
76 | }
77 | b = append(b, ')')
78 | switch t.NumOut() {
79 | case 0:
80 | // Do nothing
81 | case 1:
82 | b = append(b, ' ')
83 | b = appendTypeName(b, t.Out(0), qualified, false)
84 | default:
85 | b = append(b, " ("...)
86 | for i := 0; i < t.NumOut(); i++ {
87 | if i > 0 {
88 | b = append(b, ", "...)
89 | }
90 | b = appendTypeName(b, t.Out(i), qualified, false)
91 | }
92 | b = append(b, ')')
93 | }
94 | case reflect.Struct:
95 | b = append(b, "struct{ "...)
96 | for i := 0; i < t.NumField(); i++ {
97 | if i > 0 {
98 | b = append(b, "; "...)
99 | }
100 | sf := t.Field(i)
101 | if !sf.Anonymous {
102 | if qualified && sf.PkgPath != "" {
103 | b = append(b, '"')
104 | b = append(b, sf.PkgPath...)
105 | b = append(b, '"')
106 | b = append(b, '.')
107 | }
108 | b = append(b, sf.Name...)
109 | b = append(b, ' ')
110 | }
111 | b = appendTypeName(b, sf.Type, qualified, false)
112 | if sf.Tag != "" {
113 | b = append(b, ' ')
114 | b = strconv.AppendQuote(b, string(sf.Tag))
115 | }
116 | }
117 | if b[len(b)-1] == ' ' {
118 | b = b[:len(b)-1]
119 | } else {
120 | b = append(b, ' ')
121 | }
122 | b = append(b, '}')
123 | case reflect.Slice, reflect.Array:
124 | b = append(b, '[')
125 | if k == reflect.Array {
126 | b = strconv.AppendUint(b, uint64(t.Len()), 10)
127 | }
128 | b = append(b, ']')
129 | b = appendTypeName(b, t.Elem(), qualified, false)
130 | case reflect.Map:
131 | b = append(b, "map["...)
132 | b = appendTypeName(b, t.Key(), qualified, false)
133 | b = append(b, ']')
134 | b = appendTypeName(b, t.Elem(), qualified, false)
135 | case reflect.Ptr:
136 | b = append(b, '*')
137 | b = appendTypeName(b, t.Elem(), qualified, false)
138 | case reflect.Interface:
139 | b = append(b, "interface{ "...)
140 | for i := 0; i < t.NumMethod(); i++ {
141 | if i > 0 {
142 | b = append(b, "; "...)
143 | }
144 | m := t.Method(i)
145 | if qualified && m.PkgPath != "" {
146 | b = append(b, '"')
147 | b = append(b, m.PkgPath...)
148 | b = append(b, '"')
149 | b = append(b, '.')
150 | }
151 | b = append(b, m.Name...)
152 | b = appendTypeName(b, m.Type, qualified, true)
153 | }
154 | if b[len(b)-1] == ' ' {
155 | b = b[:len(b)-1]
156 | } else {
157 | b = append(b, ' ')
158 | }
159 | b = append(b, '}')
160 | default:
161 | panic("invalid kind: " + k.String())
162 | }
163 | return b
164 | }
165 |
--------------------------------------------------------------------------------
/cmp/internal/value/name_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package value
6 |
7 | import (
8 | "reflect"
9 | "strings"
10 | "testing"
11 | )
12 |
13 | type Named struct{}
14 |
15 | var pkgPath = reflect.TypeOf(Named{}).PkgPath()
16 |
17 | func TestTypeString(t *testing.T) {
18 | tests := []struct {
19 | in interface{}
20 | want string
21 | }{{
22 | in: bool(false),
23 | want: "bool",
24 | }, {
25 | in: int(0),
26 | want: "int",
27 | }, {
28 | in: float64(0),
29 | want: "float64",
30 | }, {
31 | in: string(""),
32 | want: "string",
33 | }, {
34 | in: Named{},
35 | want: "$PackagePath.Named",
36 | }, {
37 | in: (chan Named)(nil),
38 | want: "chan $PackagePath.Named",
39 | }, {
40 | in: (<-chan Named)(nil),
41 | want: "<-chan $PackagePath.Named",
42 | }, {
43 | in: (chan<- Named)(nil),
44 | want: "chan<- $PackagePath.Named",
45 | }, {
46 | in: (func())(nil),
47 | want: "func()",
48 | }, {
49 | in: (func(Named))(nil),
50 | want: "func($PackagePath.Named)",
51 | }, {
52 | in: (func() Named)(nil),
53 | want: "func() $PackagePath.Named",
54 | }, {
55 | in: (func(int, Named) (int, error))(nil),
56 | want: "func(int, $PackagePath.Named) (int, error)",
57 | }, {
58 | in: (func(...Named))(nil),
59 | want: "func(...$PackagePath.Named)",
60 | }, {
61 | in: struct{}{},
62 | want: "struct{}",
63 | }, {
64 | in: struct{ Named }{},
65 | want: "struct{ $PackagePath.Named }",
66 | }, {
67 | in: struct {
68 | Named `tag`
69 | }{},
70 | want: "struct{ $PackagePath.Named \"tag\" }",
71 | }, {
72 | in: struct{ Named Named }{},
73 | want: "struct{ Named $PackagePath.Named }",
74 | }, {
75 | in: struct {
76 | Named Named `tag`
77 | }{},
78 | want: "struct{ Named $PackagePath.Named \"tag\" }",
79 | }, {
80 | in: struct {
81 | Int int
82 | Named Named
83 | }{},
84 | want: "struct{ Int int; Named $PackagePath.Named }",
85 | }, {
86 | in: struct {
87 | _ int
88 | x Named
89 | }{},
90 | want: "struct{ $FieldPrefix._ int; $FieldPrefix.x $PackagePath.Named }",
91 | }, {
92 | in: []Named(nil),
93 | want: "[]$PackagePath.Named",
94 | }, {
95 | in: []*Named(nil),
96 | want: "[]*$PackagePath.Named",
97 | }, {
98 | in: [10]Named{},
99 | want: "[10]$PackagePath.Named",
100 | }, {
101 | in: [10]*Named{},
102 | want: "[10]*$PackagePath.Named",
103 | }, {
104 | in: map[string]string(nil),
105 | want: "map[string]string",
106 | }, {
107 | in: map[Named]Named(nil),
108 | want: "map[$PackagePath.Named]$PackagePath.Named",
109 | }, {
110 | in: (*Named)(nil),
111 | want: "*$PackagePath.Named",
112 | }, {
113 | in: (*interface{})(nil),
114 | want: "*any",
115 | }, {
116 | in: (*interface{ Read([]byte) (int, error) })(nil),
117 | want: "*interface{ Read([]uint8) (int, error) }",
118 | }, {
119 | in: (*interface {
120 | F1()
121 | F2(Named)
122 | F3() Named
123 | F4(int, Named) (int, error)
124 | F5(...Named)
125 | })(nil),
126 | want: "*interface{ F1(); F2($PackagePath.Named); F3() $PackagePath.Named; F4(int, $PackagePath.Named) (int, error); F5(...$PackagePath.Named) }",
127 | }}
128 |
129 | for _, tt := range tests {
130 | typ := reflect.TypeOf(tt.in)
131 | wantShort := tt.want
132 | wantShort = strings.Replace(wantShort, "$PackagePath", "value", -1)
133 | wantShort = strings.Replace(wantShort, "$FieldPrefix.", "", -1)
134 | if gotShort := TypeString(typ, false); gotShort != wantShort {
135 | t.Errorf("TypeString(%v, false) mismatch:\ngot: %v\nwant: %v", typ, gotShort, wantShort)
136 | }
137 | wantQualified := tt.want
138 | wantQualified = strings.Replace(wantQualified, "$PackagePath", `"`+pkgPath+`"`, -1)
139 | wantQualified = strings.Replace(wantQualified, "$FieldPrefix", `"`+pkgPath+`"`, -1)
140 | if gotQualified := TypeString(typ, true); gotQualified != wantQualified {
141 | t.Errorf("TypeString(%v, true) mismatch:\ngot: %v\nwant: %v", typ, gotQualified, wantQualified)
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/cmp/internal/value/pointer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package value
6 |
7 | import (
8 | "reflect"
9 | "unsafe"
10 | )
11 |
12 | // Pointer is an opaque typed pointer and is guaranteed to be comparable.
13 | type Pointer struct {
14 | p unsafe.Pointer
15 | t reflect.Type
16 | }
17 |
18 | // PointerOf returns a Pointer from v, which must be a
19 | // reflect.Ptr, reflect.Slice, or reflect.Map.
20 | func PointerOf(v reflect.Value) Pointer {
21 | // The proper representation of a pointer is unsafe.Pointer,
22 | // which is necessary if the GC ever uses a moving collector.
23 | return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
24 | }
25 |
26 | // IsNil reports whether the pointer is nil.
27 | func (p Pointer) IsNil() bool {
28 | return p.p == nil
29 | }
30 |
31 | // Uintptr returns the pointer as a uintptr.
32 | func (p Pointer) Uintptr() uintptr {
33 | return uintptr(p.p)
34 | }
35 |
--------------------------------------------------------------------------------
/cmp/internal/value/sort.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package value
6 |
7 | import (
8 | "fmt"
9 | "math"
10 | "reflect"
11 | "sort"
12 | )
13 |
14 | // SortKeys sorts a list of map keys, deduplicating keys if necessary.
15 | // The type of each value must be comparable.
16 | func SortKeys(vs []reflect.Value) []reflect.Value {
17 | if len(vs) == 0 {
18 | return vs
19 | }
20 |
21 | // Sort the map keys.
22 | sort.SliceStable(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) })
23 |
24 | // Deduplicate keys (fails for NaNs).
25 | vs2 := vs[:1]
26 | for _, v := range vs[1:] {
27 | if isLess(vs2[len(vs2)-1], v) {
28 | vs2 = append(vs2, v)
29 | }
30 | }
31 | return vs2
32 | }
33 |
34 | // isLess is a generic function for sorting arbitrary map keys.
35 | // The inputs must be of the same type and must be comparable.
36 | func isLess(x, y reflect.Value) bool {
37 | switch x.Type().Kind() {
38 | case reflect.Bool:
39 | return !x.Bool() && y.Bool()
40 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
41 | return x.Int() < y.Int()
42 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
43 | return x.Uint() < y.Uint()
44 | case reflect.Float32, reflect.Float64:
45 | // NOTE: This does not sort -0 as less than +0
46 | // since Go maps treat -0 and +0 as equal keys.
47 | fx, fy := x.Float(), y.Float()
48 | return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
49 | case reflect.Complex64, reflect.Complex128:
50 | cx, cy := x.Complex(), y.Complex()
51 | rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
52 | if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
53 | return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
54 | }
55 | return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
56 | case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
57 | return x.Pointer() < y.Pointer()
58 | case reflect.String:
59 | return x.String() < y.String()
60 | case reflect.Array:
61 | for i := 0; i < x.Len(); i++ {
62 | if isLess(x.Index(i), y.Index(i)) {
63 | return true
64 | }
65 | if isLess(y.Index(i), x.Index(i)) {
66 | return false
67 | }
68 | }
69 | return false
70 | case reflect.Struct:
71 | for i := 0; i < x.NumField(); i++ {
72 | if isLess(x.Field(i), y.Field(i)) {
73 | return true
74 | }
75 | if isLess(y.Field(i), x.Field(i)) {
76 | return false
77 | }
78 | }
79 | return false
80 | case reflect.Interface:
81 | vx, vy := x.Elem(), y.Elem()
82 | if !vx.IsValid() || !vy.IsValid() {
83 | return !vx.IsValid() && vy.IsValid()
84 | }
85 | tx, ty := vx.Type(), vy.Type()
86 | if tx == ty {
87 | return isLess(x.Elem(), y.Elem())
88 | }
89 | if tx.Kind() != ty.Kind() {
90 | return vx.Kind() < vy.Kind()
91 | }
92 | if tx.String() != ty.String() {
93 | return tx.String() < ty.String()
94 | }
95 | if tx.PkgPath() != ty.PkgPath() {
96 | return tx.PkgPath() < ty.PkgPath()
97 | }
98 | // This can happen in rare situations, so we fallback to just comparing
99 | // the unique pointer for a reflect.Type. This guarantees deterministic
100 | // ordering within a program, but it is obviously not stable.
101 | return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
102 | default:
103 | // Must be Func, Map, or Slice; which are not comparable.
104 | panic(fmt.Sprintf("%T is not comparable", x.Type()))
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/cmp/internal/value/sort_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package value_test
6 |
7 | import (
8 | "math"
9 | "reflect"
10 | "testing"
11 |
12 | "github.com/google/go-cmp/cmp"
13 | "github.com/google/go-cmp/cmp/internal/value"
14 | )
15 |
16 | func TestSortKeys(t *testing.T) {
17 | type (
18 | MyString string
19 | MyArray [2]int
20 | MyStruct struct {
21 | A MyString
22 | B MyArray
23 | C chan float64
24 | }
25 | EmptyStruct struct{}
26 | )
27 |
28 | opts := []cmp.Option{
29 | cmp.Comparer(func(x, y float64) bool {
30 | if math.IsNaN(x) && math.IsNaN(y) {
31 | return true
32 | }
33 | return x == y
34 | }),
35 | cmp.Comparer(func(x, y complex128) bool {
36 | rx, ix, ry, iy := real(x), imag(x), real(y), imag(y)
37 | if math.IsNaN(rx) && math.IsNaN(ry) {
38 | rx, ry = 0, 0
39 | }
40 | if math.IsNaN(ix) && math.IsNaN(iy) {
41 | ix, iy = 0, 0
42 | }
43 | return rx == ry && ix == iy
44 | }),
45 | cmp.Comparer(func(x, y chan bool) bool { return true }),
46 | cmp.Comparer(func(x, y chan int) bool { return true }),
47 | cmp.Comparer(func(x, y chan float64) bool { return true }),
48 | cmp.Comparer(func(x, y chan interface{}) bool { return true }),
49 | cmp.Comparer(func(x, y *int) bool { return true }),
50 | }
51 |
52 | tests := []struct {
53 | in map[interface{}]bool // Set of keys to sort
54 | want []interface{}
55 | }{{
56 | in: map[interface{}]bool{1: true, 2: true, 3: true},
57 | want: []interface{}{1, 2, 3},
58 | }, {
59 | in: map[interface{}]bool{
60 | nil: true,
61 | true: true,
62 | false: true,
63 | -5: true,
64 | -55: true,
65 | -555: true,
66 | uint(1): true,
67 | uint(11): true,
68 | uint(111): true,
69 | "abc": true,
70 | "abcd": true,
71 | "abcde": true,
72 | "foo": true,
73 | "bar": true,
74 | MyString("abc"): true,
75 | MyString("abcd"): true,
76 | MyString("abcde"): true,
77 | new(int): true,
78 | new(int): true,
79 | make(chan bool): true,
80 | make(chan bool): true,
81 | make(chan int): true,
82 | make(chan interface{}): true,
83 | math.Inf(+1): true,
84 | math.Inf(-1): true,
85 | 1.2345: true,
86 | 12.345: true,
87 | 123.45: true,
88 | 1234.5: true,
89 | 0 + 0i: true,
90 | 1 + 0i: true,
91 | 2 + 0i: true,
92 | 0 + 1i: true,
93 | 0 + 2i: true,
94 | 0 + 3i: true,
95 | [2]int{2, 3}: true,
96 | [2]int{4, 0}: true,
97 | [2]int{2, 4}: true,
98 | MyArray([2]int{2, 4}): true,
99 | EmptyStruct{}: true,
100 | MyStruct{
101 | "bravo", [2]int{2, 3}, make(chan float64),
102 | }: true,
103 | MyStruct{
104 | "alpha", [2]int{3, 3}, make(chan float64),
105 | }: true,
106 | },
107 | want: []interface{}{
108 | nil, false, true,
109 | -555, -55, -5, uint(1), uint(11), uint(111),
110 | math.Inf(-1), 1.2345, 12.345, 123.45, 1234.5, math.Inf(+1),
111 | (0 + 0i), (0 + 1i), (0 + 2i), (0 + 3i), (1 + 0i), (2 + 0i),
112 | [2]int{2, 3}, [2]int{2, 4}, [2]int{4, 0}, MyArray([2]int{2, 4}),
113 | make(chan bool), make(chan bool), make(chan int), make(chan interface{}),
114 | new(int), new(int),
115 | "abc", "abcd", "abcde", "bar", "foo",
116 | MyString("abc"), MyString("abcd"), MyString("abcde"),
117 | EmptyStruct{},
118 | MyStruct{"alpha", [2]int{3, 3}, make(chan float64)},
119 | MyStruct{"bravo", [2]int{2, 3}, make(chan float64)},
120 | },
121 | }, {
122 | // NaN values cannot be properly deduplicated.
123 | // This is okay since map entries with NaN in the keys cannot be
124 | // retrieved anyways.
125 | in: map[interface{}]bool{
126 | math.NaN(): true,
127 | math.NaN(): true,
128 | complex(0, math.NaN()): true,
129 | complex(0, math.NaN()): true,
130 | complex(math.NaN(), 0): true,
131 | complex(math.NaN(), 0): true,
132 | complex(math.NaN(), math.NaN()): true,
133 | },
134 | want: []interface{}{
135 | math.NaN(),
136 | complex(math.NaN(), math.NaN()),
137 | complex(math.NaN(), 0),
138 | complex(0, math.NaN()),
139 | },
140 | }}
141 |
142 | for i, tt := range tests {
143 | // Intentionally pass the map via an unexported field to detect panics.
144 | // Unfortunately, we cannot actually test the keys without using unsafe.
145 | v := reflect.ValueOf(struct{ x map[interface{}]bool }{tt.in}).Field(0)
146 | value.SortKeys(append(v.MapKeys(), v.MapKeys()...))
147 |
148 | // Try again, with keys that have read-write access in reflect.
149 | v = reflect.ValueOf(tt.in)
150 | keys := append(v.MapKeys(), v.MapKeys()...)
151 | var got []interface{}
152 | for _, k := range value.SortKeys(keys) {
153 | got = append(got, k.Interface())
154 | }
155 | if d := cmp.Diff(got, tt.want, opts...); d != "" {
156 | t.Errorf("test %d, Sort() mismatch (-got +want):\n%s", i, d)
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/cmp/options_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp
6 |
7 | import (
8 | "io"
9 | "reflect"
10 | "strings"
11 | "testing"
12 |
13 | ts "github.com/google/go-cmp/cmp/internal/teststructs"
14 | )
15 |
16 | // Test that the creation of Option values with non-sensible inputs produces
17 | // a run-time panic with a decent error message
18 | func TestOptionPanic(t *testing.T) {
19 | type myBool bool
20 | tests := []struct {
21 | label string // Test description
22 | fnc interface{} // Option function to call
23 | args []interface{} // Arguments to pass in
24 | wantPanic string // Expected panic message
25 | }{{
26 | label: "AllowUnexported",
27 | fnc: AllowUnexported,
28 | args: []interface{}{},
29 | }, {
30 | label: "AllowUnexported",
31 | fnc: AllowUnexported,
32 | args: []interface{}{1},
33 | wantPanic: "invalid struct type",
34 | }, {
35 | label: "AllowUnexported",
36 | fnc: AllowUnexported,
37 | args: []interface{}{ts.StructA{}},
38 | }, {
39 | label: "AllowUnexported",
40 | fnc: AllowUnexported,
41 | args: []interface{}{ts.StructA{}, ts.StructB{}, ts.StructA{}},
42 | }, {
43 | label: "AllowUnexported",
44 | fnc: AllowUnexported,
45 | args: []interface{}{ts.StructA{}, &ts.StructB{}, ts.StructA{}},
46 | wantPanic: "invalid struct type",
47 | }, {
48 | label: "Comparer",
49 | fnc: Comparer,
50 | args: []interface{}{5},
51 | wantPanic: "invalid comparer function",
52 | }, {
53 | label: "Comparer",
54 | fnc: Comparer,
55 | args: []interface{}{func(x, y interface{}) bool { return true }},
56 | }, {
57 | label: "Comparer",
58 | fnc: Comparer,
59 | args: []interface{}{func(x, y io.Reader) bool { return true }},
60 | }, {
61 | label: "Comparer",
62 | fnc: Comparer,
63 | args: []interface{}{func(x, y io.Reader) myBool { return true }},
64 | wantPanic: "invalid comparer function",
65 | }, {
66 | label: "Comparer",
67 | fnc: Comparer,
68 | args: []interface{}{func(x string, y interface{}) bool { return true }},
69 | wantPanic: "invalid comparer function",
70 | }, {
71 | label: "Comparer",
72 | fnc: Comparer,
73 | args: []interface{}{(func(int, int) bool)(nil)},
74 | wantPanic: "invalid comparer function",
75 | }, {
76 | label: "Transformer",
77 | fnc: Transformer,
78 | args: []interface{}{"", 0},
79 | wantPanic: "invalid transformer function",
80 | }, {
81 | label: "Transformer",
82 | fnc: Transformer,
83 | args: []interface{}{"", func(int) int { return 0 }},
84 | }, {
85 | label: "Transformer",
86 | fnc: Transformer,
87 | args: []interface{}{"", func(bool) bool { return true }},
88 | }, {
89 | label: "Transformer",
90 | fnc: Transformer,
91 | args: []interface{}{"", func(int) bool { return true }},
92 | }, {
93 | label: "Transformer",
94 | fnc: Transformer,
95 | args: []interface{}{"", func(int, int) bool { return true }},
96 | wantPanic: "invalid transformer function",
97 | }, {
98 | label: "Transformer",
99 | fnc: Transformer,
100 | args: []interface{}{"", (func(int) uint)(nil)},
101 | wantPanic: "invalid transformer function",
102 | }, {
103 | label: "Transformer",
104 | fnc: Transformer,
105 | args: []interface{}{"Func", func(Path) Path { return nil }},
106 | }, {
107 | label: "Transformer",
108 | fnc: Transformer,
109 | args: []interface{}{"世界", func(int) bool { return true }},
110 | }, {
111 | label: "Transformer",
112 | fnc: Transformer,
113 | args: []interface{}{"/*", func(int) bool { return true }},
114 | wantPanic: "invalid name",
115 | }, {
116 | label: "Transformer",
117 | fnc: Transformer,
118 | args: []interface{}{"_", func(int) bool { return true }},
119 | }, {
120 | label: "FilterPath",
121 | fnc: FilterPath,
122 | args: []interface{}{(func(Path) bool)(nil), Ignore()},
123 | wantPanic: "invalid path filter function",
124 | }, {
125 | label: "FilterPath",
126 | fnc: FilterPath,
127 | args: []interface{}{func(Path) bool { return true }, Ignore()},
128 | }, {
129 | label: "FilterPath",
130 | fnc: FilterPath,
131 | args: []interface{}{func(Path) bool { return true }, Reporter(&defaultReporter{})},
132 | wantPanic: "invalid option type",
133 | }, {
134 | label: "FilterPath",
135 | fnc: FilterPath,
136 | args: []interface{}{func(Path) bool { return true }, Options{Ignore(), Ignore()}},
137 | }, {
138 | label: "FilterPath",
139 | fnc: FilterPath,
140 | args: []interface{}{func(Path) bool { return true }, Options{Ignore(), Reporter(&defaultReporter{})}},
141 | wantPanic: "invalid option type",
142 | }, {
143 | label: "FilterValues",
144 | fnc: FilterValues,
145 | args: []interface{}{0, Ignore()},
146 | wantPanic: "invalid values filter function",
147 | }, {
148 | label: "FilterValues",
149 | fnc: FilterValues,
150 | args: []interface{}{func(x, y int) bool { return true }, Ignore()},
151 | }, {
152 | label: "FilterValues",
153 | fnc: FilterValues,
154 | args: []interface{}{func(x, y interface{}) bool { return true }, Ignore()},
155 | }, {
156 | label: "FilterValues",
157 | fnc: FilterValues,
158 | args: []interface{}{func(x, y interface{}) myBool { return true }, Ignore()},
159 | wantPanic: "invalid values filter function",
160 | }, {
161 | label: "FilterValues",
162 | fnc: FilterValues,
163 | args: []interface{}{func(x io.Reader, y interface{}) bool { return true }, Ignore()},
164 | wantPanic: "invalid values filter function",
165 | }, {
166 | label: "FilterValues",
167 | fnc: FilterValues,
168 | args: []interface{}{(func(int, int) bool)(nil), Ignore()},
169 | wantPanic: "invalid values filter function",
170 | }, {
171 | label: "FilterValues",
172 | fnc: FilterValues,
173 | args: []interface{}{func(int, int) bool { return true }, Reporter(&defaultReporter{})},
174 | wantPanic: "invalid option type",
175 | }, {
176 | label: "FilterValues",
177 | fnc: FilterValues,
178 | args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), Ignore()}},
179 | }, {
180 | label: "FilterValues",
181 | fnc: FilterValues,
182 | args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), Reporter(&defaultReporter{})}},
183 | wantPanic: "invalid option type",
184 | }}
185 |
186 | for _, tt := range tests {
187 | t.Run(tt.label, func(t *testing.T) {
188 | var gotPanic string
189 | func() {
190 | defer func() {
191 | if ex := recover(); ex != nil {
192 | if s, ok := ex.(string); ok {
193 | gotPanic = s
194 | } else {
195 | panic(ex)
196 | }
197 | }
198 | }()
199 | var vargs []reflect.Value
200 | for _, arg := range tt.args {
201 | vargs = append(vargs, reflect.ValueOf(arg))
202 | }
203 | reflect.ValueOf(tt.fnc).Call(vargs)
204 | }()
205 | if tt.wantPanic == "" {
206 | if gotPanic != "" {
207 | t.Fatalf("unexpected panic message: %s", gotPanic)
208 | }
209 | } else {
210 | if !strings.Contains(gotPanic, tt.wantPanic) {
211 | t.Fatalf("panic message:\ngot: %s\nwant: %s", gotPanic, tt.wantPanic)
212 | }
213 | }
214 | })
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/cmp/path.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp
6 |
7 | import (
8 | "fmt"
9 | "reflect"
10 | "strings"
11 | "unicode"
12 | "unicode/utf8"
13 |
14 | "github.com/google/go-cmp/cmp/internal/value"
15 | )
16 |
17 | // Path is a list of [PathStep] describing the sequence of operations to get
18 | // from some root type to the current position in the value tree.
19 | // The first Path element is always an operation-less [PathStep] that exists
20 | // simply to identify the initial type.
21 | //
22 | // When traversing structs with embedded structs, the embedded struct will
23 | // always be accessed as a field before traversing the fields of the
24 | // embedded struct themselves. That is, an exported field from the
25 | // embedded struct will never be accessed directly from the parent struct.
26 | type Path []PathStep
27 |
28 | // PathStep is a union-type for specific operations to traverse
29 | // a value's tree structure. Users of this package never need to implement
30 | // these types as values of this type will be returned by this package.
31 | //
32 | // Implementations of this interface:
33 | // - [StructField]
34 | // - [SliceIndex]
35 | // - [MapIndex]
36 | // - [Indirect]
37 | // - [TypeAssertion]
38 | // - [Transform]
39 | type PathStep interface {
40 | String() string
41 |
42 | // Type is the resulting type after performing the path step.
43 | Type() reflect.Type
44 |
45 | // Values is the resulting values after performing the path step.
46 | // The type of each valid value is guaranteed to be identical to Type.
47 | //
48 | // In some cases, one or both may be invalid or have restrictions:
49 | // - For StructField, both are not interface-able if the current field
50 | // is unexported and the struct type is not explicitly permitted by
51 | // an Exporter to traverse unexported fields.
52 | // - For SliceIndex, one may be invalid if an element is missing from
53 | // either the x or y slice.
54 | // - For MapIndex, one may be invalid if an entry is missing from
55 | // either the x or y map.
56 | //
57 | // The provided values must not be mutated.
58 | Values() (vx, vy reflect.Value)
59 | }
60 |
61 | var (
62 | _ PathStep = StructField{}
63 | _ PathStep = SliceIndex{}
64 | _ PathStep = MapIndex{}
65 | _ PathStep = Indirect{}
66 | _ PathStep = TypeAssertion{}
67 | _ PathStep = Transform{}
68 | )
69 |
70 | func (pa *Path) push(s PathStep) {
71 | *pa = append(*pa, s)
72 | }
73 |
74 | func (pa *Path) pop() {
75 | *pa = (*pa)[:len(*pa)-1]
76 | }
77 |
78 | // Last returns the last [PathStep] in the Path.
79 | // If the path is empty, this returns a non-nil [PathStep]
80 | // that reports a nil [PathStep.Type].
81 | func (pa Path) Last() PathStep {
82 | return pa.Index(-1)
83 | }
84 |
85 | // Index returns the ith step in the Path and supports negative indexing.
86 | // A negative index starts counting from the tail of the Path such that -1
87 | // refers to the last step, -2 refers to the second-to-last step, and so on.
88 | // If index is invalid, this returns a non-nil [PathStep]
89 | // that reports a nil [PathStep.Type].
90 | func (pa Path) Index(i int) PathStep {
91 | if i < 0 {
92 | i = len(pa) + i
93 | }
94 | if i < 0 || i >= len(pa) {
95 | return pathStep{}
96 | }
97 | return pa[i]
98 | }
99 |
100 | // String returns the simplified path to a node.
101 | // The simplified path only contains struct field accesses.
102 | //
103 | // For example:
104 | //
105 | // MyMap.MySlices.MyField
106 | func (pa Path) String() string {
107 | var ss []string
108 | for _, s := range pa {
109 | if _, ok := s.(StructField); ok {
110 | ss = append(ss, s.String())
111 | }
112 | }
113 | return strings.TrimPrefix(strings.Join(ss, ""), ".")
114 | }
115 |
116 | // GoString returns the path to a specific node using Go syntax.
117 | //
118 | // For example:
119 | //
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 | }
152 | ssPost = append(ssPost, s.String())
153 | }
154 | for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
155 | ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
156 | }
157 | return strings.Join(ssPre, "") + strings.Join(ssPost, "")
158 | }
159 |
160 | type pathStep struct {
161 | typ reflect.Type
162 | vx, vy reflect.Value
163 | }
164 |
165 | func (ps pathStep) Type() reflect.Type { return ps.typ }
166 | func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy }
167 | func (ps pathStep) String() string {
168 | if ps.typ == nil {
169 | return ""
170 | }
171 | s := value.TypeString(ps.typ, false)
172 | if s == "" || strings.ContainsAny(s, "{}\n") {
173 | return "root" // Type too simple or complex to print
174 | }
175 | return fmt.Sprintf("{%s}", s)
176 | }
177 |
178 | // StructField is a [PathStep] that represents a struct field access
179 | // on a field called [StructField.Name].
180 | type StructField struct{ *structField }
181 | type structField struct {
182 | pathStep
183 | name string
184 | idx int
185 |
186 | // These fields are used for forcibly accessing an unexported field.
187 | // pvx, pvy, and field are only valid if unexported is true.
188 | unexported bool
189 | mayForce bool // Forcibly allow visibility
190 | paddr bool // Was parent addressable?
191 | pvx, pvy reflect.Value // Parent values (always addressable)
192 | field reflect.StructField // Field information
193 | }
194 |
195 | func (sf StructField) Type() reflect.Type { return sf.typ }
196 | func (sf StructField) Values() (vx, vy reflect.Value) {
197 | if !sf.unexported {
198 | return sf.vx, sf.vy // CanInterface reports true
199 | }
200 |
201 | // Forcibly obtain read-write access to an unexported struct field.
202 | if sf.mayForce {
203 | vx = retrieveUnexportedField(sf.pvx, sf.field, sf.paddr)
204 | vy = retrieveUnexportedField(sf.pvy, sf.field, sf.paddr)
205 | return vx, vy // CanInterface reports true
206 | }
207 | return sf.vx, sf.vy // CanInterface reports false
208 | }
209 | func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) }
210 |
211 | // Name is the field name.
212 | func (sf StructField) Name() string { return sf.name }
213 |
214 | // Index is the index of the field in the parent struct type.
215 | // See [reflect.Type.Field].
216 | func (sf StructField) Index() int { return sf.idx }
217 |
218 | // SliceIndex is a [PathStep] that represents an index operation on
219 | // a slice or array at some index [SliceIndex.Key].
220 | type SliceIndex struct{ *sliceIndex }
221 | type sliceIndex struct {
222 | pathStep
223 | xkey, ykey int
224 | isSlice bool // False for reflect.Array
225 | }
226 |
227 | func (si SliceIndex) Type() reflect.Type { return si.typ }
228 | func (si SliceIndex) Values() (vx, vy reflect.Value) { return si.vx, si.vy }
229 | func (si SliceIndex) String() string {
230 | switch {
231 | case si.xkey == si.ykey:
232 | return fmt.Sprintf("[%d]", si.xkey)
233 | case si.ykey == -1:
234 | // [5->?] means "I don't know where X[5] went"
235 | return fmt.Sprintf("[%d->?]", si.xkey)
236 | case si.xkey == -1:
237 | // [?->3] means "I don't know where Y[3] came from"
238 | return fmt.Sprintf("[?->%d]", si.ykey)
239 | default:
240 | // [5->3] means "X[5] moved to Y[3]"
241 | return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
242 | }
243 | }
244 |
245 | // Key is the index key; it may return -1 if in a split state
246 | func (si SliceIndex) Key() int {
247 | if si.xkey != si.ykey {
248 | return -1
249 | }
250 | return si.xkey
251 | }
252 |
253 | // SplitKeys are the indexes for indexing into slices in the
254 | // x and y values, respectively. These indexes may differ due to the
255 | // insertion or removal of an element in one of the slices, causing
256 | // all of the indexes to be shifted. If an index is -1, then that
257 | // indicates that the element does not exist in the associated slice.
258 | //
259 | // [SliceIndex.Key] is guaranteed to return -1 if and only if the indexes
260 | // returned by SplitKeys are not the same. SplitKeys will never return -1 for
261 | // both indexes.
262 | func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey }
263 |
264 | // MapIndex is a [PathStep] that represents an index operation on a map at some index Key.
265 | type MapIndex struct{ *mapIndex }
266 | type mapIndex struct {
267 | pathStep
268 | key reflect.Value
269 | }
270 |
271 | func (mi MapIndex) Type() reflect.Type { return mi.typ }
272 | func (mi MapIndex) Values() (vx, vy reflect.Value) { return mi.vx, mi.vy }
273 | func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
274 |
275 | // Key is the value of the map key.
276 | func (mi MapIndex) Key() reflect.Value { return mi.key }
277 |
278 | // Indirect is a [PathStep] that represents pointer indirection on the parent type.
279 | type Indirect struct{ *indirect }
280 | type indirect struct {
281 | pathStep
282 | }
283 |
284 | func (in Indirect) Type() reflect.Type { return in.typ }
285 | func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy }
286 | func (in Indirect) String() string { return "*" }
287 |
288 | // TypeAssertion is a [PathStep] that represents a type assertion on an interface.
289 | type TypeAssertion struct{ *typeAssertion }
290 | type typeAssertion struct {
291 | pathStep
292 | }
293 |
294 | func (ta TypeAssertion) Type() reflect.Type { return ta.typ }
295 | func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy }
296 | func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) }
297 |
298 | // Transform is a [PathStep] that represents a transformation
299 | // from the parent type to the current type.
300 | type Transform struct{ *transform }
301 | type transform struct {
302 | pathStep
303 | trans *transformer
304 | }
305 |
306 | func (tf Transform) Type() reflect.Type { return tf.typ }
307 | func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy }
308 | func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
309 |
310 | // Name is the name of the [Transformer].
311 | func (tf Transform) Name() string { return tf.trans.name }
312 |
313 | // Func is the function pointer to the transformer function.
314 | func (tf Transform) Func() reflect.Value { return tf.trans.fnc }
315 |
316 | // Option returns the originally constructed [Transformer] option.
317 | // The == operator can be used to detect the exact option used.
318 | func (tf Transform) Option() Option { return tf.trans }
319 |
320 | // pointerPath represents a dual-stack of pointers encountered when
321 | // recursively traversing the x and y values. This data structure supports
322 | // detection of cycles and determining whether the cycles are equal.
323 | // In Go, cycles can occur via pointers, slices, and maps.
324 | //
325 | // The pointerPath uses a map to represent a stack; where descension into a
326 | // pointer pushes the address onto the stack, and ascension from a pointer
327 | // pops the address from the stack. Thus, when traversing into a pointer from
328 | // reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles
329 | // by checking whether the pointer has already been visited. The cycle detection
330 | // uses a separate stack for the x and y values.
331 | //
332 | // If a cycle is detected we need to determine whether the two pointers
333 | // should be considered equal. The definition of equality chosen by Equal
334 | // requires two graphs to have the same structure. To determine this, both the
335 | // x and y values must have a cycle where the previous pointers were also
336 | // encountered together as a pair.
337 | //
338 | // Semantically, this is equivalent to augmenting Indirect, SliceIndex, and
339 | // MapIndex with pointer information for the x and y values.
340 | // Suppose px and py are two pointers to compare, we then search the
341 | // Path for whether px was ever encountered in the Path history of x, and
342 | // similarly so with py. If either side has a cycle, the comparison is only
343 | // equal if both px and py have a cycle resulting from the same PathStep.
344 | //
345 | // Using a map as a stack is more performant as we can perform cycle detection
346 | // in O(1) instead of O(N) where N is len(Path).
347 | type pointerPath struct {
348 | // mx is keyed by x pointers, where the value is the associated y pointer.
349 | mx map[value.Pointer]value.Pointer
350 | // my is keyed by y pointers, where the value is the associated x pointer.
351 | my map[value.Pointer]value.Pointer
352 | }
353 |
354 | func (p *pointerPath) Init() {
355 | p.mx = make(map[value.Pointer]value.Pointer)
356 | p.my = make(map[value.Pointer]value.Pointer)
357 | }
358 |
359 | // Push indicates intent to descend into pointers vx and vy where
360 | // visited reports whether either has been seen before. If visited before,
361 | // equal reports whether both pointers were encountered together.
362 | // Pop must be called if and only if the pointers were never visited.
363 | //
364 | // The pointers vx and vy must be a reflect.Ptr, reflect.Slice, or reflect.Map
365 | // and be non-nil.
366 | func (p pointerPath) Push(vx, vy reflect.Value) (equal, visited bool) {
367 | px := value.PointerOf(vx)
368 | py := value.PointerOf(vy)
369 | _, ok1 := p.mx[px]
370 | _, ok2 := p.my[py]
371 | if ok1 || ok2 {
372 | equal = p.mx[px] == py && p.my[py] == px // Pointers paired together
373 | return equal, true
374 | }
375 | p.mx[px] = py
376 | p.my[py] = px
377 | return false, false
378 | }
379 |
380 | // Pop ascends from pointers vx and vy.
381 | func (p pointerPath) Pop(vx, vy reflect.Value) {
382 | delete(p.mx, value.PointerOf(vx))
383 | delete(p.my, value.PointerOf(vy))
384 | }
385 |
386 | // isExported reports whether the identifier is exported.
387 | func isExported(id string) bool {
388 | r, _ := utf8.DecodeRuneInString(id)
389 | return unicode.IsUpper(r)
390 | }
391 |
--------------------------------------------------------------------------------
/cmp/report.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp
6 |
7 | // defaultReporter implements the reporter interface.
8 | //
9 | // As Equal serially calls the PushStep, Report, and PopStep methods, the
10 | // defaultReporter constructs a tree-based representation of the compared value
11 | // and the result of each comparison (see valueNode).
12 | //
13 | // When the String method is called, the FormatDiff method transforms the
14 | // valueNode tree into a textNode tree, which is a tree-based representation
15 | // of the textual output (see textNode).
16 | //
17 | // Lastly, the textNode.String method produces the final report as a string.
18 | type defaultReporter struct {
19 | root *valueNode
20 | curr *valueNode
21 | }
22 |
23 | func (r *defaultReporter) PushStep(ps PathStep) {
24 | r.curr = r.curr.PushStep(ps)
25 | if r.root == nil {
26 | r.root = r.curr
27 | }
28 | }
29 | func (r *defaultReporter) Report(rs Result) {
30 | r.curr.Report(rs)
31 | }
32 | func (r *defaultReporter) PopStep() {
33 | r.curr = r.curr.PopStep()
34 | }
35 |
36 | // String provides a full report of the differences detected as a structured
37 | // literal in pseudo-Go syntax. String may only be called after the entire tree
38 | // has been traversed.
39 | func (r *defaultReporter) String() string {
40 | assert(r.root != nil && r.curr == nil)
41 | if r.root.NumDiff == 0 {
42 | return ""
43 | }
44 | ptrs := new(pointerReferences)
45 | text := formatOptions{}.FormatDiff(r.root, ptrs)
46 | resolveReferences(text)
47 | return text.String()
48 | }
49 |
50 | func assert(ok bool) {
51 | if !ok {
52 | panic("assertion failure")
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/cmp/report_compare.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp
6 |
7 | import (
8 | "fmt"
9 | "reflect"
10 | )
11 |
12 | // numContextRecords is the number of surrounding equal records to print.
13 | const numContextRecords = 2
14 |
15 | type diffMode byte
16 |
17 | const (
18 | diffUnknown diffMode = 0
19 | diffIdentical diffMode = ' '
20 | diffRemoved diffMode = '-'
21 | diffInserted diffMode = '+'
22 | )
23 |
24 | type typeMode int
25 |
26 | const (
27 | // emitType always prints the type.
28 | emitType typeMode = iota
29 | // elideType never prints the type.
30 | elideType
31 | // autoType prints the type only for composite kinds
32 | // (i.e., structs, slices, arrays, and maps).
33 | autoType
34 | )
35 |
36 | type formatOptions struct {
37 | // DiffMode controls the output mode of FormatDiff.
38 | //
39 | // If diffUnknown, then produce a diff of the x and y values.
40 | // If diffIdentical, then emit values as if they were equal.
41 | // If diffRemoved, then only emit x values (ignoring y values).
42 | // If diffInserted, then only emit y values (ignoring x values).
43 | DiffMode diffMode
44 |
45 | // TypeMode controls whether to print the type for the current node.
46 | //
47 | // As a general rule of thumb, we always print the type of the next node
48 | // after an interface, and always elide the type of the next node after
49 | // a slice or map node.
50 | TypeMode typeMode
51 |
52 | // formatValueOptions are options specific to printing reflect.Values.
53 | formatValueOptions
54 | }
55 |
56 | func (opts formatOptions) WithDiffMode(d diffMode) formatOptions {
57 | opts.DiffMode = d
58 | return opts
59 | }
60 | func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
61 | opts.TypeMode = t
62 | return opts
63 | }
64 | func (opts formatOptions) WithVerbosity(level int) formatOptions {
65 | opts.VerbosityLevel = level
66 | opts.LimitVerbosity = true
67 | return opts
68 | }
69 | func (opts formatOptions) verbosity() uint {
70 | switch {
71 | case opts.VerbosityLevel < 0:
72 | return 0
73 | case opts.VerbosityLevel > 16:
74 | return 16 // some reasonable maximum to avoid shift overflow
75 | default:
76 | return uint(opts.VerbosityLevel)
77 | }
78 | }
79 |
80 | const maxVerbosityPreset = 6
81 |
82 | // verbosityPreset modifies the verbosity settings given an index
83 | // between 0 and maxVerbosityPreset, inclusive.
84 | func verbosityPreset(opts formatOptions, i int) formatOptions {
85 | opts.VerbosityLevel = int(opts.verbosity()) + 2*i
86 | if i > 0 {
87 | opts.AvoidStringer = true
88 | }
89 | if i >= maxVerbosityPreset {
90 | opts.PrintAddresses = true
91 | opts.QualifiedNames = true
92 | }
93 | return opts
94 | }
95 |
96 | // FormatDiff converts a valueNode tree into a textNode tree, where the later
97 | // is a textual representation of the differences detected in the former.
98 | func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) {
99 | if opts.DiffMode == diffIdentical {
100 | opts = opts.WithVerbosity(1)
101 | } else if opts.verbosity() < 3 {
102 | opts = opts.WithVerbosity(3)
103 | }
104 |
105 | // Check whether we have specialized formatting for this node.
106 | // This is not necessary, but helpful for producing more readable outputs.
107 | if opts.CanFormatDiffSlice(v) {
108 | return opts.FormatDiffSlice(v)
109 | }
110 |
111 | var parentKind reflect.Kind
112 | if v.parent != nil && v.parent.TransformerName == "" {
113 | parentKind = v.parent.Type.Kind()
114 | }
115 |
116 | // For leaf nodes, format the value based on the reflect.Values alone.
117 | // As a special case, treat equal []byte as a leaf nodes.
118 | isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == byteType
119 | isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0
120 | if v.MaxDepth == 0 || isEqualBytes {
121 | switch opts.DiffMode {
122 | case diffUnknown, diffIdentical:
123 | // Format Equal.
124 | if v.NumDiff == 0 {
125 | outx := opts.FormatValue(v.ValueX, parentKind, ptrs)
126 | outy := opts.FormatValue(v.ValueY, parentKind, ptrs)
127 | if v.NumIgnored > 0 && v.NumSame == 0 {
128 | return textEllipsis
129 | } else if outx.Len() < outy.Len() {
130 | return outx
131 | } else {
132 | return outy
133 | }
134 | }
135 |
136 | // Format unequal.
137 | assert(opts.DiffMode == diffUnknown)
138 | var list textList
139 | outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs)
140 | outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs)
141 | for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
142 | opts2 := verbosityPreset(opts, i).WithTypeMode(elideType)
143 | outx = opts2.FormatValue(v.ValueX, parentKind, ptrs)
144 | outy = opts2.FormatValue(v.ValueY, parentKind, ptrs)
145 | }
146 | if outx != nil {
147 | list = append(list, textRecord{Diff: '-', Value: outx})
148 | }
149 | if outy != nil {
150 | list = append(list, textRecord{Diff: '+', Value: outy})
151 | }
152 | return opts.WithTypeMode(emitType).FormatType(v.Type, list)
153 | case diffRemoved:
154 | return opts.FormatValue(v.ValueX, parentKind, ptrs)
155 | case diffInserted:
156 | return opts.FormatValue(v.ValueY, parentKind, ptrs)
157 | default:
158 | panic("invalid diff mode")
159 | }
160 | }
161 |
162 | // Register slice element to support cycle detection.
163 | if parentKind == reflect.Slice {
164 | ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true)
165 | defer ptrs.Pop()
166 | defer func() { out = wrapTrunkReferences(ptrRefs, out) }()
167 | }
168 |
169 | // Descend into the child value node.
170 | if v.TransformerName != "" {
171 | out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
172 | out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"}
173 | return opts.FormatType(v.Type, out)
174 | } else {
175 | switch k := v.Type.Kind(); k {
176 | case reflect.Struct, reflect.Array, reflect.Slice:
177 | out = opts.formatDiffList(v.Records, k, ptrs)
178 | out = opts.FormatType(v.Type, out)
179 | case reflect.Map:
180 | // Register map to support cycle detection.
181 | ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
182 | defer ptrs.Pop()
183 |
184 | out = opts.formatDiffList(v.Records, k, ptrs)
185 | out = wrapTrunkReferences(ptrRefs, out)
186 | out = opts.FormatType(v.Type, out)
187 | case reflect.Ptr:
188 | // Register pointer to support cycle detection.
189 | ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
190 | defer ptrs.Pop()
191 |
192 | out = opts.FormatDiff(v.Value, ptrs)
193 | out = wrapTrunkReferences(ptrRefs, out)
194 | out = &textWrap{Prefix: "&", Value: out}
195 | case reflect.Interface:
196 | out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
197 | default:
198 | panic(fmt.Sprintf("%v cannot have children", k))
199 | }
200 | return out
201 | }
202 | }
203 |
204 | func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode {
205 | // Derive record name based on the data structure kind.
206 | var name string
207 | var formatKey func(reflect.Value) string
208 | switch k {
209 | case reflect.Struct:
210 | name = "field"
211 | opts = opts.WithTypeMode(autoType)
212 | formatKey = func(v reflect.Value) string { return v.String() }
213 | case reflect.Slice, reflect.Array:
214 | name = "element"
215 | opts = opts.WithTypeMode(elideType)
216 | formatKey = func(reflect.Value) string { return "" }
217 | case reflect.Map:
218 | name = "entry"
219 | opts = opts.WithTypeMode(elideType)
220 | formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) }
221 | }
222 |
223 | maxLen := -1
224 | if opts.LimitVerbosity {
225 | if opts.DiffMode == diffIdentical {
226 | maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
227 | } else {
228 | maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc...
229 | }
230 | opts.VerbosityLevel--
231 | }
232 |
233 | // Handle unification.
234 | switch opts.DiffMode {
235 | case diffIdentical, diffRemoved, diffInserted:
236 | var list textList
237 | var deferredEllipsis bool // Add final "..." to indicate records were dropped
238 | for _, r := range recs {
239 | if len(list) == maxLen {
240 | deferredEllipsis = true
241 | break
242 | }
243 |
244 | // Elide struct fields that are zero value.
245 | if k == reflect.Struct {
246 | var isZero bool
247 | switch opts.DiffMode {
248 | case diffIdentical:
249 | isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero()
250 | case diffRemoved:
251 | isZero = r.Value.ValueX.IsZero()
252 | case diffInserted:
253 | isZero = r.Value.ValueY.IsZero()
254 | }
255 | if isZero {
256 | continue
257 | }
258 | }
259 | // Elide ignored nodes.
260 | if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 {
261 | deferredEllipsis = !(k == reflect.Slice || k == reflect.Array)
262 | if !deferredEllipsis {
263 | list.AppendEllipsis(diffStats{})
264 | }
265 | continue
266 | }
267 | if out := opts.FormatDiff(r.Value, ptrs); out != nil {
268 | list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
269 | }
270 | }
271 | if deferredEllipsis {
272 | list.AppendEllipsis(diffStats{})
273 | }
274 | return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
275 | case diffUnknown:
276 | default:
277 | panic("invalid diff mode")
278 | }
279 |
280 | // Handle differencing.
281 | var numDiffs int
282 | var list textList
283 | var keys []reflect.Value // invariant: len(list) == len(keys)
284 | groups := coalesceAdjacentRecords(name, recs)
285 | maxGroup := diffStats{Name: name}
286 | for i, ds := range groups {
287 | if maxLen >= 0 && numDiffs >= maxLen {
288 | maxGroup = maxGroup.Append(ds)
289 | continue
290 | }
291 |
292 | // Handle equal records.
293 | if ds.NumDiff() == 0 {
294 | // Compute the number of leading and trailing records to print.
295 | var numLo, numHi int
296 | numEqual := ds.NumIgnored + ds.NumIdentical
297 | for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 {
298 | if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
299 | break
300 | }
301 | numLo++
302 | }
303 | for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
304 | if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
305 | break
306 | }
307 | numHi++
308 | }
309 | if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 {
310 | numHi++ // Avoid pointless coalescing of a single equal record
311 | }
312 |
313 | // Format the equal values.
314 | for _, r := range recs[:numLo] {
315 | out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
316 | list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
317 | keys = append(keys, r.Key)
318 | }
319 | if numEqual > numLo+numHi {
320 | ds.NumIdentical -= numLo + numHi
321 | list.AppendEllipsis(ds)
322 | for len(keys) < len(list) {
323 | keys = append(keys, reflect.Value{})
324 | }
325 | }
326 | for _, r := range recs[numEqual-numHi : numEqual] {
327 | out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
328 | list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
329 | keys = append(keys, r.Key)
330 | }
331 | recs = recs[numEqual:]
332 | continue
333 | }
334 |
335 | // Handle unequal records.
336 | for _, r := range recs[:ds.NumDiff()] {
337 | switch {
338 | case opts.CanFormatDiffSlice(r.Value):
339 | out := opts.FormatDiffSlice(r.Value)
340 | list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
341 | keys = append(keys, r.Key)
342 | case r.Value.NumChildren == r.Value.MaxDepth:
343 | outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
344 | outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
345 | for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
346 | opts2 := verbosityPreset(opts, i)
347 | outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
348 | outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
349 | }
350 | if outx != nil {
351 | list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
352 | keys = append(keys, r.Key)
353 | }
354 | if outy != nil {
355 | list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
356 | keys = append(keys, r.Key)
357 | }
358 | default:
359 | out := opts.FormatDiff(r.Value, ptrs)
360 | list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
361 | keys = append(keys, r.Key)
362 | }
363 | }
364 | recs = recs[ds.NumDiff():]
365 | numDiffs += ds.NumDiff()
366 | }
367 | if maxGroup.IsZero() {
368 | assert(len(recs) == 0)
369 | } else {
370 | list.AppendEllipsis(maxGroup)
371 | for len(keys) < len(list) {
372 | keys = append(keys, reflect.Value{})
373 | }
374 | }
375 | assert(len(list) == len(keys))
376 |
377 | // For maps, the default formatting logic uses fmt.Stringer which may
378 | // produce ambiguous output. Avoid calling String to disambiguate.
379 | if k == reflect.Map {
380 | var ambiguous bool
381 | seenKeys := map[string]reflect.Value{}
382 | for i, currKey := range keys {
383 | if currKey.IsValid() {
384 | strKey := list[i].Key
385 | prevKey, seen := seenKeys[strKey]
386 | if seen && prevKey.CanInterface() && currKey.CanInterface() {
387 | ambiguous = prevKey.Interface() != currKey.Interface()
388 | if ambiguous {
389 | break
390 | }
391 | }
392 | seenKeys[strKey] = currKey
393 | }
394 | }
395 | if ambiguous {
396 | for i, k := range keys {
397 | if k.IsValid() {
398 | list[i].Key = formatMapKey(k, true, ptrs)
399 | }
400 | }
401 | }
402 | }
403 |
404 | return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
405 | }
406 |
407 | // coalesceAdjacentRecords coalesces the list of records into groups of
408 | // adjacent equal, or unequal counts.
409 | func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) {
410 | var prevCase int // Arbitrary index into which case last occurred
411 | lastStats := func(i int) *diffStats {
412 | if prevCase != i {
413 | groups = append(groups, diffStats{Name: name})
414 | prevCase = i
415 | }
416 | return &groups[len(groups)-1]
417 | }
418 | for _, r := range recs {
419 | switch rv := r.Value; {
420 | case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0:
421 | lastStats(1).NumIgnored++
422 | case rv.NumDiff == 0:
423 | lastStats(1).NumIdentical++
424 | case rv.NumDiff > 0 && !rv.ValueY.IsValid():
425 | lastStats(2).NumRemoved++
426 | case rv.NumDiff > 0 && !rv.ValueX.IsValid():
427 | lastStats(2).NumInserted++
428 | default:
429 | lastStats(2).NumModified++
430 | }
431 | }
432 | return groups
433 | }
434 |
--------------------------------------------------------------------------------
/cmp/report_references.go:
--------------------------------------------------------------------------------
1 | // Copyright 2020, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp
6 |
7 | import (
8 | "fmt"
9 | "reflect"
10 | "strings"
11 |
12 | "github.com/google/go-cmp/cmp/internal/flags"
13 | "github.com/google/go-cmp/cmp/internal/value"
14 | )
15 |
16 | const (
17 | pointerDelimPrefix = "⟪"
18 | pointerDelimSuffix = "⟫"
19 | )
20 |
21 | // formatPointer prints the address of the pointer.
22 | func formatPointer(p value.Pointer, withDelims bool) string {
23 | v := p.Uintptr()
24 | if flags.Deterministic {
25 | v = 0xdeadf00f // Only used for stable testing purposes
26 | }
27 | if withDelims {
28 | return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix
29 | }
30 | return formatHex(uint64(v))
31 | }
32 |
33 | // pointerReferences is a stack of pointers visited so far.
34 | type pointerReferences [][2]value.Pointer
35 |
36 | func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) {
37 | if deref && vx.IsValid() {
38 | vx = vx.Addr()
39 | }
40 | if deref && vy.IsValid() {
41 | vy = vy.Addr()
42 | }
43 | switch d {
44 | case diffUnknown, diffIdentical:
45 | pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)}
46 | case diffRemoved:
47 | pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}}
48 | case diffInserted:
49 | pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)}
50 | }
51 | *ps = append(*ps, pp)
52 | return pp
53 | }
54 |
55 | func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) {
56 | p = value.PointerOf(v)
57 | for _, pp := range *ps {
58 | if p == pp[0] || p == pp[1] {
59 | return p, true
60 | }
61 | }
62 | *ps = append(*ps, [2]value.Pointer{p, p})
63 | return p, false
64 | }
65 |
66 | func (ps *pointerReferences) Pop() {
67 | *ps = (*ps)[:len(*ps)-1]
68 | }
69 |
70 | // trunkReferences is metadata for a textNode indicating that the sub-tree
71 | // represents the value for either pointer in a pair of references.
72 | type trunkReferences struct{ pp [2]value.Pointer }
73 |
74 | // trunkReference is metadata for a textNode indicating that the sub-tree
75 | // represents the value for the given pointer reference.
76 | type trunkReference struct{ p value.Pointer }
77 |
78 | // leafReference is metadata for a textNode indicating that the value is
79 | // truncated as it refers to another part of the tree (i.e., a trunk).
80 | type leafReference struct{ p value.Pointer }
81 |
82 | func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode {
83 | switch {
84 | case pp[0].IsNil():
85 | return &textWrap{Value: s, Metadata: trunkReference{pp[1]}}
86 | case pp[1].IsNil():
87 | return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
88 | case pp[0] == pp[1]:
89 | return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
90 | default:
91 | return &textWrap{Value: s, Metadata: trunkReferences{pp}}
92 | }
93 | }
94 | func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode {
95 | var prefix string
96 | if printAddress {
97 | prefix = formatPointer(p, true)
98 | }
99 | return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}}
100 | }
101 | func makeLeafReference(p value.Pointer, printAddress bool) textNode {
102 | out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"}
103 | var prefix string
104 | if printAddress {
105 | prefix = formatPointer(p, true)
106 | }
107 | return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}}
108 | }
109 |
110 | // resolveReferences walks the textNode tree searching for any leaf reference
111 | // metadata and resolves each against the corresponding trunk references.
112 | // Since pointer addresses in memory are not particularly readable to the user,
113 | // it replaces each pointer value with an arbitrary and unique reference ID.
114 | func resolveReferences(s textNode) {
115 | var walkNodes func(textNode, func(textNode))
116 | walkNodes = func(s textNode, f func(textNode)) {
117 | f(s)
118 | switch s := s.(type) {
119 | case *textWrap:
120 | walkNodes(s.Value, f)
121 | case textList:
122 | for _, r := range s {
123 | walkNodes(r.Value, f)
124 | }
125 | }
126 | }
127 |
128 | // Collect all trunks and leaves with reference metadata.
129 | var trunks, leaves []*textWrap
130 | walkNodes(s, func(s textNode) {
131 | if s, ok := s.(*textWrap); ok {
132 | switch s.Metadata.(type) {
133 | case leafReference:
134 | leaves = append(leaves, s)
135 | case trunkReference, trunkReferences:
136 | trunks = append(trunks, s)
137 | }
138 | }
139 | })
140 |
141 | // No leaf references to resolve.
142 | if len(leaves) == 0 {
143 | return
144 | }
145 |
146 | // Collect the set of all leaf references to resolve.
147 | leafPtrs := make(map[value.Pointer]bool)
148 | for _, leaf := range leaves {
149 | leafPtrs[leaf.Metadata.(leafReference).p] = true
150 | }
151 |
152 | // Collect the set of trunk pointers that are always paired together.
153 | // This allows us to assign a single ID to both pointers for brevity.
154 | // If a pointer in a pair ever occurs by itself or as a different pair,
155 | // then the pair is broken.
156 | pairedTrunkPtrs := make(map[value.Pointer]value.Pointer)
157 | unpair := func(p value.Pointer) {
158 | if !pairedTrunkPtrs[p].IsNil() {
159 | pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half
160 | }
161 | pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half
162 | }
163 | for _, trunk := range trunks {
164 | switch p := trunk.Metadata.(type) {
165 | case trunkReference:
166 | unpair(p.p) // standalone pointer cannot be part of a pair
167 | case trunkReferences:
168 | p0, ok0 := pairedTrunkPtrs[p.pp[0]]
169 | p1, ok1 := pairedTrunkPtrs[p.pp[1]]
170 | switch {
171 | case !ok0 && !ok1:
172 | // Register the newly seen pair.
173 | pairedTrunkPtrs[p.pp[0]] = p.pp[1]
174 | pairedTrunkPtrs[p.pp[1]] = p.pp[0]
175 | case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]:
176 | // Exact pair already seen; do nothing.
177 | default:
178 | // Pair conflicts with some other pair; break all pairs.
179 | unpair(p.pp[0])
180 | unpair(p.pp[1])
181 | }
182 | }
183 | }
184 |
185 | // Correlate each pointer referenced by leaves to a unique identifier,
186 | // and print the IDs for each trunk that matches those pointers.
187 | var nextID uint
188 | ptrIDs := make(map[value.Pointer]uint)
189 | newID := func() uint {
190 | id := nextID
191 | nextID++
192 | return id
193 | }
194 | for _, trunk := range trunks {
195 | switch p := trunk.Metadata.(type) {
196 | case trunkReference:
197 | if print := leafPtrs[p.p]; print {
198 | id, ok := ptrIDs[p.p]
199 | if !ok {
200 | id = newID()
201 | ptrIDs[p.p] = id
202 | }
203 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
204 | }
205 | case trunkReferences:
206 | print0 := leafPtrs[p.pp[0]]
207 | print1 := leafPtrs[p.pp[1]]
208 | if print0 || print1 {
209 | id0, ok0 := ptrIDs[p.pp[0]]
210 | id1, ok1 := ptrIDs[p.pp[1]]
211 | isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0]
212 | if isPair {
213 | var id uint
214 | assert(ok0 == ok1) // must be seen together or not at all
215 | if ok0 {
216 | assert(id0 == id1) // must have the same ID
217 | id = id0
218 | } else {
219 | id = newID()
220 | ptrIDs[p.pp[0]] = id
221 | ptrIDs[p.pp[1]] = id
222 | }
223 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
224 | } else {
225 | if print0 && !ok0 {
226 | id0 = newID()
227 | ptrIDs[p.pp[0]] = id0
228 | }
229 | if print1 && !ok1 {
230 | id1 = newID()
231 | ptrIDs[p.pp[1]] = id1
232 | }
233 | switch {
234 | case print0 && print1:
235 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1))
236 | case print0:
237 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0))
238 | case print1:
239 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1))
240 | }
241 | }
242 | }
243 | }
244 | }
245 |
246 | // Update all leaf references with the unique identifier.
247 | for _, leaf := range leaves {
248 | if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok {
249 | leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id))
250 | }
251 | }
252 | }
253 |
254 | func formatReference(id uint) string {
255 | return fmt.Sprintf("ref#%d", id)
256 | }
257 |
258 | func updateReferencePrefix(prefix, ref string) string {
259 | if prefix == "" {
260 | return pointerDelimPrefix + ref + pointerDelimSuffix
261 | }
262 | suffix := strings.TrimPrefix(prefix, pointerDelimPrefix)
263 | return pointerDelimPrefix + ref + ": " + suffix
264 | }
265 |
--------------------------------------------------------------------------------
/cmp/report_reflect.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "reflect"
11 | "strconv"
12 | "strings"
13 | "unicode"
14 | "unicode/utf8"
15 |
16 | "github.com/google/go-cmp/cmp/internal/value"
17 | )
18 |
19 | var (
20 | anyType = reflect.TypeOf((*interface{})(nil)).Elem()
21 | stringType = reflect.TypeOf((*string)(nil)).Elem()
22 | bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()
23 | byteType = reflect.TypeOf((*byte)(nil)).Elem()
24 | )
25 |
26 | type formatValueOptions struct {
27 | // AvoidStringer controls whether to avoid calling custom stringer
28 | // methods like error.Error or fmt.Stringer.String.
29 | AvoidStringer bool
30 |
31 | // PrintAddresses controls whether to print the address of all pointers,
32 | // slice elements, and maps.
33 | PrintAddresses bool
34 |
35 | // QualifiedNames controls whether FormatType uses the fully qualified name
36 | // (including the full package path as opposed to just the package name).
37 | QualifiedNames bool
38 |
39 | // VerbosityLevel controls the amount of output to produce.
40 | // A higher value produces more output. A value of zero or lower produces
41 | // no output (represented using an ellipsis).
42 | // If LimitVerbosity is false, then the level is treated as infinite.
43 | VerbosityLevel int
44 |
45 | // LimitVerbosity specifies that formatting should respect VerbosityLevel.
46 | LimitVerbosity bool
47 | }
48 |
49 | // FormatType prints the type as if it were wrapping s.
50 | // This may return s as-is depending on the current type and TypeMode mode.
51 | func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
52 | // Check whether to emit the type or not.
53 | switch opts.TypeMode {
54 | case autoType:
55 | switch t.Kind() {
56 | case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
57 | if s.Equal(textNil) {
58 | return s
59 | }
60 | default:
61 | return s
62 | }
63 | if opts.DiffMode == diffIdentical {
64 | return s // elide type for identical nodes
65 | }
66 | case elideType:
67 | return s
68 | }
69 |
70 | // Determine the type label, applying special handling for unnamed types.
71 | typeName := value.TypeString(t, opts.QualifiedNames)
72 | if t.Name() == "" {
73 | // According to Go grammar, certain type literals contain symbols that
74 | // do not strongly bind to the next lexicographical token (e.g., *T).
75 | switch t.Kind() {
76 | case reflect.Chan, reflect.Func, reflect.Ptr:
77 | typeName = "(" + typeName + ")"
78 | }
79 | }
80 | return &textWrap{Prefix: typeName, Value: wrapParens(s)}
81 | }
82 |
83 | // wrapParens wraps s with a set of parenthesis, but avoids it if the
84 | // wrapped node itself is already surrounded by a pair of parenthesis or braces.
85 | // It handles unwrapping one level of pointer-reference nodes.
86 | func wrapParens(s textNode) textNode {
87 | var refNode *textWrap
88 | if s2, ok := s.(*textWrap); ok {
89 | // Unwrap a single pointer reference node.
90 | switch s2.Metadata.(type) {
91 | case leafReference, trunkReference, trunkReferences:
92 | refNode = s2
93 | if s3, ok := refNode.Value.(*textWrap); ok {
94 | s2 = s3
95 | }
96 | }
97 |
98 | // Already has delimiters that make parenthesis unnecessary.
99 | hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
100 | hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
101 | if hasParens || hasBraces {
102 | return s
103 | }
104 | }
105 | if refNode != nil {
106 | refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}
107 | return s
108 | }
109 | return &textWrap{Prefix: "(", Value: s, Suffix: ")"}
110 | }
111 |
112 | // FormatValue prints the reflect.Value, taking extra care to avoid descending
113 | // into pointers already in ptrs. As pointers are visited, ptrs is also updated.
114 | func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
115 | if !v.IsValid() {
116 | return nil
117 | }
118 | t := v.Type()
119 |
120 | // Check slice element for cycles.
121 | if parentKind == reflect.Slice {
122 | ptrRef, visited := ptrs.Push(v.Addr())
123 | if visited {
124 | return makeLeafReference(ptrRef, false)
125 | }
126 | defer ptrs.Pop()
127 | defer func() { out = wrapTrunkReference(ptrRef, false, out) }()
128 | }
129 |
130 | // Check whether there is an Error or String method to call.
131 | if !opts.AvoidStringer && v.CanInterface() {
132 | // Avoid calling Error or String methods on nil receivers since many
133 | // implementations crash when doing so.
134 | if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
135 | var prefix, strVal string
136 | func() {
137 | // Swallow and ignore any panics from String or Error.
138 | defer func() { recover() }()
139 | switch v := v.Interface().(type) {
140 | case error:
141 | strVal = v.Error()
142 | prefix = "e"
143 | case fmt.Stringer:
144 | strVal = v.String()
145 | prefix = "s"
146 | }
147 | }()
148 | if prefix != "" {
149 | return opts.formatString(prefix, strVal)
150 | }
151 | }
152 | }
153 |
154 | // Check whether to explicitly wrap the result with the type.
155 | var skipType bool
156 | defer func() {
157 | if !skipType {
158 | out = opts.FormatType(t, out)
159 | }
160 | }()
161 |
162 | switch t.Kind() {
163 | case reflect.Bool:
164 | return textLine(fmt.Sprint(v.Bool()))
165 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
166 | return textLine(fmt.Sprint(v.Int()))
167 | case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
168 | return textLine(fmt.Sprint(v.Uint()))
169 | case reflect.Uint8:
170 | if parentKind == reflect.Slice || parentKind == reflect.Array {
171 | return textLine(formatHex(v.Uint()))
172 | }
173 | return textLine(fmt.Sprint(v.Uint()))
174 | case reflect.Uintptr:
175 | return textLine(formatHex(v.Uint()))
176 | case reflect.Float32, reflect.Float64:
177 | return textLine(fmt.Sprint(v.Float()))
178 | case reflect.Complex64, reflect.Complex128:
179 | return textLine(fmt.Sprint(v.Complex()))
180 | case reflect.String:
181 | return opts.formatString("", v.String())
182 | case reflect.UnsafePointer, reflect.Chan, reflect.Func:
183 | return textLine(formatPointer(value.PointerOf(v), true))
184 | case reflect.Struct:
185 | var list textList
186 | v := makeAddressable(v) // needed for retrieveUnexportedField
187 | maxLen := v.NumField()
188 | if opts.LimitVerbosity {
189 | maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
190 | opts.VerbosityLevel--
191 | }
192 | for i := 0; i < v.NumField(); i++ {
193 | vv := v.Field(i)
194 | if vv.IsZero() {
195 | continue // Elide fields with zero values
196 | }
197 | if len(list) == maxLen {
198 | list.AppendEllipsis(diffStats{})
199 | break
200 | }
201 | sf := t.Field(i)
202 | if !isExported(sf.Name) {
203 | vv = retrieveUnexportedField(v, sf, true)
204 | }
205 | s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
206 | list = append(list, textRecord{Key: sf.Name, Value: s})
207 | }
208 | return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
209 | case reflect.Slice:
210 | if v.IsNil() {
211 | return textNil
212 | }
213 |
214 | // Check whether this is a []byte of text data.
215 | if t.Elem() == byteType {
216 | b := v.Bytes()
217 | isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
218 | if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
219 | out = opts.formatString("", string(b))
220 | skipType = true
221 | return opts.FormatType(t, out)
222 | }
223 | }
224 |
225 | fallthrough
226 | case reflect.Array:
227 | maxLen := v.Len()
228 | if opts.LimitVerbosity {
229 | maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
230 | opts.VerbosityLevel--
231 | }
232 | var list textList
233 | for i := 0; i < v.Len(); i++ {
234 | if len(list) == maxLen {
235 | list.AppendEllipsis(diffStats{})
236 | break
237 | }
238 | s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
239 | list = append(list, textRecord{Value: s})
240 | }
241 |
242 | out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
243 | if t.Kind() == reflect.Slice && opts.PrintAddresses {
244 | header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())
245 | out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}
246 | }
247 | return out
248 | case reflect.Map:
249 | if v.IsNil() {
250 | return textNil
251 | }
252 |
253 | // Check pointer for cycles.
254 | ptrRef, visited := ptrs.Push(v)
255 | if visited {
256 | return makeLeafReference(ptrRef, opts.PrintAddresses)
257 | }
258 | defer ptrs.Pop()
259 |
260 | maxLen := v.Len()
261 | if opts.LimitVerbosity {
262 | maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
263 | opts.VerbosityLevel--
264 | }
265 | var list textList
266 | for _, k := range value.SortKeys(v.MapKeys()) {
267 | if len(list) == maxLen {
268 | list.AppendEllipsis(diffStats{})
269 | break
270 | }
271 | sk := formatMapKey(k, false, ptrs)
272 | sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)
273 | list = append(list, textRecord{Key: sk, Value: sv})
274 | }
275 |
276 | out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
277 | out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
278 | return out
279 | case reflect.Ptr:
280 | if v.IsNil() {
281 | return textNil
282 | }
283 |
284 | // Check pointer for cycles.
285 | ptrRef, visited := ptrs.Push(v)
286 | if visited {
287 | out = makeLeafReference(ptrRef, opts.PrintAddresses)
288 | return &textWrap{Prefix: "&", Value: out}
289 | }
290 | defer ptrs.Pop()
291 |
292 | // Skip the name only if this is an unnamed pointer type.
293 | // Otherwise taking the address of a value does not reproduce
294 | // the named pointer type.
295 | if v.Type().Name() == "" {
296 | skipType = true // Let the underlying value print the type instead
297 | }
298 | out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
299 | out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
300 | out = &textWrap{Prefix: "&", Value: out}
301 | return out
302 | case reflect.Interface:
303 | if v.IsNil() {
304 | return textNil
305 | }
306 | // Interfaces accept different concrete types,
307 | // so configure the underlying value to explicitly print the type.
308 | return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)
309 | default:
310 | panic(fmt.Sprintf("%v kind not handled", v.Kind()))
311 | }
312 | }
313 |
314 | func (opts formatOptions) formatString(prefix, s string) textNode {
315 | maxLen := len(s)
316 | maxLines := strings.Count(s, "\n") + 1
317 | if opts.LimitVerbosity {
318 | maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc...
319 | maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
320 | }
321 |
322 | // For multiline strings, use the triple-quote syntax,
323 | // but only use it when printing removed or inserted nodes since
324 | // we only want the extra verbosity for those cases.
325 | lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")
326 | isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')
327 | for i := 0; i < len(lines) && isTripleQuoted; i++ {
328 | lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
329 | isPrintable := func(r rune) bool {
330 | return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
331 | }
332 | line := lines[i]
333 | isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen
334 | }
335 | if isTripleQuoted {
336 | var list textList
337 | list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
338 | for i, line := range lines {
339 | if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {
340 | comment := commentString(fmt.Sprintf("%d elided lines", numElided))
341 | list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})
342 | break
343 | }
344 | list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})
345 | }
346 | list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
347 | return &textWrap{Prefix: "(", Value: list, Suffix: ")"}
348 | }
349 |
350 | // Format the string as a single-line quoted string.
351 | if len(s) > maxLen+len(textEllipsis) {
352 | return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))
353 | }
354 | return textLine(prefix + formatString(s))
355 | }
356 |
357 | // formatMapKey formats v as if it were a map key.
358 | // The result is guaranteed to be a single line.
359 | func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
360 | var opts formatOptions
361 | opts.DiffMode = diffIdentical
362 | opts.TypeMode = elideType
363 | opts.PrintAddresses = disambiguate
364 | opts.AvoidStringer = disambiguate
365 | opts.QualifiedNames = disambiguate
366 | opts.VerbosityLevel = maxVerbosityPreset
367 | opts.LimitVerbosity = true
368 | s := opts.FormatValue(v, reflect.Map, ptrs).String()
369 | return strings.TrimSpace(s)
370 | }
371 |
372 | // formatString prints s as a double-quoted or backtick-quoted string.
373 | func formatString(s string) string {
374 | // Use quoted string if it the same length as a raw string literal.
375 | // Otherwise, attempt to use the raw string form.
376 | qs := strconv.Quote(s)
377 | if len(qs) == 1+len(s)+1 {
378 | return qs
379 | }
380 |
381 | // Disallow newlines to ensure output is a single line.
382 | // Only allow printable runes for readability purposes.
383 | rawInvalid := func(r rune) bool {
384 | return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
385 | }
386 | if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 {
387 | return "`" + s + "`"
388 | }
389 | return qs
390 | }
391 |
392 | // formatHex prints u as a hexadecimal integer in Go notation.
393 | func formatHex(u uint64) string {
394 | var f string
395 | switch {
396 | case u <= 0xff:
397 | f = "0x%02x"
398 | case u <= 0xffff:
399 | f = "0x%04x"
400 | case u <= 0xffffff:
401 | f = "0x%06x"
402 | case u <= 0xffffffff:
403 | f = "0x%08x"
404 | case u <= 0xffffffffff:
405 | f = "0x%010x"
406 | case u <= 0xffffffffffff:
407 | f = "0x%012x"
408 | case u <= 0xffffffffffffff:
409 | f = "0x%014x"
410 | case u <= 0xffffffffffffffff:
411 | f = "0x%016x"
412 | }
413 | return fmt.Sprintf(f, u)
414 | }
415 |
--------------------------------------------------------------------------------
/cmp/report_text.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "math/rand"
11 | "strings"
12 | "time"
13 | "unicode/utf8"
14 |
15 | "github.com/google/go-cmp/cmp/internal/flags"
16 | )
17 |
18 | var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
19 |
20 | const maxColumnLength = 80
21 |
22 | type indentMode int
23 |
24 | func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
25 | // The output of Diff is documented as being unstable to provide future
26 | // flexibility in changing the output for more humanly readable reports.
27 | // This logic intentionally introduces instability to the exact output
28 | // so that users can detect accidental reliance on stability early on,
29 | // rather than much later when an actual change to the format occurs.
30 | if flags.Deterministic || randBool {
31 | // Use regular spaces (U+0020).
32 | switch d {
33 | case diffUnknown, diffIdentical:
34 | b = append(b, " "...)
35 | case diffRemoved:
36 | b = append(b, "- "...)
37 | case diffInserted:
38 | b = append(b, "+ "...)
39 | }
40 | } else {
41 | // Use non-breaking spaces (U+00a0).
42 | switch d {
43 | case diffUnknown, diffIdentical:
44 | b = append(b, " "...)
45 | case diffRemoved:
46 | b = append(b, "- "...)
47 | case diffInserted:
48 | b = append(b, "+ "...)
49 | }
50 | }
51 | return repeatCount(n).appendChar(b, '\t')
52 | }
53 |
54 | type repeatCount int
55 |
56 | func (n repeatCount) appendChar(b []byte, c byte) []byte {
57 | for ; n > 0; n-- {
58 | b = append(b, c)
59 | }
60 | return b
61 | }
62 |
63 | // textNode is a simplified tree-based representation of structured text.
64 | // Possible node types are textWrap, textList, or textLine.
65 | type textNode interface {
66 | // Len reports the length in bytes of a single-line version of the tree.
67 | // Nested textRecord.Diff and textRecord.Comment fields are ignored.
68 | Len() int
69 | // Equal reports whether the two trees are structurally identical.
70 | // Nested textRecord.Diff and textRecord.Comment fields are compared.
71 | Equal(textNode) bool
72 | // String returns the string representation of the text tree.
73 | // It is not guaranteed that len(x.String()) == x.Len(),
74 | // nor that x.String() == y.String() implies that x.Equal(y).
75 | String() string
76 |
77 | // formatCompactTo formats the contents of the tree as a single-line string
78 | // to the provided buffer. Any nested textRecord.Diff and textRecord.Comment
79 | // fields are ignored.
80 | //
81 | // However, not all nodes in the tree should be collapsed as a single-line.
82 | // If a node can be collapsed as a single-line, it is replaced by a textLine
83 | // node. Since the top-level node cannot replace itself, this also returns
84 | // the current node itself.
85 | //
86 | // This does not mutate the receiver.
87 | formatCompactTo([]byte, diffMode) ([]byte, textNode)
88 | // formatExpandedTo formats the contents of the tree as a multi-line string
89 | // to the provided buffer. In order for column alignment to operate well,
90 | // formatCompactTo must be called before calling formatExpandedTo.
91 | formatExpandedTo([]byte, diffMode, indentMode) []byte
92 | }
93 |
94 | // textWrap is a wrapper that concatenates a prefix and/or a suffix
95 | // to the underlying node.
96 | type textWrap struct {
97 | Prefix string // e.g., "bytes.Buffer{"
98 | Value textNode // textWrap | textList | textLine
99 | Suffix string // e.g., "}"
100 | Metadata interface{} // arbitrary metadata; has no effect on formatting
101 | }
102 |
103 | func (s *textWrap) Len() int {
104 | return len(s.Prefix) + s.Value.Len() + len(s.Suffix)
105 | }
106 | func (s1 *textWrap) Equal(s2 textNode) bool {
107 | if s2, ok := s2.(*textWrap); ok {
108 | return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
109 | }
110 | return false
111 | }
112 | func (s *textWrap) String() string {
113 | var d diffMode
114 | var n indentMode
115 | _, s2 := s.formatCompactTo(nil, d)
116 | b := n.appendIndent(nil, d) // Leading indent
117 | b = s2.formatExpandedTo(b, d, n) // Main body
118 | b = append(b, '\n') // Trailing newline
119 | return string(b)
120 | }
121 | func (s *textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
122 | n0 := len(b) // Original buffer length
123 | b = append(b, s.Prefix...)
124 | b, s.Value = s.Value.formatCompactTo(b, d)
125 | b = append(b, s.Suffix...)
126 | if _, ok := s.Value.(textLine); ok {
127 | return b, textLine(b[n0:])
128 | }
129 | return b, s
130 | }
131 | func (s *textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
132 | b = append(b, s.Prefix...)
133 | b = s.Value.formatExpandedTo(b, d, n)
134 | b = append(b, s.Suffix...)
135 | return b
136 | }
137 |
138 | // textList is a comma-separated list of textWrap or textLine nodes.
139 | // The list may be formatted as multi-lines or single-line at the discretion
140 | // of the textList.formatCompactTo method.
141 | type textList []textRecord
142 | type textRecord struct {
143 | Diff diffMode // e.g., 0 or '-' or '+'
144 | Key string // e.g., "MyField"
145 | Value textNode // textWrap | textLine
146 | ElideComma bool // avoid trailing comma
147 | Comment fmt.Stringer // e.g., "6 identical fields"
148 | }
149 |
150 | // AppendEllipsis appends a new ellipsis node to the list if none already
151 | // exists at the end. If cs is non-zero it coalesces the statistics with the
152 | // previous diffStats.
153 | func (s *textList) AppendEllipsis(ds diffStats) {
154 | hasStats := !ds.IsZero()
155 | if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) {
156 | if hasStats {
157 | *s = append(*s, textRecord{Value: textEllipsis, ElideComma: true, Comment: ds})
158 | } else {
159 | *s = append(*s, textRecord{Value: textEllipsis, ElideComma: true})
160 | }
161 | return
162 | }
163 | if hasStats {
164 | (*s)[len(*s)-1].Comment = (*s)[len(*s)-1].Comment.(diffStats).Append(ds)
165 | }
166 | }
167 |
168 | func (s textList) Len() (n int) {
169 | for i, r := range s {
170 | n += len(r.Key)
171 | if r.Key != "" {
172 | n += len(": ")
173 | }
174 | n += r.Value.Len()
175 | if i < len(s)-1 {
176 | n += len(", ")
177 | }
178 | }
179 | return n
180 | }
181 |
182 | func (s1 textList) Equal(s2 textNode) bool {
183 | if s2, ok := s2.(textList); ok {
184 | if len(s1) != len(s2) {
185 | return false
186 | }
187 | for i := range s1 {
188 | r1, r2 := s1[i], s2[i]
189 | if !(r1.Diff == r2.Diff && r1.Key == r2.Key && r1.Value.Equal(r2.Value) && r1.Comment == r2.Comment) {
190 | return false
191 | }
192 | }
193 | return true
194 | }
195 | return false
196 | }
197 |
198 | func (s textList) String() string {
199 | return (&textWrap{Prefix: "{", Value: s, Suffix: "}"}).String()
200 | }
201 |
202 | func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
203 | s = append(textList(nil), s...) // Avoid mutating original
204 |
205 | // Determine whether we can collapse this list as a single line.
206 | n0 := len(b) // Original buffer length
207 | var multiLine bool
208 | for i, r := range s {
209 | if r.Diff == diffInserted || r.Diff == diffRemoved {
210 | multiLine = true
211 | }
212 | b = append(b, r.Key...)
213 | if r.Key != "" {
214 | b = append(b, ": "...)
215 | }
216 | b, s[i].Value = r.Value.formatCompactTo(b, d|r.Diff)
217 | if _, ok := s[i].Value.(textLine); !ok {
218 | multiLine = true
219 | }
220 | if r.Comment != nil {
221 | multiLine = true
222 | }
223 | if i < len(s)-1 {
224 | b = append(b, ", "...)
225 | }
226 | }
227 | // Force multi-lined output when printing a removed/inserted node that
228 | // is sufficiently long.
229 | if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > maxColumnLength {
230 | multiLine = true
231 | }
232 | if !multiLine {
233 | return b, textLine(b[n0:])
234 | }
235 | return b, s
236 | }
237 |
238 | func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
239 | alignKeyLens := s.alignLens(
240 | func(r textRecord) bool {
241 | _, isLine := r.Value.(textLine)
242 | return r.Key == "" || !isLine
243 | },
244 | func(r textRecord) int { return utf8.RuneCountInString(r.Key) },
245 | )
246 | alignValueLens := s.alignLens(
247 | func(r textRecord) bool {
248 | _, isLine := r.Value.(textLine)
249 | return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil
250 | },
251 | func(r textRecord) int { return utf8.RuneCount(r.Value.(textLine)) },
252 | )
253 |
254 | // Format lists of simple lists in a batched form.
255 | // If the list is sequence of only textLine values,
256 | // then batch multiple values on a single line.
257 | var isSimple bool
258 | for _, r := range s {
259 | _, isLine := r.Value.(textLine)
260 | isSimple = r.Diff == 0 && r.Key == "" && isLine && r.Comment == nil
261 | if !isSimple {
262 | break
263 | }
264 | }
265 | if isSimple {
266 | n++
267 | var batch []byte
268 | emitBatch := func() {
269 | if len(batch) > 0 {
270 | b = n.appendIndent(append(b, '\n'), d)
271 | b = append(b, bytes.TrimRight(batch, " ")...)
272 | batch = batch[:0]
273 | }
274 | }
275 | for _, r := range s {
276 | line := r.Value.(textLine)
277 | if len(batch)+len(line)+len(", ") > maxColumnLength {
278 | emitBatch()
279 | }
280 | batch = append(batch, line...)
281 | batch = append(batch, ", "...)
282 | }
283 | emitBatch()
284 | n--
285 | return n.appendIndent(append(b, '\n'), d)
286 | }
287 |
288 | // Format the list as a multi-lined output.
289 | n++
290 | for i, r := range s {
291 | b = n.appendIndent(append(b, '\n'), d|r.Diff)
292 | if r.Key != "" {
293 | b = append(b, r.Key+": "...)
294 | }
295 | b = alignKeyLens[i].appendChar(b, ' ')
296 |
297 | b = r.Value.formatExpandedTo(b, d|r.Diff, n)
298 | if !r.ElideComma {
299 | b = append(b, ',')
300 | }
301 | b = alignValueLens[i].appendChar(b, ' ')
302 |
303 | if r.Comment != nil {
304 | b = append(b, " // "+r.Comment.String()...)
305 | }
306 | }
307 | n--
308 |
309 | return n.appendIndent(append(b, '\n'), d)
310 | }
311 |
312 | func (s textList) alignLens(
313 | skipFunc func(textRecord) bool,
314 | lenFunc func(textRecord) int,
315 | ) []repeatCount {
316 | var startIdx, endIdx, maxLen int
317 | lens := make([]repeatCount, len(s))
318 | for i, r := range s {
319 | if skipFunc(r) {
320 | for j := startIdx; j < endIdx && j < len(s); j++ {
321 | lens[j] = repeatCount(maxLen - lenFunc(s[j]))
322 | }
323 | startIdx, endIdx, maxLen = i+1, i+1, 0
324 | } else {
325 | if maxLen < lenFunc(r) {
326 | maxLen = lenFunc(r)
327 | }
328 | endIdx = i + 1
329 | }
330 | }
331 | for j := startIdx; j < endIdx && j < len(s); j++ {
332 | lens[j] = repeatCount(maxLen - lenFunc(s[j]))
333 | }
334 | return lens
335 | }
336 |
337 | // textLine is a single-line segment of text and is always a leaf node
338 | // in the textNode tree.
339 | type textLine []byte
340 |
341 | var (
342 | textNil = textLine("nil")
343 | textEllipsis = textLine("...")
344 | )
345 |
346 | func (s textLine) Len() int {
347 | return len(s)
348 | }
349 | func (s1 textLine) Equal(s2 textNode) bool {
350 | if s2, ok := s2.(textLine); ok {
351 | return bytes.Equal([]byte(s1), []byte(s2))
352 | }
353 | return false
354 | }
355 | func (s textLine) String() string {
356 | return string(s)
357 | }
358 | func (s textLine) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
359 | return append(b, s...), s
360 | }
361 | func (s textLine) formatExpandedTo(b []byte, _ diffMode, _ indentMode) []byte {
362 | return append(b, s...)
363 | }
364 |
365 | type diffStats struct {
366 | Name string
367 | NumIgnored int
368 | NumIdentical int
369 | NumRemoved int
370 | NumInserted int
371 | NumModified int
372 | }
373 |
374 | func (s diffStats) IsZero() bool {
375 | s.Name = ""
376 | return s == diffStats{}
377 | }
378 |
379 | func (s diffStats) NumDiff() int {
380 | return s.NumRemoved + s.NumInserted + s.NumModified
381 | }
382 |
383 | func (s diffStats) Append(ds diffStats) diffStats {
384 | assert(s.Name == ds.Name)
385 | s.NumIgnored += ds.NumIgnored
386 | s.NumIdentical += ds.NumIdentical
387 | s.NumRemoved += ds.NumRemoved
388 | s.NumInserted += ds.NumInserted
389 | s.NumModified += ds.NumModified
390 | return s
391 | }
392 |
393 | // String prints a humanly-readable summary of coalesced records.
394 | //
395 | // Example:
396 | //
397 | // diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
398 | func (s diffStats) String() string {
399 | var ss []string
400 | var sum int
401 | labels := [...]string{"ignored", "identical", "removed", "inserted", "modified"}
402 | counts := [...]int{s.NumIgnored, s.NumIdentical, s.NumRemoved, s.NumInserted, s.NumModified}
403 | for i, n := range counts {
404 | if n > 0 {
405 | ss = append(ss, fmt.Sprintf("%d %v", n, labels[i]))
406 | }
407 | sum += n
408 | }
409 |
410 | // Pluralize the name (adjusting for some obscure English grammar rules).
411 | name := s.Name
412 | if sum > 1 {
413 | name += "s"
414 | if strings.HasSuffix(name, "ys") {
415 | name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries"
416 | }
417 | }
418 |
419 | // Format the list according to English grammar (with Oxford comma).
420 | switch n := len(ss); n {
421 | case 0:
422 | return ""
423 | case 1, 2:
424 | return strings.Join(ss, " and ") + " " + name
425 | default:
426 | return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1] + " " + name
427 | }
428 | }
429 |
430 | type commentString string
431 |
432 | func (s commentString) String() string { return string(s) }
433 |
--------------------------------------------------------------------------------
/cmp/report_value.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019, The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package cmp
6 |
7 | import "reflect"
8 |
9 | // valueNode represents a single node within a report, which is a
10 | // structured representation of the value tree, containing information
11 | // regarding which nodes are equal or not.
12 | type valueNode struct {
13 | parent *valueNode
14 |
15 | Type reflect.Type
16 | ValueX reflect.Value
17 | ValueY reflect.Value
18 |
19 | // NumSame is the number of leaf nodes that are equal.
20 | // All descendants are equal only if NumDiff is 0.
21 | NumSame int
22 | // NumDiff is the number of leaf nodes that are not equal.
23 | NumDiff int
24 | // NumIgnored is the number of leaf nodes that are ignored.
25 | NumIgnored int
26 | // NumCompared is the number of leaf nodes that were compared
27 | // using an Equal method or Comparer function.
28 | NumCompared int
29 | // NumTransformed is the number of non-leaf nodes that were transformed.
30 | NumTransformed int
31 | // NumChildren is the number of transitive descendants of this node.
32 | // This counts from zero; thus, leaf nodes have no descendants.
33 | NumChildren int
34 | // MaxDepth is the maximum depth of the tree. This counts from zero;
35 | // thus, leaf nodes have a depth of zero.
36 | MaxDepth int
37 |
38 | // Records is a list of struct fields, slice elements, or map entries.
39 | Records []reportRecord // If populated, implies Value is not populated
40 |
41 | // Value is the result of a transformation, pointer indirect, of
42 | // type assertion.
43 | Value *valueNode // If populated, implies Records is not populated
44 |
45 | // TransformerName is the name of the transformer.
46 | TransformerName string // If non-empty, implies Value is populated
47 | }
48 | type reportRecord struct {
49 | Key reflect.Value // Invalid for slice element
50 | Value *valueNode
51 | }
52 |
53 | func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) {
54 | vx, vy := ps.Values()
55 | child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy}
56 | switch s := ps.(type) {
57 | case StructField:
58 | assert(parent.Value == nil)
59 | parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child})
60 | case SliceIndex:
61 | assert(parent.Value == nil)
62 | parent.Records = append(parent.Records, reportRecord{Value: child})
63 | case MapIndex:
64 | assert(parent.Value == nil)
65 | parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child})
66 | case Indirect:
67 | assert(parent.Value == nil && parent.Records == nil)
68 | parent.Value = child
69 | case TypeAssertion:
70 | assert(parent.Value == nil && parent.Records == nil)
71 | parent.Value = child
72 | case Transform:
73 | assert(parent.Value == nil && parent.Records == nil)
74 | parent.Value = child
75 | parent.TransformerName = s.Name()
76 | parent.NumTransformed++
77 | default:
78 | assert(parent == nil) // Must be the root step
79 | }
80 | return child
81 | }
82 |
83 | func (r *valueNode) Report(rs Result) {
84 | assert(r.MaxDepth == 0) // May only be called on leaf nodes
85 |
86 | if rs.ByIgnore() {
87 | r.NumIgnored++
88 | } else {
89 | if rs.Equal() {
90 | r.NumSame++
91 | } else {
92 | r.NumDiff++
93 | }
94 | }
95 | assert(r.NumSame+r.NumDiff+r.NumIgnored == 1)
96 |
97 | if rs.ByMethod() {
98 | r.NumCompared++
99 | }
100 | if rs.ByFunc() {
101 | r.NumCompared++
102 | }
103 | assert(r.NumCompared <= 1)
104 | }
105 |
106 | func (child *valueNode) PopStep() (parent *valueNode) {
107 | if child.parent == nil {
108 | return nil
109 | }
110 | parent = child.parent
111 | parent.NumSame += child.NumSame
112 | parent.NumDiff += child.NumDiff
113 | parent.NumIgnored += child.NumIgnored
114 | parent.NumCompared += child.NumCompared
115 | parent.NumTransformed += child.NumTransformed
116 | parent.NumChildren += child.NumChildren + 1
117 | if parent.MaxDepth < child.MaxDepth+1 {
118 | parent.MaxDepth = child.MaxDepth + 1
119 | }
120 | return parent
121 | }
122 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/google/go-cmp
2 |
3 | go 1.21
4 |
--------------------------------------------------------------------------------