parent
f2ac02f446
commit
8c83122eca
@ -1,3 +0,0 @@
|
|||||||
language: go
|
|
||||||
go:
|
|
||||||
- "1.15"
|
|
@ -1,7 +1,5 @@
|
|||||||
# REST API 2.0 Discogs.com client
|
# REST API 2.0 Discogs.com client
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/irlndts/go-discogs.svg?branch=master)](https://travis-ci.org/irlndts/go-discogs)[![Go Report Card](https://goreportcard.com/badge/github.com/irlndts/go-discogs)](https://goreportcard.com/report/github.com/irlndts/go-discogs)
|
|
||||||
|
|
||||||
go-discogs is a Go client library for the [Discogs API](https://www.discogs.com/developers/). Check the usage section to see how to access the Discogs API.
|
go-discogs is a Go client library for the [Discogs API](https://www.discogs.com/developers/). Check the usage section to see how to access the Discogs API.
|
||||||
|
|
||||||
The lib is under MIT but be sure you are familiar with [Discogs API Terms of Use](https://support.discogs.com/hc/en-us/articles/360009334593-API-Terms-of-Use).
|
The lib is under MIT but be sure you are familiar with [Discogs API Terms of Use](https://support.discogs.com/hc/en-us/articles/360009334593-API-Terms-of-Use).
|
||||||
|
4
go.mod
4
go.mod
@ -1,5 +1,5 @@
|
|||||||
module github.com/irlndts/go-discogs
|
module github.com/irlndts/go-discogs
|
||||||
|
|
||||||
go 1.15
|
go 1.16
|
||||||
|
|
||||||
require github.com/google/go-cmp v0.4.1
|
require github.com/google/go-cmp v0.5.4
|
||||||
|
4
go.sum
4
go.sum
@ -1,4 +1,4 @@
|
|||||||
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
|
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
81
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
81
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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.
|
// Package cmp determines equality of values.
|
||||||
//
|
//
|
||||||
@ -90,6 +90,52 @@ import (
|
|||||||
// If there is a cycle, then the pointed at values are considered equal
|
// If there is a cycle, then the pointed at values are considered equal
|
||||||
// only if both addresses were previously visited in the same path step.
|
// only if both addresses were previously visited in the same path step.
|
||||||
func Equal(x, y interface{}, opts ...Option) bool {
|
func Equal(x, y interface{}, opts ...Option) bool {
|
||||||
|
s := newState(opts)
|
||||||
|
s.compareAny(rootStep(x, y))
|
||||||
|
return s.result.Equal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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
|
||||||
|
// 's' or 'e' character, respectively, to indicate that the method was called.
|
||||||
|
//
|
||||||
|
// Do not depend on this output being stable. If you need the ability to
|
||||||
|
// programmatically interpret the difference, consider using a custom Reporter.
|
||||||
|
func Diff(x, y interface{}, opts ...Option) string {
|
||||||
|
s := newState(opts)
|
||||||
|
|
||||||
|
// Optimization: If there are no other reporters, we can optimize for the
|
||||||
|
// common case where the result is equal (and thus no reported difference).
|
||||||
|
// This avoids the expensive construction of a difference tree.
|
||||||
|
if len(s.reporters) == 0 {
|
||||||
|
s.compareAny(rootStep(x, y))
|
||||||
|
if s.result.Equal() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
s.result = diff.Result{} // Reset results
|
||||||
|
}
|
||||||
|
|
||||||
|
r := new(defaultReporter)
|
||||||
|
s.reporters = append(s.reporters, reporter{r})
|
||||||
|
s.compareAny(rootStep(x, y))
|
||||||
|
d := r.String()
|
||||||
|
if (d == "") != s.result.Equal() {
|
||||||
|
panic("inconsistent difference and equality results")
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// rootStep constructs the first path step. If x and y have differing types,
|
||||||
|
// then they are stored within an empty interface type.
|
||||||
|
func rootStep(x, y interface{}) PathStep {
|
||||||
vx := reflect.ValueOf(x)
|
vx := reflect.ValueOf(x)
|
||||||
vy := reflect.ValueOf(y)
|
vy := reflect.ValueOf(y)
|
||||||
|
|
||||||
@ -112,33 +158,7 @@ func Equal(x, y interface{}, opts ...Option) bool {
|
|||||||
t = vx.Type()
|
t = vx.Type()
|
||||||
}
|
}
|
||||||
|
|
||||||
s := newState(opts)
|
return &pathStep{t, vx, vy}
|
||||||
s.compareAny(&pathStep{t, vx, vy})
|
|
||||||
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.
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
// 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
|
|
||||||
// 's' or 'e' character, respectively, to indicate that the method was called.
|
|
||||||
//
|
|
||||||
// Do not depend on this output being stable. If you need the ability to
|
|
||||||
// programmatically interpret the difference, consider using a custom Reporter.
|
|
||||||
func Diff(x, y interface{}, opts ...Option) string {
|
|
||||||
r := new(defaultReporter)
|
|
||||||
eq := Equal(x, y, Options(opts), Reporter(r))
|
|
||||||
d := r.String()
|
|
||||||
if (d == "") != eq {
|
|
||||||
panic("inconsistent difference and equality results")
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type state struct {
|
type state struct {
|
||||||
@ -356,7 +376,7 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
|
|||||||
// assuming that T is assignable to R.
|
// assuming that T is assignable to R.
|
||||||
// Otherwise, it returns the input value as is.
|
// Otherwise, it returns the input value as is.
|
||||||
func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
|
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 !flags.AtLeastGo110 {
|
||||||
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
|
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
|
||||||
return reflect.New(t).Elem()
|
return reflect.New(t).Elem()
|
||||||
@ -366,6 +386,7 @@ func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
||||||
|
var addr bool
|
||||||
var vax, vay reflect.Value // Addressable versions of vx and vy
|
var vax, vay reflect.Value // Addressable versions of vx and vy
|
||||||
|
|
||||||
var mayForce, mayForceInit bool
|
var mayForce, mayForceInit bool
|
||||||
@ -387,6 +408,7 @@ func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
|||||||
// For retrieveUnexportedField to work, the parent struct must
|
// For retrieveUnexportedField to work, the parent struct must
|
||||||
// be addressable. Create a new copy of the values if
|
// be addressable. Create a new copy of the values if
|
||||||
// necessary to make them addressable.
|
// necessary to make them addressable.
|
||||||
|
addr = vx.CanAddr() || vy.CanAddr()
|
||||||
vax = makeAddressable(vx)
|
vax = makeAddressable(vx)
|
||||||
vay = makeAddressable(vy)
|
vay = makeAddressable(vy)
|
||||||
}
|
}
|
||||||
@ -397,6 +419,7 @@ func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
|||||||
mayForceInit = true
|
mayForceInit = true
|
||||||
}
|
}
|
||||||
step.mayForce = mayForce
|
step.mayForce = mayForce
|
||||||
|
step.paddr = addr
|
||||||
step.pvx = vax
|
step.pvx = vax
|
||||||
step.pvy = vay
|
step.pvy = vay
|
||||||
step.field = t.Field(i)
|
step.field = t.Field(i)
|
||||||
|
4
vendor/github.com/google/go-cmp/cmp/export_panic.go
generated
vendored
4
vendor/github.com/google/go-cmp/cmp/export_panic.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
// +build purego
|
||||||
|
|
||||||
@ -10,6 +10,6 @@ import "reflect"
|
|||||||
|
|
||||||
const supportExporters = false
|
const supportExporters = false
|
||||||
|
|
||||||
func retrieveUnexportedField(reflect.Value, reflect.StructField) reflect.Value {
|
func retrieveUnexportedField(reflect.Value, reflect.StructField, bool) reflect.Value {
|
||||||
panic("no support for forcibly accessing unexported fields")
|
panic("no support for forcibly accessing unexported fields")
|
||||||
}
|
}
|
||||||
|
22
vendor/github.com/google/go-cmp/cmp/export_unsafe.go
generated
vendored
22
vendor/github.com/google/go-cmp/cmp/export_unsafe.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
// +build !purego
|
||||||
|
|
||||||
@ -17,9 +17,19 @@ const supportExporters = true
|
|||||||
// a struct such that the value has read-write permissions.
|
// a struct such that the value has read-write permissions.
|
||||||
//
|
//
|
||||||
// The parent struct, v, must be addressable, while f must be a StructField
|
// The parent struct, v, must be addressable, while f must be a StructField
|
||||||
// describing the field to retrieve.
|
// describing the field to retrieve. If addr is false,
|
||||||
func retrieveUnexportedField(v reflect.Value, f reflect.StructField) reflect.Value {
|
// then the returned value will be shallowed copied to be non-addressable.
|
||||||
// See https://github.com/google/go-cmp/issues/167 for discussion of the
|
func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value {
|
||||||
// following expression.
|
ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
|
||||||
return reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
|
if !addr {
|
||||||
|
// A field is addressable if and only if the struct is addressable.
|
||||||
|
// If the original parent value was not addressable, shallow copy the
|
||||||
|
// value to make it non-addressable to avoid leaking an implementation
|
||||||
|
// detail of how forcibly exporting a field works.
|
||||||
|
if ve.Kind() == reflect.Interface && ve.IsNil() {
|
||||||
|
return reflect.Zero(f.Type)
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(ve.Interface()).Convert(f.Type)
|
||||||
|
}
|
||||||
|
return ve
|
||||||
}
|
}
|
||||||
|
2
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
2
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
// +build !cmp_debug
|
||||||
|
|
||||||
|
2
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
2
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
// +build cmp_debug
|
||||||
|
|
||||||
|
44
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
44
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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.
|
// Package diff implements an algorithm for producing edit-scripts.
|
||||||
// The edit-script is a sequence of operations needed to transform one list
|
// 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.
|
// is more important than obtaining a minimal Levenshtein distance.
|
||||||
package diff
|
package diff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/flags"
|
||||||
|
)
|
||||||
|
|
||||||
// EditType represents a single operation within an edit-script.
|
// EditType represents a single operation within an edit-script.
|
||||||
type EditType uint8
|
type EditType uint8
|
||||||
|
|
||||||
@ -112,6 +119,8 @@ func (r Result) Similar() bool {
|
|||||||
return r.NumSame+1 >= r.NumDiff
|
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
|
// Difference reports whether two lists of lengths nx and ny are equal
|
||||||
// given the definition of equality provided as f.
|
// 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.
|
// approximately the square-root of the search budget.
|
||||||
searchBudget := 4 * (nx + ny) // O(n)
|
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
|
// The algorithm below is a greedy, meet-in-the-middle algorithm for
|
||||||
// computing sub-optimal edit-scripts between two lists.
|
// 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.
|
// frontier towards the opposite corner.
|
||||||
// • This algorithm terminates when either the X coordinates or the
|
// • This algorithm terminates when either the X coordinates or the
|
||||||
// Y coordinates of the forward and reverse frontier points ever intersect.
|
// Y coordinates of the forward and reverse frontier points ever intersect.
|
||||||
//
|
|
||||||
// This algorithm is correct even if searching only in the forward direction
|
// 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
|
// 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
|
// that two lists commonly differ because elements were added to the front
|
||||||
// or end of the other list.
|
// or end of the other list.
|
||||||
//
|
//
|
||||||
// Running the tests with the "cmp_debug" build tag prints a visualization
|
// Non-deterministically start with either the forward or reverse direction
|
||||||
// of the algorithm running in real-time. This is educational for
|
// to introduce some deliberate instability so that we have the flexibility
|
||||||
// understanding how the algorithm works. See debug_enable.go.
|
// to change this algorithm in the future.
|
||||||
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
|
if flags.Deterministic || randBool {
|
||||||
for {
|
goto forwardSearch
|
||||||
|
} else {
|
||||||
|
goto reverseSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardSearch:
|
||||||
|
{
|
||||||
// Forward search from the beginning.
|
// Forward search from the beginning.
|
||||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
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++ {
|
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||||
// Search in a diagonal pattern for a match.
|
// Search in a diagonal pattern for a match.
|
||||||
@ -242,10 +262,14 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
|
|||||||
} else {
|
} else {
|
||||||
fwdFrontier.Y++
|
fwdFrontier.Y++
|
||||||
}
|
}
|
||||||
|
goto reverseSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseSearch:
|
||||||
|
{
|
||||||
// Reverse search from the end.
|
// Reverse search from the end.
|
||||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
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++ {
|
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||||
// Search in a diagonal pattern for a match.
|
// Search in a diagonal pattern for a match.
|
||||||
@ -280,8 +304,10 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
|
|||||||
} else {
|
} else {
|
||||||
revFrontier.Y--
|
revFrontier.Y--
|
||||||
}
|
}
|
||||||
|
goto forwardSearch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finishSearch:
|
||||||
// Join the forward and reverse paths and then append the reverse path.
|
// Join the forward and reverse paths and then append the reverse path.
|
||||||
fwdPath.connect(revPath.point, f)
|
fwdPath.connect(revPath.point, f)
|
||||||
for i := len(revPath.es) - 1; i >= 0; i-- {
|
for i := len(revPath.es) - 1; i >= 0; i-- {
|
||||||
|
2
vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go
generated
vendored
2
vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2019, The Go Authors. All rights reserved.
|
// Copyright 2019, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package flags
|
||||||
|
|
||||||
|
2
vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go
generated
vendored
2
vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2019, The Go Authors. All rights reserved.
|
// Copyright 2019, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
// +build !go1.10
|
||||||
|
|
||||||
|
2
vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go
generated
vendored
2
vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2019, The Go Authors. All rights reserved.
|
// Copyright 2019, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
// +build go1.10
|
||||||
|
|
||||||
|
2
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
2
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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 provides functionality for identifying function types.
|
||||||
package function
|
package function
|
||||||
|
157
vendor/github.com/google/go-cmp/cmp/internal/value/name.go
generated
vendored
Normal file
157
vendor/github.com/google/go-cmp/cmp/internal/value/name.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
12
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go
generated
vendored
12
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2018, The Go Authors. All rights reserved.
|
// Copyright 2018, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
// +build purego
|
||||||
|
|
||||||
@ -21,3 +21,13 @@ func PointerOf(v reflect.Value) Pointer {
|
|||||||
// assumes that the GC implementation does not use a moving collector.
|
// assumes that the GC implementation does not use a moving collector.
|
||||||
return Pointer{v.Pointer(), v.Type()}
|
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
|
||||||
|
}
|
||||||
|
12
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go
generated
vendored
12
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2018, The Go Authors. All rights reserved.
|
// Copyright 2018, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
// +build !purego
|
||||||
|
|
||||||
@ -24,3 +24,13 @@ func PointerOf(v reflect.Value) Pointer {
|
|||||||
// which is necessary if the GC ever uses a moving collector.
|
// which is necessary if the GC ever uses a moving collector.
|
||||||
return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
|
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)
|
||||||
|
}
|
||||||
|
2
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
2
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package value
|
||||||
|
|
||||||
|
2
vendor/github.com/google/go-cmp/cmp/internal/value/zero.go
generated
vendored
2
vendor/github.com/google/go-cmp/cmp/internal/value/zero.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package value
|
||||||
|
|
||||||
|
7
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
7
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package cmp
|
||||||
|
|
||||||
@ -225,11 +225,14 @@ func (validator) apply(s *state, vx, vy reflect.Value) {
|
|||||||
|
|
||||||
// Unable to Interface implies unexported field without visibility access.
|
// Unable to Interface implies unexported field without visibility access.
|
||||||
if !vx.CanInterface() || !vy.CanInterface() {
|
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
|
var name string
|
||||||
if t := s.curPath.Index(-2).Type(); t.Name() != "" {
|
if t := s.curPath.Index(-2).Type(); t.Name() != "" {
|
||||||
// Named type with unexported fields.
|
// Named type with unexported fields.
|
||||||
name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
|
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 {
|
} else {
|
||||||
// Unnamed type with unexported fields. Derive PkgPath from field.
|
// Unnamed type with unexported fields. Derive PkgPath from field.
|
||||||
var pkgPath string
|
var pkgPath string
|
||||||
|
9
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
9
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package cmp
|
||||||
|
|
||||||
@ -177,7 +177,8 @@ type structField struct {
|
|||||||
// pvx, pvy, and field are only valid if unexported is true.
|
// pvx, pvy, and field are only valid if unexported is true.
|
||||||
unexported bool
|
unexported bool
|
||||||
mayForce bool // Forcibly allow visibility
|
mayForce bool // Forcibly allow visibility
|
||||||
pvx, pvy reflect.Value // Parent values
|
paddr bool // Was parent addressable?
|
||||||
|
pvx, pvy reflect.Value // Parent values (always addressible)
|
||||||
field reflect.StructField // Field information
|
field reflect.StructField // Field information
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,8 +190,8 @@ func (sf StructField) Values() (vx, vy reflect.Value) {
|
|||||||
|
|
||||||
// Forcibly obtain read-write access to an unexported struct field.
|
// Forcibly obtain read-write access to an unexported struct field.
|
||||||
if sf.mayForce {
|
if sf.mayForce {
|
||||||
vx = retrieveUnexportedField(sf.pvx, sf.field)
|
vx = retrieveUnexportedField(sf.pvx, sf.field, sf.paddr)
|
||||||
vy = retrieveUnexportedField(sf.pvy, sf.field)
|
vy = retrieveUnexportedField(sf.pvy, sf.field, sf.paddr)
|
||||||
return vx, vy // CanInterface reports true
|
return vx, vy // CanInterface reports true
|
||||||
}
|
}
|
||||||
return sf.vx, sf.vy // CanInterface reports false
|
return sf.vx, sf.vy // CanInterface reports false
|
||||||
|
7
vendor/github.com/google/go-cmp/cmp/report.go
generated
vendored
7
vendor/github.com/google/go-cmp/cmp/report.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2017, The Go Authors. All rights reserved.
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package cmp
|
||||||
|
|
||||||
@ -41,7 +41,10 @@ func (r *defaultReporter) String() string {
|
|||||||
if r.root.NumDiff == 0 {
|
if r.root.NumDiff == 0 {
|
||||||
return ""
|
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) {
|
func assert(ok bool) {
|
||||||
|
201
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
201
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2019, The Go Authors. All rights reserved.
|
// Copyright 2019, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package cmp
|
||||||
|
|
||||||
@ -11,14 +11,6 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp/internal/value"
|
"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.
|
// numContextRecords is the number of surrounding equal records to print.
|
||||||
const numContextRecords = 2
|
const numContextRecords = 2
|
||||||
|
|
||||||
@ -71,19 +63,56 @@ func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
|
|||||||
opts.TypeMode = t
|
opts.TypeMode = t
|
||||||
return opts
|
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 = 3
|
||||||
|
|
||||||
|
// 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
|
// FormatDiff converts a valueNode tree into a textNode tree, where the later
|
||||||
// is a textual representation of the differences detected in the former.
|
// 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 {
|
||||||
|
opts = opts.WithVerbosity(3)
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether we have specialized formatting for this node.
|
// Check whether we have specialized formatting for this node.
|
||||||
// This is not necessary, but helpful for producing more readable outputs.
|
// This is not necessary, but helpful for producing more readable outputs.
|
||||||
if opts.CanFormatDiffSlice(v) {
|
if opts.CanFormatDiffSlice(v) {
|
||||||
return opts.FormatDiffSlice(v)
|
return opts.FormatDiffSlice(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
var withinSlice bool
|
var parentKind reflect.Kind
|
||||||
if v.parent != nil && (v.parent.Type.Kind() == reflect.Slice || v.parent.Type.Kind() == reflect.Array) {
|
if v.parent != nil && v.parent.TransformerName == "" {
|
||||||
withinSlice = true
|
parentKind = v.parent.Type.Kind()
|
||||||
}
|
}
|
||||||
|
|
||||||
// For leaf nodes, format the value based on the reflect.Values alone.
|
// 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:
|
case diffUnknown, diffIdentical:
|
||||||
// Format Equal.
|
// Format Equal.
|
||||||
if v.NumDiff == 0 {
|
if v.NumDiff == 0 {
|
||||||
outx := opts.FormatValue(v.ValueX, withinSlice, visitedPointers{})
|
outx := opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||||
outy := opts.FormatValue(v.ValueY, withinSlice, visitedPointers{})
|
outy := opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||||
if v.NumIgnored > 0 && v.NumSame == 0 {
|
if v.NumIgnored > 0 && v.NumSame == 0 {
|
||||||
return textEllipsis
|
return textEllipsis
|
||||||
} else if outx.Len() < outy.Len() {
|
} else if outx.Len() < outy.Len() {
|
||||||
@ -106,8 +135,13 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode {
|
|||||||
// Format unequal.
|
// Format unequal.
|
||||||
assert(opts.DiffMode == diffUnknown)
|
assert(opts.DiffMode == diffUnknown)
|
||||||
var list textList
|
var list textList
|
||||||
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, withinSlice, visitedPointers{})
|
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs)
|
||||||
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, withinSlice, visitedPointers{})
|
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 {
|
if outx != nil {
|
||||||
list = append(list, textRecord{Diff: '-', Value: outx})
|
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)
|
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
|
||||||
case diffRemoved:
|
case diffRemoved:
|
||||||
return opts.FormatValue(v.ValueX, withinSlice, visitedPointers{})
|
return opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||||
case diffInserted:
|
case diffInserted:
|
||||||
return opts.FormatValue(v.ValueY, withinSlice, visitedPointers{})
|
return opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||||
default:
|
default:
|
||||||
panic("invalid diff mode")
|
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.
|
// Descend into the child value node.
|
||||||
if v.TransformerName != "" {
|
if v.TransformerName != "" {
|
||||||
out := opts.WithTypeMode(emitType).FormatDiff(v.Value)
|
out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||||
out = textWrap{"Inverse(" + v.TransformerName + ", ", out, ")"}
|
out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"}
|
||||||
return opts.FormatType(v.Type, out)
|
return opts.FormatType(v.Type, out)
|
||||||
} else {
|
} else {
|
||||||
switch k := v.Type.Kind(); k {
|
switch k := v.Type.Kind(); k {
|
||||||
case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map:
|
case reflect.Struct, reflect.Array, reflect.Slice:
|
||||||
return opts.FormatType(v.Type, opts.formatDiffList(v.Records, k))
|
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:
|
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:
|
case reflect.Interface:
|
||||||
return opts.WithTypeMode(emitType).FormatDiff(v.Value)
|
out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("%v cannot have children", k))
|
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.
|
// Derive record name based on the data structure kind.
|
||||||
var name string
|
var name string
|
||||||
var formatKey func(reflect.Value) string
|
var formatKey func(reflect.Value) string
|
||||||
@ -159,7 +216,17 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
|
|||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
name = "entry"
|
name = "entry"
|
||||||
opts = opts.WithTypeMode(elideType)
|
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.
|
// Handle unification.
|
||||||
@ -168,6 +235,11 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
|
|||||||
var list textList
|
var list textList
|
||||||
var deferredEllipsis bool // Add final "..." to indicate records were dropped
|
var deferredEllipsis bool // Add final "..." to indicate records were dropped
|
||||||
for _, r := range recs {
|
for _, r := range recs {
|
||||||
|
if len(list) == maxLen {
|
||||||
|
deferredEllipsis = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
// Elide struct fields that are zero value.
|
// Elide struct fields that are zero value.
|
||||||
if k == reflect.Struct {
|
if k == reflect.Struct {
|
||||||
var isZero bool
|
var isZero bool
|
||||||
@ -191,23 +263,31 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
|
|||||||
}
|
}
|
||||||
continue
|
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})
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if deferredEllipsis {
|
if deferredEllipsis {
|
||||||
list.AppendEllipsis(diffStats{})
|
list.AppendEllipsis(diffStats{})
|
||||||
}
|
}
|
||||||
return textWrap{"{", list, "}"}
|
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
case diffUnknown:
|
case diffUnknown:
|
||||||
default:
|
default:
|
||||||
panic("invalid diff mode")
|
panic("invalid diff mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle differencing.
|
// Handle differencing.
|
||||||
|
var numDiffs int
|
||||||
var list textList
|
var list textList
|
||||||
|
var keys []reflect.Value // invariant: len(list) == len(keys)
|
||||||
groups := coalesceAdjacentRecords(name, recs)
|
groups := coalesceAdjacentRecords(name, recs)
|
||||||
|
maxGroup := diffStats{Name: name}
|
||||||
for i, ds := range groups {
|
for i, ds := range groups {
|
||||||
|
if maxLen >= 0 && numDiffs >= maxLen {
|
||||||
|
maxGroup = maxGroup.Append(ds)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Handle equal records.
|
// Handle equal records.
|
||||||
if ds.NumDiff() == 0 {
|
if ds.NumDiff() == 0 {
|
||||||
// Compute the number of leading and trailing records to print.
|
// 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.
|
// Format the equal values.
|
||||||
for _, r := range recs[:numLo] {
|
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})
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
}
|
}
|
||||||
if numEqual > numLo+numHi {
|
if numEqual > numLo+numHi {
|
||||||
ds.NumIdentical -= numLo + numHi
|
ds.NumIdentical -= numLo + numHi
|
||||||
list.AppendEllipsis(ds)
|
list.AppendEllipsis(ds)
|
||||||
|
for len(keys) < len(list) {
|
||||||
|
keys = append(keys, reflect.Value{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, r := range recs[numEqual-numHi : numEqual] {
|
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})
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
}
|
}
|
||||||
recs = recs[numEqual:]
|
recs = recs[numEqual:]
|
||||||
continue
|
continue
|
||||||
@ -252,24 +337,70 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te
|
|||||||
case opts.CanFormatDiffSlice(r.Value):
|
case opts.CanFormatDiffSlice(r.Value):
|
||||||
out := opts.FormatDiffSlice(r.Value)
|
out := opts.FormatDiffSlice(r.Value)
|
||||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
case r.Value.NumChildren == r.Value.MaxDepth:
|
case r.Value.NumChildren == r.Value.MaxDepth:
|
||||||
outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value)
|
outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
||||||
outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value)
|
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 {
|
if outx != nil {
|
||||||
list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
|
list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
}
|
}
|
||||||
if outy != nil {
|
if outy != nil {
|
||||||
list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
|
list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
out := opts.FormatDiff(r.Value)
|
out := opts.FormatDiff(r.Value, ptrs)
|
||||||
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recs = recs[ds.NumDiff():]
|
recs = recs[ds.NumDiff():]
|
||||||
|
numDiffs += ds.NumDiff()
|
||||||
}
|
}
|
||||||
|
if maxGroup.IsZero() {
|
||||||
assert(len(recs) == 0)
|
assert(len(recs) == 0)
|
||||||
return textWrap{"{", list, "}"}
|
} 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// coalesceAdjacentRecords coalesces the list of records into groups of
|
// coalesceAdjacentRecords coalesces the list of records into groups of
|
||||||
|
264
vendor/github.com/google/go-cmp/cmp/report_references.go
generated
vendored
Normal file
264
vendor/github.com/google/go-cmp/cmp/report_references.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
285
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
285
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
@ -1,17 +1,18 @@
|
|||||||
// Copyright 2019, The Go Authors. All rights reserved.
|
// Copyright 2019, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package cmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp/internal/flags"
|
|
||||||
"github.com/google/go-cmp/cmp/internal/value"
|
"github.com/google/go-cmp/cmp/internal/value"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,14 +21,22 @@ type formatValueOptions struct {
|
|||||||
// methods like error.Error or fmt.Stringer.String.
|
// methods like error.Error or fmt.Stringer.String.
|
||||||
AvoidStringer bool
|
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,
|
// PrintAddresses controls whether to print the address of all pointers,
|
||||||
// slice elements, and maps.
|
// slice elements, and maps.
|
||||||
PrintAddresses bool
|
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.
|
// 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:
|
default:
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
if opts.DiffMode == diffIdentical {
|
||||||
|
return s // elide type for identical nodes
|
||||||
|
}
|
||||||
case elideType:
|
case elideType:
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the type label, applying special handling for unnamed types.
|
// Determine the type label, applying special handling for unnamed types.
|
||||||
typeName := t.String()
|
typeName := value.TypeString(t, opts.QualifiedNames)
|
||||||
if t.Name() == "" {
|
if t.Name() == "" {
|
||||||
// According to Go grammar, certain type literals contain symbols that
|
// According to Go grammar, certain type literals contain symbols that
|
||||||
// do not strongly bind to the next lexicographical token (e.g., *T).
|
// 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:
|
case reflect.Chan, reflect.Func, reflect.Ptr:
|
||||||
typeName = "(" + typeName + ")"
|
typeName = "(" + typeName + ")"
|
||||||
}
|
}
|
||||||
typeName = strings.Replace(typeName, "struct {", "struct{", -1)
|
}
|
||||||
typeName = strings.Replace(typeName, "interface {", "interface{", -1)
|
return &textWrap{Prefix: typeName, Value: wrapParens(s)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid wrap the value in parenthesis if unnecessary.
|
// Already has delimiters that make parenthesis unnecessary.
|
||||||
if s, ok := s.(textWrap); ok {
|
hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
|
||||||
hasParens := strings.HasPrefix(s.Prefix, "(") && strings.HasSuffix(s.Suffix, ")")
|
hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
|
||||||
hasBraces := strings.HasPrefix(s.Prefix, "{") && strings.HasSuffix(s.Suffix, "}")
|
|
||||||
if hasParens || hasBraces {
|
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
|
// FormatValue prints the reflect.Value, taking extra care to avoid descending
|
||||||
// into pointers already in m. As pointers are visited, m is also updated.
|
// into pointers already in ptrs. As pointers are visited, ptrs is also updated.
|
||||||
func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visitedPointers) (out textNode) {
|
func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
|
||||||
if !v.IsValid() {
|
if !v.IsValid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
t := v.Type()
|
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.
|
// Check whether there is an Error or String method to call.
|
||||||
if !opts.AvoidStringer && v.CanInterface() {
|
if !opts.AvoidStringer && v.CanInterface() {
|
||||||
// Avoid calling Error or String methods on nil receivers since many
|
// Avoid calling Error or String methods on nil receivers since many
|
||||||
// implementations crash when doing so.
|
// implementations crash when doing so.
|
||||||
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
|
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
|
||||||
|
var prefix, strVal string
|
||||||
|
func() {
|
||||||
|
// Swallow and ignore any panics from String or Error.
|
||||||
|
defer func() { recover() }()
|
||||||
switch v := v.Interface().(type) {
|
switch v := v.Interface().(type) {
|
||||||
case error:
|
case error:
|
||||||
return textLine("e" + formatString(v.Error()))
|
strVal = v.Error()
|
||||||
|
prefix = "e"
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
return textLine("s" + formatString(v.String()))
|
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() {
|
switch t.Kind() {
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return textLine(fmt.Sprint(v.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:
|
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
return textLine(fmt.Sprint(v.Uint()))
|
return textLine(fmt.Sprint(v.Uint()))
|
||||||
case reflect.Uint8:
|
case reflect.Uint8:
|
||||||
if withinSlice {
|
if parentKind == reflect.Slice || parentKind == reflect.Array {
|
||||||
return textLine(formatHex(v.Uint()))
|
return textLine(formatHex(v.Uint()))
|
||||||
}
|
}
|
||||||
return textLine(fmt.Sprint(v.Uint()))
|
return textLine(fmt.Sprint(v.Uint()))
|
||||||
@ -122,77 +171,121 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit
|
|||||||
case reflect.Complex64, reflect.Complex128:
|
case reflect.Complex64, reflect.Complex128:
|
||||||
return textLine(fmt.Sprint(v.Complex()))
|
return textLine(fmt.Sprint(v.Complex()))
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return textLine(formatString(v.String()))
|
return opts.formatString("", v.String())
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
return textLine(formatPointer(v))
|
return textLine(formatPointer(value.PointerOf(v), true))
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
var list textList
|
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++ {
|
for i := 0; i < v.NumField(); i++ {
|
||||||
vv := v.Field(i)
|
vv := v.Field(i)
|
||||||
if value.IsZero(vv) {
|
if value.IsZero(vv) {
|
||||||
continue // Elide fields with zero values
|
continue // Elide fields with zero values
|
||||||
}
|
}
|
||||||
s := opts.WithTypeMode(autoType).FormatValue(vv, false, m)
|
if len(list) == maxLen {
|
||||||
list = append(list, textRecord{Key: t.Field(i).Name, Value: s})
|
list.AppendEllipsis(diffStats{})
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return textWrap{"{", list, "}"}
|
sf := t.Field(i)
|
||||||
|
if supportExporters && !isExported(sf.Name) {
|
||||||
|
vv = retrieveUnexportedField(v, sf, true)
|
||||||
|
}
|
||||||
|
s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
|
||||||
|
list = append(list, textRecord{Key: sf.Name, Value: s})
|
||||||
|
}
|
||||||
|
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
return textNil
|
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
|
fallthrough
|
||||||
case reflect.Array:
|
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
|
var list textList
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
vi := v.Index(i)
|
if len(list) == maxLen {
|
||||||
if vi.CanAddr() { // Check for cyclic elements
|
list.AppendEllipsis(diffStats{})
|
||||||
p := vi.Addr()
|
break
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
|
||||||
s := opts.WithTypeMode(elideType).FormatValue(vi, true, m)
|
|
||||||
list = append(list, textRecord{Value: s})
|
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:
|
case reflect.Map:
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
return textNil
|
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
|
var list textList
|
||||||
for _, k := range value.SortKeys(v.MapKeys()) {
|
for _, k := range value.SortKeys(v.MapKeys()) {
|
||||||
sk := formatMapKey(k)
|
if len(list) == maxLen {
|
||||||
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), false, m)
|
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})
|
list = append(list, textRecord{Key: sk, Value: sv})
|
||||||
}
|
}
|
||||||
if opts.PrintAddresses {
|
|
||||||
ptr = formatPointer(v)
|
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
}
|
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
|
||||||
return textWrap{ptr + "{", list, "}"}
|
return out
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
return textNil
|
return textNil
|
||||||
}
|
}
|
||||||
if m.Visit(v) || opts.ShallowPointers {
|
|
||||||
return textLine(formatPointer(v))
|
// Check pointer for cycles.
|
||||||
}
|
ptrRef, visited := ptrs.Push(v)
|
||||||
if opts.PrintAddresses {
|
if visited {
|
||||||
ptr = formatPointer(v)
|
out = makeLeafReference(ptrRef, opts.PrintAddresses)
|
||||||
|
return &textWrap{Prefix: "&", Value: out}
|
||||||
}
|
}
|
||||||
|
defer ptrs.Pop()
|
||||||
|
|
||||||
skipType = true // Let the underlying value print the type instead
|
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:
|
case reflect.Interface:
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
return textNil
|
return textNil
|
||||||
@ -200,19 +293,67 @@ func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visit
|
|||||||
// Interfaces accept different concrete types,
|
// Interfaces accept different concrete types,
|
||||||
// so configure the underlying value to explicitly print the type.
|
// so configure the underlying value to explicitly print the type.
|
||||||
skipType = true // Print the concrete type instead
|
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:
|
default:
|
||||||
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
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.
|
// formatMapKey formats v as if it were a map key.
|
||||||
// The result is guaranteed to be a single line.
|
// 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
|
var opts formatOptions
|
||||||
|
opts.DiffMode = diffIdentical
|
||||||
opts.TypeMode = elideType
|
opts.TypeMode = elideType
|
||||||
opts.ShallowPointers = true
|
opts.PrintAddresses = disambiguate
|
||||||
s := opts.FormatValue(v, false, visitedPointers{}).String()
|
opts.AvoidStringer = disambiguate
|
||||||
|
opts.QualifiedNames = disambiguate
|
||||||
|
opts.VerbosityLevel = maxVerbosityPreset
|
||||||
|
opts.LimitVerbosity = true
|
||||||
|
s := opts.FormatValue(v, reflect.Map, ptrs).String()
|
||||||
return strings.TrimSpace(s)
|
return strings.TrimSpace(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +371,7 @@ func formatString(s string) string {
|
|||||||
rawInvalid := func(r rune) bool {
|
rawInvalid := func(r rune) bool {
|
||||||
return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
|
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 "`" + s + "`"
|
||||||
}
|
}
|
||||||
return qs
|
return qs
|
||||||
@ -259,23 +400,3 @@ func formatHex(u uint64) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf(f, u)
|
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
|
|
||||||
}
|
|
||||||
|
131
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
131
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2019, The Go Authors. All rights reserved.
|
// Copyright 2019, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package cmp
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@ -23,11 +24,25 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
|
|||||||
return false // Must be formatting in diff mode
|
return false // Must be formatting in diff mode
|
||||||
case v.NumDiff == 0:
|
case v.NumDiff == 0:
|
||||||
return false // No differences detected
|
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():
|
case !v.ValueX.IsValid() || !v.ValueY.IsValid():
|
||||||
return false // Both values must be valid
|
return false // Both values must be valid
|
||||||
|
case v.Type.Kind() == reflect.Slice && (v.ValueX.Len() == 0 || v.ValueY.Len() == 0):
|
||||||
|
return false // Both slice values have to be non-empty
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := v.Type; t.Kind() {
|
switch t := v.Type; t.Kind() {
|
||||||
@ -82,7 +97,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||||||
}
|
}
|
||||||
if isText || isBinary {
|
if isText || isBinary {
|
||||||
var numLines, lastLineIdx, maxLineLen int
|
var numLines, lastLineIdx, maxLineLen int
|
||||||
isBinary = false
|
isBinary = !utf8.ValidString(sx) || !utf8.ValidString(sy)
|
||||||
for i, r := range sx + sy {
|
for i, r := range sx + sy {
|
||||||
if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError {
|
if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError {
|
||||||
isBinary = true
|
isBinary = true
|
||||||
@ -97,7 +112,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
isText = !isBinary
|
isText = !isBinary
|
||||||
isLinedText = isText && numLines >= 4 && maxLineLen <= 256
|
isLinedText = isText && numLines >= 4 && maxLineLen <= 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format the string into printable records.
|
// Format the string into printable records.
|
||||||
@ -117,6 +132,83 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
delim = "\n"
|
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,
|
// If the text appears to be single-lined text,
|
||||||
// then perform differencing in approximately fixed-sized chunks.
|
// then perform differencing in approximately fixed-sized chunks.
|
||||||
// The output is printed as quoted strings.
|
// The output is printed as quoted strings.
|
||||||
@ -129,6 +221,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
delim = ""
|
delim = ""
|
||||||
|
|
||||||
// If the text appears to be binary data,
|
// If the text appears to be binary data,
|
||||||
// then perform differencing in approximately fixed-sized chunks.
|
// then perform differencing in approximately fixed-sized chunks.
|
||||||
// The output is inspired by hexdump.
|
// The output is inspired by hexdump.
|
||||||
@ -145,6 +238,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||||||
return textRecord{Diff: d, Value: textLine(s), Comment: comment}
|
return textRecord{Diff: d, Value: textLine(s), Comment: comment}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// For all other slices of primitive types,
|
// For all other slices of primitive types,
|
||||||
// then perform differencing in approximately fixed-sized chunks.
|
// then perform differencing in approximately fixed-sized chunks.
|
||||||
// The size of each chunk depends on the width of the element kind.
|
// The size of each chunk depends on the width of the element kind.
|
||||||
@ -187,7 +281,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wrap the output with appropriate type information.
|
// Wrap the output with appropriate type information.
|
||||||
var out textNode = textWrap{"{", list, "}"}
|
var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
if !isText {
|
if !isText {
|
||||||
// The "{...}" byte-sequence literal is not valid Go syntax for strings.
|
// The "{...}" byte-sequence literal is not valid Go syntax for strings.
|
||||||
// Emit the type for extra clarity (e.g. "string{...}").
|
// Emit the type for extra clarity (e.g. "string{...}").
|
||||||
@ -198,12 +292,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
|||||||
}
|
}
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case reflect.String:
|
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("")) {
|
if t != reflect.TypeOf(string("")) {
|
||||||
out = opts.FormatType(t, out)
|
out = opts.FormatType(t, out)
|
||||||
}
|
}
|
||||||
case reflect.Slice:
|
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)) {
|
if t != reflect.TypeOf([]byte(nil)) {
|
||||||
out = opts.FormatType(t, out)
|
out = opts.FormatType(t, out)
|
||||||
}
|
}
|
||||||
@ -244,9 +338,22 @@ func (opts formatOptions) formatDiffSlice(
|
|||||||
return n0 - v.Len()
|
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 := coalesceAdjacentEdits(name, es)
|
||||||
groups = coalesceInterveningIdentical(groups, chunkSize/4)
|
groups = coalesceInterveningIdentical(groups, chunkSize/4)
|
||||||
|
maxGroup := diffStats{Name: name}
|
||||||
for i, ds := range groups {
|
for i, ds := range groups {
|
||||||
|
if maxLen >= 0 && numDiffs >= maxLen {
|
||||||
|
maxGroup = maxGroup.Append(ds)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Print equal.
|
// Print equal.
|
||||||
if ds.NumDiff() == 0 {
|
if ds.NumDiff() == 0 {
|
||||||
// Compute the number of leading and trailing equal bytes to print.
|
// Compute the number of leading and trailing equal bytes to print.
|
||||||
@ -275,12 +382,18 @@ func (opts formatOptions) formatDiffSlice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print unequal.
|
// Print unequal.
|
||||||
|
len0 := len(list)
|
||||||
nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved)
|
nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved)
|
||||||
vx = vx.Slice(nx, vx.Len())
|
vx = vx.Slice(nx, vx.Len())
|
||||||
ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted)
|
ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted)
|
||||||
vy = vy.Slice(ny, vy.Len())
|
vy = vy.Slice(ny, vy.Len())
|
||||||
|
numDiffs += len(list) - len0
|
||||||
}
|
}
|
||||||
|
if maxGroup.IsZero() {
|
||||||
assert(vx.Len() == 0 && vy.Len() == 0)
|
assert(vx.Len() == 0 && vy.Len() == 0)
|
||||||
|
} else {
|
||||||
|
list.AppendEllipsis(maxGroup)
|
||||||
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
74
vendor/github.com/google/go-cmp/cmp/report_text.go
generated
vendored
74
vendor/github.com/google/go-cmp/cmp/report_text.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2019, The Go Authors. All rights reserved.
|
// Copyright 2019, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package cmp
|
||||||
|
|
||||||
@ -10,12 +10,15 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp/internal/flags"
|
"github.com/google/go-cmp/cmp/internal/flags"
|
||||||
)
|
)
|
||||||
|
|
||||||
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
||||||
|
|
||||||
|
const maxColumnLength = 80
|
||||||
|
|
||||||
type indentMode int
|
type indentMode int
|
||||||
|
|
||||||
func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
|
func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
|
||||||
@ -94,18 +97,19 @@ type textWrap struct {
|
|||||||
Prefix string // e.g., "bytes.Buffer{"
|
Prefix string // e.g., "bytes.Buffer{"
|
||||||
Value textNode // textWrap | textList | textLine
|
Value textNode // textWrap | textList | textLine
|
||||||
Suffix string // e.g., "}"
|
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)
|
return len(s.Prefix) + s.Value.Len() + len(s.Suffix)
|
||||||
}
|
}
|
||||||
func (s1 textWrap) Equal(s2 textNode) bool {
|
func (s1 *textWrap) Equal(s2 textNode) bool {
|
||||||
if s2, ok := s2.(textWrap); ok {
|
if s2, ok := s2.(*textWrap); ok {
|
||||||
return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
|
return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (s textWrap) String() string {
|
func (s *textWrap) String() string {
|
||||||
var d diffMode
|
var d diffMode
|
||||||
var n indentMode
|
var n indentMode
|
||||||
_, s2 := s.formatCompactTo(nil, d)
|
_, s2 := s.formatCompactTo(nil, d)
|
||||||
@ -114,7 +118,7 @@ func (s textWrap) String() string {
|
|||||||
b = append(b, '\n') // Trailing newline
|
b = append(b, '\n') // Trailing newline
|
||||||
return string(b)
|
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
|
n0 := len(b) // Original buffer length
|
||||||
b = append(b, s.Prefix...)
|
b = append(b, s.Prefix...)
|
||||||
b, s.Value = s.Value.formatCompactTo(b, d)
|
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
|
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 = append(b, s.Prefix...)
|
||||||
b = s.Value.formatExpandedTo(b, d, n)
|
b = s.Value.formatExpandedTo(b, d, n)
|
||||||
b = append(b, s.Suffix...)
|
b = append(b, s.Suffix...)
|
||||||
@ -139,6 +143,7 @@ type textRecord struct {
|
|||||||
Diff diffMode // e.g., 0 or '-' or '+'
|
Diff diffMode // e.g., 0 or '-' or '+'
|
||||||
Key string // e.g., "MyField"
|
Key string // e.g., "MyField"
|
||||||
Value textNode // textWrap | textLine
|
Value textNode // textWrap | textLine
|
||||||
|
ElideComma bool // avoid trailing comma
|
||||||
Comment fmt.Stringer // e.g., "6 identical fields"
|
Comment fmt.Stringer // e.g., "6 identical fields"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,12 +151,12 @@ type textRecord struct {
|
|||||||
// exists at the end. If cs is non-zero it coalesces the statistics with the
|
// exists at the end. If cs is non-zero it coalesces the statistics with the
|
||||||
// previous diffStats.
|
// previous diffStats.
|
||||||
func (s *textList) AppendEllipsis(ds 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 len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) {
|
||||||
if hasStats {
|
if hasStats {
|
||||||
*s = append(*s, textRecord{Value: textEllipsis, Comment: ds})
|
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true, Comment: ds})
|
||||||
} else {
|
} else {
|
||||||
*s = append(*s, textRecord{Value: textEllipsis})
|
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -191,7 +196,7 @@ func (s1 textList) Equal(s2 textNode) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s textList) String() string {
|
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) {
|
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
|
// Force multi-lined output when printing a removed/inserted node that
|
||||||
// is sufficiently long.
|
// is sufficiently long.
|
||||||
if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > 80 {
|
if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > maxColumnLength {
|
||||||
multiLine = true
|
multiLine = true
|
||||||
}
|
}
|
||||||
if !multiLine {
|
if !multiLine {
|
||||||
@ -236,16 +241,50 @@ func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
|||||||
_, isLine := r.Value.(textLine)
|
_, isLine := r.Value.(textLine)
|
||||||
return r.Key == "" || !isLine
|
return r.Key == "" || !isLine
|
||||||
},
|
},
|
||||||
func(r textRecord) int { return len(r.Key) },
|
func(r textRecord) int { return utf8.RuneCountInString(r.Key) },
|
||||||
)
|
)
|
||||||
alignValueLens := s.alignLens(
|
alignValueLens := s.alignLens(
|
||||||
func(r textRecord) bool {
|
func(r textRecord) bool {
|
||||||
_, isLine := r.Value.(textLine)
|
_, isLine := r.Value.(textLine)
|
||||||
return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil
|
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.
|
// Format the list as a multi-lined output.
|
||||||
n++
|
n++
|
||||||
for i, r := range s {
|
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 = alignKeyLens[i].appendChar(b, ' ')
|
||||||
|
|
||||||
b = r.Value.formatExpandedTo(b, d|r.Diff, n)
|
b = r.Value.formatExpandedTo(b, d|r.Diff, n)
|
||||||
if !r.Value.Equal(textEllipsis) {
|
if !r.ElideComma {
|
||||||
b = append(b, ',')
|
b = append(b, ',')
|
||||||
}
|
}
|
||||||
b = alignValueLens[i].appendChar(b, ' ')
|
b = alignValueLens[i].appendChar(b, ' ')
|
||||||
@ -332,6 +371,11 @@ type diffStats struct {
|
|||||||
NumModified int
|
NumModified int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s diffStats) IsZero() bool {
|
||||||
|
s.Name = ""
|
||||||
|
return s == diffStats{}
|
||||||
|
}
|
||||||
|
|
||||||
func (s diffStats) NumDiff() int {
|
func (s diffStats) NumDiff() int {
|
||||||
return s.NumRemoved + s.NumInserted + s.NumModified
|
return s.NumRemoved + s.NumInserted + s.NumModified
|
||||||
}
|
}
|
||||||
|
2
vendor/github.com/google/go-cmp/cmp/report_value.go
generated
vendored
2
vendor/github.com/google/go-cmp/cmp/report_value.go
generated
vendored
@ -1,6 +1,6 @@
|
|||||||
// Copyright 2019, The Go Authors. All rights reserved.
|
// Copyright 2019, The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
package cmp
|
||||||
|
|
||||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -1,4 +1,4 @@
|
|||||||
# github.com/google/go-cmp v0.4.1
|
# github.com/google/go-cmp v0.5.4
|
||||||
## explicit
|
## explicit
|
||||||
github.com/google/go-cmp/cmp
|
github.com/google/go-cmp/cmp
|
||||||
github.com/google/go-cmp/cmp/internal/diff
|
github.com/google/go-cmp/cmp/internal/diff
|
||||||
|
Reference in New Issue
Block a user