├── .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](https://img.shields.io/static/v1?label=godev&message=reference&color=00add8)][godev] 4 | [![Build Status](https://github.com/google/go-cmp/actions/workflows/test.yml/badge.svg?branch=master)][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 | --------------------------------------------------------------------------------