aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Willemsen <dwillemsen@google.com>2021-05-19 19:41:22 -0700
committerDan Willemsen <dwillemsen@google.com>2021-05-19 19:48:30 -0700
commit58624c34adaf78ed2b01a5ef75c69e57df24eab9 (patch)
tree2f31755658aa0ca6e1de7ed0c7e23c66d49ca237
parent0f7c828c80e325c9fc2af078ffb108362bb84c15 (diff)
parent8fa37b4dd109f12e42b131e485268768f18bcbf8 (diff)
downloadgo-cmp-58624c34adaf78ed2b01a5ef75c69e57df24eab9.tar.gz
Update go-cmp to 'v0.5.5'
Change-Id: Ic01ac28abadcda509aab67e4da9c0e0ea83769f5
-rw-r--r--.github/workflows/test.yml30
-rw-r--r--.travis.yml32
-rw-r--r--Android.bp5
-rw-r--r--METADATA4
-rw-r--r--cmp/cmpopts/equate.go10
-rw-r--r--cmp/cmpopts/errors_go113.go15
-rw-r--r--cmp/cmpopts/errors_xerrors.go18
-rw-r--r--cmp/cmpopts/example_test.go130
-rw-r--r--cmp/cmpopts/ignore.go11
-rw-r--r--cmp/cmpopts/sort.go2
-rw-r--r--cmp/cmpopts/struct_filter.go4
-rw-r--r--cmp/cmpopts/util_test.go8
-rw-r--r--cmp/cmpopts/xform.go2
-rw-r--r--cmp/compare.go12
-rw-r--r--cmp/compare_test.go961
-rw-r--r--cmp/example_reporter_test.go2
-rw-r--r--cmp/example_test.go4
-rw-r--r--cmp/export_panic.go2
-rw-r--r--cmp/export_unsafe.go2
-rw-r--r--cmp/internal/diff/debug_disable.go2
-rw-r--r--cmp/internal/diff/debug_enable.go2
-rw-r--r--cmp/internal/diff/diff.go44
-rw-r--r--cmp/internal/diff/diff_test.go23
-rw-r--r--cmp/internal/flags/flags.go2
-rw-r--r--cmp/internal/flags/toolchain_legacy.go2
-rw-r--r--cmp/internal/flags/toolchain_recent.go2
-rw-r--r--cmp/internal/function/func.go2
-rw-r--r--cmp/internal/function/func_test.go2
-rw-r--r--cmp/internal/testprotos/protos.go2
-rw-r--r--cmp/internal/teststructs/foo1/foo.go10
-rw-r--r--cmp/internal/teststructs/foo2/foo.go10
-rw-r--r--cmp/internal/teststructs/project1.go2
-rw-r--r--cmp/internal/teststructs/project2.go2
-rw-r--r--cmp/internal/teststructs/project3.go2
-rw-r--r--cmp/internal/teststructs/project4.go2
-rw-r--r--cmp/internal/teststructs/structs.go2
-rw-r--r--cmp/internal/value/name.go157
-rw-r--r--cmp/internal/value/name_test.go144
-rw-r--r--cmp/internal/value/pointer_purego.go12
-rw-r--r--cmp/internal/value/pointer_unsafe.go12
-rw-r--r--cmp/internal/value/sort.go2
-rw-r--r--cmp/internal/value/sort_test.go2
-rw-r--r--cmp/internal/value/zero.go2
-rw-r--r--cmp/internal/value/zero_test.go2
-rw-r--r--cmp/options.go7
-rw-r--r--cmp/options_test.go2
-rw-r--r--cmp/path.go2
-rw-r--r--cmp/report.go7
-rw-r--r--cmp/report_compare.go203
-rw-r--r--cmp/report_references.go264
-rw-r--r--cmp/report_reflect.go282
-rw-r--r--cmp/report_slices.go156
-rw-r--r--cmp/report_text.go88
-rw-r--r--cmp/report_value.go2
-rw-r--r--cmp/testdata/diffs1075
55 files changed, 2979 insertions, 809 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..6b1b1c4
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,30 @@
+on: [push, pull_request]
+name: Test
+jobs:
+ test:
+ env:
+ GOPATH: ${{ github.workspace }}
+ defaults:
+ run:
+ working-directory: ${{ env.GOPATH }}/src/github.com/${{ github.repository }}
+ strategy:
+ matrix:
+ go-version: [1.8.x, 1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x]
+ os: [ubuntu-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Install Go
+ uses: actions/setup-go@v2
+ with:
+ go-version: ${{ matrix.go-version }}
+ - name: Checkout code
+ uses: actions/checkout@v2
+ with:
+ path: ${{ env.GOPATH }}/src/github.com/${{ github.repository }}
+ - name: Checkout dependencies
+ run: go get golang.org/x/xerrors
+ - name: Test
+ run: go test -v -race ./...
+ - name: Format
+ if: matrix.go-version == '1.16.x'
+ run: diff -u <(echo -n) <(gofmt -d .)
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index efb4782..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-sudo: false
-language: go
-matrix:
- include:
- - go: 1.8.x
- script:
- - go test -v -race ./...
- - go: 1.9.x
- script:
- - go test -v -race ./...
- - go: 1.10.x
- script:
- - go test -v -race ./...
- - go: 1.11.x
- script:
- - go test -v -race ./...
- - go: 1.12.x
- script:
- - go test -v -race ./...
- - go: 1.13.x
- script:
- - go test -v -race ./...
- - go: 1.14.x
- script:
- - diff -u <(echo -n) <(gofmt -d .)
- - go test -v -race ./...
- - go: master
- script:
- - go test -v -race ./...
- allow_failures:
- - go: master
- fast_finish: true
diff --git a/Android.bp b/Android.bp
index 59adc80..bc68bb5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8,6 +8,7 @@ bootstrap_go_package {
"cmp/path.go",
"cmp/report.go",
"cmp/report_compare.go",
+ "cmp/report_references.go",
"cmp/report_reflect.go",
"cmp/report_slices.go",
"cmp/report_text.go",
@@ -28,6 +29,9 @@ bootstrap_go_package {
"cmp/internal/diff/debug_enable.go",
"cmp/internal/diff/diff.go",
],
+ deps: [
+ "go-cmp-internal-flags",
+ ],
}
bootstrap_go_package {
@@ -51,6 +55,7 @@ bootstrap_go_package {
name: "go-cmp-internal-value",
pkgPath: "github.com/google/go-cmp/cmp/internal/value",
srcs: [
+ "cmp/internal/value/name.go",
"cmp/internal/value/pointer_unsafe.go",
"cmp/internal/value/sort.go",
"cmp/internal/value/zero.go",
diff --git a/METADATA b/METADATA
index 58d714f..122afbe 100644
--- a/METADATA
+++ b/METADATA
@@ -12,7 +12,7 @@ third_party {
type: GIT
value: "https://github.com/google/go-cmp.git"
}
- version: "v0.4.1"
- last_upgrade_date { year: 2020 month: 5 day: 14 }
+ version: "v0.5.5"
+ last_upgrade_date { year: 2021 month: 5 day: 19 }
license_type: NOTICE
}
diff --git a/cmp/cmpopts/equate.go b/cmp/cmpopts/equate.go
index e102849..e4ffca8 100644
--- a/cmp/cmpopts/equate.go
+++ b/cmp/cmpopts/equate.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// Package cmpopts provides common options for the cmp package.
package cmpopts
@@ -11,7 +11,6 @@ import (
"time"
"github.com/google/go-cmp/cmp"
- "golang.org/x/xerrors"
)
func equateAlways(_, _ interface{}) bool { return true }
@@ -147,10 +146,3 @@ func areConcreteErrors(x, y interface{}) bool {
_, ok2 := y.(error)
return ok1 && ok2
}
-
-func compareErrors(x, y interface{}) bool {
- xe := x.(error)
- ye := y.(error)
- // TODO: Use errors.Is when go1.13 is the minimally supported version of Go.
- return xerrors.Is(xe, ye) || xerrors.Is(ye, xe)
-}
diff --git a/cmp/cmpopts/errors_go113.go b/cmp/cmpopts/errors_go113.go
new file mode 100644
index 0000000..26fe25d
--- /dev/null
+++ b/cmp/cmpopts/errors_go113.go
@@ -0,0 +1,15 @@
+// Copyright 2021, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
+package cmpopts
+
+import "errors"
+
+func compareErrors(x, y interface{}) bool {
+ xe := x.(error)
+ ye := y.(error)
+ return errors.Is(xe, ye) || errors.Is(ye, xe)
+}
diff --git a/cmp/cmpopts/errors_xerrors.go b/cmp/cmpopts/errors_xerrors.go
new file mode 100644
index 0000000..6eeb8d6
--- /dev/null
+++ b/cmp/cmpopts/errors_xerrors.go
@@ -0,0 +1,18 @@
+// Copyright 2021, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.13
+
+// TODO(≥go1.13): For support on <go1.13, we use the xerrors package.
+// Drop this file when we no longer support older Go versions.
+
+package cmpopts
+
+import "golang.org/x/xerrors"
+
+func compareErrors(x, y interface{}) bool {
+ xe := x.(error)
+ ye := y.(error)
+ return xerrors.Is(xe, ye) || xerrors.Is(ye, xe)
+}
diff --git a/cmp/cmpopts/example_test.go b/cmp/cmpopts/example_test.go
new file mode 100644
index 0000000..0cf2513
--- /dev/null
+++ b/cmp/cmpopts/example_test.go
@@ -0,0 +1,130 @@
+// Copyright 2020, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cmpopts_test
+
+import (
+ "fmt"
+ "net"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+ "github.com/google/go-cmp/cmp/internal/flags"
+)
+
+func init() {
+ flags.Deterministic = true
+}
+
+// Use IgnoreFields to ignore fields on a struct type when comparing
+// by providing a value of the type and the field names to ignore.
+// Typically, a zero value of the type is used (e.g., foo.MyStruct{}).
+func ExampleIgnoreFields_testing() {
+ // Let got be the hypothetical value obtained from some logic under test
+ // and want be the expected golden data.
+ got, want := MakeGatewayInfo()
+
+ // While the specified fields will be semantically ignored for the comparison,
+ // the fields may be printed in the diff when displaying entire values
+ // that are already determined to be different.
+ if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(Client{}, "IPAddress")); diff != "" {
+ t.Errorf("MakeGatewayInfo() mismatch (-want +got):\n%s", diff)
+ }
+
+ // Output:
+ // MakeGatewayInfo() mismatch (-want +got):
+ // cmpopts_test.Gateway{
+ // SSID: "CoffeeShopWiFi",
+ // - IPAddress: s"192.168.0.2",
+ // + IPAddress: s"192.168.0.1",
+ // NetMask: {0xff, 0xff, 0x00, 0x00},
+ // Clients: []cmpopts_test.Client{
+ // ... // 3 identical elements
+ // {Hostname: "espresso", ...},
+ // {Hostname: "latte", LastSeen: s"2009-11-10 23:00:23 +0000 UTC", ...},
+ // + {
+ // + Hostname: "americano",
+ // + IPAddress: s"192.168.0.188",
+ // + LastSeen: s"2009-11-10 23:03:05 +0000 UTC",
+ // + },
+ // },
+ // }
+}
+
+type (
+ Gateway struct {
+ SSID string
+ IPAddress net.IP
+ NetMask net.IPMask
+ Clients []Client
+ }
+ Client struct {
+ Hostname string
+ IPAddress net.IP
+ LastSeen time.Time
+ }
+)
+
+func MakeGatewayInfo() (x, y Gateway) {
+ x = Gateway{
+ SSID: "CoffeeShopWiFi",
+ IPAddress: net.IPv4(192, 168, 0, 1),
+ NetMask: net.IPv4Mask(255, 255, 0, 0),
+ Clients: []Client{{
+ Hostname: "ristretto",
+ IPAddress: net.IPv4(192, 168, 0, 116),
+ }, {
+ Hostname: "aribica",
+ IPAddress: net.IPv4(192, 168, 0, 104),
+ LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC),
+ }, {
+ Hostname: "macchiato",
+ IPAddress: net.IPv4(192, 168, 0, 153),
+ LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC),
+ }, {
+ Hostname: "espresso",
+ IPAddress: net.IPv4(192, 168, 0, 121),
+ }, {
+ Hostname: "latte",
+ IPAddress: net.IPv4(192, 168, 0, 219),
+ LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC),
+ }, {
+ Hostname: "americano",
+ IPAddress: net.IPv4(192, 168, 0, 188),
+ LastSeen: time.Date(2009, time.November, 10, 23, 3, 5, 0, time.UTC),
+ }},
+ }
+ y = Gateway{
+ SSID: "CoffeeShopWiFi",
+ IPAddress: net.IPv4(192, 168, 0, 2),
+ NetMask: net.IPv4Mask(255, 255, 0, 0),
+ Clients: []Client{{
+ Hostname: "ristretto",
+ IPAddress: net.IPv4(192, 168, 0, 116),
+ }, {
+ Hostname: "aribica",
+ IPAddress: net.IPv4(192, 168, 0, 104),
+ LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC),
+ }, {
+ Hostname: "macchiato",
+ IPAddress: net.IPv4(192, 168, 0, 153),
+ LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC),
+ }, {
+ Hostname: "espresso",
+ IPAddress: net.IPv4(192, 168, 0, 121),
+ }, {
+ Hostname: "latte",
+ IPAddress: net.IPv4(192, 168, 0, 221),
+ LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC),
+ }},
+ }
+ return x, y
+}
+
+var t fakeT
+
+type fakeT struct{}
+
+func (t fakeT) Errorf(format string, args ...interface{}) { fmt.Printf(format+"\n", args...) }
diff --git a/cmp/cmpopts/ignore.go b/cmp/cmpopts/ignore.go
index ff8e785..80c6061 100644
--- a/cmp/cmpopts/ignore.go
+++ b/cmp/cmpopts/ignore.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmpopts
@@ -14,14 +14,13 @@ import (
"github.com/google/go-cmp/cmp/internal/function"
)
-// IgnoreFields returns an Option that ignores exported fields of the
-// given names on a single struct type.
+// IgnoreFields returns an Option that ignores fields of the
+// given names on a single struct type. It respects the names of exported fields
+// that are forwarded due to struct embedding.
// The struct type is specified by passing in a value of that type.
//
// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
// specific sub-field that is embedded or nested within the parent struct.
-//
-// This does not handle unexported fields; use IgnoreUnexported instead.
func IgnoreFields(typ interface{}, names ...string) cmp.Option {
sf := newStructFilter(typ, names...)
return cmp.FilterPath(sf.filter, cmp.Ignore())
@@ -129,7 +128,7 @@ func newUnexportedFilter(typs ...interface{}) unexportedFilter {
for _, typ := range typs {
t := reflect.TypeOf(typ)
if t == nil || t.Kind() != reflect.Struct {
- panic(fmt.Sprintf("invalid struct type: %T", typ))
+ panic(fmt.Sprintf("%T must be a non-pointer struct", typ))
}
ux.m[t] = true
}
diff --git a/cmp/cmpopts/sort.go b/cmp/cmpopts/sort.go
index 3a48046..a646d74 100644
--- a/cmp/cmpopts/sort.go
+++ b/cmp/cmpopts/sort.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmpopts
diff --git a/cmp/cmpopts/struct_filter.go b/cmp/cmpopts/struct_filter.go
index dae7ced..a09829c 100644
--- a/cmp/cmpopts/struct_filter.go
+++ b/cmp/cmpopts/struct_filter.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmpopts
@@ -42,7 +42,7 @@ func newStructFilter(typ interface{}, names ...string) structFilter {
t := reflect.TypeOf(typ)
if t == nil || t.Kind() != reflect.Struct {
- panic(fmt.Sprintf("%T must be a struct", typ))
+ panic(fmt.Sprintf("%T must be a non-pointer struct", typ))
}
var ft fieldTree
for _, name := range names {
diff --git a/cmp/cmpopts/util_test.go b/cmp/cmpopts/util_test.go
index 37704c8..b19bcab 100644
--- a/cmp/cmpopts/util_test.go
+++ b/cmp/cmpopts/util_test.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmpopts
@@ -1223,7 +1223,7 @@ func TestPanic(t *testing.T) {
label: "IgnoreFields",
fnc: IgnoreFields,
args: args(&Foo1{}, "Alpha"),
- wantPanic: "must be a struct",
+ wantPanic: "must be a non-pointer struct",
reason: "the type must be a struct (not pointer to a struct)",
}, {
label: "IgnoreFields",
@@ -1304,13 +1304,13 @@ func TestPanic(t *testing.T) {
label: "IgnoreUnexported",
fnc: IgnoreUnexported,
args: args(nil),
- wantPanic: "invalid struct type",
+ wantPanic: "must be a non-pointer struct",
reason: "input must not be nil value",
}, {
label: "IgnoreUnexported",
fnc: IgnoreUnexported,
args: args(&Foo1{}),
- wantPanic: "invalid struct type",
+ wantPanic: "must be a non-pointer struct",
reason: "input must be a struct type (not a pointer to a struct)",
}, {
label: "IgnoreUnexported",
diff --git a/cmp/cmpopts/xform.go b/cmp/cmpopts/xform.go
index 9d65155..4eb49d6 100644
--- a/cmp/cmpopts/xform.go
+++ b/cmp/cmpopts/xform.go
@@ -1,6 +1,6 @@
// Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmpopts
diff --git a/cmp/compare.go b/cmp/compare.go
index c82c062..86d0903 100644
--- a/cmp/compare.go
+++ b/cmp/compare.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// Package cmp determines equality of values.
//
@@ -95,13 +95,13 @@ func Equal(x, y interface{}, opts ...Option) bool {
return s.result.Equal()
}
-// Diff returns a human-readable report of the differences between two values.
-// It returns an empty string if and only if Equal returns true for the same
-// input values and options.
+// Diff returns a human-readable report of the differences between two values:
+// y - x. It returns an empty string if and only if Equal returns true for the
+// same input values and options.
//
// The output is displayed as a literal in pseudo-Go syntax.
// At the start of each line, a "-" prefix indicates an element removed from x,
-// a "+" prefix to indicates an element added to y, and the lack of a prefix
+// a "+" prefix to indicates an element added from y, and the lack of a prefix
// indicates an element common to both x and y. If possible, the output
// uses fmt.Stringer.String or error.Error methods to produce more humanly
// readable outputs. In such cases, the string is prefixed with either an
@@ -376,7 +376,7 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
// assuming that T is assignable to R.
// Otherwise, it returns the input value as is.
func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
- // TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/22143).
+ // TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/22143).
if !flags.AtLeastGo110 {
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
return reflect.New(t).Elem()
diff --git a/cmp/compare_test.go b/cmp/compare_test.go
index 4ffa0eb..f7b1f13 100644
--- a/cmp/compare_test.go
+++ b/cmp/compare_test.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp_test
@@ -8,6 +8,7 @@ import (
"bytes"
"crypto/md5"
"encoding/json"
+ "errors"
"flag"
"fmt"
"io"
@@ -29,6 +30,8 @@ import (
pb "github.com/google/go-cmp/cmp/internal/testprotos"
ts "github.com/google/go-cmp/cmp/internal/teststructs"
+ foo1 "github.com/google/go-cmp/cmp/internal/teststructs/foo1"
+ foo2 "github.com/google/go-cmp/cmp/internal/teststructs/foo2"
)
func init() {
@@ -101,7 +104,12 @@ func mustFormatGolden(path string, in []struct{ Name, Data string }) {
var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC)
-func intPtr(n int) *int { return &n }
+func newInt(n int) *int { return &n }
+
+type Stringer string
+
+func newStringer(s string) fmt.Stringer { return (*Stringer)(&s) }
+func (s Stringer) String() string { return string(s) }
type test struct {
label string // Test name
@@ -148,8 +156,12 @@ func TestDiff(t *testing.T) {
gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...)
}()
- // TODO: Require every test case to provide a reason.
- if tt.wantPanic == "" {
+ switch {
+ case strings.Contains(t.Name(), "#"):
+ panic("unique test name must be provided")
+ case tt.reason == "":
+ panic("reason must be provided")
+ case tt.wantPanic == "":
if gotPanic != "" {
t.Fatalf("unexpected panic message: %s\nreason: %v", gotPanic, tt.reason)
}
@@ -159,15 +171,15 @@ func TestDiff(t *testing.T) {
}
} else {
wantDiff := wantDiffs[t.Name()]
- if gotDiff != wantDiff {
- t.Fatalf("Diff:\ngot:\n%s\nwant:\n%s\nreason: %v", gotDiff, wantDiff, tt.reason)
+ if diff := cmp.Diff(wantDiff, gotDiff); diff != "" {
+ t.Fatalf("Diff:\ngot:\n%s\nwant:\n%s\ndiff: (-want +got)\n%s\nreason: %v", gotDiff, wantDiff, diff, tt.reason)
}
}
gotEqual := gotDiff == ""
if gotEqual != tt.wantEqual {
t.Fatalf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
}
- } else {
+ default:
if !strings.Contains(gotPanic, tt.wantPanic) {
t.Fatalf("panic message:\ngot: %s\nwant: %s\nreason: %v", gotPanic, tt.wantPanic, tt.reason)
}
@@ -226,35 +238,40 @@ func comparerTests() []test {
}
return []test{{
- label: label,
+ label: label + "/Nil",
x: nil,
y: nil,
wantEqual: true,
+ reason: "nils are equal",
}, {
- label: label,
+ label: label + "/Integer",
x: 1,
y: 1,
wantEqual: true,
+ reason: "identical integers are equal",
}, {
- label: label,
+ label: label + "/UnfilteredIgnore",
x: 1,
y: 1,
opts: []cmp.Option{cmp.Ignore()},
wantPanic: "cannot use an unfiltered option",
+ reason: "unfiltered options are functionally useless",
}, {
- label: label,
+ label: label + "/UnfilteredCompare",
x: 1,
y: 1,
opts: []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })},
wantPanic: "cannot use an unfiltered option",
+ reason: "unfiltered options are functionally useless",
}, {
- label: label,
+ label: label + "/UnfilteredTransform",
x: 1,
y: 1,
opts: []cmp.Option{cmp.Transformer("λ", func(x interface{}) interface{} { return x })},
wantPanic: "cannot use an unfiltered option",
+ reason: "unfiltered options are functionally useless",
}, {
- label: label,
+ label: label + "/AmbiguousOptions",
x: 1,
y: 1,
opts: []cmp.Option{
@@ -262,8 +279,9 @@ func comparerTests() []test {
cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
},
wantPanic: "ambiguous set of applicable options",
+ reason: "both options apply on int, leading to ambiguity",
}, {
- label: label,
+ label: label + "/IgnorePrecedence",
x: 1,
y: 1,
opts: []cmp.Option{
@@ -274,84 +292,98 @@ func comparerTests() []test {
cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
},
wantEqual: true,
+ reason: "ignore takes precedence over other options",
}, {
- label: label,
+ label: label + "/UnknownOption",
opts: []cmp.Option{struct{ cmp.Option }{}},
wantPanic: "unknown option",
+ reason: "use of unknown option should panic",
}, {
- label: label,
+ label: label + "/StructEqual",
x: struct{ A, B, C int }{1, 2, 3},
y: struct{ A, B, C int }{1, 2, 3},
wantEqual: true,
+ reason: "struct comparison with all equal fields",
}, {
- label: label,
+ label: label + "/StructInequal",
x: struct{ A, B, C int }{1, 2, 3},
y: struct{ A, B, C int }{1, 2, 4},
wantEqual: false,
+ reason: "struct comparison with inequal C field",
}, {
- label: label,
+ label: label + "/StructUnexported",
x: struct{ a, b, c int }{1, 2, 3},
y: struct{ a, b, c int }{1, 2, 4},
wantPanic: "cannot handle unexported field",
+ reason: "unexported fields result in a panic by default",
}, {
- label: label,
- x: &struct{ A *int }{intPtr(4)},
- y: &struct{ A *int }{intPtr(4)},
+ label: label + "/PointerStructEqual",
+ x: &struct{ A *int }{newInt(4)},
+ y: &struct{ A *int }{newInt(4)},
wantEqual: true,
+ reason: "comparison of pointer to struct with equal A field",
}, {
- label: label,
- x: &struct{ A *int }{intPtr(4)},
- y: &struct{ A *int }{intPtr(5)},
+ label: label + "/PointerStructInequal",
+ x: &struct{ A *int }{newInt(4)},
+ y: &struct{ A *int }{newInt(5)},
wantEqual: false,
+ reason: "comparison of pointer to struct with inequal A field",
}, {
- label: label,
- x: &struct{ A *int }{intPtr(4)},
- y: &struct{ A *int }{intPtr(5)},
+ label: label + "/PointerStructTrueComparer",
+ x: &struct{ A *int }{newInt(4)},
+ y: &struct{ A *int }{newInt(5)},
opts: []cmp.Option{
cmp.Comparer(func(x, y int) bool { return true }),
},
wantEqual: true,
+ reason: "comparison of pointer to struct with inequal A field, but treated as equal with always equal comparer",
}, {
- label: label,
- x: &struct{ A *int }{intPtr(4)},
- y: &struct{ A *int }{intPtr(5)},
+ label: label + "/PointerStructNonNilComparer",
+ x: &struct{ A *int }{newInt(4)},
+ y: &struct{ A *int }{newInt(5)},
opts: []cmp.Option{
cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }),
},
wantEqual: true,
+ reason: "comparison of pointer to struct with inequal A field, but treated as equal with comparer checking pointers for nilness",
}, {
- label: label,
+ label: label + "/StructNestedPointerEqual",
x: &struct{ R *bytes.Buffer }{},
y: &struct{ R *bytes.Buffer }{},
wantEqual: true,
+ reason: "equal since both pointers in R field are nil",
}, {
- label: label,
+ label: label + "/StructNestedPointerInequal",
x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
y: &struct{ R *bytes.Buffer }{},
wantEqual: false,
+ reason: "inequal since R field is inequal",
}, {
- label: label,
+ label: label + "/StructNestedPointerTrueComparer",
x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
y: &struct{ R *bytes.Buffer }{},
opts: []cmp.Option{
cmp.Comparer(func(x, y io.Reader) bool { return true }),
},
wantEqual: true,
+ reason: "equal despite inequal R field values since the comparer always reports true",
}, {
- label: label,
+ label: label + "/StructNestedValueUnexportedPanic1",
x: &struct{ R bytes.Buffer }{},
y: &struct{ R bytes.Buffer }{},
wantPanic: "cannot handle unexported field",
+ reason: "bytes.Buffer contains unexported fields",
}, {
- label: label,
+ label: label + "/StructNestedValueUnexportedPanic2",
x: &struct{ R bytes.Buffer }{},
y: &struct{ R bytes.Buffer }{},
opts: []cmp.Option{
cmp.Comparer(func(x, y io.Reader) bool { return true }),
},
wantPanic: "cannot handle unexported field",
+ reason: "bytes.Buffer value does not implement io.Reader",
}, {
- label: label,
+ label: label + "/StructNestedValueEqual",
x: &struct{ R bytes.Buffer }{},
y: &struct{ R bytes.Buffer }{},
opts: []cmp.Option{
@@ -359,13 +391,15 @@ func comparerTests() []test {
cmp.Comparer(func(x, y io.Reader) bool { return true }),
},
wantEqual: true,
+ reason: "bytes.Buffer pointer due to shallow copy does implement io.Reader",
}, {
- label: label,
+ label: label + "/RegexpUnexportedPanic",
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
wantPanic: "cannot handle unexported field",
+ reason: "regexp.Regexp contains unexported fields",
}, {
- label: label,
+ label: label + "/RegexpEqual",
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
@@ -375,8 +409,9 @@ func comparerTests() []test {
return x.String() == y.String()
})},
wantEqual: true,
+ reason: "comparer for *regexp.Regexp applied with equal regexp strings",
}, {
- label: label,
+ label: label + "/RegexpInequal",
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
@@ -386,8 +421,9 @@ func comparerTests() []test {
return x.String() == y.String()
})},
wantEqual: false,
+ reason: "comparer for *regexp.Regexp applied with inequal regexp strings",
}, {
- label: label,
+ label: label + "/TriplePointerEqual",
x: func() ***int {
a := 0
b := &a
@@ -401,8 +437,9 @@ func comparerTests() []test {
return &c
}(),
wantEqual: true,
+ reason: "three layers of pointers to the same value",
}, {
- label: label,
+ label: label + "/TriplePointerInequal",
x: func() ***int {
a := 0
b := &a
@@ -416,40 +453,47 @@ func comparerTests() []test {
return &c
}(),
wantEqual: false,
+ reason: "three layers of pointers to different values",
}, {
- label: label,
+ label: label + "/SliceWithDifferingCapacity",
x: []int{1, 2, 3, 4, 5}[:3],
y: []int{1, 2, 3},
wantEqual: true,
+ reason: "elements past the slice length are not compared",
}, {
- label: label,
+ label: label + "/StringerEqual",
x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
y: struct{ fmt.Stringer }{regexp.MustCompile("hello")},
opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
wantEqual: true,
+ reason: "comparer for fmt.Stringer used to compare differing types with same string",
}, {
- label: label,
+ label: label + "/StringerInequal",
x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
y: struct{ fmt.Stringer }{regexp.MustCompile("hello2")},
opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
wantEqual: false,
+ reason: "comparer for fmt.Stringer used to compare differing types with different strings",
}, {
- label: label,
+ label: label + "/DifferingHash",
x: md5.Sum([]byte{'a'}),
y: md5.Sum([]byte{'b'}),
wantEqual: false,
+ reason: "hash differs",
}, {
- label: label,
+ label: label + "/NilStringer",
x: new(fmt.Stringer),
y: nil,
wantEqual: false,
+ reason: "by default differing types are always inequal",
}, {
- label: label,
+ label: label + "/TarHeaders",
x: makeTarHeaders('0'),
y: makeTarHeaders('\x00'),
wantEqual: false,
+ reason: "type flag differs between the headers",
}, {
- label: label,
+ label: label + "/NonDeterministicComparer",
x: make([]int, 1000),
y: make([]int, 1000),
opts: []cmp.Option{
@@ -458,8 +502,9 @@ func comparerTests() []test {
}),
},
wantPanic: "non-deterministic or non-symmetric function detected",
+ reason: "non-deterministic comparer",
}, {
- label: label,
+ label: label + "/NonDeterministicFilter",
x: make([]int, 1000),
y: make([]int, 1000),
opts: []cmp.Option{
@@ -468,8 +513,9 @@ func comparerTests() []test {
}, cmp.Ignore()),
},
wantPanic: "non-deterministic or non-symmetric function detected",
+ reason: "non-deterministic filter",
}, {
- label: label,
+ label: label + "/AssymetricComparer",
x: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
y: []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
opts: []cmp.Option{
@@ -478,8 +524,9 @@ func comparerTests() []test {
}),
},
wantPanic: "non-deterministic or non-symmetric function detected",
+ reason: "asymmetric comparer",
}, {
- label: label,
+ label: label + "/NonDeterministicTransformer",
x: make([]string, 1000),
y: make([]string, 1000),
opts: []cmp.Option{
@@ -488,10 +535,9 @@ func comparerTests() []test {
}),
},
wantPanic: "non-deterministic function detected",
+ reason: "non-deterministic transformer",
}, {
- // Make sure the dynamic checks don't raise a false positive for
- // non-reflexive comparisons.
- label: label,
+ label: label + "/IrreflexiveComparison",
x: make([]int, 10),
y: make([]int, 10),
opts: []cmp.Option{
@@ -500,19 +546,20 @@ func comparerTests() []test {
}),
},
wantEqual: false,
+ reason: "dynamic checks should not panic for non-reflexive comparisons",
}, {
- // Ensure reasonable Stringer formatting of map keys.
- label: label,
+ label: label + "/StringerMapKey",
x: map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}},
y: map[*pb.Stringer]*pb.Stringer(nil),
wantEqual: false,
+ reason: "stringer should be used to format the map key",
}, {
- // Ensure Stringer avoids double-quote escaping if possible.
- label: label,
+ label: label + "/StringerBacktick",
x: []*pb.Stringer{{`multi\nline\nline\nline`}},
wantEqual: false,
+ reason: "stringer should use backtick quoting if more readable",
}, {
- label: label,
+ label: label + "/AvoidPanicAssignableConverter",
x: struct{ I Iface2 }{},
y: struct{ I Iface2 }{},
opts: []cmp.Option{
@@ -521,8 +568,9 @@ func comparerTests() []test {
}),
},
wantEqual: true,
+ reason: "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
}, {
- label: label,
+ label: label + "/AvoidPanicAssignableTransformer",
x: struct{ I Iface2 }{},
y: struct{ I Iface2 }{},
opts: []cmp.Option{
@@ -531,8 +579,9 @@ func comparerTests() []test {
}),
},
wantEqual: true,
+ reason: "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
}, {
- label: label,
+ label: label + "/AvoidPanicAssignableFilter",
x: struct{ I Iface2 }{},
y: struct{ I Iface2 }{},
opts: []cmp.Option{
@@ -541,13 +590,15 @@ func comparerTests() []test {
}, cmp.Ignore()),
},
wantEqual: true,
+ reason: "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
}, {
- label: label,
+ label: label + "/DynamicMap",
x: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63, "name": "Sammy Sosa"}},
y: []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65.0, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63.0, "name": "Sammy Sosa"}},
wantEqual: false,
+ reason: "dynamic map with differing types (but semantically equivalent values) should be inequal",
}, {
- label: label,
+ label: label + "/MapKeyPointer",
x: map[*int]string{
new(int): "hello",
},
@@ -555,17 +606,9 @@ func comparerTests() []test {
new(int): "world",
},
wantEqual: false,
+ reason: "map keys should use shallow (rather than deep) pointer comparison",
}, {
- label: label,
- x: intPtr(0),
- y: intPtr(0),
- opts: []cmp.Option{
- cmp.Comparer(func(x, y *int) bool { return x == y }),
- },
- // TODO: This diff output is unhelpful and should show the address.
- wantEqual: false,
- }, {
- label: label,
+ label: label + "/IgnoreSliceElements",
x: [2][]int{
{0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 7, 8, 0, 9, 0, 0},
{0, 1, 0, 0, 0, 20},
@@ -589,7 +632,7 @@ func comparerTests() []test {
wantEqual: false,
reason: "all zero slice elements are ignored (even if missing)",
}, {
- label: label,
+ label: label + "/IgnoreMapEntries",
x: [2]map[string]int{
{"ignore1": 0, "ignore2": 0, "keep1": 1, "keep2": 2, "KEEP3": 3, "IGNORE3": 0},
{"keep1": 1, "ignore1": 0},
@@ -613,19 +656,19 @@ func comparerTests() []test {
wantEqual: false,
reason: "all zero map entries are ignored (even if missing)",
}, {
- label: label,
+ label: label + "/PanicUnexportedNamed",
x: namedWithUnexported{},
y: namedWithUnexported{},
wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".namedWithUnexported",
reason: "panic on named struct type with unexported field",
}, {
- label: label,
+ label: label + "/PanicUnexportedUnnamed",
x: struct{ a int }{},
y: struct{ a int }{},
wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".(struct { a int })",
reason: "panic on unnamed struct type with unexported field",
}, {
- label: label,
+ label: label + "/UnaddressableStruct",
x: struct{ s fmt.Stringer }{new(bytes.Buffer)},
y: struct{ s fmt.Stringer }{nil},
opts: []cmp.Option{
@@ -653,6 +696,19 @@ func comparerTests() []test {
},
wantEqual: true,
reason: "verify that exporter does not leak implementation details",
+ }, {
+ label: label + "/ErrorPanic",
+ x: io.EOF,
+ y: io.EOF,
+ wantPanic: "consider using cmpopts.EquateErrors",
+ reason: "suggest cmpopts.EquateErrors when accessing unexported fields of error types",
+ }, {
+ label: label + "/ErrorEqual",
+ x: io.EOF,
+ y: io.EOF,
+ opts: []cmp.Option{cmpopts.EquateErrors()},
+ wantEqual: true,
+ reason: "cmpopts.EquateErrors should equate these two errors as sentinel values",
}}
}
@@ -677,7 +733,7 @@ func transformerTests() []test {
}
return []test{{
- label: label,
+ label: label + "/Uints",
x: uint8(0),
y: uint8(1),
opts: []cmp.Option{
@@ -686,8 +742,9 @@ func transformerTests() []test {
cmp.Transformer("λ", func(in uint32) uint64 { return uint64(in) }),
},
wantEqual: false,
+ reason: "transform uint8 -> uint16 -> uint32 -> uint64",
}, {
- label: label,
+ label: label + "/Ambiguous",
x: 0,
y: 1,
opts: []cmp.Option{
@@ -695,8 +752,9 @@ func transformerTests() []test {
cmp.Transformer("λ", func(in int) int { return in }),
},
wantPanic: "ambiguous set of applicable options",
+ reason: "both transformers apply on int",
}, {
- label: label,
+ label: label + "/Filtered",
x: []int{0, -5, 0, -1},
y: []int{1, 3, 0, -5},
opts: []cmp.Option{
@@ -710,8 +768,9 @@ func transformerTests() []test {
),
},
wantEqual: false,
+ reason: "disjoint transformers filtered based on the values",
}, {
- label: label,
+ label: label + "/DisjointOutput",
x: 0,
y: 1,
opts: []cmp.Option{
@@ -723,8 +782,9 @@ func transformerTests() []test {
}),
},
wantEqual: false,
+ reason: "output type differs based on input value",
}, {
- label: label,
+ label: label + "/JSON",
x: `{
"firstName": "John",
"lastName": "Smith",
@@ -762,8 +822,9 @@ func transformerTests() []test {
}),
},
wantEqual: false,
+ reason: "transformer used to parse JSON input",
}, {
- label: label,
+ label: label + "/AcyclicString",
x: StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")},
y: StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")},
opts: []cmp.Option{
@@ -771,22 +832,27 @@ func transformerTests() []test {
transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }),
},
wantEqual: false,
+ reason: "string -> []string and []byte -> [][]byte transformer only applied once",
}, {
- x: "a\nb\nc\n",
- y: "a\nb\nc\n",
+ label: label + "/CyclicString",
+ x: "a\nb\nc\n",
+ y: "a\nb\nc\n",
opts: []cmp.Option{
cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }),
},
wantPanic: "recursive set of Transformers detected",
+ reason: "cyclic transformation from string -> []string -> string",
}, {
- x: complex64(0),
- y: complex64(0),
+ label: label + "/CyclicComplex",
+ x: complex64(0),
+ y: complex64(0),
opts: []cmp.Option{
cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }),
cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }),
cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }),
},
wantPanic: "recursive set of Transformers detected",
+ reason: "cyclic transformation from complex64 -> complex128 -> [2]float64 -> complex64",
}}
}
@@ -822,19 +888,144 @@ func reporterTests() []test {
)
return []test{{
- label: label,
+ label: label + "/PanicStringer",
+ x: struct{ X fmt.Stringer }{struct{ fmt.Stringer }{nil}},
+ y: struct{ X fmt.Stringer }{bytes.NewBuffer(nil)},
+ wantEqual: false,
+ reason: "panic from fmt.Stringer should not crash the reporter",
+ }, {
+ label: label + "/PanicError",
+ x: struct{ X error }{struct{ error }{nil}},
+ y: struct{ X error }{errors.New("")},
+ wantEqual: false,
+ reason: "panic from error should not crash the reporter",
+ }, {
+ label: label + "/AmbiguousType",
+ x: foo1.Bar{},
+ y: foo2.Bar{},
+ wantEqual: false,
+ reason: "reporter should display the qualified type name to disambiguate between the two values",
+ }, {
+ label: label + "/AmbiguousPointer",
+ x: newInt(0),
+ y: newInt(0),
+ opts: []cmp.Option{
+ cmp.Comparer(func(x, y *int) bool { return x == y }),
+ },
+ wantEqual: false,
+ reason: "reporter should display the address to disambiguate between the two values",
+ }, {
+ label: label + "/AmbiguousPointerStruct",
+ x: struct{ I *int }{newInt(0)},
+ y: struct{ I *int }{newInt(0)},
+ opts: []cmp.Option{
+ cmp.Comparer(func(x, y *int) bool { return x == y }),
+ },
+ wantEqual: false,
+ reason: "reporter should display the address to disambiguate between the two struct fields",
+ }, {
+ label: label + "/AmbiguousPointerSlice",
+ x: []*int{newInt(0)},
+ y: []*int{newInt(0)},
+ opts: []cmp.Option{
+ cmp.Comparer(func(x, y *int) bool { return x == y }),
+ },
+ wantEqual: false,
+ reason: "reporter should display the address to disambiguate between the two slice elements",
+ }, {
+ label: label + "/AmbiguousPointerMap",
+ x: map[string]*int{"zero": newInt(0)},
+ y: map[string]*int{"zero": newInt(0)},
+ opts: []cmp.Option{
+ cmp.Comparer(func(x, y *int) bool { return x == y }),
+ },
+ wantEqual: false,
+ reason: "reporter should display the address to disambiguate between the two map values",
+ }, {
+ label: label + "/AmbiguousStringer",
+ x: Stringer("hello"),
+ y: newStringer("hello"),
+ wantEqual: false,
+ reason: "reporter should avoid calling String to disambiguate between the two values",
+ }, {
+ label: label + "/AmbiguousStringerStruct",
+ x: struct{ S fmt.Stringer }{Stringer("hello")},
+ y: struct{ S fmt.Stringer }{newStringer("hello")},
+ wantEqual: false,
+ reason: "reporter should avoid calling String to disambiguate between the two struct fields",
+ }, {
+ label: label + "/AmbiguousStringerSlice",
+ x: []fmt.Stringer{Stringer("hello")},
+ y: []fmt.Stringer{newStringer("hello")},
+ wantEqual: false,
+ reason: "reporter should avoid calling String to disambiguate between the two slice elements",
+ }, {
+ label: label + "/AmbiguousStringerMap",
+ x: map[string]fmt.Stringer{"zero": Stringer("hello")},
+ y: map[string]fmt.Stringer{"zero": newStringer("hello")},
+ wantEqual: false,
+ reason: "reporter should avoid calling String to disambiguate between the two map values",
+ }, {
+ label: label + "/AmbiguousSliceHeader",
+ x: make([]int, 0, 5),
+ y: make([]int, 0, 1000),
+ opts: []cmp.Option{
+ cmp.Comparer(func(x, y []int) bool { return cap(x) == cap(y) }),
+ },
+ wantEqual: false,
+ reason: "reporter should display the slice header to disambiguate between the two slice values",
+ }, {
+ label: label + "/AmbiguousStringerMapKey",
+ x: map[interface{}]string{
+ nil: "nil",
+ Stringer("hello"): "goodbye",
+ foo1.Bar{"fizz"}: "buzz",
+ },
+ y: map[interface{}]string{
+ newStringer("hello"): "goodbye",
+ foo2.Bar{"fizz"}: "buzz",
+ },
+ wantEqual: false,
+ reason: "reporter should avoid calling String to disambiguate between the two map keys",
+ }, {
+ label: label + "/NonAmbiguousStringerMapKey",
+ x: map[interface{}]string{Stringer("hello"): "goodbye"},
+ y: map[interface{}]string{newStringer("fizz"): "buzz"},
+ wantEqual: false,
+ reason: "reporter should call String as there is no ambiguity between the two map keys",
+ }, {
+ label: label + "/InvalidUTF8",
+ x: MyString("\xed\xa0\x80"),
+ wantEqual: false,
+ reason: "invalid UTF-8 should format as quoted string",
+ }, {
+ label: label + "/UnbatchedSlice",
x: MyComposite{IntsA: []int8{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
y: MyComposite{IntsA: []int8{10, 11, 21, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
wantEqual: false,
reason: "unbatched diffing desired since few elements differ",
}, {
- label: label,
+ label: label + "/BatchedSlice",
x: MyComposite{IntsA: []int8{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
y: MyComposite{IntsA: []int8{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
wantEqual: false,
reason: "batched diffing desired since many elements differ",
}, {
- label: label,
+ label: label + "/BatchedWithComparer",
+ x: MyComposite{BytesA: []byte{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
+ y: MyComposite{BytesA: []byte{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
+ wantEqual: false,
+ opts: []cmp.Option{
+ cmp.Comparer(bytes.Equal),
+ },
+ reason: "batched diffing desired since many elements differ",
+ }, {
+ label: label + "/BatchedLong",
+ x: MyComposite{IntsA: []int8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}},
+ wantEqual: false,
+ reason: "batched output desired for a single slice of primitives unique to one of the inputs",
+ }, {
+ label: label + "/BatchedNamedAndUnnamed",
x: MyComposite{
BytesA: []byte{1, 2, 3},
BytesB: []MyByte{4, 5, 6},
@@ -866,25 +1057,138 @@ func reporterTests() []test {
wantEqual: false,
reason: "batched diffing available for both named and unnamed slices",
}, {
- label: label,
+ label: label + "/BinaryHexdump",
x: MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeX\x95A\xfd$fX\x8byT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1U~{\xf6\xb3~\x1dWi \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")},
y: MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1u-[]]\xf6\xb3haha~\x1dWI \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")},
wantEqual: false,
reason: "binary diff in hexdump form since data is binary data",
}, {
- label: label,
+ label: label + "/StringHexdump",
x: MyComposite{StringB: MyString("readme.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000046\x0000000000000\x00011173\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")},
y: MyComposite{StringB: MyString("gopher.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000043\x0000000000000\x00011217\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")},
wantEqual: false,
reason: "binary diff desired since string looks like binary data",
}, {
- label: label,
+ label: label + "/BinaryString",
x: MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"314 54th Avenue","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`)},
y: MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`)},
wantEqual: false,
reason: "batched textual diff desired since bytes looks like textual data",
}, {
- label: label,
+ label: label + "/TripleQuote",
+ x: MyComposite{StringA: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n"},
+ y: MyComposite{StringA: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n"},
+ wantEqual: false,
+ reason: "use triple-quote syntax",
+ }, {
+ label: label + "/TripleQuoteSlice",
+ x: []string{
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ },
+ y: []string{
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\n",
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ },
+ wantEqual: false,
+ reason: "use triple-quote syntax for slices of strings",
+ }, {
+ label: label + "/TripleQuoteNamedTypes",
+ x: MyComposite{
+ StringB: MyString("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
+ BytesC: MyBytes("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
+ },
+ y: MyComposite{
+ StringB: MyString("aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
+ BytesC: MyBytes("aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
+ },
+ wantEqual: false,
+ reason: "use triple-quote syntax for named types",
+ }, {
+ label: label + "/TripleQuoteSliceNamedTypes",
+ x: []MyString{
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ },
+ y: []MyString{
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\n",
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ },
+ wantEqual: false,
+ reason: "use triple-quote syntax for slices of named strings",
+ }, {
+ label: label + "/TripleQuoteEndlines",
+ x: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\r\nhhh\n\riii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n\r",
+ y: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\r\nhhh\n\riii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz",
+ wantEqual: false,
+ reason: "use triple-quote syntax",
+ }, {
+ label: label + "/AvoidTripleQuoteAmbiguousQuotes",
+ x: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ y: "aaa\nbbb\nCCC\nddd\neee\n\"\"\"\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ wantEqual: false,
+ reason: "avoid triple-quote syntax due to presence of ambiguous triple quotes",
+ }, {
+ label: label + "/AvoidTripleQuoteAmbiguousEllipsis",
+ x: "aaa\nbbb\nccc\n...\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ y: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ wantEqual: false,
+ reason: "avoid triple-quote syntax due to presence of ambiguous ellipsis",
+ }, {
+ label: label + "/AvoidTripleQuoteNonPrintable",
+ x: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ y: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\no\roo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ wantEqual: false,
+ reason: "use triple-quote syntax",
+ }, {
+ label: label + "/AvoidTripleQuoteIdenticalWhitespace",
+ x: "aaa\nbbb\nccc\n ddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ y: "aaa\nbbb\nccc \nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
+ wantEqual: false,
+ reason: "avoid triple-quote syntax due to visual equivalence of differences",
+ }, {
+ label: label + "/TripleQuoteStringer",
+ x: []fmt.Stringer{
+ bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")),
+ bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n)\n\nfunc main() {\n\tfmt.Println(\"My favorite number is\", rand.Intn(10))\n}\n")),
+ },
+ y: []fmt.Stringer{
+ bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")),
+ bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n\t\"math\"\n)\n\nfunc main() {\n\tfmt.Printf(\"Now you have %g problems.\\n\", math.Sqrt(7))\n}\n")),
+ },
+ opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
+ wantEqual: false,
+ reason: "multi-line String output should be formatted with triple quote",
+ }, {
+ label: label + "/LimitMaximumBytesDiffs",
+ x: []byte("\xcd====\x06\x1f\xc2\xcc\xc2-S=====\x1d\xdfa\xae\x98\x9fH======ǰ\xb7=======\xef====:\\\x94\xe6J\xc7=====\xb4======\n\n\xf7\x94===========\xf2\x9c\xc0f=====4\xf6\xf1\xc3\x17\x82======n\x16`\x91D\xc6\x06=======\x1cE====.===========\xc4\x18=======\x8a\x8d\x0e====\x87\xb1\xa5\x8e\xc3=====z\x0f1\xaeU======G,=======5\xe75\xee\x82\xf4\xce====\x11r===========\xaf]=======z\x05\xb3\x91\x88%\xd2====\n1\x89=====i\xb7\x055\xe6\x81\xd2=============\x883=@̾====\x14\x05\x96%^t\x04=====\xe7Ȉ\x90\x1d============="),
+ y: []byte("\\====|\x96\xe7SB\xa0\xab=====\xf0\xbd\xa5q\xab\x17;======\xabP\x00=======\xeb====\xa5\x14\xe6O(\xe4=====(======/c@?===========\xd9x\xed\x13=====J\xfc\x918B\x8d======a8A\xebs\x04\xae=======\aC====\x1c===========\x91\"=======uؾ====s\xec\x845\a=====;\xabS9t======\x1f\x1b=======\x80\xab/\xed+:;====\xeaI===========\xabl=======\xb9\xe9\xfdH\x93\x8e\u007f====ח\xe5=====Ig\x88m\xf5\x01V=============\xf7+4\xb0\x92E====\x9fj\xf8&\xd0h\xf9=====\xeeΨ\r\xbf============="),
+ wantEqual: false,
+ reason: "total bytes difference output is truncated due to excessive number of differences",
+ }, {
+ label: label + "/LimitMaximumStringDiffs",
+ x: "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n",
+ y: "aa\nb\ncc\nd\nee\nf\ngg\nh\nii\nj\nkk\nl\nmm\nn\noo\np\nqq\nr\nss\nt\nuu\nv\nww\nx\nyy\nz\nAA\nB\nCC\nD\nEE\nF\nGG\nH\nII\nJ\nKK\nL\nMM\nN\nOO\nP\nQQ\nR\nSS\nT\nUU\nV\nWW\nX\nYY\nZ\n",
+ wantEqual: false,
+ reason: "total string difference output is truncated due to excessive number of differences",
+ }, {
+ label: label + "/LimitMaximumSliceDiffs",
+ x: func() (out []struct{ S string }) {
+ for _, s := range strings.Split("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n", "\n") {
+ out = append(out, struct{ S string }{s})
+ }
+ return out
+ }(),
+ y: func() (out []struct{ S string }) {
+ for _, s := range strings.Split("aa\nb\ncc\nd\nee\nf\ngg\nh\nii\nj\nkk\nl\nmm\nn\noo\np\nqq\nr\nss\nt\nuu\nv\nww\nx\nyy\nz\nAA\nB\nCC\nD\nEE\nF\nGG\nH\nII\nJ\nKK\nL\nMM\nN\nOO\nP\nQQ\nR\nSS\nT\nUU\nV\nWW\nX\nYY\nZ\n", "\n") {
+ out = append(out, struct{ S string }{s})
+ }
+ return out
+ }(),
+ wantEqual: false,
+ reason: "total slice difference output is truncated due to excessive number of differences",
+ }, {
+ label: label + "/MultilineString",
x: MyComposite{
StringA: strings.TrimPrefix(`
Package cmp determines equality of values.
@@ -935,7 +1239,7 @@ using the AllowUnexported option.`, "\n"),
wantEqual: false,
reason: "batched per-line diff desired since string looks like multi-line textual data",
}, {
- label: label,
+ label: label + "/Slices",
x: MyComposite{
BytesA: []byte{1, 2, 3},
BytesB: []MyByte{4, 5, 6},
@@ -954,7 +1258,7 @@ using the AllowUnexported option.`, "\n"),
wantEqual: false,
reason: "batched diffing for non-nil slices and nil slices",
}, {
- label: label,
+ label: label + "/EmptySlices",
x: MyComposite{
BytesA: []byte{},
BytesB: []MyByte{},
@@ -972,11 +1276,37 @@ using the AllowUnexported option.`, "\n"),
y: MyComposite{},
wantEqual: false,
reason: "batched diffing for empty slices and nil slices",
+ }, {
+ label: label + "/LargeMapKey",
+ x: map[*[]byte]int{func() *[]byte {
+ b := make([]byte, 1<<20, 1<<20)
+ return &b
+ }(): 0},
+ y: map[*[]byte]int{func() *[]byte {
+ b := make([]byte, 1<<20, 1<<20)
+ return &b
+ }(): 0},
+ reason: "printing map keys should have some verbosity limit imposed",
+ }, {
+ label: label + "/LargeStringInInterface",
+ x: struct{ X interface{} }{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis."},
+ y: struct{ X interface{} }{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,"},
+ reason: "strings within an interface should benefit from specialized diffing",
+ }, {
+ label: label + "/LargeBytesInInterface",
+ x: struct{ X interface{} }{[]byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis.")},
+ y: struct{ X interface{} }{[]byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,")},
+ reason: "bytes slice within an interface should benefit from specialized diffing",
+ }, {
+ label: label + "/LargeStandaloneString",
+ x: struct{ X interface{} }{[1]string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis."}},
+ y: struct{ X interface{} }{[1]string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,"}},
+ reason: "printing a large standalone string that is different should print enough context to see the difference",
}}
}
func embeddedTests() []test {
- const label = "EmbeddedStruct/"
+ const label = "EmbeddedStruct"
privateStruct := *new(ts.ParentStructA).PrivateStruct()
@@ -1068,7 +1398,7 @@ func embeddedTests() []test {
return s
}
- // TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/21122).
+ // TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/21122).
wantPanicNotGo110 := func(s string) string {
if !flags.AtLeastGo110 {
return ""
@@ -1077,52 +1407,58 @@ func embeddedTests() []test {
}
return []test{{
- label: label + "ParentStructA",
+ label: label + "/ParentStructA/PanicUnexported1",
x: ts.ParentStructA{},
y: ts.ParentStructA{},
wantPanic: "cannot handle unexported field",
+ reason: "ParentStructA has an unexported field",
}, {
- label: label + "ParentStructA",
+ label: label + "/ParentStructA/Ignored",
x: ts.ParentStructA{},
y: ts.ParentStructA{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructA{}),
},
wantEqual: true,
+ reason: "the only field (which is unexported) of ParentStructA is ignored",
}, {
- label: label + "ParentStructA",
+ label: label + "/ParentStructA/PanicUnexported2",
x: createStructA(0),
y: createStructA(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructA{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "privateStruct also has unexported fields",
}, {
- label: label + "ParentStructA",
+ label: label + "/ParentStructA/Equal",
x: createStructA(0),
y: createStructA(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructA and privateStruct are allowed",
}, {
- label: label + "ParentStructA",
+ label: label + "/ParentStructA/Inequal",
x: createStructA(0),
y: createStructA(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}, {
- label: label + "ParentStructB",
+ label: label + "/ParentStructB/PanicUnexported1",
x: ts.ParentStructB{},
y: ts.ParentStructB{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructB{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct has an unexported field",
}, {
- label: label + "ParentStructB",
+ label: label + "/ParentStructB/Ignored",
x: ts.ParentStructB{},
y: ts.ParentStructB{},
opts: []cmp.Option{
@@ -1130,77 +1466,87 @@ func embeddedTests() []test {
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructB and PublicStruct are ignored",
}, {
- label: label + "ParentStructB",
+ label: label + "/ParentStructB/PanicUnexported2",
x: createStructB(0),
y: createStructB(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructB{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct also has unexported fields",
}, {
- label: label + "ParentStructB",
+ label: label + "/ParentStructB/Equal",
x: createStructB(0),
y: createStructB(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructB and PublicStruct are allowed",
}, {
- label: label + "ParentStructB",
+ label: label + "/ParentStructB/Inequal",
x: createStructB(0),
y: createStructB(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}, {
- label: label + "ParentStructC",
+ label: label + "/ParentStructC/PanicUnexported1",
x: ts.ParentStructC{},
y: ts.ParentStructC{},
wantPanic: "cannot handle unexported field",
+ reason: "ParentStructC has unexported fields",
}, {
- label: label + "ParentStructC",
+ label: label + "/ParentStructC/Ignored",
x: ts.ParentStructC{},
y: ts.ParentStructC{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructC{}),
},
wantEqual: true,
+ reason: "unexported fields of ParentStructC are ignored",
}, {
- label: label + "ParentStructC",
+ label: label + "/ParentStructC/PanicUnexported2",
x: createStructC(0),
y: createStructC(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructC{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "privateStruct also has unexported fields",
}, {
- label: label + "ParentStructC",
+ label: label + "/ParentStructC/Equal",
x: createStructC(0),
y: createStructC(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructC and privateStruct are allowed",
}, {
- label: label + "ParentStructC",
+ label: label + "/ParentStructC/Inequal",
x: createStructC(0),
y: createStructC(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}, {
- label: label + "ParentStructD",
+ label: label + "/ParentStructD/PanicUnexported1",
x: ts.ParentStructD{},
y: ts.ParentStructD{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructD{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "ParentStructD has unexported fields",
}, {
- label: label + "ParentStructD",
+ label: label + "/ParentStructD/Ignored",
x: ts.ParentStructD{},
y: ts.ParentStructD{},
opts: []cmp.Option{
@@ -1208,40 +1554,45 @@ func embeddedTests() []test {
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
},
wantEqual: true,
+ reason: "unexported fields of ParentStructD and PublicStruct are ignored",
}, {
- label: label + "ParentStructD",
+ label: label + "/ParentStructD/PanicUnexported2",
x: createStructD(0),
y: createStructD(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructD{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct also has unexported fields",
}, {
- label: label + "ParentStructD",
+ label: label + "/ParentStructD/Equal",
x: createStructD(0),
y: createStructD(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructD and PublicStruct are allowed",
}, {
- label: label + "ParentStructD",
+ label: label + "/ParentStructD/Inequal",
x: createStructD(0),
y: createStructD(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}, {
- label: label + "ParentStructE",
+ label: label + "/ParentStructE/PanicUnexported1",
x: ts.ParentStructE{},
y: ts.ParentStructE{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructE{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "ParentStructE has unexported fields",
}, {
- label: label + "ParentStructE",
+ label: label + "/ParentStructE/Ignored",
x: ts.ParentStructE{},
y: ts.ParentStructE{},
opts: []cmp.Option{
@@ -1249,48 +1600,54 @@ func embeddedTests() []test {
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
},
wantEqual: true,
+ reason: "unexported fields of ParentStructE and PublicStruct are ignored",
}, {
- label: label + "ParentStructE",
+ label: label + "/ParentStructE/PanicUnexported2",
x: createStructE(0),
y: createStructE(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructE{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct and privateStruct also has unexported fields",
}, {
- label: label + "ParentStructE",
+ label: label + "/ParentStructE/PanicUnexported3",
x: createStructE(0),
y: createStructE(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "privateStruct also has unexported fields",
}, {
- label: label + "ParentStructE",
+ label: label + "/ParentStructE/Equal",
x: createStructE(0),
y: createStructE(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructE, PublicStruct, and privateStruct are allowed",
}, {
- label: label + "ParentStructE",
+ label: label + "/ParentStructE/Inequal",
x: createStructE(0),
y: createStructE(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}, {
- label: label + "ParentStructF",
+ label: label + "/ParentStructF/PanicUnexported1",
x: ts.ParentStructF{},
y: ts.ParentStructF{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructF{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "ParentStructF has unexported fields",
}, {
- label: label + "ParentStructF",
+ label: label + "/ParentStructF/Ignored",
x: ts.ParentStructF{},
y: ts.ParentStructF{},
opts: []cmp.Option{
@@ -1298,227 +1655,256 @@ func embeddedTests() []test {
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
},
wantEqual: true,
+ reason: "unexported fields of ParentStructF and PublicStruct are ignored",
}, {
- label: label + "ParentStructF",
+ label: label + "/ParentStructF/PanicUnexported2",
x: createStructF(0),
y: createStructF(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructF{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct and privateStruct also has unexported fields",
}, {
- label: label + "ParentStructF",
+ label: label + "/ParentStructF/PanicUnexported3",
x: createStructF(0),
y: createStructF(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "privateStruct also has unexported fields",
}, {
- label: label + "ParentStructF",
+ label: label + "/ParentStructF/Equal",
x: createStructF(0),
y: createStructF(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructF, PublicStruct, and privateStruct are allowed",
}, {
- label: label + "ParentStructF",
+ label: label + "/ParentStructF/Inequal",
x: createStructF(0),
y: createStructF(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}, {
- label: label + "ParentStructG",
+ label: label + "/ParentStructG/PanicUnexported1",
x: ts.ParentStructG{},
y: ts.ParentStructG{},
wantPanic: wantPanicNotGo110("cannot handle unexported field"),
wantEqual: !flags.AtLeastGo110,
+ reason: "ParentStructG has unexported fields",
}, {
- label: label + "ParentStructG",
+ label: label + "/ParentStructG/Ignored",
x: ts.ParentStructG{},
y: ts.ParentStructG{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructG{}),
},
wantEqual: true,
+ reason: "unexported fields of ParentStructG are ignored",
}, {
- label: label + "ParentStructG",
+ label: label + "/ParentStructG/PanicUnexported2",
x: createStructG(0),
y: createStructG(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructG{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "privateStruct also has unexported fields",
}, {
- label: label + "ParentStructG",
+ label: label + "/ParentStructG/Equal",
x: createStructG(0),
y: createStructG(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructG and privateStruct are allowed",
}, {
- label: label + "ParentStructG",
+ label: label + "/ParentStructG/Inequal",
x: createStructG(0),
y: createStructG(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}, {
- label: label + "ParentStructH",
+ label: label + "/ParentStructH/EqualNil",
x: ts.ParentStructH{},
y: ts.ParentStructH{},
wantEqual: true,
+ reason: "PublicStruct is not compared because the pointer is nil",
}, {
- label: label + "ParentStructH",
+ label: label + "/ParentStructH/PanicUnexported1",
x: createStructH(0),
y: createStructH(0),
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct has unexported fields",
}, {
- label: label + "ParentStructH",
+ label: label + "/ParentStructH/Ignored",
x: ts.ParentStructH{},
y: ts.ParentStructH{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructH{}),
},
wantEqual: true,
+ reason: "unexported fields of ParentStructH are ignored (it has none)",
}, {
- label: label + "ParentStructH",
+ label: label + "/ParentStructH/PanicUnexported2",
x: createStructH(0),
y: createStructH(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructH{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct also has unexported fields",
}, {
- label: label + "ParentStructH",
+ label: label + "/ParentStructH/Equal",
x: createStructH(0),
y: createStructH(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructH and PublicStruct are allowed",
}, {
- label: label + "ParentStructH",
+ label: label + "/ParentStructH/Inequal",
x: createStructH(0),
y: createStructH(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}, {
- label: label + "ParentStructI",
+ label: label + "/ParentStructI/PanicUnexported1",
x: ts.ParentStructI{},
y: ts.ParentStructI{},
wantPanic: wantPanicNotGo110("cannot handle unexported field"),
wantEqual: !flags.AtLeastGo110,
+ reason: "ParentStructI has unexported fields",
}, {
- label: label + "ParentStructI",
+ label: label + "/ParentStructI/Ignored1",
x: ts.ParentStructI{},
y: ts.ParentStructI{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructI{}),
},
wantEqual: true,
+ reason: "unexported fields of ParentStructI are ignored",
}, {
- label: label + "ParentStructI",
+ label: label + "/ParentStructI/PanicUnexported2",
x: createStructI(0),
y: createStructI(0),
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructI{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct and privateStruct also has unexported fields",
}, {
- label: label + "ParentStructI",
+ label: label + "/ParentStructI/Ignored2",
x: createStructI(0),
y: createStructI(0),
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
},
wantEqual: true,
+ reason: "unexported fields of ParentStructI and PublicStruct are ignored",
}, {
- label: label + "ParentStructI",
+ label: label + "/ParentStructI/PanicUnexported3",
x: createStructI(0),
y: createStructI(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructI{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct and privateStruct also has unexported fields",
}, {
- label: label + "ParentStructI",
+ label: label + "/ParentStructI/Equal",
x: createStructI(0),
y: createStructI(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructI, PublicStruct, and privateStruct are allowed",
}, {
- label: label + "ParentStructI",
+ label: label + "/ParentStructI/Inequal",
x: createStructI(0),
y: createStructI(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}, {
- label: label + "ParentStructJ",
+ label: label + "/ParentStructJ/PanicUnexported1",
x: ts.ParentStructJ{},
y: ts.ParentStructJ{},
wantPanic: "cannot handle unexported field",
+ reason: "ParentStructJ has unexported fields",
}, {
- label: label + "ParentStructJ",
+ label: label + "/ParentStructJ/PanicUnexported2",
x: ts.ParentStructJ{},
y: ts.ParentStructJ{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "PublicStruct and privateStruct also has unexported fields",
}, {
- label: label + "ParentStructJ",
+ label: label + "/ParentStructJ/Ignored",
x: ts.ParentStructJ{},
y: ts.ParentStructJ{},
opts: []cmp.Option{
cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
},
wantEqual: true,
+ reason: "unexported fields of ParentStructJ and PublicStruct are ignored",
}, {
- label: label + "ParentStructJ",
+ label: label + "/ParentStructJ/PanicUnexported3",
x: createStructJ(0),
y: createStructJ(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
},
wantPanic: "cannot handle unexported field",
+ reason: "privateStruct also has unexported fields",
}, {
- label: label + "ParentStructJ",
+ label: label + "/ParentStructJ/Equal",
x: createStructJ(0),
y: createStructJ(0),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
},
wantEqual: true,
+ reason: "unexported fields of both ParentStructJ, PublicStruct, and privateStruct are allowed",
}, {
- label: label + "ParentStructJ",
+ label: label + "/ParentStructJ/Inequal",
x: createStructJ(0),
y: createStructJ(1),
opts: []cmp.Option{
cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
},
wantEqual: false,
+ reason: "the two values differ on some fields",
}}
}
func methodTests() []test {
- const label = "EqualMethod/"
+ const label = "EqualMethod"
// A common mistake that the Equal method is on a pointer receiver,
// but only a non-pointer value is present in the struct.
// A transform can be used to forcibly reference the value.
- derefTransform := cmp.FilterPath(func(p cmp.Path) bool {
+ addrTransform := cmp.FilterPath(func(p cmp.Path) bool {
if len(p) == 0 {
return false
}
@@ -1532,7 +1918,7 @@ func methodTests() []test {
tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true)
}
return false
- }, cmp.Transformer("Ref", func(x interface{}) interface{} {
+ }, cmp.Transformer("Addr", func(x interface{}) interface{} {
v := reflect.ValueOf(x)
vp := reflect.New(v.Type())
vp.Elem().Set(v)
@@ -1543,284 +1929,338 @@ func methodTests() []test {
// returns true, while the underlying data are fundamentally different.
// Since the method should be called, these are expected to be equal.
return []test{{
- label: label + "StructA",
+ label: label + "/StructA/ValueEqual",
x: ts.StructA{X: "NotEqual"},
y: ts.StructA{X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructA value called",
}, {
- label: label + "StructA",
+ label: label + "/StructA/PointerEqual",
x: &ts.StructA{X: "NotEqual"},
y: &ts.StructA{X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructA pointer called",
}, {
- label: label + "StructB",
+ label: label + "/StructB/ValueInequal",
x: ts.StructB{X: "NotEqual"},
y: ts.StructB{X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructB value not called",
}, {
- label: label + "StructB",
+ label: label + "/StructB/ValueAddrEqual",
x: ts.StructB{X: "NotEqual"},
y: ts.StructB{X: "not_equal"},
- opts: []cmp.Option{derefTransform},
+ opts: []cmp.Option{addrTransform},
wantEqual: true,
+ reason: "Equal method on StructB pointer called due to shallow copy transform",
}, {
- label: label + "StructB",
+ label: label + "/StructB/PointerEqual",
x: &ts.StructB{X: "NotEqual"},
y: &ts.StructB{X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructB pointer called",
}, {
- label: label + "StructC",
+ label: label + "/StructC/ValueEqual",
x: ts.StructC{X: "NotEqual"},
y: ts.StructC{X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructC value called",
}, {
- label: label + "StructC",
+ label: label + "/StructC/PointerEqual",
x: &ts.StructC{X: "NotEqual"},
y: &ts.StructC{X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructC pointer called",
}, {
- label: label + "StructD",
+ label: label + "/StructD/ValueInequal",
x: ts.StructD{X: "NotEqual"},
y: ts.StructD{X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructD value not called",
}, {
- label: label + "StructD",
+ label: label + "/StructD/ValueAddrEqual",
x: ts.StructD{X: "NotEqual"},
y: ts.StructD{X: "not_equal"},
- opts: []cmp.Option{derefTransform},
+ opts: []cmp.Option{addrTransform},
wantEqual: true,
+ reason: "Equal method on StructD pointer called due to shallow copy transform",
}, {
- label: label + "StructD",
+ label: label + "/StructD/PointerEqual",
x: &ts.StructD{X: "NotEqual"},
y: &ts.StructD{X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructD pointer called",
}, {
- label: label + "StructE",
+ label: label + "/StructE/ValueInequal",
x: ts.StructE{X: "NotEqual"},
y: ts.StructE{X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructE value not called",
}, {
- label: label + "StructE",
+ label: label + "/StructE/ValueAddrEqual",
x: ts.StructE{X: "NotEqual"},
y: ts.StructE{X: "not_equal"},
- opts: []cmp.Option{derefTransform},
+ opts: []cmp.Option{addrTransform},
wantEqual: true,
+ reason: "Equal method on StructE pointer called due to shallow copy transform",
}, {
- label: label + "StructE",
+ label: label + "/StructE/PointerEqual",
x: &ts.StructE{X: "NotEqual"},
y: &ts.StructE{X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructE pointer called",
}, {
- label: label + "StructF",
+ label: label + "/StructF/ValueInequal",
x: ts.StructF{X: "NotEqual"},
y: ts.StructF{X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructF value not called",
}, {
- label: label + "StructF",
+ label: label + "/StructF/PointerEqual",
x: &ts.StructF{X: "NotEqual"},
y: &ts.StructF{X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructF pointer called",
}, {
- label: label + "StructA1",
+ label: label + "/StructA1/ValueEqual",
x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
wantEqual: true,
+ reason: "Equal method on StructA value called with equal X field",
}, {
- label: label + "StructA1",
+ label: label + "/StructA1/ValueInequal",
x: ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructA value called, but inequal X field",
}, {
- label: label + "StructA1",
+ label: label + "/StructA1/PointerEqual",
x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
wantEqual: true,
+ reason: "Equal method on StructA value called with equal X field",
}, {
- label: label + "StructA1",
+ label: label + "/StructA1/PointerInequal",
x: &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructA value called, but inequal X field",
}, {
- label: label + "StructB1",
+ label: label + "/StructB1/ValueEqual",
x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
- opts: []cmp.Option{derefTransform},
+ opts: []cmp.Option{addrTransform},
wantEqual: true,
+ reason: "Equal method on StructB pointer called due to shallow copy transform with equal X field",
}, {
- label: label + "StructB1",
+ label: label + "/StructB1/ValueInequal",
x: ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
- opts: []cmp.Option{derefTransform},
+ opts: []cmp.Option{addrTransform},
wantEqual: false,
+ reason: "Equal method on StructB pointer called due to shallow copy transform, but inequal X field",
}, {
- label: label + "StructB1",
+ label: label + "/StructB1/PointerEqual",
x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
- opts: []cmp.Option{derefTransform},
+ opts: []cmp.Option{addrTransform},
wantEqual: true,
+ reason: "Equal method on StructB pointer called due to shallow copy transform with equal X field",
}, {
- label: label + "StructB1",
+ label: label + "/StructB1/PointerInequal",
x: &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
- opts: []cmp.Option{derefTransform},
+ opts: []cmp.Option{addrTransform},
wantEqual: false,
+ reason: "Equal method on StructB pointer called due to shallow copy transform, but inequal X field",
}, {
- label: label + "StructC1",
+ label: label + "/StructC1/ValueEqual",
x: ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructC1 value called",
}, {
- label: label + "StructC1",
+ label: label + "/StructC1/PointerEqual",
x: &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructC1 pointer called",
}, {
- label: label + "StructD1",
+ label: label + "/StructD1/ValueInequal",
x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructD1 value not called",
}, {
- label: label + "StructD1",
+ label: label + "/StructD1/PointerAddrEqual",
x: ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
- opts: []cmp.Option{derefTransform},
+ opts: []cmp.Option{addrTransform},
wantEqual: true,
+ reason: "Equal method on StructD1 pointer called due to shallow copy transform",
}, {
- label: label + "StructD1",
+ label: label + "/StructD1/PointerEqual",
x: &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructD1 pointer called",
}, {
- label: label + "StructE1",
+ label: label + "/StructE1/ValueInequal",
x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructE1 value not called",
}, {
- label: label + "StructE1",
+ label: label + "/StructE1/ValueAddrEqual",
x: ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
- opts: []cmp.Option{derefTransform},
+ opts: []cmp.Option{addrTransform},
wantEqual: true,
+ reason: "Equal method on StructE1 pointer called due to shallow copy transform",
}, {
- label: label + "StructE1",
+ label: label + "/StructE1/PointerEqual",
x: &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructE1 pointer called",
}, {
- label: label + "StructF1",
+ label: label + "/StructF1/ValueInequal",
x: ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructF1 value not called",
}, {
- label: label + "StructF1",
+ label: label + "/StructF1/PointerEqual",
x: &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method on StructF1 pointer called",
}, {
- label: label + "StructA2",
+ label: label + "/StructA2/ValueEqual",
x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
wantEqual: true,
+ reason: "Equal method on StructA pointer called with equal X field",
}, {
- label: label + "StructA2",
+ label: label + "/StructA2/ValueInequal",
x: ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructA pointer called, but inequal X field",
}, {
- label: label + "StructA2",
+ label: label + "/StructA2/PointerEqual",
x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
wantEqual: true,
+ reason: "Equal method on StructA pointer called with equal X field",
}, {
- label: label + "StructA2",
+ label: label + "/StructA2/PointerInequal",
x: &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructA pointer called, but inequal X field",
}, {
- label: label + "StructB2",
+ label: label + "/StructB2/ValueEqual",
x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
wantEqual: true,
+ reason: "Equal method on StructB pointer called with equal X field",
}, {
- label: label + "StructB2",
+ label: label + "/StructB2/ValueInequal",
x: ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructB pointer called, but inequal X field",
}, {
- label: label + "StructB2",
+ label: label + "/StructB2/PointerEqual",
x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
wantEqual: true,
+ reason: "Equal method on StructB pointer called with equal X field",
}, {
- label: label + "StructB2",
+ label: label + "/StructB2/PointerInequal",
x: &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
wantEqual: false,
+ reason: "Equal method on StructB pointer called, but inequal X field",
}, {
- label: label + "StructC2",
+ label: label + "/StructC2/ValueEqual",
x: ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method called on StructC2 value due to forwarded StructC pointer",
}, {
- label: label + "StructC2",
+ label: label + "/StructC2/PointerEqual",
x: &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method called on StructC2 pointer due to forwarded StructC pointer",
}, {
- label: label + "StructD2",
+ label: label + "/StructD2/ValueEqual",
x: ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method called on StructD2 value due to forwarded StructD pointer",
}, {
- label: label + "StructD2",
+ label: label + "/StructD2/PointerEqual",
x: &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method called on StructD2 pointer due to forwarded StructD pointer",
}, {
- label: label + "StructE2",
+ label: label + "/StructE2/ValueEqual",
x: ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method called on StructE2 value due to forwarded StructE pointer",
}, {
- label: label + "StructE2",
+ label: label + "/StructE2/PointerEqual",
x: &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method called on StructE2 pointer due to forwarded StructE pointer",
}, {
- label: label + "StructF2",
+ label: label + "/StructF2/ValueEqual",
x: ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
y: ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method called on StructF2 value due to forwarded StructF pointer",
}, {
- label: label + "StructF2",
+ label: label + "/StructF2/PointerEqual",
x: &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
y: &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
wantEqual: true,
+ reason: "Equal method called on StructF2 pointer due to forwarded StructF pointer",
}, {
- label: label + "StructNo",
+ label: label + "/StructNo/Inequal",
x: ts.StructNo{X: "NotEqual"},
y: ts.StructNo{X: "not_equal"},
wantEqual: false,
+ reason: "Equal method not called since StructNo is not assignable to InterfaceA",
}, {
- label: label + "AssignA",
+ label: label + "/AssignA/Equal",
x: ts.AssignA(func() int { return 0 }),
y: ts.AssignA(func() int { return 1 }),
wantEqual: true,
+ reason: "Equal method called since named func is assignable to unnamed func",
}, {
- label: label + "AssignB",
+ label: label + "/AssignB/Equal",
x: ts.AssignB(struct{ A int }{0}),
y: ts.AssignB(struct{ A int }{1}),
wantEqual: true,
+ reason: "Equal method called since named struct is assignable to unnamed struct",
}, {
- label: label + "AssignC",
+ label: label + "/AssignC/Equal",
x: ts.AssignC(make(chan bool)),
y: ts.AssignC(make(chan bool)),
wantEqual: true,
+ reason: "Equal method called since named channel is assignable to unnamed channel",
}, {
- label: label + "AssignD",
+ label: label + "/AssignD/Equal",
x: ts.AssignD(make(chan bool)),
y: ts.AssignD(make(chan bool)),
wantEqual: true,
+ reason: "Equal method called since named channel is assignable to unnamed channel",
}}
}
@@ -1905,10 +2345,12 @@ func cycleTests() []test {
var tests []test
type XY struct{ x, y interface{} }
for _, tt := range []struct {
+ label string
in XY
wantEqual bool
reason string
}{{
+ label: "PointersEqual",
in: func() XY {
x := new(P)
*x = x
@@ -1917,7 +2359,9 @@ func cycleTests() []test {
return XY{x, y}
}(),
wantEqual: true,
+ reason: "equal pair of single-node pointers",
}, {
+ label: "PointersInequal",
in: func() XY {
x := new(P)
*x = x
@@ -1927,7 +2371,9 @@ func cycleTests() []test {
return XY{x, y1}
}(),
wantEqual: false,
+ reason: "inequal pair of single-node and double-node pointers",
}, {
+ label: "SlicesEqual",
in: func() XY {
x := S{nil}
x[0] = x
@@ -1936,7 +2382,9 @@ func cycleTests() []test {
return XY{x, y}
}(),
wantEqual: true,
+ reason: "equal pair of single-node slices",
}, {
+ label: "SlicesInequal",
in: func() XY {
x := S{nil}
x[0] = x
@@ -1946,7 +2394,9 @@ func cycleTests() []test {
return XY{x, y1}
}(),
wantEqual: false,
+ reason: "inequal pair of single-node and double node slices",
}, {
+ label: "MapsEqual",
in: func() XY {
x := M{0: nil}
x[0] = x
@@ -1955,7 +2405,9 @@ func cycleTests() []test {
return XY{x, y}
}(),
wantEqual: true,
+ reason: "equal pair of single-node maps",
}, {
+ label: "MapsInequal",
in: func() XY {
x := M{0: nil}
x[0] = x
@@ -1965,10 +2417,14 @@ func cycleTests() []test {
return XY{x, y1}
}(),
wantEqual: false,
+ reason: "inequal pair of single-node and double-node maps",
}, {
+ label: "GraphEqual",
in: XY{makeGraph(), makeGraph()},
wantEqual: true,
+ reason: "graphs are equal since they have identical forms",
}, {
+ label: "GraphInequalZeroed",
in: func() XY {
x := makeGraph()
y := makeGraph()
@@ -1978,7 +2434,9 @@ func cycleTests() []test {
return XY{x, y}
}(),
wantEqual: false,
+ reason: "graphs are inequal because the ID fields are different",
}, {
+ label: "GraphInequalStruct",
in: func() XY {
x := makeGraph()
y := makeGraph()
@@ -1989,9 +2447,10 @@ func cycleTests() []test {
return XY{x, y}
}(),
wantEqual: false,
+ reason: "graphs are inequal because they differ on a map element",
}} {
tests = append(tests, test{
- label: label,
+ label: label + "/" + tt.label,
x: tt.in.x,
y: tt.in.y,
wantEqual: tt.wantEqual,
@@ -2027,7 +2486,7 @@ func project1Tests() []test {
Target: "corporation",
Immutable: &ts.GoatImmutable{
ID: "southbay",
- State: (*pb.Goat_States)(intPtr(5)),
+ State: (*pb.Goat_States)(newInt(5)),
Started: now,
},
},
@@ -2055,13 +2514,13 @@ func project1Tests() []test {
Immutable: &ts.EagleImmutable{
ID: "eagleID",
Birthday: now,
- MissingCall: (*pb.Eagle_MissingCalls)(intPtr(55)),
+ MissingCall: (*pb.Eagle_MissingCalls)(newInt(55)),
},
}
}
return []test{{
- label: label,
+ label: label + "/PanicUnexported",
x: ts.Eagle{Slaps: []ts.Slap{{
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
}}},
@@ -2069,8 +2528,9 @@ func project1Tests() []test {
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
}}},
wantPanic: "cannot handle unexported field",
+ reason: "struct contains unexported fields",
}, {
- label: label,
+ label: label + "/ProtoEqual",
x: ts.Eagle{Slaps: []ts.Slap{{
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
}}},
@@ -2079,8 +2539,9 @@ func project1Tests() []test {
}}},
opts: []cmp.Option{cmp.Comparer(pb.Equal)},
wantEqual: true,
+ reason: "simulated protobuf messages contain the same values",
}, {
- label: label,
+ label: label + "/ProtoInequal",
x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
}}},
@@ -2089,18 +2550,20 @@ func project1Tests() []test {
}}},
opts: []cmp.Option{cmp.Comparer(pb.Equal)},
wantEqual: false,
+ reason: "simulated protobuf messages contain different values",
}, {
- label: label,
+ label: label + "/Equal",
x: createEagle(),
y: createEagle(),
opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
wantEqual: true,
+ reason: "equal because values are the same",
}, {
- label: label,
+ label: label + "/Inequal",
x: func() ts.Eagle {
eg := createEagle()
eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2"
- eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(intPtr(6))
+ eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(newInt(6))
eg.Slaps[0].Immutable.MildSlap = false
return eg
}(),
@@ -2112,6 +2575,7 @@ func project1Tests() []test {
}(),
opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
wantEqual: false,
+ reason: "inequal because some values are different",
}}
}
@@ -2171,18 +2635,20 @@ func project2Tests() []test {
}
return []test{{
- label: label,
+ label: label + "/PanicUnexported",
x: createBatch(),
y: createBatch(),
wantPanic: "cannot handle unexported field",
+ reason: "struct contains unexported fields",
}, {
- label: label,
+ label: label + "/Equal",
x: createBatch(),
y: createBatch(),
opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
wantEqual: true,
+ reason: "equal because identical values are compared",
}, {
- label: label,
+ label: label + "/InequalOrder",
x: createBatch(),
y: func() ts.GermBatch {
gb := createBatch()
@@ -2192,8 +2658,9 @@ func project2Tests() []test {
}(),
opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
wantEqual: false,
+ reason: "inequal because slice contains elements in differing order",
}, {
- label: label,
+ label: label + "/EqualOrder",
x: createBatch(),
y: func() ts.GermBatch {
gb := createBatch()
@@ -2203,8 +2670,9 @@ func project2Tests() []test {
}(),
opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
wantEqual: true,
+ reason: "equal because unordered slice is sorted using transformer",
}, {
- label: label,
+ label: label + "/Inequal",
x: func() ts.GermBatch {
gb := createBatch()
delete(gb.DirtyGerms, 17)
@@ -2219,6 +2687,7 @@ func project2Tests() []test {
}(),
opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
wantEqual: false,
+ reason: "inequal because some values are different",
}}
}
@@ -2256,24 +2725,27 @@ func project3Tests() []test {
}
return []test{{
- label: label,
+ label: label + "/PanicUnexported1",
x: createDirt(),
y: createDirt(),
wantPanic: "cannot handle unexported field",
+ reason: "struct contains unexported fields",
}, {
- label: label,
+ label: label + "/PanicUnexported2",
x: createDirt(),
y: createDirt(),
opts: []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
wantPanic: "cannot handle unexported field",
+ reason: "struct contains references to simulated protobuf types with unexported fields",
}, {
- label: label,
+ label: label + "/Equal",
x: createDirt(),
y: createDirt(),
opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
wantEqual: true,
+ reason: "transformer used to create reference to protobuf message so it works with pb.Equal",
}, {
- label: label,
+ label: label + "/Inequal",
x: func() ts.Dirt {
d := createDirt()
d.SetTable(ts.CreateMockTable([]string{"a", "c"}))
@@ -2290,6 +2762,7 @@ func project3Tests() []test {
}(),
opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
wantEqual: false,
+ reason: "inequal because some values are different",
}}
}
@@ -2332,24 +2805,27 @@ func project4Tests() []test {
}
return []test{{
- label: label,
+ label: label + "/PanicUnexported1",
x: createCartel(),
y: createCartel(),
wantPanic: "cannot handle unexported field",
+ reason: "struct contains unexported fields",
}, {
- label: label,
+ label: label + "/PanicUnexported2",
x: createCartel(),
y: createCartel(),
opts: []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)},
wantPanic: "cannot handle unexported field",
+ reason: "struct contains references to simulated protobuf types with unexported fields",
}, {
- label: label,
+ label: label + "/Equal",
x: createCartel(),
y: createCartel(),
opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
wantEqual: true,
+ reason: "transformer used to create reference to protobuf message so it works with pb.Equal",
}, {
- label: label,
+ label: label + "/Inequal",
x: func() ts.Cartel {
d := createCartel()
var p1, p2 ts.Poison
@@ -2369,6 +2845,7 @@ func project4Tests() []test {
}(),
opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
wantEqual: false,
+ reason: "inequal because some values are different",
}}
}
diff --git a/cmp/example_reporter_test.go b/cmp/example_reporter_test.go
index bc1932e..bacba28 100644
--- a/cmp/example_reporter_test.go
+++ b/cmp/example_reporter_test.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp_test
diff --git a/cmp/example_test.go b/cmp/example_test.go
index 2689efb..d165383 100644
--- a/cmp/example_test.go
+++ b/cmp/example_test.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp_test
@@ -37,7 +37,7 @@ func ExampleDiff_testing() {
// SSID: "CoffeeShopWiFi",
// - IPAddress: s"192.168.0.2",
// + IPAddress: s"192.168.0.1",
- // NetMask: net.IPMask{0xff, 0xff, 0x00, 0x00},
+ // NetMask: {0xff, 0xff, 0x00, 0x00},
// Clients: []cmp_test.Client{
// ... // 2 identical elements
// {Hostname: "macchiato", IPAddress: s"192.168.0.153", LastSeen: s"2009-11-10 23:39:43 +0000 UTC"},
diff --git a/cmp/export_panic.go b/cmp/export_panic.go
index dfa5d21..5ff0b42 100644
--- a/cmp/export_panic.go
+++ b/cmp/export_panic.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// +build purego
diff --git a/cmp/export_unsafe.go b/cmp/export_unsafe.go
index 351f1a3..21eb548 100644
--- a/cmp/export_unsafe.go
+++ b/cmp/export_unsafe.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// +build !purego
diff --git a/cmp/internal/diff/debug_disable.go b/cmp/internal/diff/debug_disable.go
index fe98dcc..1daaaac 100644
--- a/cmp/internal/diff/debug_disable.go
+++ b/cmp/internal/diff/debug_disable.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// +build !cmp_debug
diff --git a/cmp/internal/diff/debug_enable.go b/cmp/internal/diff/debug_enable.go
index 597b6ae..4b91dbc 100644
--- a/cmp/internal/diff/debug_enable.go
+++ b/cmp/internal/diff/debug_enable.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// +build cmp_debug
diff --git a/cmp/internal/diff/diff.go b/cmp/internal/diff/diff.go
index 3d2e426..bc196b1 100644
--- a/cmp/internal/diff/diff.go
+++ b/cmp/internal/diff/diff.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// Package diff implements an algorithm for producing edit-scripts.
// The edit-script is a sequence of operations needed to transform one list
@@ -12,6 +12,13 @@
// is more important than obtaining a minimal Levenshtein distance.
package diff
+import (
+ "math/rand"
+ "time"
+
+ "github.com/google/go-cmp/cmp/internal/flags"
+)
+
// EditType represents a single operation within an edit-script.
type EditType uint8
@@ -112,6 +119,8 @@ func (r Result) Similar() bool {
return r.NumSame+1 >= r.NumDiff
}
+var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
+
// Difference reports whether two lists of lengths nx and ny are equal
// given the definition of equality provided as f.
//
@@ -177,6 +186,11 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// approximately the square-root of the search budget.
searchBudget := 4 * (nx + ny) // O(n)
+ // Running the tests with the "cmp_debug" build tag prints a visualization
+ // of the algorithm running in real-time. This is educational for
+ // understanding how the algorithm works. See debug_enable.go.
+ f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
+
// The algorithm below is a greedy, meet-in-the-middle algorithm for
// computing sub-optimal edit-scripts between two lists.
//
@@ -194,20 +208,26 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// frontier towards the opposite corner.
// • This algorithm terminates when either the X coordinates or the
// Y coordinates of the forward and reverse frontier points ever intersect.
- //
+
// This algorithm is correct even if searching only in the forward direction
// or in the reverse direction. We do both because it is commonly observed
// that two lists commonly differ because elements were added to the front
// or end of the other list.
//
- // Running the tests with the "cmp_debug" build tag prints a visualization
- // of the algorithm running in real-time. This is educational for
- // understanding how the algorithm works. See debug_enable.go.
- f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
- for {
+ // Non-deterministically start with either the forward or reverse direction
+ // to introduce some deliberate instability so that we have the flexibility
+ // to change this algorithm in the future.
+ if flags.Deterministic || randBool {
+ goto forwardSearch
+ } else {
+ goto reverseSearch
+ }
+
+forwardSearch:
+ {
// Forward search from the beginning.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
- break
+ goto finishSearch
}
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match.
@@ -242,10 +262,14 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
} else {
fwdFrontier.Y++
}
+ goto reverseSearch
+ }
+reverseSearch:
+ {
// Reverse search from the end.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
- break
+ goto finishSearch
}
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match.
@@ -280,8 +304,10 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
} else {
revFrontier.Y--
}
+ goto forwardSearch
}
+finishSearch:
// Join the forward and reverse paths and then append the reverse path.
fwdPath.connect(revPath.point, f)
for i := len(revPath.es) - 1; i >= 0; i-- {
diff --git a/cmp/internal/diff/diff_test.go b/cmp/internal/diff/diff_test.go
index ef39077..eacf072 100644
--- a/cmp/internal/diff/diff_test.go
+++ b/cmp/internal/diff/diff_test.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package diff
@@ -18,7 +18,7 @@ func TestDifference(t *testing.T) {
// they can be used by the test author to indicate a missing symbol
// in one of the lists.
x, y string
- want string
+ want string // '|' separated list of possible outputs
}{{
x: "",
y: "",
@@ -30,7 +30,7 @@ func TestDifference(t *testing.T) {
}, {
x: "##",
y: "# ",
- want: ".X",
+ want: ".X|X.",
}, {
x: "a#",
y: "A ",
@@ -42,7 +42,7 @@ func TestDifference(t *testing.T) {
}, {
x: "# ",
y: "##",
- want: ".Y",
+ want: ".Y|Y.",
}, {
x: " #",
y: "@#",
@@ -142,7 +142,7 @@ func TestDifference(t *testing.T) {
}, {
x: "ABCAB BA ",
y: " C BABAC",
- want: "XX.X.Y..Y",
+ want: "XX.X.Y..Y|XX.Y.X..Y",
}, {
x: "# #### ###",
y: "#y####yy###",
@@ -158,7 +158,7 @@ func TestDifference(t *testing.T) {
}, {
x: "0 12z3x 456789 x x 0",
y: "0y12Z3 y456789y y y0",
- want: ".Y..M.XY......YXYXY.",
+ want: ".Y..M.XY......YXYXY.|.Y..M.XY......XYXYY.",
}, {
x: "0 2 4 6 8 ..................abXXcdEXF.ghXi",
y: " 1 3 5 7 9..................AB CDE F.GH I",
@@ -210,7 +210,7 @@ func TestDifference(t *testing.T) {
}, {
x: "0123456789 ",
y: " 5678901234",
- want: "XXXXX.....YYYYY",
+ want: "XXXXX.....YYYYY|YYYYY.....XXXXX",
}, {
x: "0123456789 ",
y: " 4567890123",
@@ -246,9 +246,14 @@ func TestDifference(t *testing.T) {
x := strings.Replace(tt.x, " ", "", -1)
y := strings.Replace(tt.y, " ", "", -1)
es := testStrings(t, x, y)
- if got := es.String(); got != tt.want {
- t.Errorf("Difference(%s, %s):\ngot %s\nwant %s", x, y, got, tt.want)
+ var want string
+ got := es.String()
+ for _, want = range strings.Split(tt.want, "|") {
+ if got == want {
+ return
+ }
}
+ t.Errorf("Difference(%s, %s):\ngot %s\nwant %s", x, y, got, want)
})
}
}
diff --git a/cmp/internal/flags/flags.go b/cmp/internal/flags/flags.go
index a9e7fc0..d8e459c 100644
--- a/cmp/internal/flags/flags.go
+++ b/cmp/internal/flags/flags.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package flags
diff --git a/cmp/internal/flags/toolchain_legacy.go b/cmp/internal/flags/toolchain_legacy.go
index 01aed0a..82d1d7f 100644
--- a/cmp/internal/flags/toolchain_legacy.go
+++ b/cmp/internal/flags/toolchain_legacy.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// +build !go1.10
diff --git a/cmp/internal/flags/toolchain_recent.go b/cmp/internal/flags/toolchain_recent.go
index c0b667f..8646f05 100644
--- a/cmp/internal/flags/toolchain_recent.go
+++ b/cmp/internal/flags/toolchain_recent.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// +build go1.10
diff --git a/cmp/internal/function/func.go b/cmp/internal/function/func.go
index ace1dbe..d127d43 100644
--- a/cmp/internal/function/func.go
+++ b/cmp/internal/function/func.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// Package function provides functionality for identifying function types.
package function
diff --git a/cmp/internal/function/func_test.go b/cmp/internal/function/func_test.go
index 61eeccd..f03ef45 100644
--- a/cmp/internal/function/func_test.go
+++ b/cmp/internal/function/func_test.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package function
diff --git a/cmp/internal/testprotos/protos.go b/cmp/internal/testprotos/protos.go
index 120c8b0..81622d3 100644
--- a/cmp/internal/testprotos/protos.go
+++ b/cmp/internal/testprotos/protos.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package testprotos
diff --git a/cmp/internal/teststructs/foo1/foo.go b/cmp/internal/teststructs/foo1/foo.go
new file mode 100644
index 0000000..c0882fb
--- /dev/null
+++ b/cmp/internal/teststructs/foo1/foo.go
@@ -0,0 +1,10 @@
+// Copyright 2020, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package foo is deliberately named differently than the parent directory.
+// It contain declarations that have ambiguity in their short names,
+// relative to a different package also called foo.
+package foo
+
+type Bar struct{ S string }
diff --git a/cmp/internal/teststructs/foo2/foo.go b/cmp/internal/teststructs/foo2/foo.go
new file mode 100644
index 0000000..c0882fb
--- /dev/null
+++ b/cmp/internal/teststructs/foo2/foo.go
@@ -0,0 +1,10 @@
+// Copyright 2020, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package foo is deliberately named differently than the parent directory.
+// It contain declarations that have ambiguity in their short names,
+// relative to a different package also called foo.
+package foo
+
+type Bar struct{ S string }
diff --git a/cmp/internal/teststructs/project1.go b/cmp/internal/teststructs/project1.go
index 1999e38..223d6ab 100644
--- a/cmp/internal/teststructs/project1.go
+++ b/cmp/internal/teststructs/project1.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package teststructs
diff --git a/cmp/internal/teststructs/project2.go b/cmp/internal/teststructs/project2.go
index 536592b..1616dd8 100644
--- a/cmp/internal/teststructs/project2.go
+++ b/cmp/internal/teststructs/project2.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package teststructs
diff --git a/cmp/internal/teststructs/project3.go b/cmp/internal/teststructs/project3.go
index 957d093..9e56dfa 100644
--- a/cmp/internal/teststructs/project3.go
+++ b/cmp/internal/teststructs/project3.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package teststructs
diff --git a/cmp/internal/teststructs/project4.go b/cmp/internal/teststructs/project4.go
index 49920f2..a09aba2 100644
--- a/cmp/internal/teststructs/project4.go
+++ b/cmp/internal/teststructs/project4.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package teststructs
diff --git a/cmp/internal/teststructs/structs.go b/cmp/internal/teststructs/structs.go
index 6b4d2a7..bfd2de8 100644
--- a/cmp/internal/teststructs/structs.go
+++ b/cmp/internal/teststructs/structs.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package teststructs
diff --git a/cmp/internal/value/name.go b/cmp/internal/value/name.go
new file mode 100644
index 0000000..b6c12ce
--- /dev/null
+++ b/cmp/internal/value/name.go
@@ -0,0 +1,157 @@
+// Copyright 2020, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package value
+
+import (
+ "reflect"
+ "strconv"
+)
+
+// TypeString is nearly identical to reflect.Type.String,
+// but has an additional option to specify that full type names be used.
+func TypeString(t reflect.Type, qualified bool) string {
+ return string(appendTypeName(nil, t, qualified, false))
+}
+
+func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte {
+ // BUG: Go reflection provides no way to disambiguate two named types
+ // of the same name and within the same package,
+ // but declared within the namespace of different functions.
+
+ // Named type.
+ if t.Name() != "" {
+ if qualified && t.PkgPath() != "" {
+ b = append(b, '"')
+ b = append(b, t.PkgPath()...)
+ b = append(b, '"')
+ b = append(b, '.')
+ b = append(b, t.Name()...)
+ } else {
+ b = append(b, t.String()...)
+ }
+ return b
+ }
+
+ // Unnamed type.
+ switch k := t.Kind(); k {
+ case reflect.Bool, reflect.String, reflect.UnsafePointer,
+ reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+ reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
+ reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
+ b = append(b, k.String()...)
+ case reflect.Chan:
+ if t.ChanDir() == reflect.RecvDir {
+ b = append(b, "<-"...)
+ }
+ b = append(b, "chan"...)
+ if t.ChanDir() == reflect.SendDir {
+ b = append(b, "<-"...)
+ }
+ b = append(b, ' ')
+ b = appendTypeName(b, t.Elem(), qualified, false)
+ case reflect.Func:
+ if !elideFunc {
+ b = append(b, "func"...)
+ }
+ b = append(b, '(')
+ for i := 0; i < t.NumIn(); i++ {
+ if i > 0 {
+ b = append(b, ", "...)
+ }
+ if i == t.NumIn()-1 && t.IsVariadic() {
+ b = append(b, "..."...)
+ b = appendTypeName(b, t.In(i).Elem(), qualified, false)
+ } else {
+ b = appendTypeName(b, t.In(i), qualified, false)
+ }
+ }
+ b = append(b, ')')
+ switch t.NumOut() {
+ case 0:
+ // Do nothing
+ case 1:
+ b = append(b, ' ')
+ b = appendTypeName(b, t.Out(0), qualified, false)
+ default:
+ b = append(b, " ("...)
+ for i := 0; i < t.NumOut(); i++ {
+ if i > 0 {
+ b = append(b, ", "...)
+ }
+ b = appendTypeName(b, t.Out(i), qualified, false)
+ }
+ b = append(b, ')')
+ }
+ case reflect.Struct:
+ b = append(b, "struct{ "...)
+ for i := 0; i < t.NumField(); i++ {
+ if i > 0 {
+ b = append(b, "; "...)
+ }
+ sf := t.Field(i)
+ if !sf.Anonymous {
+ if qualified && sf.PkgPath != "" {
+ b = append(b, '"')
+ b = append(b, sf.PkgPath...)
+ b = append(b, '"')
+ b = append(b, '.')
+ }
+ b = append(b, sf.Name...)
+ b = append(b, ' ')
+ }
+ b = appendTypeName(b, sf.Type, qualified, false)
+ if sf.Tag != "" {
+ b = append(b, ' ')
+ b = strconv.AppendQuote(b, string(sf.Tag))
+ }
+ }
+ if b[len(b)-1] == ' ' {
+ b = b[:len(b)-1]
+ } else {
+ b = append(b, ' ')
+ }
+ b = append(b, '}')
+ case reflect.Slice, reflect.Array:
+ b = append(b, '[')
+ if k == reflect.Array {
+ b = strconv.AppendUint(b, uint64(t.Len()), 10)
+ }
+ b = append(b, ']')
+ b = appendTypeName(b, t.Elem(), qualified, false)
+ case reflect.Map:
+ b = append(b, "map["...)
+ b = appendTypeName(b, t.Key(), qualified, false)
+ b = append(b, ']')
+ b = appendTypeName(b, t.Elem(), qualified, false)
+ case reflect.Ptr:
+ b = append(b, '*')
+ b = appendTypeName(b, t.Elem(), qualified, false)
+ case reflect.Interface:
+ b = append(b, "interface{ "...)
+ for i := 0; i < t.NumMethod(); i++ {
+ if i > 0 {
+ b = append(b, "; "...)
+ }
+ m := t.Method(i)
+ if qualified && m.PkgPath != "" {
+ b = append(b, '"')
+ b = append(b, m.PkgPath...)
+ b = append(b, '"')
+ b = append(b, '.')
+ }
+ b = append(b, m.Name...)
+ b = appendTypeName(b, m.Type, qualified, true)
+ }
+ if b[len(b)-1] == ' ' {
+ b = b[:len(b)-1]
+ } else {
+ b = append(b, ' ')
+ }
+ b = append(b, '}')
+ default:
+ panic("invalid kind: " + k.String())
+ }
+ return b
+}
diff --git a/cmp/internal/value/name_test.go b/cmp/internal/value/name_test.go
new file mode 100644
index 0000000..3eec91c
--- /dev/null
+++ b/cmp/internal/value/name_test.go
@@ -0,0 +1,144 @@
+// Copyright 2020, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package value
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+type Named struct{}
+
+var pkgPath = reflect.TypeOf(Named{}).PkgPath()
+
+func TestTypeString(t *testing.T) {
+ tests := []struct {
+ in interface{}
+ want string
+ }{{
+ in: bool(false),
+ want: "bool",
+ }, {
+ in: int(0),
+ want: "int",
+ }, {
+ in: float64(0),
+ want: "float64",
+ }, {
+ in: string(""),
+ want: "string",
+ }, {
+ in: Named{},
+ want: "$PackagePath.Named",
+ }, {
+ in: (chan Named)(nil),
+ want: "chan $PackagePath.Named",
+ }, {
+ in: (<-chan Named)(nil),
+ want: "<-chan $PackagePath.Named",
+ }, {
+ in: (chan<- Named)(nil),
+ want: "chan<- $PackagePath.Named",
+ }, {
+ in: (func())(nil),
+ want: "func()",
+ }, {
+ in: (func(Named))(nil),
+ want: "func($PackagePath.Named)",
+ }, {
+ in: (func() Named)(nil),
+ want: "func() $PackagePath.Named",
+ }, {
+ in: (func(int, Named) (int, error))(nil),
+ want: "func(int, $PackagePath.Named) (int, error)",
+ }, {
+ in: (func(...Named))(nil),
+ want: "func(...$PackagePath.Named)",
+ }, {
+ in: struct{}{},
+ want: "struct{}",
+ }, {
+ in: struct{ Named }{},
+ want: "struct{ $PackagePath.Named }",
+ }, {
+ in: struct {
+ Named `tag`
+ }{},
+ want: "struct{ $PackagePath.Named \"tag\" }",
+ }, {
+ in: struct{ Named Named }{},
+ want: "struct{ Named $PackagePath.Named }",
+ }, {
+ in: struct {
+ Named Named `tag`
+ }{},
+ want: "struct{ Named $PackagePath.Named \"tag\" }",
+ }, {
+ in: struct {
+ Int int
+ Named Named
+ }{},
+ want: "struct{ Int int; Named $PackagePath.Named }",
+ }, {
+ in: struct {
+ _ int
+ x Named
+ }{},
+ want: "struct{ $FieldPrefix._ int; $FieldPrefix.x $PackagePath.Named }",
+ }, {
+ in: []Named(nil),
+ want: "[]$PackagePath.Named",
+ }, {
+ in: []*Named(nil),
+ want: "[]*$PackagePath.Named",
+ }, {
+ in: [10]Named{},
+ want: "[10]$PackagePath.Named",
+ }, {
+ in: [10]*Named{},
+ want: "[10]*$PackagePath.Named",
+ }, {
+ in: map[string]string(nil),
+ want: "map[string]string",
+ }, {
+ in: map[Named]Named(nil),
+ want: "map[$PackagePath.Named]$PackagePath.Named",
+ }, {
+ in: (*Named)(nil),
+ want: "*$PackagePath.Named",
+ }, {
+ in: (*interface{})(nil),
+ want: "*interface{}",
+ }, {
+ in: (*interface{ Read([]byte) (int, error) })(nil),
+ want: "*interface{ Read([]uint8) (int, error) }",
+ }, {
+ in: (*interface {
+ F1()
+ F2(Named)
+ F3() Named
+ F4(int, Named) (int, error)
+ F5(...Named)
+ })(nil),
+ want: "*interface{ F1(); F2($PackagePath.Named); F3() $PackagePath.Named; F4(int, $PackagePath.Named) (int, error); F5(...$PackagePath.Named) }",
+ }}
+
+ for _, tt := range tests {
+ typ := reflect.TypeOf(tt.in)
+ wantShort := tt.want
+ wantShort = strings.Replace(wantShort, "$PackagePath", "value", -1)
+ wantShort = strings.Replace(wantShort, "$FieldPrefix.", "", -1)
+ if gotShort := TypeString(typ, false); gotShort != wantShort {
+ t.Errorf("TypeString(%v, false) mismatch:\ngot: %v\nwant: %v", typ, gotShort, wantShort)
+ }
+ wantQualified := tt.want
+ wantQualified = strings.Replace(wantQualified, "$PackagePath", `"`+pkgPath+`"`, -1)
+ wantQualified = strings.Replace(wantQualified, "$FieldPrefix", `"`+pkgPath+`"`, -1)
+ if gotQualified := TypeString(typ, true); gotQualified != wantQualified {
+ t.Errorf("TypeString(%v, true) mismatch:\ngot: %v\nwant: %v", typ, gotQualified, wantQualified)
+ }
+ }
+}
diff --git a/cmp/internal/value/pointer_purego.go b/cmp/internal/value/pointer_purego.go
index 0a01c47..44f4a5a 100644
--- a/cmp/internal/value/pointer_purego.go
+++ b/cmp/internal/value/pointer_purego.go
@@ -1,6 +1,6 @@
// Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// +build purego
@@ -21,3 +21,13 @@ func PointerOf(v reflect.Value) Pointer {
// assumes that the GC implementation does not use a moving collector.
return Pointer{v.Pointer(), v.Type()}
}
+
+// IsNil reports whether the pointer is nil.
+func (p Pointer) IsNil() bool {
+ return p.p == 0
+}
+
+// Uintptr returns the pointer as a uintptr.
+func (p Pointer) Uintptr() uintptr {
+ return p.p
+}
diff --git a/cmp/internal/value/pointer_unsafe.go b/cmp/internal/value/pointer_unsafe.go
index da134ae..a605953 100644
--- a/cmp/internal/value/pointer_unsafe.go
+++ b/cmp/internal/value/pointer_unsafe.go
@@ -1,6 +1,6 @@
// Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
// +build !purego
@@ -24,3 +24,13 @@ func PointerOf(v reflect.Value) Pointer {
// which is necessary if the GC ever uses a moving collector.
return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
}
+
+// IsNil reports whether the pointer is nil.
+func (p Pointer) IsNil() bool {
+ return p.p == nil
+}
+
+// Uintptr returns the pointer as a uintptr.
+func (p Pointer) Uintptr() uintptr {
+ return uintptr(p.p)
+}
diff --git a/cmp/internal/value/sort.go b/cmp/internal/value/sort.go
index 24fbae6..98533b0 100644
--- a/cmp/internal/value/sort.go
+++ b/cmp/internal/value/sort.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package value
diff --git a/cmp/internal/value/sort_test.go b/cmp/internal/value/sort_test.go
index fb86fce..26222d6 100644
--- a/cmp/internal/value/sort_test.go
+++ b/cmp/internal/value/sort_test.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package value_test
diff --git a/cmp/internal/value/zero.go b/cmp/internal/value/zero.go
index 06a8ffd..9147a29 100644
--- a/cmp/internal/value/zero.go
+++ b/cmp/internal/value/zero.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package value
diff --git a/cmp/internal/value/zero_test.go b/cmp/internal/value/zero_test.go
index 1d6c434..ddaa337 100644
--- a/cmp/internal/value/zero_test.go
+++ b/cmp/internal/value/zero_test.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package value
diff --git a/cmp/options.go b/cmp/options.go
index abbd2a6..e57b9eb 100644
--- a/cmp/options.go
+++ b/cmp/options.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp
@@ -225,11 +225,14 @@ func (validator) apply(s *state, vx, vy reflect.Value) {
// Unable to Interface implies unexported field without visibility access.
if !vx.CanInterface() || !vy.CanInterface() {
- const help = "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"
+ help := "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"
var name string
if t := s.curPath.Index(-2).Type(); t.Name() != "" {
// Named type with unexported fields.
name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
+ if _, ok := reflect.New(t).Interface().(error); ok {
+ help = "consider using cmpopts.EquateErrors to compare error values"
+ }
} else {
// Unnamed type with unexported fields. Derive PkgPath from field.
var pkgPath string
diff --git a/cmp/options_test.go b/cmp/options_test.go
index f8066c7..c7d45f3 100644
--- a/cmp/options_test.go
+++ b/cmp/options_test.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp
diff --git a/cmp/path.go b/cmp/path.go
index 603dbb0..3d45c1a 100644
--- a/cmp/path.go
+++ b/cmp/path.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp
diff --git a/cmp/report.go b/cmp/report.go
index 6ddf299..f43cd12 100644
--- a/cmp/report.go
+++ b/cmp/report.go
@@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp
@@ -41,7 +41,10 @@ func (r *defaultReporter) String() string {
if r.root.NumDiff == 0 {
return ""
}
- return formatOptions{}.FormatDiff(r.root).String()
+ ptrs := new(pointerReferences)
+ text := formatOptions{}.FormatDiff(r.root, ptrs)
+ resolveReferences(text)
+ return text.String()
}
func assert(ok bool) {
diff --git a/cmp/report_compare.go b/cmp/report_compare.go
index d3fa154..104bb30 100644
--- a/cmp/report_compare.go
+++ b/cmp/report_compare.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp
@@ -11,14 +11,6 @@ import (
"github.com/google/go-cmp/cmp/internal/value"
)
-// TODO: Enforce limits?
-// * Enforce maximum number of records to print per node?
-// * Enforce maximum size in bytes allowed?
-// * As a heuristic, use less verbosity for equal nodes than unequal nodes.
-// TODO: Enforce unique outputs?
-// * Avoid Stringer methods if it results in same output?
-// * Print pointer address if outputs still equal?
-
// numContextRecords is the number of surrounding equal records to print.
const numContextRecords = 2
@@ -71,19 +63,56 @@ func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
opts.TypeMode = t
return opts
}
+func (opts formatOptions) WithVerbosity(level int) formatOptions {
+ opts.VerbosityLevel = level
+ opts.LimitVerbosity = true
+ return opts
+}
+func (opts formatOptions) verbosity() uint {
+ switch {
+ case opts.VerbosityLevel < 0:
+ return 0
+ case opts.VerbosityLevel > 16:
+ return 16 // some reasonable maximum to avoid shift overflow
+ default:
+ return uint(opts.VerbosityLevel)
+ }
+}
+
+const maxVerbosityPreset = 6
+
+// verbosityPreset modifies the verbosity settings given an index
+// between 0 and maxVerbosityPreset, inclusive.
+func verbosityPreset(opts formatOptions, i int) formatOptions {
+ opts.VerbosityLevel = int(opts.verbosity()) + 2*i
+ if i > 0 {
+ opts.AvoidStringer = true
+ }
+ if i >= maxVerbosityPreset {
+ opts.PrintAddresses = true
+ opts.QualifiedNames = true
+ }
+ return opts
+}
// FormatDiff converts a valueNode tree into a textNode tree, where the later
// is a textual representation of the differences detected in the former.
-func (opts formatOptions) FormatDiff(v *valueNode) textNode {
+func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) {
+ if opts.DiffMode == diffIdentical {
+ opts = opts.WithVerbosity(1)
+ } else if opts.verbosity() < 3 {
+ opts = opts.WithVerbosity(3)
+ }
+
// Check whether we have specialized formatting for this node.
// This is not necessary, but helpful for producing more readable outputs.
if opts.CanFormatDiffSlice(v) {
return opts.FormatDiffSlice(v)
}
- var withinSlice bool
- if v.parent != nil && (v.parent.Type.Kind() == reflect.Slice || v.parent.Type.Kind() == reflect.Array) {
- withinSlice = true
+ var parentKind reflect.Kind
+ if v.parent != nil && v.parent.TransformerName == "" {
+ parentKind = v.parent.Type.Kind()
}
// For leaf nodes, format the value based on the reflect.Values alone.
@@ -92,8 +121,8 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode {
case diffUnknown, diffIdentical:
// Format Equal.
if v.NumDiff == 0 {
- outx := opts.FormatValue(v.ValueX, withinSlice, visitedPointers{})
- outy := opts.FormatValue(v.ValueY, withinSlice, visitedPointers{})
+ outx := opts.FormatValue(v.ValueX, parentKind, ptrs)
+ outy := opts.FormatValue(v.ValueY, parentKind, ptrs)
if v.NumIgnored > 0 && v.NumSame == 0 {
return textEllipsis
} else if outx.Len() < outy.Len() {
@@ -106,8 +135,13 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode {
// Format unequal.
assert(opts.DiffMode == diffUnknown)
var list textList
- outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, withinSlice, visitedPointers{})
- outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, withinSlice, visitedPointers{})
+ outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs)
+ outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs)
+ for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
+ opts2 := verbosityPreset(opts, i).WithTypeMode(elideType)
+ outx = opts2.FormatValue(v.ValueX, parentKind, ptrs)
+ outy = opts2.FormatValue(v.ValueY, parentKind, ptrs)
+ }
if outx != nil {
list = append(list, textRecord{Diff: '-', Value: outx})
}
@@ -116,34 +150,57 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode {
}
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
case diffRemoved:
- return opts.FormatValue(v.ValueX, withinSlice, visitedPointers{})
+ return opts.FormatValue(v.ValueX, parentKind, ptrs)
case diffInserted:
- return opts.FormatValue(v.ValueY, withinSlice, visitedPointers{})
+ return opts.FormatValue(v.ValueY, parentKind, ptrs)
default:
panic("invalid diff mode")
}
}
+ // Register slice element to support cycle detection.
+ if parentKind == reflect.Slice {
+ ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true)
+ defer ptrs.Pop()
+ defer func() { out = wrapTrunkReferences(ptrRefs, out) }()
+ }
+
// Descend into the child value node.
if v.TransformerName != "" {
- out := opts.WithTypeMode(emitType).FormatDiff(v.Value)
- out = textWrap{"Inverse(" + v.TransformerName + ", ", out, ")"}
+ out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
+ out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"}
return opts.FormatType(v.Type, out)
} else {
switch k := v.Type.Kind(); k {
- case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map:
- return opts.FormatType(v.Type, opts.formatDiffList(v.Records, k))
+ case reflect.Struct, reflect.Array, reflect.Slice:
+ out = opts.formatDiffList(v.Records, k, ptrs)
+ out = opts.FormatType(v.Type, out)
+ case reflect.Map:
+ // Register map to support cycle detection.
+ ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
+ defer ptrs.Pop()
+
+ out = opts.formatDiffList(v.Records, k, ptrs)
+ out = wrapTrunkReferences(ptrRefs, out)
+ out = opts.FormatType(v.Type, out)
case reflect.Ptr:
- return textWrap{"&", opts.FormatDiff(v.Value), ""}
+ // Register pointer to support cycle detection.
+ ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
+ defer ptrs.Pop()
+
+ out = opts.FormatDiff(v.Value, ptrs)
+ out = wrapTrunkReferences(ptrRefs, out)
+ out = &textWrap{Prefix: "&", Value: out}
case reflect.Interface:
- return opts.WithTypeMode(emitType).FormatDiff(v.Value)
+ out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
default:
panic(fmt.Sprintf("%v cannot have children", k))
}
+ return out
}
}
-func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) textNode {
+func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode {
// Derive record name based on the data structure kind.
var name string
var formatKey func(reflect.Value) string
@@ -159,7 +216,17 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
case reflect.Map:
name = "entry"
opts = opts.WithTypeMode(elideType)
- formatKey = formatMapKey
+ formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) }
+ }
+
+ maxLen := -1
+ if opts.LimitVerbosity {
+ if opts.DiffMode == diffIdentical {
+ maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
+ } else {
+ maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc...
+ }
+ opts.VerbosityLevel--
}
// Handle unification.
@@ -168,6 +235,11 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
var list textList
var deferredEllipsis bool // Add final "..." to indicate records were dropped
for _, r := range recs {
+ if len(list) == maxLen {
+ deferredEllipsis = true
+ break
+ }
+
// Elide struct fields that are zero value.
if k == reflect.Struct {
var isZero bool
@@ -191,23 +263,31 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
}
continue
}
- if out := opts.FormatDiff(r.Value); out != nil {
+ if out := opts.FormatDiff(r.Value, ptrs); out != nil {
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
}
}
if deferredEllipsis {
list.AppendEllipsis(diffStats{})
}
- return textWrap{"{", list, "}"}
+ return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
case diffUnknown:
default:
panic("invalid diff mode")
}
// Handle differencing.
+ var numDiffs int
var list textList
+ var keys []reflect.Value // invariant: len(list) == len(keys)
groups := coalesceAdjacentRecords(name, recs)
+ maxGroup := diffStats{Name: name}
for i, ds := range groups {
+ if maxLen >= 0 && numDiffs >= maxLen {
+ maxGroup = maxGroup.Append(ds)
+ continue
+ }
+
// Handle equal records.
if ds.NumDiff() == 0 {
// Compute the number of leading and trailing records to print.
@@ -231,16 +311,21 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
// Format the equal values.
for _, r := range recs[:numLo] {
- out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value)
+ out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
+ keys = append(keys, r.Key)
}
if numEqual > numLo+numHi {
ds.NumIdentical -= numLo + numHi
list.AppendEllipsis(ds)
+ for len(keys) < len(list) {
+ keys = append(keys, reflect.Value{})
+ }
}
for _, r := range recs[numEqual-numHi : numEqual] {
- out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value)
+ out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
+ keys = append(keys, r.Key)
}
recs = recs[numEqual:]
continue
@@ -252,24 +337,70 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
case opts.CanFormatDiffSlice(r.Value):
out := opts.FormatDiffSlice(r.Value)
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
+ keys = append(keys, r.Key)
case r.Value.NumChildren == r.Value.MaxDepth:
- outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value)
- outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value)
+ outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
+ outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
+ for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
+ opts2 := verbosityPreset(opts, i)
+ outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
+ outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
+ }
if outx != nil {
list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
+ keys = append(keys, r.Key)
}
if outy != nil {
list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
+ keys = append(keys, r.Key)
}
default:
- out := opts.FormatDiff(r.Value)
+ out := opts.FormatDiff(r.Value, ptrs)
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
+ keys = append(keys, r.Key)
}
}
recs = recs[ds.NumDiff():]
+ numDiffs += ds.NumDiff()
+ }
+ if maxGroup.IsZero() {
+ assert(len(recs) == 0)
+ } else {
+ list.AppendEllipsis(maxGroup)
+ for len(keys) < len(list) {
+ keys = append(keys, reflect.Value{})
+ }
+ }
+ assert(len(list) == len(keys))
+
+ // For maps, the default formatting logic uses fmt.Stringer which may
+ // produce ambiguous output. Avoid calling String to disambiguate.
+ if k == reflect.Map {
+ var ambiguous bool
+ seenKeys := map[string]reflect.Value{}
+ for i, currKey := range keys {
+ if currKey.IsValid() {
+ strKey := list[i].Key
+ prevKey, seen := seenKeys[strKey]
+ if seen && prevKey.CanInterface() && currKey.CanInterface() {
+ ambiguous = prevKey.Interface() != currKey.Interface()
+ if ambiguous {
+ break
+ }
+ }
+ seenKeys[strKey] = currKey
+ }
+ }
+ if ambiguous {
+ for i, k := range keys {
+ if k.IsValid() {
+ list[i].Key = formatMapKey(k, true, ptrs)
+ }
+ }
+ }
}
- assert(len(recs) == 0)
- return textWrap{"{", list, "}"}
+
+ return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
}
// coalesceAdjacentRecords coalesces the list of records into groups of
diff --git a/cmp/report_references.go b/cmp/report_references.go
new file mode 100644
index 0000000..be31b33
--- /dev/null
+++ b/cmp/report_references.go
@@ -0,0 +1,264 @@
+// Copyright 2020, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cmp
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/google/go-cmp/cmp/internal/flags"
+ "github.com/google/go-cmp/cmp/internal/value"
+)
+
+const (
+ pointerDelimPrefix = "⟪"
+ pointerDelimSuffix = "⟫"
+)
+
+// formatPointer prints the address of the pointer.
+func formatPointer(p value.Pointer, withDelims bool) string {
+ v := p.Uintptr()
+ if flags.Deterministic {
+ v = 0xdeadf00f // Only used for stable testing purposes
+ }
+ if withDelims {
+ return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix
+ }
+ return formatHex(uint64(v))
+}
+
+// pointerReferences is a stack of pointers visited so far.
+type pointerReferences [][2]value.Pointer
+
+func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) {
+ if deref && vx.IsValid() {
+ vx = vx.Addr()
+ }
+ if deref && vy.IsValid() {
+ vy = vy.Addr()
+ }
+ switch d {
+ case diffUnknown, diffIdentical:
+ pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)}
+ case diffRemoved:
+ pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}}
+ case diffInserted:
+ pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)}
+ }
+ *ps = append(*ps, pp)
+ return pp
+}
+
+func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) {
+ p = value.PointerOf(v)
+ for _, pp := range *ps {
+ if p == pp[0] || p == pp[1] {
+ return p, true
+ }
+ }
+ *ps = append(*ps, [2]value.Pointer{p, p})
+ return p, false
+}
+
+func (ps *pointerReferences) Pop() {
+ *ps = (*ps)[:len(*ps)-1]
+}
+
+// trunkReferences is metadata for a textNode indicating that the sub-tree
+// represents the value for either pointer in a pair of references.
+type trunkReferences struct{ pp [2]value.Pointer }
+
+// trunkReference is metadata for a textNode indicating that the sub-tree
+// represents the value for the given pointer reference.
+type trunkReference struct{ p value.Pointer }
+
+// leafReference is metadata for a textNode indicating that the value is
+// truncated as it refers to another part of the tree (i.e., a trunk).
+type leafReference struct{ p value.Pointer }
+
+func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode {
+ switch {
+ case pp[0].IsNil():
+ return &textWrap{Value: s, Metadata: trunkReference{pp[1]}}
+ case pp[1].IsNil():
+ return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
+ case pp[0] == pp[1]:
+ return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
+ default:
+ return &textWrap{Value: s, Metadata: trunkReferences{pp}}
+ }
+}
+func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode {
+ var prefix string
+ if printAddress {
+ prefix = formatPointer(p, true)
+ }
+ return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}}
+}
+func makeLeafReference(p value.Pointer, printAddress bool) textNode {
+ out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"}
+ var prefix string
+ if printAddress {
+ prefix = formatPointer(p, true)
+ }
+ return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}}
+}
+
+// resolveReferences walks the textNode tree searching for any leaf reference
+// metadata and resolves each against the corresponding trunk references.
+// Since pointer addresses in memory are not particularly readable to the user,
+// it replaces each pointer value with an arbitrary and unique reference ID.
+func resolveReferences(s textNode) {
+ var walkNodes func(textNode, func(textNode))
+ walkNodes = func(s textNode, f func(textNode)) {
+ f(s)
+ switch s := s.(type) {
+ case *textWrap:
+ walkNodes(s.Value, f)
+ case textList:
+ for _, r := range s {
+ walkNodes(r.Value, f)
+ }
+ }
+ }
+
+ // Collect all trunks and leaves with reference metadata.
+ var trunks, leaves []*textWrap
+ walkNodes(s, func(s textNode) {
+ if s, ok := s.(*textWrap); ok {
+ switch s.Metadata.(type) {
+ case leafReference:
+ leaves = append(leaves, s)
+ case trunkReference, trunkReferences:
+ trunks = append(trunks, s)
+ }
+ }
+ })
+
+ // No leaf references to resolve.
+ if len(leaves) == 0 {
+ return
+ }
+
+ // Collect the set of all leaf references to resolve.
+ leafPtrs := make(map[value.Pointer]bool)
+ for _, leaf := range leaves {
+ leafPtrs[leaf.Metadata.(leafReference).p] = true
+ }
+
+ // Collect the set of trunk pointers that are always paired together.
+ // This allows us to assign a single ID to both pointers for brevity.
+ // If a pointer in a pair ever occurs by itself or as a different pair,
+ // then the pair is broken.
+ pairedTrunkPtrs := make(map[value.Pointer]value.Pointer)
+ unpair := func(p value.Pointer) {
+ if !pairedTrunkPtrs[p].IsNil() {
+ pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half
+ }
+ pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half
+ }
+ for _, trunk := range trunks {
+ switch p := trunk.Metadata.(type) {
+ case trunkReference:
+ unpair(p.p) // standalone pointer cannot be part of a pair
+ case trunkReferences:
+ p0, ok0 := pairedTrunkPtrs[p.pp[0]]
+ p1, ok1 := pairedTrunkPtrs[p.pp[1]]
+ switch {
+ case !ok0 && !ok1:
+ // Register the newly seen pair.
+ pairedTrunkPtrs[p.pp[0]] = p.pp[1]
+ pairedTrunkPtrs[p.pp[1]] = p.pp[0]
+ case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]:
+ // Exact pair already seen; do nothing.
+ default:
+ // Pair conflicts with some other pair; break all pairs.
+ unpair(p.pp[0])
+ unpair(p.pp[1])
+ }
+ }
+ }
+
+ // Correlate each pointer referenced by leaves to a unique identifier,
+ // and print the IDs for each trunk that matches those pointers.
+ var nextID uint
+ ptrIDs := make(map[value.Pointer]uint)
+ newID := func() uint {
+ id := nextID
+ nextID++
+ return id
+ }
+ for _, trunk := range trunks {
+ switch p := trunk.Metadata.(type) {
+ case trunkReference:
+ if print := leafPtrs[p.p]; print {
+ id, ok := ptrIDs[p.p]
+ if !ok {
+ id = newID()
+ ptrIDs[p.p] = id
+ }
+ trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
+ }
+ case trunkReferences:
+ print0 := leafPtrs[p.pp[0]]
+ print1 := leafPtrs[p.pp[1]]
+ if print0 || print1 {
+ id0, ok0 := ptrIDs[p.pp[0]]
+ id1, ok1 := ptrIDs[p.pp[1]]
+ isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0]
+ if isPair {
+ var id uint
+ assert(ok0 == ok1) // must be seen together or not at all
+ if ok0 {
+ assert(id0 == id1) // must have the same ID
+ id = id0
+ } else {
+ id = newID()
+ ptrIDs[p.pp[0]] = id
+ ptrIDs[p.pp[1]] = id
+ }
+ trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
+ } else {
+ if print0 && !ok0 {
+ id0 = newID()
+ ptrIDs[p.pp[0]] = id0
+ }
+ if print1 && !ok1 {
+ id1 = newID()
+ ptrIDs[p.pp[1]] = id1
+ }
+ switch {
+ case print0 && print1:
+ trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1))
+ case print0:
+ trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0))
+ case print1:
+ trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1))
+ }
+ }
+ }
+ }
+ }
+
+ // Update all leaf references with the unique identifier.
+ for _, leaf := range leaves {
+ if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok {
+ leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id))
+ }
+ }
+}
+
+func formatReference(id uint) string {
+ return fmt.Sprintf("ref#%d", id)
+}
+
+func updateReferencePrefix(prefix, ref string) string {
+ if prefix == "" {
+ return pointerDelimPrefix + ref + pointerDelimSuffix
+ }
+ suffix := strings.TrimPrefix(prefix, pointerDelimPrefix)
+ return pointerDelimPrefix + ref + ": " + suffix
+}
diff --git a/cmp/report_reflect.go b/cmp/report_reflect.go
index 642622e..33f0357 100644
--- a/cmp/report_reflect.go
+++ b/cmp/report_reflect.go
@@ -1,17 +1,18 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp
import (
+ "bytes"
"fmt"
"reflect"
"strconv"
"strings"
"unicode"
+ "unicode/utf8"
- "github.com/google/go-cmp/cmp/internal/flags"
"github.com/google/go-cmp/cmp/internal/value"
)
@@ -20,14 +21,22 @@ type formatValueOptions struct {
// methods like error.Error or fmt.Stringer.String.
AvoidStringer bool
- // ShallowPointers controls whether to avoid descending into pointers.
- // Useful when printing map keys, where pointer comparison is performed
- // on the pointer address rather than the pointed-at value.
- ShallowPointers bool
-
// PrintAddresses controls whether to print the address of all pointers,
// slice elements, and maps.
PrintAddresses bool
+
+ // QualifiedNames controls whether FormatType uses the fully qualified name
+ // (including the full package path as opposed to just the package name).
+ QualifiedNames bool
+
+ // VerbosityLevel controls the amount of output to produce.
+ // A higher value produces more output. A value of zero or lower produces
+ // no output (represented using an ellipsis).
+ // If LimitVerbosity is false, then the level is treated as infinite.
+ VerbosityLevel int
+
+ // LimitVerbosity specifies that formatting should respect VerbosityLevel.
+ LimitVerbosity bool
}
// FormatType prints the type as if it were wrapping s.
@@ -44,12 +53,15 @@ func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
default:
return s
}
+ if opts.DiffMode == diffIdentical {
+ return s // elide type for identical nodes
+ }
case elideType:
return s
}
// Determine the type label, applying special handling for unnamed types.
- typeName := t.String()
+ typeName := value.TypeString(t, opts.QualifiedNames)
if t.Name() == "" {
// According to Go grammar, certain type literals contain symbols that
// do not strongly bind to the next lexicographical token (e.g., *T).
@@ -57,39 +69,77 @@ func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
case reflect.Chan, reflect.Func, reflect.Ptr:
typeName = "(" + typeName + ")"
}
- typeName = strings.Replace(typeName, "struct {", "struct{", -1)
- typeName = strings.Replace(typeName, "interface {", "interface{", -1)
}
+ return &textWrap{Prefix: typeName, Value: wrapParens(s)}
+}
- // Avoid wrap the value in parenthesis if unnecessary.
- if s, ok := s.(textWrap); ok {
- hasParens := strings.HasPrefix(s.Prefix, "(") && strings.HasSuffix(s.Suffix, ")")
- hasBraces := strings.HasPrefix(s.Prefix, "{") && strings.HasSuffix(s.Suffix, "}")
+// wrapParens wraps s with a set of parenthesis, but avoids it if the
+// wrapped node itself is already surrounded by a pair of parenthesis or braces.
+// It handles unwrapping one level of pointer-reference nodes.
+func wrapParens(s textNode) textNode {
+ var refNode *textWrap
+ if s2, ok := s.(*textWrap); ok {
+ // Unwrap a single pointer reference node.
+ switch s2.Metadata.(type) {
+ case leafReference, trunkReference, trunkReferences:
+ refNode = s2
+ if s3, ok := refNode.Value.(*textWrap); ok {
+ s2 = s3
+ }
+ }
+
+ // Already has delimiters that make parenthesis unnecessary.
+ hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
+ hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
if hasParens || hasBraces {
- return textWrap{typeName, s, ""}
+ return s
}
}
- return textWrap{typeName + "(", s, ")"}
+ if refNode != nil {
+ refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}
+ return s
+ }
+ return &textWrap{Prefix: "(", Value: s, Suffix: ")"}
}
// FormatValue prints the reflect.Value, taking extra care to avoid descending
-// into pointers already in m. As pointers are visited, m is also updated.
-func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visitedPointers) (out textNode) {
+// into pointers already in ptrs. As pointers are visited, ptrs is also updated.
+func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
if !v.IsValid() {
return nil
}
t := v.Type()
+ // Check slice element for cycles.
+ if parentKind == reflect.Slice {
+ ptrRef, visited := ptrs.Push(v.Addr())
+ if visited {
+ return makeLeafReference(ptrRef, false)
+ }
+ defer ptrs.Pop()
+ defer func() { out = wrapTrunkReference(ptrRef, false, out) }()
+ }
+
// Check whether there is an Error or String method to call.
if !opts.AvoidStringer && v.CanInterface() {
// Avoid calling Error or String methods on nil receivers since many
// implementations crash when doing so.
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
- switch v := v.Interface().(type) {
- case error:
- return textLine("e" + formatString(v.Error()))
- case fmt.Stringer:
- return textLine("s" + formatString(v.String()))
+ var prefix, strVal string
+ func() {
+ // Swallow and ignore any panics from String or Error.
+ defer func() { recover() }()
+ switch v := v.Interface().(type) {
+ case error:
+ strVal = v.Error()
+ prefix = "e"
+ case fmt.Stringer:
+ strVal = v.String()
+ prefix = "s"
+ }
+ }()
+ if prefix != "" {
+ return opts.formatString(prefix, strVal)
}
}
}
@@ -102,7 +152,6 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit
}
}()
- var ptr string
switch t.Kind() {
case reflect.Bool:
return textLine(fmt.Sprint(v.Bool()))
@@ -111,7 +160,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return textLine(fmt.Sprint(v.Uint()))
case reflect.Uint8:
- if withinSlice {
+ if parentKind == reflect.Slice || parentKind == reflect.Array {
return textLine(formatHex(v.Uint()))
}
return textLine(fmt.Sprint(v.Uint()))
@@ -122,82 +171,121 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit
case reflect.Complex64, reflect.Complex128:
return textLine(fmt.Sprint(v.Complex()))
case reflect.String:
- return textLine(formatString(v.String()))
+ return opts.formatString("", v.String())
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
- return textLine(formatPointer(v))
+ return textLine(formatPointer(value.PointerOf(v), true))
case reflect.Struct:
var list textList
v := makeAddressable(v) // needed for retrieveUnexportedField
+ maxLen := v.NumField()
+ if opts.LimitVerbosity {
+ maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
+ opts.VerbosityLevel--
+ }
for i := 0; i < v.NumField(); i++ {
vv := v.Field(i)
if value.IsZero(vv) {
continue // Elide fields with zero values
}
+ if len(list) == maxLen {
+ list.AppendEllipsis(diffStats{})
+ break
+ }
sf := t.Field(i)
if supportExporters && !isExported(sf.Name) {
vv = retrieveUnexportedField(v, sf, true)
}
- s := opts.WithTypeMode(autoType).FormatValue(vv, false, m)
+ s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
list = append(list, textRecord{Key: sf.Name, Value: s})
}
- return textWrap{"{", list, "}"}
+ return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
case reflect.Slice:
if v.IsNil() {
return textNil
}
- if opts.PrintAddresses {
- ptr = formatPointer(v)
+
+ // Check whether this is a []byte of text data.
+ if t.Elem() == reflect.TypeOf(byte(0)) {
+ b := v.Bytes()
+ isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) && unicode.IsSpace(r) }
+ if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
+ out = opts.formatString("", string(b))
+ return opts.WithTypeMode(emitType).FormatType(t, out)
+ }
}
+
fallthrough
case reflect.Array:
+ maxLen := v.Len()
+ if opts.LimitVerbosity {
+ maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
+ opts.VerbosityLevel--
+ }
var list textList
for i := 0; i < v.Len(); i++ {
- vi := v.Index(i)
- if vi.CanAddr() { // Check for cyclic elements
- p := vi.Addr()
- if m.Visit(p) {
- var out textNode
- out = textLine(formatPointer(p))
- out = opts.WithTypeMode(emitType).FormatType(p.Type(), out)
- out = textWrap{"*", out, ""}
- list = append(list, textRecord{Value: out})
- continue
- }
+ if len(list) == maxLen {
+ list.AppendEllipsis(diffStats{})
+ break
}
- s := opts.WithTypeMode(elideType).FormatValue(vi, true, m)
+ s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
list = append(list, textRecord{Value: s})
}
- return textWrap{ptr + "{", list, "}"}
+
+ out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
+ if t.Kind() == reflect.Slice && opts.PrintAddresses {
+ header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())
+ out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}
+ }
+ return out
case reflect.Map:
if v.IsNil() {
return textNil
}
- if m.Visit(v) {
- return textLine(formatPointer(v))
+
+ // Check pointer for cycles.
+ ptrRef, visited := ptrs.Push(v)
+ if visited {
+ return makeLeafReference(ptrRef, opts.PrintAddresses)
}
+ defer ptrs.Pop()
+ maxLen := v.Len()
+ if opts.LimitVerbosity {
+ maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
+ opts.VerbosityLevel--
+ }
var list textList
for _, k := range value.SortKeys(v.MapKeys()) {
- sk := formatMapKey(k)
- sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), false, m)
+ if len(list) == maxLen {
+ list.AppendEllipsis(diffStats{})
+ break
+ }
+ sk := formatMapKey(k, false, ptrs)
+ sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)
list = append(list, textRecord{Key: sk, Value: sv})
}
- if opts.PrintAddresses {
- ptr = formatPointer(v)
- }
- return textWrap{ptr + "{", list, "}"}
+
+ out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
+ out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
+ return out
case reflect.Ptr:
if v.IsNil() {
return textNil
}
- if m.Visit(v) || opts.ShallowPointers {
- return textLine(formatPointer(v))
- }
- if opts.PrintAddresses {
- ptr = formatPointer(v)
+
+ // Check pointer for cycles.
+ ptrRef, visited := ptrs.Push(v)
+ if visited {
+ out = makeLeafReference(ptrRef, opts.PrintAddresses)
+ return &textWrap{Prefix: "&", Value: out}
}
+ defer ptrs.Pop()
+
skipType = true // Let the underlying value print the type instead
- return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), false, m), ""}
+ out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
+ out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
+ out = &textWrap{Prefix: "&", Value: out}
+ return out
case reflect.Interface:
if v.IsNil() {
return textNil
@@ -205,19 +293,67 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit
// Interfaces accept different concrete types,
// so configure the underlying value to explicitly print the type.
skipType = true // Print the concrete type instead
- return opts.WithTypeMode(emitType).FormatValue(v.Elem(), false, m)
+ return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)
default:
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
}
}
+func (opts formatOptions) formatString(prefix, s string) textNode {
+ maxLen := len(s)
+ maxLines := strings.Count(s, "\n") + 1
+ if opts.LimitVerbosity {
+ maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc...
+ maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
+ }
+
+ // For multiline strings, use the triple-quote syntax,
+ // but only use it when printing removed or inserted nodes since
+ // we only want the extra verbosity for those cases.
+ lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")
+ isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')
+ for i := 0; i < len(lines) && isTripleQuoted; i++ {
+ lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
+ isPrintable := func(r rune) bool {
+ return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
+ }
+ line := lines[i]
+ isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen
+ }
+ if isTripleQuoted {
+ var list textList
+ list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
+ for i, line := range lines {
+ if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {
+ comment := commentString(fmt.Sprintf("%d elided lines", numElided))
+ list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})
+ break
+ }
+ list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})
+ }
+ list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
+ return &textWrap{Prefix: "(", Value: list, Suffix: ")"}
+ }
+
+ // Format the string as a single-line quoted string.
+ if len(s) > maxLen+len(textEllipsis) {
+ return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))
+ }
+ return textLine(prefix + formatString(s))
+}
+
// formatMapKey formats v as if it were a map key.
// The result is guaranteed to be a single line.
-func formatMapKey(v reflect.Value) string {
+func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
var opts formatOptions
+ opts.DiffMode = diffIdentical
opts.TypeMode = elideType
- opts.ShallowPointers = true
- s := opts.FormatValue(v, false, visitedPointers{}).String()
+ opts.PrintAddresses = disambiguate
+ opts.AvoidStringer = disambiguate
+ opts.QualifiedNames = disambiguate
+ opts.VerbosityLevel = maxVerbosityPreset
+ opts.LimitVerbosity = true
+ s := opts.FormatValue(v, reflect.Map, ptrs).String()
return strings.TrimSpace(s)
}
@@ -235,7 +371,7 @@ func formatString(s string) string {
rawInvalid := func(r rune) bool {
return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
}
- if strings.IndexFunc(s, rawInvalid) < 0 {
+ if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 {
return "`" + s + "`"
}
return qs
@@ -264,23 +400,3 @@ func formatHex(u uint64) string {
}
return fmt.Sprintf(f, u)
}
-
-// formatPointer prints the address of the pointer.
-func formatPointer(v reflect.Value) string {
- p := v.Pointer()
- if flags.Deterministic {
- p = 0xdeadf00f // Only used for stable testing purposes
- }
- return fmt.Sprintf("⟪0x%x⟫", p)
-}
-
-type visitedPointers map[value.Pointer]struct{}
-
-// Visit inserts pointer v into the visited map and reports whether it had
-// already been visited before.
-func (m visitedPointers) Visit(v reflect.Value) bool {
- p := value.PointerOf(v)
- _, visited := m[p]
- m[p] = struct{}{}
- return visited
-}
diff --git a/cmp/report_slices.go b/cmp/report_slices.go
index 344cbac..168f92f 100644
--- a/cmp/report_slices.go
+++ b/cmp/report_slices.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp
@@ -8,6 +8,7 @@ import (
"bytes"
"fmt"
"reflect"
+ "strconv"
"strings"
"unicode"
"unicode/utf8"
@@ -23,16 +24,35 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
return false // Must be formatting in diff mode
case v.NumDiff == 0:
return false // No differences detected
- case v.NumIgnored+v.NumCompared+v.NumTransformed > 0:
- // TODO: Handle the case where someone uses bytes.Equal on a large slice.
- return false // Some custom option was used to determined equality
case !v.ValueX.IsValid() || !v.ValueY.IsValid():
return false // Both values must be valid
- case v.Type.Kind() == reflect.Slice && (v.ValueX.IsNil() || v.ValueY.IsNil()):
- return false // Both of values have to be non-nil
+ case v.NumIgnored > 0:
+ return false // Some ignore option was used
+ case v.NumTransformed > 0:
+ return false // Some transform option was used
+ case v.NumCompared > 1:
+ return false // More than one comparison was used
+ case v.NumCompared == 1 && v.Type.Name() != "":
+ // The need for cmp to check applicability of options on every element
+ // in a slice is a significant performance detriment for large []byte.
+ // The workaround is to specify Comparer(bytes.Equal),
+ // which enables cmp to compare []byte more efficiently.
+ // If they differ, we still want to provide batched diffing.
+ // The logic disallows named types since they tend to have their own
+ // String method, with nicer formatting than what this provides.
+ return false
+ }
+
+ // Check whether this is an interface with the same concrete types.
+ t := v.Type
+ vx, vy := v.ValueX, v.ValueY
+ if t.Kind() == reflect.Interface && !vx.IsNil() && !vy.IsNil() && vx.Elem().Type() == vy.Elem().Type() {
+ vx, vy = vx.Elem(), vy.Elem()
+ t = vx.Type()
}
- switch t := v.Type; t.Kind() {
+ // Check whether we provide specialized diffing for this type.
+ switch t.Kind() {
case reflect.String:
case reflect.Array, reflect.Slice:
// Only slices of primitive types have specialized handling.
@@ -44,6 +64,11 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
return false
}
+ // Both slice values have to be non-empty.
+ if t.Kind() == reflect.Slice && (vx.Len() == 0 || vy.Len() == 0) {
+ return false
+ }
+
// If a sufficient number of elements already differ,
// use specialized formatting even if length requirement is not met.
if v.NumDiff > v.NumSame {
@@ -55,7 +80,7 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
// Use specialized string diffing for longer slices or strings.
const minLength = 64
- return v.ValueX.Len() >= minLength && v.ValueY.Len() >= minLength
+ return vx.Len() >= minLength && vy.Len() >= minLength
}
// FormatDiffSlice prints a diff for the slices (or strings) represented by v.
@@ -64,6 +89,11 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
assert(opts.DiffMode == diffUnknown)
t, vx, vy := v.Type, v.ValueX, v.ValueY
+ if t.Kind() == reflect.Interface {
+ vx, vy = vx.Elem(), vy.Elem()
+ t = vx.Type()
+ opts = opts.WithTypeMode(emitType)
+ }
// Auto-detect the type of the data.
var isLinedText, isText, isBinary bool
@@ -84,7 +114,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
}
if isText || isBinary {
var numLines, lastLineIdx, maxLineLen int
- isBinary = false
+ isBinary = !utf8.ValidString(sx) || !utf8.ValidString(sy)
for i, r := range sx + sy {
if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError {
isBinary = true
@@ -99,7 +129,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
}
}
isText = !isBinary
- isLinedText = isText && numLines >= 4 && maxLineLen <= 256
+ isLinedText = isText && numLines >= 4 && maxLineLen <= 1024
}
// Format the string into printable records.
@@ -119,6 +149,83 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
},
)
delim = "\n"
+
+ // If possible, use a custom triple-quote (""") syntax for printing
+ // differences in a string literal. This format is more readable,
+ // but has edge-cases where differences are visually indistinguishable.
+ // This format is avoided under the following conditions:
+ // • A line starts with `"""`
+ // • A line starts with "..."
+ // • A line contains non-printable characters
+ // • Adjacent different lines differ only by whitespace
+ //
+ // For example:
+ // """
+ // ... // 3 identical lines
+ // foo
+ // bar
+ // - baz
+ // + BAZ
+ // """
+ isTripleQuoted := true
+ prevRemoveLines := map[string]bool{}
+ prevInsertLines := map[string]bool{}
+ var list2 textList
+ list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
+ for _, r := range list {
+ if !r.Value.Equal(textEllipsis) {
+ line, _ := strconv.Unquote(string(r.Value.(textLine)))
+ line = strings.TrimPrefix(strings.TrimSuffix(line, "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
+ normLine := strings.Map(func(r rune) rune {
+ if unicode.IsSpace(r) {
+ return -1 // drop whitespace to avoid visually indistinguishable output
+ }
+ return r
+ }, line)
+ isPrintable := func(r rune) bool {
+ return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
+ }
+ isTripleQuoted = !strings.HasPrefix(line, `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == ""
+ switch r.Diff {
+ case diffRemoved:
+ isTripleQuoted = isTripleQuoted && !prevInsertLines[normLine]
+ prevRemoveLines[normLine] = true
+ case diffInserted:
+ isTripleQuoted = isTripleQuoted && !prevRemoveLines[normLine]
+ prevInsertLines[normLine] = true
+ }
+ if !isTripleQuoted {
+ break
+ }
+ r.Value = textLine(line)
+ r.ElideComma = true
+ }
+ if !(r.Diff == diffRemoved || r.Diff == diffInserted) { // start a new non-adjacent difference group
+ prevRemoveLines = map[string]bool{}
+ prevInsertLines = map[string]bool{}
+ }
+ list2 = append(list2, r)
+ }
+ if r := list2[len(list2)-1]; r.Diff == diffIdentical && len(r.Value.(textLine)) == 0 {
+ list2 = list2[:len(list2)-1] // elide single empty line at the end
+ }
+ list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
+ if isTripleQuoted {
+ var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"}
+ switch t.Kind() {
+ case reflect.String:
+ if t != reflect.TypeOf(string("")) {
+ out = opts.FormatType(t, out)
+ }
+ case reflect.Slice:
+ // Always emit type for slices since the triple-quote syntax
+ // looks like a string (not a slice).
+ opts = opts.WithTypeMode(emitType)
+ out = opts.FormatType(t, out)
+ }
+ return out
+ }
+
// If the text appears to be single-lined text,
// then perform differencing in approximately fixed-sized chunks.
// The output is printed as quoted strings.
@@ -131,6 +238,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
},
)
delim = ""
+
// If the text appears to be binary data,
// then perform differencing in approximately fixed-sized chunks.
// The output is inspired by hexdump.
@@ -147,6 +255,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
return textRecord{Diff: d, Value: textLine(s), Comment: comment}
},
)
+
// For all other slices of primitive types,
// then perform differencing in approximately fixed-sized chunks.
// The size of each chunk depends on the width of the element kind.
@@ -189,7 +298,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
}
// Wrap the output with appropriate type information.
- var out textNode = textWrap{"{", list, "}"}
+ var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
if !isText {
// The "{...}" byte-sequence literal is not valid Go syntax for strings.
// Emit the type for extra clarity (e.g. "string{...}").
@@ -200,12 +309,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
}
switch t.Kind() {
case reflect.String:
- out = textWrap{"strings.Join(", out, fmt.Sprintf(", %q)", delim)}
+ out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
if t != reflect.TypeOf(string("")) {
out = opts.FormatType(t, out)
}
case reflect.Slice:
- out = textWrap{"bytes.Join(", out, fmt.Sprintf(", %q)", delim)}
+ out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
if t != reflect.TypeOf([]byte(nil)) {
out = opts.FormatType(t, out)
}
@@ -246,9 +355,22 @@ func (opts formatOptions) formatDiffSlice(
return n0 - v.Len()
}
+ var numDiffs int
+ maxLen := -1
+ if opts.LimitVerbosity {
+ maxLen = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
+ opts.VerbosityLevel--
+ }
+
groups := coalesceAdjacentEdits(name, es)
groups = coalesceInterveningIdentical(groups, chunkSize/4)
+ maxGroup := diffStats{Name: name}
for i, ds := range groups {
+ if maxLen >= 0 && numDiffs >= maxLen {
+ maxGroup = maxGroup.Append(ds)
+ continue
+ }
+
// Print equal.
if ds.NumDiff() == 0 {
// Compute the number of leading and trailing equal bytes to print.
@@ -277,12 +399,18 @@ func (opts formatOptions) formatDiffSlice(
}
// Print unequal.
+ len0 := len(list)
nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved)
vx = vx.Slice(nx, vx.Len())
ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted)
vy = vy.Slice(ny, vy.Len())
+ numDiffs += len(list) - len0
+ }
+ if maxGroup.IsZero() {
+ assert(vx.Len() == 0 && vy.Len() == 0)
+ } else {
+ list.AppendEllipsis(maxGroup)
}
- assert(vx.Len() == 0 && vy.Len() == 0)
return list
}
diff --git a/cmp/report_text.go b/cmp/report_text.go
index 8b8fcab..0fd46d7 100644
--- a/cmp/report_text.go
+++ b/cmp/report_text.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp
@@ -10,12 +10,15 @@ import (
"math/rand"
"strings"
"time"
+ "unicode/utf8"
"github.com/google/go-cmp/cmp/internal/flags"
)
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
+const maxColumnLength = 80
+
type indentMode int
func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
@@ -91,21 +94,22 @@ type textNode interface {
// textWrap is a wrapper that concatenates a prefix and/or a suffix
// to the underlying node.
type textWrap struct {
- Prefix string // e.g., "bytes.Buffer{"
- Value textNode // textWrap | textList | textLine
- Suffix string // e.g., "}"
+ Prefix string // e.g., "bytes.Buffer{"
+ Value textNode // textWrap | textList | textLine
+ Suffix string // e.g., "}"
+ Metadata interface{} // arbitrary metadata; has no effect on formatting
}
-func (s textWrap) Len() int {
+func (s *textWrap) Len() int {
return len(s.Prefix) + s.Value.Len() + len(s.Suffix)
}
-func (s1 textWrap) Equal(s2 textNode) bool {
- if s2, ok := s2.(textWrap); ok {
+func (s1 *textWrap) Equal(s2 textNode) bool {
+ if s2, ok := s2.(*textWrap); ok {
return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
}
return false
}
-func (s textWrap) String() string {
+func (s *textWrap) String() string {
var d diffMode
var n indentMode
_, s2 := s.formatCompactTo(nil, d)
@@ -114,7 +118,7 @@ func (s textWrap) String() string {
b = append(b, '\n') // Trailing newline
return string(b)
}
-func (s textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
+func (s *textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
n0 := len(b) // Original buffer length
b = append(b, s.Prefix...)
b, s.Value = s.Value.formatCompactTo(b, d)
@@ -124,7 +128,7 @@ func (s textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
}
return b, s
}
-func (s textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
+func (s *textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
b = append(b, s.Prefix...)
b = s.Value.formatExpandedTo(b, d, n)
b = append(b, s.Suffix...)
@@ -136,22 +140,23 @@ func (s textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
// of the textList.formatCompactTo method.
type textList []textRecord
type textRecord struct {
- Diff diffMode // e.g., 0 or '-' or '+'
- Key string // e.g., "MyField"
- Value textNode // textWrap | textLine
- Comment fmt.Stringer // e.g., "6 identical fields"
+ Diff diffMode // e.g., 0 or '-' or '+'
+ Key string // e.g., "MyField"
+ Value textNode // textWrap | textLine
+ ElideComma bool // avoid trailing comma
+ Comment fmt.Stringer // e.g., "6 identical fields"
}
// AppendEllipsis appends a new ellipsis node to the list if none already
// exists at the end. If cs is non-zero it coalesces the statistics with the
// previous diffStats.
func (s *textList) AppendEllipsis(ds diffStats) {
- hasStats := ds != diffStats{}
+ hasStats := !ds.IsZero()
if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) {
if hasStats {
- *s = append(*s, textRecord{Value: textEllipsis, Comment: ds})
+ *s = append(*s, textRecord{Value: textEllipsis, ElideComma: true, Comment: ds})
} else {
- *s = append(*s, textRecord{Value: textEllipsis})
+ *s = append(*s, textRecord{Value: textEllipsis, ElideComma: true})
}
return
}
@@ -191,7 +196,7 @@ func (s1 textList) Equal(s2 textNode) bool {
}
func (s textList) String() string {
- return textWrap{"{", s, "}"}.String()
+ return (&textWrap{Prefix: "{", Value: s, Suffix: "}"}).String()
}
func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
@@ -221,7 +226,7 @@ func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
}
// Force multi-lined output when printing a removed/inserted node that
// is sufficiently long.
- if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > 80 {
+ if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > maxColumnLength {
multiLine = true
}
if !multiLine {
@@ -236,16 +241,50 @@ func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
_, isLine := r.Value.(textLine)
return r.Key == "" || !isLine
},
- func(r textRecord) int { return len(r.Key) },
+ func(r textRecord) int { return utf8.RuneCountInString(r.Key) },
)
alignValueLens := s.alignLens(
func(r textRecord) bool {
_, isLine := r.Value.(textLine)
return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil
},
- func(r textRecord) int { return len(r.Value.(textLine)) },
+ func(r textRecord) int { return utf8.RuneCount(r.Value.(textLine)) },
)
+ // Format lists of simple lists in a batched form.
+ // If the list is sequence of only textLine values,
+ // then batch multiple values on a single line.
+ var isSimple bool
+ for _, r := range s {
+ _, isLine := r.Value.(textLine)
+ isSimple = r.Diff == 0 && r.Key == "" && isLine && r.Comment == nil
+ if !isSimple {
+ break
+ }
+ }
+ if isSimple {
+ n++
+ var batch []byte
+ emitBatch := func() {
+ if len(batch) > 0 {
+ b = n.appendIndent(append(b, '\n'), d)
+ b = append(b, bytes.TrimRight(batch, " ")...)
+ batch = batch[:0]
+ }
+ }
+ for _, r := range s {
+ line := r.Value.(textLine)
+ if len(batch)+len(line)+len(", ") > maxColumnLength {
+ emitBatch()
+ }
+ batch = append(batch, line...)
+ batch = append(batch, ", "...)
+ }
+ emitBatch()
+ n--
+ return n.appendIndent(append(b, '\n'), d)
+ }
+
// Format the list as a multi-lined output.
n++
for i, r := range s {
@@ -256,7 +295,7 @@ func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
b = alignKeyLens[i].appendChar(b, ' ')
b = r.Value.formatExpandedTo(b, d|r.Diff, n)
- if !r.Value.Equal(textEllipsis) {
+ if !r.ElideComma {
b = append(b, ',')
}
b = alignValueLens[i].appendChar(b, ' ')
@@ -332,6 +371,11 @@ type diffStats struct {
NumModified int
}
+func (s diffStats) IsZero() bool {
+ s.Name = ""
+ return s == diffStats{}
+}
+
func (s diffStats) NumDiff() int {
return s.NumRemoved + s.NumInserted + s.NumModified
}
diff --git a/cmp/report_value.go b/cmp/report_value.go
index 83031a7..668d470 100644
--- a/cmp/report_value.go
+++ b/cmp/report_value.go
@@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE.md file.
+// license that can be found in the LICENSE file.
package cmp
diff --git a/cmp/testdata/diffs b/cmp/testdata/diffs
index 56a9a32..05118be 100644
--- a/cmp/testdata/diffs
+++ b/cmp/testdata/diffs
@@ -1,54 +1,54 @@
-<<< TestDiff/Comparer#09
+<<< TestDiff/Comparer/StructInequal
struct{ A int; B int; C int }{
A: 1,
B: 2,
- C: 3,
+ C: 4,
}
->>> TestDiff/Comparer#09
-<<< TestDiff/Comparer#12
+>>> TestDiff/Comparer/StructInequal
+<<< TestDiff/Comparer/PointerStructInequal
&struct{ A *int }{
- A: &4,
+ A: &5,
}
->>> TestDiff/Comparer#12
-<<< TestDiff/Comparer#16
+>>> TestDiff/Comparer/PointerStructInequal
+<<< TestDiff/Comparer/StructNestedPointerInequal
&struct{ R *bytes.Buffer }{
- R: s"",
+ R: nil,
}
->>> TestDiff/Comparer#16
-<<< TestDiff/Comparer#23
+>>> TestDiff/Comparer/StructNestedPointerInequal
+<<< TestDiff/Comparer/RegexpInequal
[]*regexp.Regexp{
nil,
- s"a*b*c*",
+ s"a*b*d*",
}
->>> TestDiff/Comparer#23
-<<< TestDiff/Comparer#25
+>>> TestDiff/Comparer/RegexpInequal
+<<< TestDiff/Comparer/TriplePointerInequal
&&&int(
- 0,
+ 1,
)
->>> TestDiff/Comparer#25
-<<< TestDiff/Comparer#28
+>>> TestDiff/Comparer/TriplePointerInequal
+<<< TestDiff/Comparer/StringerInequal
struct{ fmt.Stringer }(
- s"hello",
+ s"hello2",
)
->>> TestDiff/Comparer#28
-<<< TestDiff/Comparer#29
+>>> TestDiff/Comparer/StringerInequal
+<<< TestDiff/Comparer/DifferingHash
[16]uint8{
- 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61,
+ 0x92, 0xeb, 0x5f, 0xfe, 0xe6, 0xae, 0x2f, 0xec, 0x3a, 0xd7, 0x1c, 0x77, 0x75, 0x31, 0x57, 0x8f,
}
->>> TestDiff/Comparer#29
-<<< TestDiff/Comparer#30
+>>> TestDiff/Comparer/DifferingHash
+<<< TestDiff/Comparer/NilStringer
interface{}(
- &fmt.Stringer(nil),
)
->>> TestDiff/Comparer#30
-<<< TestDiff/Comparer#31
+>>> TestDiff/Comparer/NilStringer
+<<< TestDiff/Comparer/TarHeaders
[]cmp_test.tarHeader{
{
... // 4 identical fields
@@ -101,8 +101,8 @@
... // 6 identical fields
},
}
->>> TestDiff/Comparer#31
-<<< TestDiff/Comparer#36
+>>> TestDiff/Comparer/TarHeaders
+<<< TestDiff/Comparer/IrreflexiveComparison
[]int{
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
@@ -125,19 +125,19 @@
- Inverse(λ, float64(NaN)),
+ Inverse(λ, float64(NaN)),
}
->>> TestDiff/Comparer#36
-<<< TestDiff/Comparer#37
+>>> TestDiff/Comparer/IrreflexiveComparison
+<<< TestDiff/Comparer/StringerMapKey
map[*testprotos.Stringer]*testprotos.Stringer(
- {s"hello": s"world"},
+ nil,
)
->>> TestDiff/Comparer#37
-<<< TestDiff/Comparer#38
+>>> TestDiff/Comparer/StringerMapKey
+<<< TestDiff/Comparer/StringerBacktick
interface{}(
- []*testprotos.Stringer{s`multi\nline\nline\nline`},
)
->>> TestDiff/Comparer#38
-<<< TestDiff/Comparer#42
+>>> TestDiff/Comparer/StringerBacktick
+<<< TestDiff/Comparer/DynamicMap
[]interface{}{
map[string]interface{}{
"avg": float64(0.278),
@@ -152,22 +152,16 @@
"name": string("Sammy Sosa"),
},
}
->>> TestDiff/Comparer#42
-<<< TestDiff/Comparer#43
+>>> TestDiff/Comparer/DynamicMap
+<<< TestDiff/Comparer/MapKeyPointer
map[*int]string{
-- ⟪0xdeadf00f⟫: "hello",
-+ ⟪0xdeadf00f⟫: "world",
+- &⟪0xdeadf00f⟫0: "hello",
++ &⟪0xdeadf00f⟫0: "world",
}
->>> TestDiff/Comparer#43
-<<< TestDiff/Comparer#44
- (*int)(
-- &0,
-+ &0,
- )
->>> TestDiff/Comparer#44
-<<< TestDiff/Comparer#45
+>>> TestDiff/Comparer/MapKeyPointer
+<<< TestDiff/Comparer/IgnoreSliceElements
[2][]int{
- {..., 1, 2, 3, ..., 4, 5, 6, 7, ..., 8, ..., 9, ...},
+ {..., 1, 2, 3, ...},
{
... // 6 ignored and 1 identical elements
- 20,
@@ -175,8 +169,8 @@
... // 3 ignored elements
},
}
->>> TestDiff/Comparer#45
-<<< TestDiff/Comparer#46
+>>> TestDiff/Comparer/IgnoreSliceElements
+<<< TestDiff/Comparer/IgnoreMapEntries
[2]map[string]int{
{"KEEP3": 3, "keep1": 1, "keep2": 2, ...},
{
@@ -185,14 +179,14 @@
+ "keep2": 2,
},
}
->>> TestDiff/Comparer#46
-<<< TestDiff/Transformer
+>>> TestDiff/Comparer/IgnoreMapEntries
+<<< TestDiff/Transformer/Uints
uint8(Inverse(λ, uint16(Inverse(λ, uint32(Inverse(λ, uint64(
- 0,
+ 1,
)))))))
->>> TestDiff/Transformer
-<<< TestDiff/Transformer#02
+>>> TestDiff/Transformer/Uints
+<<< TestDiff/Transformer/Filtered
[]int{
Inverse(λ, int64(0)),
- Inverse(λ, int64(-5)),
@@ -201,14 +195,14 @@
- Inverse(λ, int64(-1)),
+ Inverse(λ, int64(-5)),
}
->>> TestDiff/Transformer#02
-<<< TestDiff/Transformer#03
+>>> TestDiff/Transformer/Filtered
+<<< TestDiff/Transformer/DisjointOutput
int(Inverse(λ, interface{}(
- string("zero"),
+ float64(1),
)))
->>> TestDiff/Transformer#03
-<<< TestDiff/Transformer#04
+>>> TestDiff/Transformer/DisjointOutput
+<<< TestDiff/Transformer/JSON
string(Inverse(ParseJSON, map[string]interface{}{
"address": map[string]interface{}{
- "city": string("Los Angeles"),
@@ -234,8 +228,8 @@
},
+ "spouse": nil,
}))
->>> TestDiff/Transformer#04
-<<< TestDiff/Transformer#05
+>>> TestDiff/Transformer/JSON
+<<< TestDiff/Transformer/AcyclicString
cmp_test.StringBytes{
String: Inverse(SplitString, []string{
"some",
@@ -246,7 +240,7 @@
}),
Bytes: []uint8(Inverse(SplitBytes, [][]uint8{
{0x73, 0x6f, 0x6d, 0x65},
- {0x6d, 0x75, 0x6c, 0x74, 0x69},
+ {0x6d, 0x75, 0x6c, 0x74, ...},
{0x6c, 0x69, 0x6e, 0x65},
{
- 0x62,
@@ -257,8 +251,100 @@
},
})),
}
->>> TestDiff/Transformer#05
-<<< TestDiff/Reporter
+>>> TestDiff/Transformer/AcyclicString
+<<< TestDiff/Reporter/PanicStringer
+ struct{ X fmt.Stringer }{
+- X: struct{ fmt.Stringer }{},
++ X: s"",
+ }
+>>> TestDiff/Reporter/PanicStringer
+<<< TestDiff/Reporter/PanicError
+ struct{ X error }{
+- X: struct{ error }{},
++ X: e"",
+ }
+>>> TestDiff/Reporter/PanicError
+<<< TestDiff/Reporter/AmbiguousType
+ interface{}(
+- "github.com/google/go-cmp/cmp/internal/teststructs/foo1".Bar{},
++ "github.com/google/go-cmp/cmp/internal/teststructs/foo2".Bar{},
+ )
+>>> TestDiff/Reporter/AmbiguousType
+<<< TestDiff/Reporter/AmbiguousPointer
+ (*int)(
+- &⟪0xdeadf00f⟫0,
++ &⟪0xdeadf00f⟫0,
+ )
+>>> TestDiff/Reporter/AmbiguousPointer
+<<< TestDiff/Reporter/AmbiguousPointerStruct
+ struct{ I *int }{
+- I: &⟪0xdeadf00f⟫0,
++ I: &⟪0xdeadf00f⟫0,
+ }
+>>> TestDiff/Reporter/AmbiguousPointerStruct
+<<< TestDiff/Reporter/AmbiguousPointerSlice
+ []*int{
+- &⟪0xdeadf00f⟫0,
++ &⟪0xdeadf00f⟫0,
+ }
+>>> TestDiff/Reporter/AmbiguousPointerSlice
+<<< TestDiff/Reporter/AmbiguousPointerMap
+ map[string]*int{
+- "zero": &⟪0xdeadf00f⟫0,
++ "zero": &⟪0xdeadf00f⟫0,
+ }
+>>> TestDiff/Reporter/AmbiguousPointerMap
+<<< TestDiff/Reporter/AmbiguousStringer
+ interface{}(
+- cmp_test.Stringer("hello"),
++ &cmp_test.Stringer("hello"),
+ )
+>>> TestDiff/Reporter/AmbiguousStringer
+<<< TestDiff/Reporter/AmbiguousStringerStruct
+ struct{ S fmt.Stringer }{
+- S: cmp_test.Stringer("hello"),
++ S: &cmp_test.Stringer("hello"),
+ }
+>>> TestDiff/Reporter/AmbiguousStringerStruct
+<<< TestDiff/Reporter/AmbiguousStringerSlice
+ []fmt.Stringer{
+- cmp_test.Stringer("hello"),
++ &cmp_test.Stringer("hello"),
+ }
+>>> TestDiff/Reporter/AmbiguousStringerSlice
+<<< TestDiff/Reporter/AmbiguousStringerMap
+ map[string]fmt.Stringer{
+- "zero": cmp_test.Stringer("hello"),
++ "zero": &cmp_test.Stringer("hello"),
+ }
+>>> TestDiff/Reporter/AmbiguousStringerMap
+<<< TestDiff/Reporter/AmbiguousSliceHeader
+ []int(
+- ⟪ptr:0xdeadf00f, len:0, cap:5⟫{},
++ ⟪ptr:0xdeadf00f, len:0, cap:1000⟫{},
+ )
+>>> TestDiff/Reporter/AmbiguousSliceHeader
+<<< TestDiff/Reporter/AmbiguousStringerMapKey
+ map[interface{}]string{
+- nil: "nil",
++ &⟪0xdeadf00f⟫"github.com/google/go-cmp/cmp_test".Stringer("hello"): "goodbye",
+- "github.com/google/go-cmp/cmp_test".Stringer("hello"): "goodbye",
+- "github.com/google/go-cmp/cmp/internal/teststructs/foo1".Bar{S: "fizz"}: "buzz",
++ "github.com/google/go-cmp/cmp/internal/teststructs/foo2".Bar{S: "fizz"}: "buzz",
+ }
+>>> TestDiff/Reporter/AmbiguousStringerMapKey
+<<< TestDiff/Reporter/NonAmbiguousStringerMapKey
+ map[interface{}]string{
++ s"fizz": "buzz",
+- s"hello": "goodbye",
+ }
+>>> TestDiff/Reporter/NonAmbiguousStringerMapKey
+<<< TestDiff/Reporter/InvalidUTF8
+ interface{}(
+- cmp_test.MyString("\xed\xa0\x80"),
+ )
+>>> TestDiff/Reporter/InvalidUTF8
+<<< TestDiff/Reporter/UnbatchedSlice
cmp_test.MyComposite{
... // 3 identical fields
BytesB: nil,
@@ -276,8 +362,8 @@
IntsC: nil,
... // 6 identical fields
}
->>> TestDiff/Reporter
-<<< TestDiff/Reporter#01
+>>> TestDiff/Reporter/UnbatchedSlice
+<<< TestDiff/Reporter/BatchedSlice
cmp_test.MyComposite{
... // 3 identical fields
BytesB: nil,
@@ -293,8 +379,29 @@
IntsC: nil,
... // 6 identical fields
}
->>> TestDiff/Reporter#01
-<<< TestDiff/Reporter#02
+>>> TestDiff/Reporter/BatchedSlice
+<<< TestDiff/Reporter/BatchedWithComparer
+ cmp_test.MyComposite{
+ StringA: "",
+ StringB: "",
+ BytesA: []uint8{
+- 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, // -|.......|
++ 0x0c, 0x1d, 0x0d, 0x1b, 0x16, 0x17, // +|......|
+ 0x11, 0x12, 0x13, 0x14, 0x15, // |.....|
+- 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // -|........|
++ 0x0a, 0x1a, 0x10, 0x19, 0x1c, 0x0b, 0x0f, 0x18, 0x0e, // +|.........|
+ },
+ BytesB: nil,
+ BytesC: nil,
+ ... // 9 identical fields
+ }
+>>> TestDiff/Reporter/BatchedWithComparer
+<<< TestDiff/Reporter/BatchedLong
+ interface{}(
+- cmp_test.MyComposite{IntsA: []int8{0, 1, 2, 3, 4, 5, 6, 7, ...}},
+ )
+>>> TestDiff/Reporter/BatchedLong
+<<< TestDiff/Reporter/BatchedNamedAndUnnamed
cmp_test.MyComposite{
StringA: "",
StringB: "",
@@ -347,8 +454,8 @@
+ 9.5, 8.5, 7.5,
},
}
->>> TestDiff/Reporter#02
-<<< TestDiff/Reporter#03
+>>> TestDiff/Reporter/BatchedNamedAndUnnamed
+<<< TestDiff/Reporter/BinaryHexdump
cmp_test.MyComposite{
StringA: "",
StringB: "",
@@ -369,8 +476,8 @@
BytesC: nil,
... // 9 identical fields
}
->>> TestDiff/Reporter#03
-<<< TestDiff/Reporter#04
+>>> TestDiff/Reporter/BinaryHexdump
+<<< TestDiff/Reporter/StringHexdump
cmp_test.MyComposite{
StringA: "",
StringB: cmp_test.MyString{
@@ -394,8 +501,8 @@
BytesB: nil,
... // 10 identical fields
}
->>> TestDiff/Reporter#04
-<<< TestDiff/Reporter#05
+>>> TestDiff/Reporter/StringHexdump
+<<< TestDiff/Reporter/BinaryString
cmp_test.MyComposite{
StringA: "",
StringB: "",
@@ -412,34 +519,436 @@
BytesC: nil,
... // 9 identical fields
}
->>> TestDiff/Reporter#05
-<<< TestDiff/Reporter#06
+>>> TestDiff/Reporter/BinaryString
+<<< TestDiff/Reporter/TripleQuote
cmp_test.MyComposite{
- StringA: strings.Join({
-- "Package cmp determines equality of values.",
-+ "Package cmp determines equality of value.",
- "",
- "This package is intended to be a more powerful and safer alternative to",
+ StringA: (
+ """
+ aaa
+ bbb
+- ccc
++ CCC
+ ddd
+ eee
+ ... // 10 identical lines
+ ppp
+ qqq
+- RRR
+- sss
++ rrr
++ SSS
+ ttt
+ uuu
... // 6 identical lines
- "For example, an equality function may report floats as equal so long as they",
- "are within some tolerance of each other.",
-- "",
-- "• Types that have an Equal method may use that method to determine equality.",
-- "This allows package authors to determine the equality operation for the types",
-- "that they define.",
- "",
- "• If no custom equality functions are used and no Equal method is defined,",
+ """
+ ),
+ StringB: "",
+ BytesA: nil,
+ ... // 11 identical fields
+ }
+>>> TestDiff/Reporter/TripleQuote
+<<< TestDiff/Reporter/TripleQuoteSlice
+ []string{
+ (
+ """
+ ... // 23 identical lines
+ xxx
+ yyy
+- zzz
+ """
+ ),
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\n"...,
+ }
+>>> TestDiff/Reporter/TripleQuoteSlice
+<<< TestDiff/Reporter/TripleQuoteNamedTypes
+ cmp_test.MyComposite{
+ StringA: "",
+ StringB: (
+ """
+ aaa
+ bbb
+- ccc
++ CCC
+ ddd
+ eee
+ ... // 10 identical lines
+ ppp
+ qqq
+- RRR
+- sss
++ rrr
++ SSS
+ ttt
+ uuu
+ ... // 5 identical lines
+ """
+ ),
+ BytesA: nil,
+ BytesB: nil,
+ BytesC: cmp_test.MyBytes(
+ """
+ aaa
+ bbb
+- ccc
++ CCC
+ ddd
+ eee
+ ... // 10 identical lines
+ ppp
+ qqq
+- RRR
+- sss
++ rrr
++ SSS
+ ttt
+ uuu
+ ... // 5 identical lines
+ """
+ ),
+ IntsA: nil,
+ IntsB: nil,
+ ... // 7 identical fields
+ }
+>>> TestDiff/Reporter/TripleQuoteNamedTypes
+<<< TestDiff/Reporter/TripleQuoteSliceNamedTypes
+ []cmp_test.MyString{
+ (
+ """
+ ... // 23 identical lines
+ xxx
+ yyy
+- zzz
+ """
+ ),
+ "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\n"...,
+ }
+>>> TestDiff/Reporter/TripleQuoteSliceNamedTypes
+<<< TestDiff/Reporter/TripleQuoteEndlines
+ (
+ """
+ aaa
+ bbb
+- ccc
++ CCC
+ ddd
+ eee
+ ... // 10 identical lines
+ ppp
+ qqq
+- RRR
++ rrr
+ sss
+ ttt
+ ... // 4 identical lines
+ yyy
+ zzz
+-
+ """
+ )
+>>> TestDiff/Reporter/TripleQuoteEndlines
+<<< TestDiff/Reporter/AvoidTripleQuoteAmbiguousQuotes
+ strings.Join({
+ "aaa",
+ "bbb",
+- "ccc",
++ "CCC",
+ "ddd",
+ "eee",
+- "fff",
++ `"""`,
+ "ggg",
+ "hhh",
+ ... // 7 identical lines
+ "ppp",
+ "qqq",
+- "RRR",
++ "rrr",
+ "sss",
+ "ttt",
+ ... // 7 identical lines
+ }, "\n")
+>>> TestDiff/Reporter/AvoidTripleQuoteAmbiguousQuotes
+<<< TestDiff/Reporter/AvoidTripleQuoteAmbiguousEllipsis
+ strings.Join({
+ "aaa",
+ "bbb",
+- "ccc",
+- "...",
++ "CCC",
++ "ddd",
+ "eee",
+ "fff",
+ ... // 9 identical lines
+ "ppp",
+ "qqq",
+- "RRR",
++ "rrr",
+ "sss",
+ "ttt",
+ ... // 7 identical lines
+ }, "\n")
+>>> TestDiff/Reporter/AvoidTripleQuoteAmbiguousEllipsis
+<<< TestDiff/Reporter/AvoidTripleQuoteNonPrintable
+ strings.Join({
+ "aaa",
+ "bbb",
+- "ccc",
++ "CCC",
+ "ddd",
+ "eee",
+ ... // 7 identical lines
+ "mmm",
+ "nnn",
+- "ooo",
++ "o\roo",
+ "ppp",
+ "qqq",
+- "RRR",
++ "rrr",
+ "sss",
+ "ttt",
+ ... // 7 identical lines
+ }, "\n")
+>>> TestDiff/Reporter/AvoidTripleQuoteNonPrintable
+<<< TestDiff/Reporter/AvoidTripleQuoteIdenticalWhitespace
+ strings.Join({
+ "aaa",
+ "bbb",
+- "ccc",
+- " ddd",
++ "ccc ",
++ "ddd",
+ "eee",
+ "fff",
+ ... // 9 identical lines
+ "ppp",
+ "qqq",
+- "RRR",
++ "rrr",
+ "sss",
+ "ttt",
+ ... // 7 identical lines
+ }, "\n")
+>>> TestDiff/Reporter/AvoidTripleQuoteIdenticalWhitespace
+<<< TestDiff/Reporter/TripleQuoteStringer
+ []fmt.Stringer{
+ s"package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hel"...,
+- (
+- s"""
+- package main
+-
+- import (
+- "fmt"
+- "math/rand"
+- )
+-
+- func main() {
+- fmt.Println("My favorite number is", rand.Intn(10))
+- }
+- s"""
+- ),
++ (
++ s"""
++ package main
++
++ import (
++ "fmt"
++ "math"
++ )
++
++ func main() {
++ fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
++ }
++ s"""
++ ),
+ }
+>>> TestDiff/Reporter/TripleQuoteStringer
+<<< TestDiff/Reporter/LimitMaximumBytesDiffs
+ []uint8{
+- 0xcd, 0x3d, 0x3d, 0x3d, 0x3d, 0x06, 0x1f, 0xc2, 0xcc, 0xc2, 0x2d, 0x53, // -|.====.....-S|
++ 0x5c, 0x3d, 0x3d, 0x3d, 0x3d, 0x7c, 0x96, 0xe7, 0x53, 0x42, 0xa0, 0xab, // +|\====|..SB..|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |=====|
+- 0x1d, 0xdf, 0x61, 0xae, 0x98, 0x9f, 0x48, // -|..a...H|
++ 0xf0, 0xbd, 0xa5, 0x71, 0xab, 0x17, 0x3b, // +|...q..;|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |======|
+- 0xc7, 0xb0, 0xb7, // -|...|
++ 0xab, 0x50, 0x00, // +|.P.|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |=======|
+- 0xef, 0x3d, 0x3d, 0x3d, 0x3d, 0x3a, 0x5c, 0x94, 0xe6, 0x4a, 0xc7, // -|.====:\..J.|
++ 0xeb, 0x3d, 0x3d, 0x3d, 0x3d, 0xa5, 0x14, 0xe6, 0x4f, 0x28, 0xe4, // +|.====...O(.|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |=====|
+- 0xb4, // -|.|
++ 0x28, // +|(|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |======|
+- 0x0a, 0x0a, 0xf7, 0x94, // -|....|
++ 0x2f, 0x63, 0x40, 0x3f, // +|/c@?|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |===========|
+- 0xf2, 0x9c, 0xc0, 0x66, // -|...f|
++ 0xd9, 0x78, 0xed, 0x13, // +|.x..|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |=====|
+- 0x34, 0xf6, 0xf1, 0xc3, 0x17, 0x82, // -|4.....|
++ 0x4a, 0xfc, 0x91, 0x38, 0x42, 0x8d, // +|J..8B.|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |======|
+- 0x6e, 0x16, 0x60, 0x91, 0x44, 0xc6, 0x06, // -|n.`.D..|
++ 0x61, 0x38, 0x41, 0xeb, 0x73, 0x04, 0xae, // +|a8A.s..|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |=======|
+- 0x1c, 0x45, 0x3d, 0x3d, 0x3d, 0x3d, 0x2e, // -|.E====.|
++ 0x07, 0x43, 0x3d, 0x3d, 0x3d, 0x3d, 0x1c, // +|.C====.|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |===========|
+- 0xc4, 0x18, // -|..|
++ 0x91, 0x22, // +|."|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |=======|
+- 0x8a, 0x8d, 0x0e, 0x3d, 0x3d, 0x3d, 0x3d, 0x87, 0xb1, 0xa5, 0x8e, 0xc3, 0x3d, 0x3d, 0x3d, 0x3d, // -|...====.....====|
+- 0x3d, 0x7a, 0x0f, 0x31, 0xae, 0x55, 0x3d, // -|=z.1.U=|
++ 0x75, 0xd8, 0xbe, 0x3d, 0x3d, 0x3d, 0x3d, 0x73, 0xec, 0x84, 0x35, 0x07, 0x3d, 0x3d, 0x3d, 0x3d, // +|u..====s..5.====|
++ 0x3d, 0x3b, 0xab, 0x53, 0x39, 0x74, // +|=;.S9t|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |=====|
+- 0x47, 0x2c, 0x3d, // -|G,=|
++ 0x3d, 0x1f, 0x1b, // +|=..|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |======|
+- 0x35, 0xe7, 0x35, 0xee, 0x82, 0xf4, 0xce, 0x3d, 0x3d, 0x3d, 0x3d, 0x11, 0x72, 0x3d, // -|5.5....====.r=|
++ 0x3d, 0x80, 0xab, 0x2f, 0xed, 0x2b, 0x3a, 0x3b, 0x3d, 0x3d, 0x3d, 0x3d, 0xea, 0x49, // +|=../.+:;====.I|
+ 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, // |==========|
+- 0xaf, 0x5d, 0x3d, // -|.]=|
++ 0x3d, 0xab, 0x6c, // +|=.l|
+ ... // 51 identical, 34 removed, and 35 inserted bytes
+ }
+>>> TestDiff/Reporter/LimitMaximumBytesDiffs
+<<< TestDiff/Reporter/LimitMaximumStringDiffs
+ (
+ """
+- a
++ aa
+ b
+- c
++ cc
+ d
+- e
++ ee
+ f
+- g
++ gg
+ h
+- i
++ ii
+ j
+- k
++ kk
+ l
+- m
++ mm
+ n
+- o
++ oo
+ p
+- q
++ qq
+ r
+- s
++ ss
+ t
+- u
++ uu
+ v
+- w
++ ww
+ x
+- y
++ yy
+ z
+- A
++ AA
+ B
+- C
++ CC
+ D
+- E
++ EE
+ ... // 12 identical, 10 removed, and 10 inserted lines
+ """
+ )
+>>> TestDiff/Reporter/LimitMaximumStringDiffs
+<<< TestDiff/Reporter/LimitMaximumSliceDiffs
+ []struct{ S string }{
+- {S: "a"},
++ {S: "aa"},
+ {S: "b"},
+- {S: "c"},
++ {S: "cc"},
+ {S: "d"},
+- {S: "e"},
++ {S: "ee"},
+ {S: "f"},
+- {S: "g"},
++ {S: "gg"},
+ {S: "h"},
+- {S: "i"},
++ {S: "ii"},
+ {S: "j"},
+- {S: "k"},
++ {S: "kk"},
+ {S: "l"},
+- {S: "m"},
++ {S: "mm"},
+ {S: "n"},
+- {S: "o"},
++ {S: "oo"},
+ {S: "p"},
+- {S: "q"},
++ {S: "qq"},
+ {S: "r"},
+- {S: "s"},
++ {S: "ss"},
+ {S: "t"},
+- {S: "u"},
++ {S: "uu"},
+ {S: "v"},
+- {S: "w"},
++ {S: "ww"},
+ {S: "x"},
+- {S: "y"},
++ {S: "yy"},
+ {S: "z"},
+- {S: "A"},
++ {S: "AA"},
+ {S: "B"},
+- {S: "C"},
++ {S: "CC"},
+ {S: "D"},
+- {S: "E"},
++ {S: "EE"},
+ ... // 12 identical and 10 modified elements
+ }
+>>> TestDiff/Reporter/LimitMaximumSliceDiffs
+<<< TestDiff/Reporter/MultilineString
+ cmp_test.MyComposite{
+ StringA: (
+ """
+- Package cmp determines equality of values.
++ Package cmp determines equality of value.
+
+ This package is intended to be a more powerful and safer alternative to
+ ... // 6 identical lines
+ For example, an equality function may report floats as equal so long as they
+ are within some tolerance of each other.
+-
+- • Types that have an Equal method may use that method to determine equality.
+- This allows package authors to determine the equality operation for the types
+- that they define.
+
+ • If no custom equality functions are used and no Equal method is defined,
... // 3 identical lines
- "by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared",
- "using the AllowUnexported option.",
-- "",
- }, "\n"),
+ by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
+ using the AllowUnexported option.
+-
+ """
+ ),
StringB: "",
BytesA: nil,
... // 11 identical fields
}
->>> TestDiff/Reporter#06
-<<< TestDiff/Reporter#07
+>>> TestDiff/Reporter/MultilineString
+<<< TestDiff/Reporter/Slices
cmp_test.MyComposite{
StringA: "",
StringB: "",
@@ -468,8 +977,8 @@
- FloatsC: cmp_test.MyFloats{7.5, 8.5, 9.5},
+ FloatsC: nil,
}
->>> TestDiff/Reporter#07
-<<< TestDiff/Reporter#08
+>>> TestDiff/Reporter/Slices
+<<< TestDiff/Reporter/EmptySlices
cmp_test.MyComposite{
StringA: "",
StringB: "",
@@ -498,8 +1007,46 @@
- FloatsC: cmp_test.MyFloats{},
+ FloatsC: nil,
}
->>> TestDiff/Reporter#08
-<<< TestDiff/EmbeddedStruct/ParentStructA#04
+>>> TestDiff/Reporter/EmptySlices
+<<< TestDiff/Reporter/LargeMapKey
+ map[*[]uint8]int{
+- &⟪0xdeadf00f⟫⟪ptr:0xdeadf00f, len:1048576, cap:1048576⟫{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ...}: 0,
++ &⟪0xdeadf00f⟫⟪ptr:0xdeadf00f, len:1048576, cap:1048576⟫{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ...}: 0,
+ }
+>>> TestDiff/Reporter/LargeMapKey
+<<< TestDiff/Reporter/LargeStringInInterface
+ struct{ X interface{} }{
+ X: strings.Join({
+ ... // 485 identical bytes
+ "s mus. Pellentesque mi lorem, consectetur id porttitor id, solli",
+ "citudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis",
+- ".",
++ ",",
+ }, ""),
+ }
+>>> TestDiff/Reporter/LargeStringInInterface
+<<< TestDiff/Reporter/LargeBytesInInterface
+ struct{ X interface{} }{
+ X: bytes.Join({
+ ... // 485 identical bytes
+ "s mus. Pellentesque mi lorem, consectetur id porttitor id, solli",
+ "citudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis",
+- ".",
++ ",",
+ }, ""),
+ }
+>>> TestDiff/Reporter/LargeBytesInInterface
+<<< TestDiff/Reporter/LargeStandaloneString
+ struct{ X interface{} }{
+- X: [1]string{
+- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis.",
+- },
++ X: [1]string{
++ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,",
++ },
+ }
+>>> TestDiff/Reporter/LargeStandaloneString
+<<< TestDiff/EmbeddedStruct/ParentStructA/Inequal
teststructs.ParentStructA{
privateStruct: teststructs.privateStruct{
- Public: 1,
@@ -508,8 +1055,8 @@
+ private: 3,
},
}
->>> TestDiff/EmbeddedStruct/ParentStructA#04
-<<< TestDiff/EmbeddedStruct/ParentStructB#04
+>>> TestDiff/EmbeddedStruct/ParentStructA/Inequal
+<<< TestDiff/EmbeddedStruct/ParentStructB/Inequal
teststructs.ParentStructB{
PublicStruct: teststructs.PublicStruct{
- Public: 1,
@@ -518,8 +1065,8 @@
+ private: 3,
},
}
->>> TestDiff/EmbeddedStruct/ParentStructB#04
-<<< TestDiff/EmbeddedStruct/ParentStructC#04
+>>> TestDiff/EmbeddedStruct/ParentStructB/Inequal
+<<< TestDiff/EmbeddedStruct/ParentStructC/Inequal
teststructs.ParentStructC{
privateStruct: teststructs.privateStruct{
- Public: 1,
@@ -532,8 +1079,8 @@
- private: 4,
+ private: 5,
}
->>> TestDiff/EmbeddedStruct/ParentStructC#04
-<<< TestDiff/EmbeddedStruct/ParentStructD#04
+>>> TestDiff/EmbeddedStruct/ParentStructC/Inequal
+<<< TestDiff/EmbeddedStruct/ParentStructD/Inequal
teststructs.ParentStructD{
PublicStruct: teststructs.PublicStruct{
- Public: 1,
@@ -546,8 +1093,8 @@
- private: 4,
+ private: 5,
}
->>> TestDiff/EmbeddedStruct/ParentStructD#04
-<<< TestDiff/EmbeddedStruct/ParentStructE#05
+>>> TestDiff/EmbeddedStruct/ParentStructD/Inequal
+<<< TestDiff/EmbeddedStruct/ParentStructE/Inequal
teststructs.ParentStructE{
privateStruct: teststructs.privateStruct{
- Public: 1,
@@ -562,8 +1109,8 @@
+ private: 5,
},
}
->>> TestDiff/EmbeddedStruct/ParentStructE#05
-<<< TestDiff/EmbeddedStruct/ParentStructF#05
+>>> TestDiff/EmbeddedStruct/ParentStructE/Inequal
+<<< TestDiff/EmbeddedStruct/ParentStructF/Inequal
teststructs.ParentStructF{
privateStruct: teststructs.privateStruct{
- Public: 1,
@@ -582,8 +1129,8 @@
- private: 6,
+ private: 7,
}
->>> TestDiff/EmbeddedStruct/ParentStructF#05
-<<< TestDiff/EmbeddedStruct/ParentStructG#04
+>>> TestDiff/EmbeddedStruct/ParentStructF/Inequal
+<<< TestDiff/EmbeddedStruct/ParentStructG/Inequal
&teststructs.ParentStructG{
privateStruct: &teststructs.privateStruct{
- Public: 1,
@@ -592,8 +1139,8 @@
+ private: 3,
},
}
->>> TestDiff/EmbeddedStruct/ParentStructG#04
-<<< TestDiff/EmbeddedStruct/ParentStructH#05
+>>> TestDiff/EmbeddedStruct/ParentStructG/Inequal
+<<< TestDiff/EmbeddedStruct/ParentStructH/Inequal
&teststructs.ParentStructH{
PublicStruct: &teststructs.PublicStruct{
- Public: 1,
@@ -602,8 +1149,8 @@
+ private: 3,
},
}
->>> TestDiff/EmbeddedStruct/ParentStructH#05
-<<< TestDiff/EmbeddedStruct/ParentStructI#06
+>>> TestDiff/EmbeddedStruct/ParentStructH/Inequal
+<<< TestDiff/EmbeddedStruct/ParentStructI/Inequal
&teststructs.ParentStructI{
privateStruct: &teststructs.privateStruct{
- Public: 1,
@@ -618,8 +1165,8 @@
+ private: 5,
},
}
->>> TestDiff/EmbeddedStruct/ParentStructI#06
-<<< TestDiff/EmbeddedStruct/ParentStructJ#05
+>>> TestDiff/EmbeddedStruct/ParentStructI/Inequal
+<<< TestDiff/EmbeddedStruct/ParentStructJ/Inequal
&teststructs.ParentStructJ{
privateStruct: &teststructs.privateStruct{
- Public: 1,
@@ -646,236 +1193,236 @@
+ private: 7,
},
}
->>> TestDiff/EmbeddedStruct/ParentStructJ#05
-<<< TestDiff/EqualMethod/StructB
+>>> TestDiff/EmbeddedStruct/ParentStructJ/Inequal
+<<< TestDiff/EqualMethod/StructB/ValueInequal
teststructs.StructB{
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructB
-<<< TestDiff/EqualMethod/StructD
+>>> TestDiff/EqualMethod/StructB/ValueInequal
+<<< TestDiff/EqualMethod/StructD/ValueInequal
teststructs.StructD{
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructD
-<<< TestDiff/EqualMethod/StructE
+>>> TestDiff/EqualMethod/StructD/ValueInequal
+<<< TestDiff/EqualMethod/StructE/ValueInequal
teststructs.StructE{
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructE
-<<< TestDiff/EqualMethod/StructF
+>>> TestDiff/EqualMethod/StructE/ValueInequal
+<<< TestDiff/EqualMethod/StructF/ValueInequal
teststructs.StructF{
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructF
-<<< TestDiff/EqualMethod/StructA1#01
+>>> TestDiff/EqualMethod/StructF/ValueInequal
+<<< TestDiff/EqualMethod/StructA1/ValueInequal
teststructs.StructA1{
- StructA: teststructs.StructA{X: "NotEqual"},
+ StructA: {X: "NotEqual"},
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructA1#01
-<<< TestDiff/EqualMethod/StructA1#03
+>>> TestDiff/EqualMethod/StructA1/ValueInequal
+<<< TestDiff/EqualMethod/StructA1/PointerInequal
&teststructs.StructA1{
- StructA: teststructs.StructA{X: "NotEqual"},
+ StructA: {X: "NotEqual"},
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructA1#03
-<<< TestDiff/EqualMethod/StructB1#01
+>>> TestDiff/EqualMethod/StructA1/PointerInequal
+<<< TestDiff/EqualMethod/StructB1/ValueInequal
teststructs.StructB1{
- StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})),
+ StructB: Inverse(Addr, &teststructs.StructB{X: "NotEqual"}),
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructB1#01
-<<< TestDiff/EqualMethod/StructB1#03
+>>> TestDiff/EqualMethod/StructB1/ValueInequal
+<<< TestDiff/EqualMethod/StructB1/PointerInequal
&teststructs.StructB1{
- StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})),
+ StructB: Inverse(Addr, &teststructs.StructB{X: "NotEqual"}),
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructB1#03
-<<< TestDiff/EqualMethod/StructD1
+>>> TestDiff/EqualMethod/StructB1/PointerInequal
+<<< TestDiff/EqualMethod/StructD1/ValueInequal
teststructs.StructD1{
- StructD: teststructs.StructD{X: "NotEqual"},
+ StructD: teststructs.StructD{X: "not_equal"},
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructD1
-<<< TestDiff/EqualMethod/StructE1
+>>> TestDiff/EqualMethod/StructD1/ValueInequal
+<<< TestDiff/EqualMethod/StructE1/ValueInequal
teststructs.StructE1{
- StructE: teststructs.StructE{X: "NotEqual"},
+ StructE: teststructs.StructE{X: "not_equal"},
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructE1
-<<< TestDiff/EqualMethod/StructF1
+>>> TestDiff/EqualMethod/StructE1/ValueInequal
+<<< TestDiff/EqualMethod/StructF1/ValueInequal
teststructs.StructF1{
- StructF: teststructs.StructF{X: "NotEqual"},
+ StructF: teststructs.StructF{X: "not_equal"},
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructF1
-<<< TestDiff/EqualMethod/StructA2#01
+>>> TestDiff/EqualMethod/StructF1/ValueInequal
+<<< TestDiff/EqualMethod/StructA2/ValueInequal
teststructs.StructA2{
- StructA: &teststructs.StructA{X: "NotEqual"},
+ StructA: &{X: "NotEqual"},
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructA2#01
-<<< TestDiff/EqualMethod/StructA2#03
+>>> TestDiff/EqualMethod/StructA2/ValueInequal
+<<< TestDiff/EqualMethod/StructA2/PointerInequal
&teststructs.StructA2{
- StructA: &teststructs.StructA{X: "NotEqual"},
+ StructA: &{X: "NotEqual"},
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructA2#03
-<<< TestDiff/EqualMethod/StructB2#01
+>>> TestDiff/EqualMethod/StructA2/PointerInequal
+<<< TestDiff/EqualMethod/StructB2/ValueInequal
teststructs.StructB2{
- StructB: &teststructs.StructB{X: "NotEqual"},
+ StructB: &{X: "NotEqual"},
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructB2#01
-<<< TestDiff/EqualMethod/StructB2#03
+>>> TestDiff/EqualMethod/StructB2/ValueInequal
+<<< TestDiff/EqualMethod/StructB2/PointerInequal
&teststructs.StructB2{
- StructB: &teststructs.StructB{X: "NotEqual"},
+ StructB: &{X: "NotEqual"},
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructB2#03
-<<< TestDiff/EqualMethod/StructNo
+>>> TestDiff/EqualMethod/StructB2/PointerInequal
+<<< TestDiff/EqualMethod/StructNo/Inequal
teststructs.StructNo{
- X: "NotEqual",
+ X: "not_equal",
}
->>> TestDiff/EqualMethod/StructNo
-<<< TestDiff/Cycle#01
- &&cmp_test.P(
-- &⟪0xdeadf00f⟫,
-+ &&⟪0xdeadf00f⟫,
+>>> TestDiff/EqualMethod/StructNo/Inequal
+<<< TestDiff/Cycle/PointersInequal
+ &&⟪ref#0⟫cmp_test.P(
+- &⟪ref#0⟫(...),
++ &&⟪ref#0⟫(...),
)
->>> TestDiff/Cycle#01
-<<< TestDiff/Cycle#03
+>>> TestDiff/Cycle/PointersInequal
+<<< TestDiff/Cycle/SlicesInequal
cmp_test.S{
-- {{{*(*cmp_test.S)(⟪0xdeadf00f⟫)}}},
-+ {{{{*(*cmp_test.S)(⟪0xdeadf00f⟫)}}}},
+- ⟪ref#0⟫{⟪ref#0⟫(...)},
++ ⟪ref#1⟫{{⟪ref#1⟫(...)}},
}
->>> TestDiff/Cycle#03
-<<< TestDiff/Cycle#05
- cmp_test.M{
-- 0: {0: ⟪0xdeadf00f⟫},
-+ 0: {0: {0: ⟪0xdeadf00f⟫}},
+>>> TestDiff/Cycle/SlicesInequal
+<<< TestDiff/Cycle/MapsInequal
+ cmp_test.M⟪ref#0⟫{
+- 0: ⟪ref#0⟫(...),
++ 0: {0: ⟪ref#0⟫(...)},
}
->>> TestDiff/Cycle#05
-<<< TestDiff/Cycle#07
+>>> TestDiff/Cycle/MapsInequal
+<<< TestDiff/Cycle/GraphInequalZeroed
map[string]*cmp_test.CycleAlpha{
- "Bar": &{
+ "Bar": &⟪ref#0⟫{
Name: "Bar",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{
+ "BarBuzzBravo": &⟪ref#1⟫{
- ID: 102,
+ ID: 0,
Name: "BarBuzzBravo",
Mods: 2,
Alphas: map[string]*cmp_test.CycleAlpha{
- "Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
- "Buzz": &{
+ "Bar": &⟪ref#0⟫(...),
+ "Buzz": &⟪ref#2⟫{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}},
- "BuzzBarBravo": &{
+ "BarBuzzBravo": &⟪ref#1⟫(...),
+ "BuzzBarBravo": &⟪ref#3⟫{
- ID: 103,
+ ID: 0,
Name: "BuzzBarBravo",
Mods: 0,
- Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
+ Alphas: {"Bar": &⟪ref#0⟫(...), "Buzz": &⟪ref#2⟫(...)},
},
},
},
},
},
- "BuzzBarBravo": &{
+ "BuzzBarBravo": &⟪ref#3⟫{
- ID: 103,
+ ID: 0,
Name: "BuzzBarBravo",
Mods: 0,
Alphas: map[string]*cmp_test.CycleAlpha{
- "Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
- "Buzz": &{
+ "Bar": &⟪ref#0⟫(...),
+ "Buzz": &⟪ref#2⟫{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{
+ "BarBuzzBravo": &⟪ref#1⟫{
- ID: 102,
+ ID: 0,
Name: "BarBuzzBravo",
Mods: 2,
- Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
+ Alphas: {"Bar": &⟪ref#0⟫(...), "Buzz": &⟪ref#2⟫(...)},
},
- "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": ⟪0xdeadf00f⟫}},
+ "BuzzBarBravo": &⟪ref#3⟫(...),
},
},
},
},
},
},
- "Buzz": &{
+ "Buzz": &⟪ref#2⟫{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{
+ "BarBuzzBravo": &⟪ref#1⟫{
- ID: 102,
+ ID: 0,
Name: "BarBuzzBravo",
Mods: 2,
Alphas: map[string]*cmp_test.CycleAlpha{
- "Bar": &{
+ "Bar": &⟪ref#0⟫{
Name: "Bar",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}},
- "BuzzBarBravo": &{
+ "BarBuzzBravo": &⟪ref#1⟫(...),
+ "BuzzBarBravo": &⟪ref#3⟫{
- ID: 103,
+ ID: 0,
Name: "BuzzBarBravo",
Mods: 0,
- Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
+ Alphas: {"Bar": &⟪ref#0⟫(...), "Buzz": &⟪ref#2⟫(...)},
},
},
},
- "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
+ "Buzz": &⟪ref#2⟫(...),
},
},
- "BuzzBarBravo": &{
+ "BuzzBarBravo": &⟪ref#3⟫{
- ID: 103,
+ ID: 0,
Name: "BuzzBarBravo",
Mods: 0,
Alphas: map[string]*cmp_test.CycleAlpha{
- "Bar": &{
+ "Bar": &⟪ref#0⟫{
Name: "Bar",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{
+ "BarBuzzBravo": &⟪ref#1⟫{
- ID: 102,
+ ID: 0,
Name: "BarBuzzBravo",
Mods: 2,
- Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
+ Alphas: {"Bar": &⟪ref#0⟫(...), "Buzz": &⟪ref#2⟫(...)},
},
- "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": ⟪0xdeadf00f⟫}},
+ "BuzzBarBravo": &⟪ref#3⟫(...),
},
},
- "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
+ "Buzz": &⟪ref#2⟫(...),
},
},
},
},
- "Foo": &{
+ "Foo": &⟪ref#4⟫{
Name: "Foo",
Bravos: map[string]*cmp_test.CycleBravo{
"FooBravo": &{
@@ -883,151 +1430,79 @@
+ ID: 0,
Name: "FooBravo",
Mods: 100,
- Alphas: map[string]*cmp_test.CycleAlpha{"Foo": &{Name: "Foo", Bravos: map[string]*cmp_test.CycleBravo{"FooBravo": &{Name: "FooBravo", Mods: 100, Alphas: map[string]*cmp_test.CycleAlpha{"Foo": ⟪0xdeadf00f⟫}}}}},
+ Alphas: {"Foo": &⟪ref#4⟫(...)},
},
},
},
}
->>> TestDiff/Cycle#07
-<<< TestDiff/Cycle#08
+>>> TestDiff/Cycle/GraphInequalZeroed
+<<< TestDiff/Cycle/GraphInequalStruct
map[string]*cmp_test.CycleAlpha{
- "Bar": &{
+ "Bar": &⟪ref#0⟫{
Name: "Bar",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{
+ "BarBuzzBravo": &⟪ref#1⟫{
ID: 102,
Name: "BarBuzzBravo",
Mods: 2,
Alphas: map[string]*cmp_test.CycleAlpha{
- "Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
- "Buzz": &{
+ "Bar": &⟪ref#0⟫(...),
+ "Buzz": &⟪ref#2⟫{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}},
- "BuzzBarBravo": &{
+ "BarBuzzBravo": &⟪ref#1⟫(...),
+ "BuzzBarBravo": &⟪ref#3⟫{
ID: 103,
Name: "BuzzBarBravo",
Mods: 0,
- Alphas: nil,
-+ Alphas: map[string]*cmp_test.CycleAlpha{
-+ "Bar": &{
-+ Name: "Bar",
-+ Bravos: map[string]*cmp_test.CycleBravo{
-+ "BarBuzzBravo": &{
-+ ID: 102,
-+ Name: "BarBuzzBravo",
-+ Mods: 2,
-+ Alphas: map[string]*cmp_test.CycleAlpha{
-+ "Bar": ⟪0xdeadf00f⟫,
-+ "Buzz": &{
-+ Name: "Buzz",
-+ Bravos: map[string]*cmp_test.CycleBravo{
-+ "BarBuzzBravo": ⟪0xdeadf00f⟫,
-+ "BuzzBarBravo": &{
-+ ID: 103,
-+ Name: "BuzzBarBravo",
-+ Alphas: map[string]*cmp_test.CycleAlpha(⟪0xdeadf00f⟫),
-+ },
-+ },
-+ },
-+ },
-+ },
-+ "BuzzBarBravo": ⟪0xdeadf00f⟫,
-+ },
-+ },
-+ "Buzz": ⟪0xdeadf00f⟫,
-+ },
++ Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &⟪ref#0⟫(...), "Buzz": &⟪ref#2⟫(...)},
},
},
},
},
},
- "BuzzBarBravo": &{
+ "BuzzBarBravo": &⟪ref#3⟫{
ID: 103,
Name: "BuzzBarBravo",
Mods: 0,
Alphas: map[string]*cmp_test.CycleAlpha{
- "Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
- "Buzz": &{
+ "Bar": &⟪ref#0⟫(...),
+ "Buzz": &⟪ref#2⟫{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}},
+ "BarBuzzBravo": &⟪ref#1⟫{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: {"Bar": &⟪ref#0⟫(...), "Buzz": &⟪ref#2⟫(...)}},
- "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo"},
-+ "BuzzBarBravo": &{
-+ ID: 103,
-+ Name: "BuzzBarBravo",
-+ Alphas: map[string]*cmp_test.CycleAlpha{
-+ "Bar": &{
-+ Name: "Bar",
-+ Bravos: map[string]*cmp_test.CycleBravo{
-+ "BarBuzzBravo": &{
-+ ID: 102,
-+ Name: "BarBuzzBravo",
-+ Mods: 2,
-+ Alphas: map[string]*cmp_test.CycleAlpha{
-+ "Bar": ⟪0xdeadf00f⟫,
-+ "Buzz": &{
-+ Name: "Buzz",
-+ Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫},
-+ },
-+ },
-+ },
-+ "BuzzBarBravo": ⟪0xdeadf00f⟫,
-+ },
-+ },
-+ "Buzz": ⟪0xdeadf00f⟫,
-+ },
-+ },
++ "BuzzBarBravo": &⟪ref#3⟫(...),
},
},
},
},
},
},
- "Buzz": &{
+ "Buzz": &⟪ref#2⟫{
Name: "Buzz",
Bravos: map[string]*cmp_test.CycleBravo{
- "BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}},
- "BuzzBarBravo": &{
+ "BarBuzzBravo": &⟪ref#1⟫{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: {"Bar": &⟪ref#0⟫{Name: "Bar", Bravos: {"BarBuzzBravo": &⟪ref#1⟫(...), "BuzzBarBravo": &⟪ref#3⟫{ID: 103, Name: "BuzzBarBravo", Alphas: {"Bar": &⟪ref#0⟫(...), "Buzz": &⟪ref#2⟫(...)}}}}, "Buzz": &⟪ref#2⟫(...)}},
+ "BuzzBarBravo": &⟪ref#3⟫{
ID: 103,
Name: "BuzzBarBravo",
Mods: 0,
- Alphas: nil,
+ Alphas: map[string]*cmp_test.CycleAlpha{
-+ "Bar": &{
-+ Name: "Bar",
-+ Bravos: map[string]*cmp_test.CycleBravo{
-+ "BarBuzzBravo": &{
-+ ID: 102,
-+ Name: "BarBuzzBravo",
-+ Mods: 2,
-+ Alphas: map[string]*cmp_test.CycleAlpha{
-+ "Bar": ⟪0xdeadf00f⟫,
-+ "Buzz": &{
-+ Name: "Buzz",
-+ Bravos: map[string]*cmp_test.CycleBravo{
-+ "BarBuzzBravo": ⟪0xdeadf00f⟫,
-+ "BuzzBarBravo": &{
-+ ID: 103,
-+ Name: "BuzzBarBravo",
-+ Alphas: map[string]*cmp_test.CycleAlpha(⟪0xdeadf00f⟫),
-+ },
-+ },
-+ },
-+ },
-+ },
-+ "BuzzBarBravo": ⟪0xdeadf00f⟫,
-+ },
++ "Bar": &⟪ref#0⟫{
++ Name: "Bar",
++ Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &⟪ref#1⟫{...}, "BuzzBarBravo": &⟪ref#3⟫(...)},
+ },
-+ "Buzz": ⟪0xdeadf00f⟫,
++ "Buzz": &⟪ref#2⟫(...),
+ },
},
},
},
- "Foo": &{Name: "Foo", Bravos: map[string]*cmp_test.CycleBravo{"FooBravo": &{ID: 101, Name: "FooBravo", Mods: 100, Alphas: map[string]*cmp_test.CycleAlpha{"Foo": &{Name: "Foo", Bravos: map[string]*cmp_test.CycleBravo{"FooBravo": &{ID: 101, Name: "FooBravo", Mods: 100, Alphas: map[string]*cmp_test.CycleAlpha{"Foo": ⟪0xdeadf00f⟫}}}}}}}},
+ "Foo": &⟪ref#4⟫{Name: "Foo", Bravos: {"FooBravo": &{ID: 101, Name: "FooBravo", Mods: 100, Alphas: {"Foo": &⟪ref#4⟫(...)}}}},
}
->>> TestDiff/Cycle#08
-<<< TestDiff/Project1#02
+>>> TestDiff/Cycle/GraphInequalStruct
+<<< TestDiff/Project1/ProtoInequal
teststructs.Eagle{
... // 4 identical fields
Dreamers: nil,
@@ -1051,8 +1526,8 @@
PrankRating: "",
... // 2 identical fields
}
->>> TestDiff/Project1#02
-<<< TestDiff/Project1#04
+>>> TestDiff/Project1/ProtoInequal
+<<< TestDiff/Project1/Inequal
teststructs.Eagle{
... // 2 identical fields
Desc: "some description",
@@ -1109,7 +1584,7 @@
- "bar",
- "baz",
},
- ChangeType: []testprotos.SummerType{1, 2, 3},
+ ChangeType: {1, 2, 3},
... // 1 ignored field
},
... // 1 ignored field
@@ -1124,8 +1599,8 @@
PrankRating: "",
... // 2 identical fields
}
->>> TestDiff/Project1#04
-<<< TestDiff/Project2#02
+>>> TestDiff/Project1/Inequal
+<<< TestDiff/Project2/InequalOrder
teststructs.GermBatch{
DirtyGerms: map[int32][]*testprotos.Germ{
17: {s"germ1"},
@@ -1137,11 +1612,11 @@
},
},
CleanGerms: nil,
- GermMap: map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"},
+ GermMap: {13: s"germ13", 21: s"germ21"},
... // 7 identical fields
}
->>> TestDiff/Project2#02
-<<< TestDiff/Project2#04
+>>> TestDiff/Project2/InequalOrder
+<<< TestDiff/Project2/Inequal
teststructs.GermBatch{
DirtyGerms: map[int32][]*testprotos.Germ{
+ 17: {s"germ1"},
@@ -1152,7 +1627,7 @@
}),
},
CleanGerms: nil,
- GermMap: map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"},
+ GermMap: {13: s"germ13", 21: s"germ21"},
DishMap: map[int32]*teststructs.Dish{
0: &{err: e"EOF"},
- 1: nil,
@@ -1167,8 +1642,8 @@
TotalDirtyGerms: 0,
InfectedAt: s"2009-11-10 23:00:00 +0000 UTC",
}
->>> TestDiff/Project2#04
-<<< TestDiff/Project3#03
+>>> TestDiff/Project2/Inequal
+<<< TestDiff/Project3/Inequal
teststructs.Dirt{
- table: &teststructs.MockTable{state: []string{"a", "c"}},
+ table: &teststructs.MockTable{state: []string{"a", "b", "c"}},
@@ -1186,8 +1661,8 @@
lastTime: 54321,
... // 1 ignored field
}
->>> TestDiff/Project3#03
-<<< TestDiff/Project4#03
+>>> TestDiff/Project3/Inequal
+<<< TestDiff/Project4/Inequal
teststructs.Cartel{
Headquarter: teststructs.Headquarter{
id: 5,
@@ -1228,4 +1703,4 @@
- &{poisonType: 2, manufacturer: "acme2"},
},
}
->>> TestDiff/Project4#03
+>>> TestDiff/Project4/Inequal