Involved Source Files
Package cmp determines equality of values.
This package is intended to be a more powerful and safer alternative to
reflect.DeepEqual for comparing whether two values are semantically equal.
It is intended to only be used in tests, as performance is not a goal and
it may panic if it cannot compare the values. Its propensity towards
panicking means that its unsuitable for production environments where a
spurious panic may be fatal.
The primary features of cmp are:
- When the default behavior of equality does not suit the test's needs,
custom equality functions can override the equality operation.
For example, an equality function may report floats as equal so long as
they are within some tolerance of each other.
- Types with an Equal method may use that method to determine equality.
This allows package authors to determine the equality operation
for the types that they define.
- If no custom equality functions are used and no Equal method is defined,
equality is determined by recursively comparing the primitive kinds on
both values, much like reflect.DeepEqual. Unlike reflect.DeepEqual,
unexported fields are not compared by default; they result in panics
unless suppressed by using an Ignore option (see cmpopts.IgnoreUnexported)
or explicitly compared using the Exporter option.
export_unsafe.gooptions.gopath.goreport.goreport_compare.goreport_references.goreport_reflect.goreport_slices.goreport_text.goreport_value.go
Code Examples
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
"net"
"time"
)
func main() {
// Let got be the hypothetical value obtained from some logic under test
// and want be the expected golden data.
got, want := MakeGatewayInfo()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("MakeGatewayInfo() mismatch (-want +got):\n%s", diff)
}
}
type (
Gateway struct {
SSID string
IPAddress net.IP
NetMask net.IPMask
Clients []Client
}
Client struct {
Hostname string
IPAddress net.IP
LastSeen time.Time
}
)
func MakeGatewayInfo() (x, y Gateway) {
x = Gateway{
SSID: "CoffeeShopWiFi",
IPAddress: net.IPv4(192, 168, 0, 1),
NetMask: net.IPv4Mask(255, 255, 0, 0),
Clients: []Client{{
Hostname: "ristretto",
IPAddress: net.IPv4(192, 168, 0, 116),
}, {
Hostname: "aribica",
IPAddress: net.IPv4(192, 168, 0, 104),
LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC),
}, {
Hostname: "macchiato",
IPAddress: net.IPv4(192, 168, 0, 153),
LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC),
}, {
Hostname: "espresso",
IPAddress: net.IPv4(192, 168, 0, 121),
}, {
Hostname: "latte",
IPAddress: net.IPv4(192, 168, 0, 219),
LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC),
}, {
Hostname: "americano",
IPAddress: net.IPv4(192, 168, 0, 188),
LastSeen: time.Date(2009, time.November, 10, 23, 3, 5, 0, time.UTC),
}},
}
y = Gateway{
SSID: "CoffeeShopWiFi",
IPAddress: net.IPv4(192, 168, 0, 2),
NetMask: net.IPv4Mask(255, 255, 0, 0),
Clients: []Client{{
Hostname: "ristretto",
IPAddress: net.IPv4(192, 168, 0, 116),
}, {
Hostname: "aribica",
IPAddress: net.IPv4(192, 168, 0, 104),
LastSeen: time.Date(2009, time.November, 10, 23, 6, 32, 0, time.UTC),
}, {
Hostname: "macchiato",
IPAddress: net.IPv4(192, 168, 0, 153),
LastSeen: time.Date(2009, time.November, 10, 23, 39, 43, 0, time.UTC),
}, {
Hostname: "espresso",
IPAddress: net.IPv4(192, 168, 0, 121),
}, {
Hostname: "latte",
IPAddress: net.IPv4(192, 168, 0, 221),
LastSeen: time.Date(2009, time.November, 10, 23, 0, 23, 0, time.UTC),
}},
}
return x, y
}
var t fakeT
type fakeT struct{}
func (t fakeT) Errorf(format string, args ...interface{}) { fmt.Printf(format+"\n", args...) }
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
"math"
)
func main() {
// This Comparer only operates on float64.
// To handle float32s, either define a similar function for that type
// or use a Transformer to convert float32s into float64s.
opt := cmp.Comparer(func(x, y float64) bool {
delta := math.Abs(x - y)
mean := math.Abs(x+y) / 2.0
return delta/mean < 0.00001
})
x := []float64{1.0, 1.1, 1.2, math.Pi}
y := []float64{1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi
z := []float64{1.0, 1.1, 1.2, 3.1415} // Diverges too far from Pi
fmt.Println(cmp.Equal(x, y, opt))
fmt.Println(cmp.Equal(y, z, opt))
fmt.Println(cmp.Equal(z, x, opt))
}
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
"strings"
)
type otherString string
func (x otherString) Equal(y otherString) bool {
return strings.EqualFold(string(x), string(y))
}
func main() {
// Suppose otherString.Equal performs a case-insensitive equality,
// which is too loose for our needs.
// We can avoid the methods of otherString by declaring a new type.
type myString otherString
// This transformer converts otherString to myString, allowing Equal to use
// other Options to determine equality.
trans := cmp.Transformer("", func(in otherString) myString {
return myString(in)
})
x := []otherString{"foo", "bar", "baz"}
y := []otherString{"fOO", "bAr", "Baz"} // Same as before, but with different case
fmt.Println(cmp.Equal(x, y)) // Equal because of case-insensitivity
fmt.Println(cmp.Equal(x, y, trans)) // Not equal because of more exact equality
}
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
"reflect"
)
func main() {
alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
// This option handles slices and maps of any type.
opt := cmp.FilterValues(func(x, y interface{}) bool {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
return (vx.IsValid() && vy.IsValid() && vx.Type() == vy.Type()) &&
(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
(vx.Len() == 0 && vy.Len() == 0)
}, alwaysEqual)
type S struct {
A []int
B map[string]bool
}
x := S{nil, make(map[string]bool, 100)}
y := S{make([]int, 0, 200), nil}
z := S{[]int{0}, nil} // []int has a single element (i.e., not empty)
fmt.Println(cmp.Equal(x, y, opt))
fmt.Println(cmp.Equal(y, z, opt))
fmt.Println(cmp.Equal(z, x, opt))
}
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
"math"
)
func main() {
// This Comparer only operates on float64.
// To handle float32s, either define a similar function for that type
// or use a Transformer to convert float32s into float64s.
opt := cmp.Comparer(func(x, y float64) bool {
return (math.IsNaN(x) && math.IsNaN(y)) || x == y
})
x := []float64{1.0, math.NaN(), math.E, 0.0}
y := []float64{1.0, math.NaN(), math.E, 0.0}
z := []float64{1.0, math.NaN(), math.Pi, 0.0} // Pi constant instead of E
fmt.Println(cmp.Equal(x, y, opt))
fmt.Println(cmp.Equal(y, z, opt))
fmt.Println(cmp.Equal(z, x, opt))
}
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
"math"
)
func main() {
alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
opts := cmp.Options{
// This option declares that a float64 comparison is equal only if
// both inputs are NaN.
cmp.FilterValues(func(x, y float64) bool {
return math.IsNaN(x) && math.IsNaN(y)
}, alwaysEqual),
// This option declares approximate equality on float64s only if
// both inputs are not NaN.
cmp.FilterValues(func(x, y float64) bool {
return !math.IsNaN(x) && !math.IsNaN(y)
}, cmp.Comparer(func(x, y float64) bool {
delta := math.Abs(x - y)
mean := math.Abs(x+y) / 2.0
return delta/mean < 0.00001
})),
}
x := []float64{math.NaN(), 1.0, 1.1, 1.2, math.Pi}
y := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi
z := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.1415} // Diverges too far from Pi
fmt.Println(cmp.Equal(x, y, opts))
fmt.Println(cmp.Equal(y, z, opts))
fmt.Println(cmp.Equal(z, x, opts))
}
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
"sort"
)
func main() {
// This Transformer sorts a []int.
trans := cmp.Transformer("Sort", func(in []int) []int {
out := append([]int(nil), in...) // Copy input to avoid mutating it
sort.Ints(out)
return out
})
x := struct{ Ints []int }{[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}
y := struct{ Ints []int }{[]int{2, 8, 0, 9, 6, 1, 4, 7, 3, 5}}
z := struct{ Ints []int }{[]int{0, 0, 1, 2, 3, 4, 5, 6, 7, 8}}
fmt.Println(cmp.Equal(x, y, trans))
fmt.Println(cmp.Equal(y, z, trans))
fmt.Println(cmp.Equal(z, x, trans))
}
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
"math"
)
func roundF64(z float64) float64 {
if z < 0 {
return math.Ceil(z - 0.5)
}
return math.Floor(z + 0.5)
}
func main() {
opts := []cmp.Option{
// This transformer decomposes complex128 into a pair of float64s.
cmp.Transformer("T1", func(in complex128) (out struct{ Real, Imag float64 }) {
out.Real, out.Imag = real(in), imag(in)
return out
}),
// This transformer converts complex64 to complex128 to allow the
// above transform to take effect.
cmp.Transformer("T2", func(in complex64) complex128 {
return complex128(in)
}),
// This transformer converts float32 to float64.
cmp.Transformer("T3", func(in float32) float64 {
return float64(in)
}),
// This equality function compares float64s as rounded integers.
cmp.Comparer(func(x, y float64) bool {
return roundF64(x) == roundF64(y)
}),
}
x := []interface{}{
complex128(3.0), complex64(5.1 + 2.9i), float32(-1.2), float64(12.3),
}
y := []interface{}{
complex128(3.1), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7),
}
z := []interface{}{
complex128(3.8), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7),
}
fmt.Println(cmp.Equal(x, y, opts...))
fmt.Println(cmp.Equal(y, z, opts...))
fmt.Println(cmp.Equal(z, x, opts...))
}
package main
import (
"fmt"
"strings"
"github.com/google/go-cmp/cmp"
)
// DiffReporter is a simple custom reporter that only records differences
// detected during comparison.
type DiffReporter struct {
path cmp.Path
diffs []string
}
func (r *DiffReporter) PushStep(ps cmp.PathStep) {
r.path = append(r.path, ps)
}
func (r *DiffReporter) Report(rs cmp.Result) {
if !rs.Equal() {
vx, vy := r.path.Last().Values()
r.diffs = append(r.diffs, fmt.Sprintf("%#v:\n\t-: %+v\n\t+: %+v\n", r.path, vx, vy))
}
}
func (r *DiffReporter) PopStep() {
r.path = r.path[:len(r.path)-1]
}
func (r *DiffReporter) String() string {
return strings.Join(r.diffs, "\n")
}
func main() {
x, y := MakeGatewayInfo()
var r DiffReporter
cmp.Equal(x, y, cmp.Reporter(&r))
fmt.Print(r.String())
}
Package-Level Type Names (total 55, in which 11 are exported)
Option configures for specific behavior of Equal and Diff. In particular,
the fundamental Option functions (Ignore, Transformer, and Comparer),
configure how equality is determined.
The fundamental options may be composed with filters (FilterPath and
FilterValues) to control the scope over which they are applied.
The cmp/cmpopts package provides helper functions for creating options that
may be used with Equal and Diff.
filter applies all filters and returns the option that remains.
Each option may only read s.curPath and call s.callTTBFunc.
An Options is returned only if multiple comparers or transformers
can apply simultaneously and will only contain values of those types
or sub-Options containing values of those types.
OptionsapplicableOption(interface)
*comparercoreOption(interface)exporterignorepathFilterreporter
*transformervalidatorvaluesFilter
func AllowUnexported(types ...interface{}) Option
func Comparer(f interface{}) Option
func Exporter(f func(reflect.Type) bool) Option
func FilterPath(f func(Path) bool, opt Option) Option
func FilterValues(f interface{}, opt Option) Option
func Ignore() Option
func Reporter(r interface{PushStep(PathStep); Report(Result); PopStep()}) Option
func Transformer(name string, f interface{}) Option
func Transform.Option() Option
func normalizeOption(src Option) Option
func Diff(x, y interface{}, opts ...Option) string
func Equal(x, y interface{}, opts ...Option) bool
func FilterPath(f func(Path) bool, opt Option) Option
func FilterValues(f interface{}, opt Option) Option
func gotest.tools/v3/assert.DeepEqual(t assert.TestingT, x, y interface{}, opts ...Option)
func gotest.tools/v3/assert/cmp.DeepEqual(x, y interface{}, opts ...Option) cmp.Comparison
func newState(opts []Option) *state
func normalizeOption(src Option) Option
Options is a list of Option values that also satisfies the Option interface.
Helper comparison packages may return an Options value when packing multiple
Option values into a single Option. When this package processes an Options,
it will be implicitly expanded into a flat list.
Applying a filter on an Options is equivalent to applying that same filter
on all individual options held within.
( Options) String() string( Options) apply(s *state, _, _ reflect.Value)( Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption)
Options : Option
Options : expvar.Var
Options : fmt.Stringer
Options : applicableOption
Options : github.com/aws/smithy-go/middleware.stringer
Options : context.stringer
Options : runtime.stringer
func flattenOptions(dst, src Options) Options
func flattenOptions(dst, src Options) Options
Path is a list of PathSteps describing the sequence of operations to get
from some root type to the current position in the value tree.
The first Path element is always an operation-less PathStep that exists
simply to identify the initial type.
When traversing structs with embedded structs, the embedded struct will
always be accessed as a field before traversing the fields of the
embedded struct themselves. That is, an exported field from the
embedded struct will never be accessed directly from the parent struct.
GoString returns the path to a specific node using Go syntax.
For example:
(*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
Index returns the ith step in the Path and supports negative indexing.
A negative index starts counting from the tail of the Path such that -1
refers to the last step, -2 refers to the second-to-last step, and so on.
If index is invalid, this returns a non-nil PathStep that reports a nil Type.
Last returns the last PathStep in the Path.
If the path is empty, this returns a non-nil PathStep that reports a nil Type.
String returns the simplified path to a node.
The simplified path only contains struct field accesses.
For example:
MyMap.MySlices.MyField
(*Path) pop()(*Path) push(s PathStep)
Path : expvar.Var
Path : fmt.GoStringer
Path : fmt.Stringer
Path : github.com/aws/smithy-go/middleware.stringer
Path : context.stringer
Path : runtime.stringer
PathStep is a union-type for specific operations to traverse
a value's tree structure. Users of this package never need to implement
these types as values of this type will be returned by this package.
Implementations of this interface are
StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform.
( PathStep) String() string
Type is the resulting type after performing the path step.
Values is the resulting values after performing the path step.
The type of each valid value is guaranteed to be identical to Type.
In some cases, one or both may be invalid or have restrictions:
- For StructField, both are not interface-able if the current field
is unexported and the struct type is not explicitly permitted by
an Exporter to traverse unexported fields.
- For SliceIndex, one may be invalid if an element is missing from
either the x or y slice.
- For MapIndex, one may be invalid if an entry is missing from
either the x or y map.
The provided values must not be mutated.
IndirectMapIndexSliceIndexStructFieldTransformTypeAssertionindirectmapIndexpathStepsliceIndexstructFieldtransformtypeAssertion
PathStep : expvar.Var
PathStep : fmt.Stringer
PathStep : github.com/aws/smithy-go/middleware.stringer
PathStep : context.stringer
PathStep : runtime.stringer
func Path.Index(i int) PathStep
func Path.Last() PathStep
func rootStep(x, y interface{}) PathStep
func (*Path).push(s PathStep)
Result represents the comparison result for a single node and
is provided by cmp when calling Report (see Reporter).
flagsresultFlags
ByCycle reports whether a reference cycle was detected.
ByFunc reports whether a Comparer function determined equality.
ByIgnore reports whether the node is equal because it was ignored.
This never reports true if Equal reports false.
ByMethod reports whether the Equal method determined equality.
Equal reports whether the node was determined to be equal or not.
As a special case, ignored nodes are considered equal.
SliceIndex is an index operation on a slice or array at some index Key.
sliceIndex*sliceIndex
// False for reflect.Array
sliceIndex.pathSteppathStepsliceIndex.pathStep.typreflect.TypesliceIndex.pathStep.vxreflect.ValuesliceIndex.pathStep.vyreflect.ValuesliceIndex.xkeyintsliceIndex.ykeyint
Key is the index key; it may return -1 if in a split state
SplitKeys are the indexes for indexing into slices in the
x and y values, respectively. These indexes may differ due to the
insertion or removal of an element in one of the slices, causing
all of the indexes to be shifted. If an index is -1, then that
indicates that the element does not exist in the associated slice.
Key is guaranteed to return -1 if and only if the indexes returned
by SplitKeys are not the same. SplitKeys will never return -1 for
both indexes.
( SliceIndex) String() string( SliceIndex) Type() reflect.Type( SliceIndex) Values() (vx, vy reflect.Value)
SliceIndex : PathStep
SliceIndex : expvar.Var
SliceIndex : fmt.Stringer
SliceIndex : github.com/aws/smithy-go/middleware.stringer
SliceIndex : context.stringer
SliceIndex : runtime.stringer
applicableOption represents the following types:
Fundamental: ignore | validator | *comparer | *transformer
Grouping: Options
apply executes the option, which may mutate s or panic.
filter applies all filters and returns the option that remains.
Each option may only read s.curPath and call s.callTTBFunc.
An Options is returned only if multiple comparers or transformers
can apply simultaneously and will only contain values of those types
or sub-Options containing values of those types.
Options
*comparerignore
*transformervalidator
applicableOption : Option
func Options.filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption)
coreOption represents the following types:
Fundamental: ignore | validator | *comparer | *transformer
Filters: *pathFilter | *valuesFilter
filter applies all filters and returns the option that remains.
Each option may only read s.curPath and call s.callTTBFunc.
An Options is returned only if multiple comparers or transformers
can apply simultaneously and will only contain values of those types
or sub-Options containing values of those types.
( coreOption) isCore()
*comparerignorepathFilter
*transformervalidatorvaluesFilter
coreOption : Option
defaultReporter implements the reporter interface.
As Equal serially calls the PushStep, Report, and PopStep methods, the
defaultReporter constructs a tree-based representation of the compared value
and the result of each comparison (see valueNode).
When the String method is called, the FormatDiff method transforms the
valueNode tree into a textNode tree, which is a tree-based representation
of the textual output (see textNode).
Lastly, the textNode.String method produces the final report as a string.
curr*valueNoderoot*valueNode(*defaultReporter) PopStep()(*defaultReporter) PushStep(ps PathStep)(*defaultReporter) Report(rs Result)
String provides a full report of the differences detected as a structured
literal in pseudo-Go syntax. String may only be called after the entire tree
has been traversed.
*defaultReporter : expvar.Var
*defaultReporter : fmt.Stringer
*defaultReporter : reporterIface
*defaultReporter : github.com/aws/smithy-go/middleware.stringer
*defaultReporter : context.stringer
*defaultReporter : runtime.stringer
dynChecker tracks the state needed to periodically perform checks that
user provided functions are symmetric and deterministic.
The zero value is safe for immediate use.
currintnextint
Next increments the state and reports whether a check should be performed.
Checks occur every Nth function call, where N is a triangular number:
0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
See https://en.wikipedia.org/wiki/Triangular_number
This sequence ensures that the cost of checks drops significantly as
the number of functions calls grows larger.
DiffMode controls the output mode of FormatDiff.
If diffUnknown, then produce a diff of the x and y values.
If diffIdentical, then emit values as if they were equal.
If diffRemoved, then only emit x values (ignoring y values).
If diffInserted, then only emit y values (ignoring x values).
AvoidStringer controls whether to avoid calling custom stringer
methods like error.Error or fmt.Stringer.String.
LimitVerbosity specifies that formatting should respect VerbosityLevel.
PrintAddresses controls whether to print the address of all pointers,
slice elements, and maps.
QualifiedNames controls whether FormatType uses the fully qualified name
(including the full package path as opposed to just the package name).
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.
TypeMode controls whether to print the type for the current node.
As a general rule of thumb, we always print the type of the next node
after an interface, and always elide the type of the next node after
a slice or map node.
formatValueOptions are options specific to printing reflect.Values.
CanFormatDiffSlice reports whether we support custom formatting for nodes
that are slices of primitive kinds or strings.
FormatDiff converts a valueNode tree into a textNode tree, where the later
is a textual representation of the differences detected in the former.
FormatDiffSlice prints a diff for the slices (or strings) represented by v.
This provides custom-tailored logic to make printing of differences in
textual strings and slices of primitive kinds more readable.
FormatType prints the type as if it were wrapping s.
This may return s as-is depending on the current type and TypeMode mode.
FormatValue prints the reflect.Value, taking extra care to avoid descending
into pointers already in ptrs. As pointers are visited, ptrs is also updated.
( formatOptions) WithDiffMode(d diffMode) formatOptions( formatOptions) WithTypeMode(t typeMode) formatOptions( formatOptions) WithVerbosity(level int) formatOptions( formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode( formatOptions) formatDiffSlice(vx, vy reflect.Value, chunkSize int, name string, makeRec func(reflect.Value, diffMode) textRecord) (list textList)( formatOptions) formatString(prefix, s string) textNode( formatOptions) verbosity() uint
func verbosityPreset(opts formatOptions, i int) formatOptions
func verbosityPreset(opts formatOptions, i int) formatOptions
AvoidStringer controls whether to avoid calling custom stringer
methods like error.Error or fmt.Stringer.String.
LimitVerbosity specifies that formatting should respect VerbosityLevel.
PrintAddresses controls whether to print the address of all pointers,
slice elements, and maps.
QualifiedNames controls whether FormatType uses the fully qualified name
(including the full package path as opposed to just the package name).
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.
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).
pvalue.Pointer
pointerPath represents a dual-stack of pointers encountered when
recursively traversing the x and y values. This data structure supports
detection of cycles and determining whether the cycles are equal.
In Go, cycles can occur via pointers, slices, and maps.
The pointerPath uses a map to represent a stack; where descension into a
pointer pushes the address onto the stack, and ascension from a pointer
pops the address from the stack. Thus, when traversing into a pointer from
reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles
by checking whether the pointer has already been visited. The cycle detection
uses a separate stack for the x and y values.
If a cycle is detected we need to determine whether the two pointers
should be considered equal. The definition of equality chosen by Equal
requires two graphs to have the same structure. To determine this, both the
x and y values must have a cycle where the previous pointers were also
encountered together as a pair.
Semantically, this is equivalent to augmenting Indirect, SliceIndex, and
MapIndex with pointer information for the x and y values.
Suppose px and py are two pointers to compare, we then search the
Path for whether px was ever encountered in the Path history of x, and
similarly so with py. If either side has a cycle, the comparison is only
equal if both px and py have a cycle resulting from the same PathStep.
Using a map as a stack is more performant as we can perform cycle detection
in O(1) instead of O(N) where N is len(Path).
mx is keyed by x pointers, where the value is the associated y pointer.
my is keyed by y pointers, where the value is the associated x pointer.
(*pointerPath) Init()
Pop ascends from pointers vx and vy.
Push indicates intent to descend into pointers vx and vy where
visited reports whether either has been seen before. If visited before,
equal reports whether both pointers were encountered together.
Pop must be called if and only if the pointers were never visited.
The pointers vx and vy must be a reflect.Ptr, reflect.Slice, or reflect.Map
and be non-nil.
recChecker tracks the state needed to periodically perform checks that
user provided transformers are not stuck in an infinitely recursive cycle.
nextint
Check scans the Path for any recursive transformers and panics when any
recursive transformers are detected. Note that the presence of a
recursive Transformer does not necessarily imply an infinite cycle.
As such, this check only activates after some minimal number of path steps.
textList is a comma-separated list of textWrap or textLine nodes.
The list may be formatted as multi-lines or single-line at the discretion
of the textList.formatCompactTo method.
AppendEllipsis appends a new ellipsis node to the list if none already
exists at the end. If cs is non-zero it coalesces the statistics with the
previous diffStats.
( textList) Equal(s2 textNode) bool( textList) Len() (n int)( textList) String() string( textList) alignLens(skipFunc func(textRecord) bool, lenFunc func(textRecord) int) []repeatCount( textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode)( textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte
textList : expvar.Var
textList : fmt.Stringer
textList : textNode
textList : github.com/aws/smithy-go/middleware.stringer
textList : context.stringer
textList : runtime.stringer
textNode is a simplified tree-based representation of structured text.
Possible node types are textWrap, textList, or textLine.
Equal reports whether the two trees are structurally identical.
Nested textRecord.Diff and textRecord.Comment fields are compared.
Len reports the length in bytes of a single-line version of the tree.
Nested textRecord.Diff and textRecord.Comment fields are ignored.
String returns the string representation of the text tree.
It is not guaranteed that len(x.String()) == x.Len(),
nor that x.String() == y.String() implies that x.Equal(y).
formatCompactTo formats the contents of the tree as a single-line string
to the provided buffer. Any nested textRecord.Diff and textRecord.Comment
fields are ignored.
However, not all nodes in the tree should be collapsed as a single-line.
If a node can be collapsed as a single-line, it is replaced by a textLine
node. Since the top-level node cannot replace itself, this also returns
the current node itself.
This does not mutate the receiver.
formatExpandedTo formats the contents of the tree as a multi-line string
to the provided buffer. In order for column alignment to operate well,
formatCompactTo must be called before calling formatExpandedTo.
textLinetextList
*textWrap
textNode : expvar.Var
textNode : fmt.Stringer
textNode : github.com/aws/smithy-go/middleware.stringer
textNode : context.stringer
textNode : runtime.stringer
func makeLeafReference(p value.Pointer, printAddress bool) textNode
func wrapParens(s textNode) textNode
func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode
func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode
func resolveReferences(s textNode)
func wrapParens(s textNode) textNode
func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode
func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode
trunkReference is metadata for a textNode indicating that the sub-tree
represents the value for the given pointer reference.
pvalue.Pointer
trunkReferences is metadata for a textNode indicating that the sub-tree
represents the value for either pointer in a pair of references.
pp[2]value.Pointer
valueNode represents a single node within a report, which is a
structured representation of the value tree, containing information
regarding which nodes are equal or not.
MaxDepth is the maximum depth of the tree. This counts from zero;
thus, leaf nodes have a depth of zero.
NumChildren is the number of transitive descendants of this node.
This counts from zero; thus, leaf nodes have no descendants.
NumCompared is the number of leaf nodes that were compared
using an Equal method or Comparer function.
NumDiff is the number of leaf nodes that are not equal.
NumIgnored is the number of leaf nodes that are ignored.
NumSame is the number of leaf nodes that are equal.
All descendants are equal only if NumDiff is 0.
NumTransformed is the number of non-leaf nodes that were transformed.
Records is a list of struct fields, slice elements, or map entries.
// If populated, implies Value is not populated
TransformerName is the name of the transformer.
// If non-empty, implies Value is populated
Typereflect.Type
Value is the result of a transformation, pointer indirect, of
type assertion.
// If populated, implies Records is not populated
ValueXreflect.ValueValueYreflect.Valueparent*valueNode(*valueNode) PopStep() (parent *valueNode)(*valueNode) PushStep(ps PathStep) (child *valueNode)(*valueNode) Report(rs Result)
Package-Level Functions (total 36, in which 10 are exported)
AllowUnexported returns an Options that allows Equal to forcibly introspect
unexported fields of the specified struct types.
See Exporter for the proper use of this option.
Comparer returns an Option that determines whether two values are equal
to each other.
The comparer f must be a function "func(T, T) bool" and is implicitly
filtered to input values assignable to T. If T is an interface, it is
possible that f is called with two values of different concrete types that
both implement T.
The equality function must be:
- Symmetric: equal(x, y) == equal(y, x)
- Deterministic: equal(x, y) == equal(x, y)
- Pure: equal(x, y) does not modify x or y
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.
Equal reports whether x and y are equal by recursively applying the
following rules in the given order to x and y and all of their sub-values:
- Let S be the set of all Ignore, Transformer, and Comparer options that
remain after applying all path filters, value filters, and type filters.
If at least one Ignore exists in S, then the comparison is ignored.
If the number of Transformer and Comparer options in S is non-zero,
then Equal panics because it is ambiguous which option to use.
If S contains a single Transformer, then use that to transform
the current values and recursively call Equal on the output values.
If S contains a single Comparer, then use that to compare the current values.
Otherwise, evaluation proceeds to the next rule.
- If the values have an Equal method of the form "(T) Equal(T) bool" or
"(T) Equal(I) bool" where T is assignable to I, then use the result of
x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
evaluation proceeds to the next rule.
- Lastly, try to compare x and y based on their basic kinds.
Simple kinds like booleans, integers, floats, complex numbers, strings,
and channels are compared using the equivalent of the == operator in Go.
Functions are only equal if they are both nil, otherwise they are unequal.
Structs are equal if recursively calling Equal on all fields report equal.
If a struct contains unexported fields, Equal panics unless an Ignore option
(e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option
explicitly permits comparing the unexported field.
Slices are equal if they are both nil or both non-nil, where recursively
calling Equal on all non-ignored slice or array elements report equal.
Empty non-nil slices and nil slices are not equal; to equate empty slices,
consider using cmpopts.EquateEmpty.
Maps are equal if they are both nil or both non-nil, where recursively
calling Equal on all non-ignored map entries report equal.
Map keys are equal according to the == operator.
To use custom comparisons for map keys, consider using cmpopts.SortMaps.
Empty non-nil maps and nil maps are not equal; to equate empty maps,
consider using cmpopts.EquateEmpty.
Pointers and interfaces are equal if they are both nil or both non-nil,
where they have the same underlying concrete type and recursively
calling Equal on the underlying values reports equal.
Before recursing into a pointer, slice element, or map, the current path
is checked to detect whether the address has already been visited.
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.
Exporter returns an Option that specifies whether Equal is allowed to
introspect into the unexported fields of certain struct types.
Users of this option must understand that comparing on unexported fields
from external packages is not safe since changes in the internal
implementation of some external package may cause the result of Equal
to unexpectedly change. However, it may be valid to use this option on types
defined in an internal package where the semantic meaning of an unexported
field is in the control of the user.
In many cases, a custom Comparer should be used instead that defines
equality as a function of the public API of a type rather than the underlying
unexported implementation.
For example, the reflect.Type documentation defines equality to be determined
by the == operator on the interface (essentially performing a shallow pointer
comparison) and most attempts to compare *regexp.Regexp types are interested
in only checking that the regular expression strings are equal.
Both of these are accomplished using Comparers:
Comparer(func(x, y reflect.Type) bool { return x == y })
Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
all unexported fields on specified struct types.
FilterPath returns a new Option where opt is only evaluated if filter f
returns true for the current Path in the value tree.
This filter is called even if a slice element or map entry is missing and
provides an opportunity to ignore such cases. The filter function must be
symmetric such that the filter result is identical regardless of whether the
missing value is from x or y.
The option passed in may be an Ignore, Transformer, Comparer, Options, or
a previously filtered Option.
FilterValues returns a new Option where opt is only evaluated if filter f,
which is a function of the form "func(T, T) bool", returns true for the
current pair of values being compared. If either value is invalid or
the type of the values is not assignable to T, then this filter implicitly
returns false.
The filter function must be
symmetric (i.e., agnostic to the order of the inputs) and
deterministic (i.e., produces the same result when given the same inputs).
If T is an interface, it is possible that f is called with two values with
different concrete types that both implement T.
The option passed in may be an Ignore, Transformer, Comparer, Options, or
a previously filtered Option.
Ignore is an Option that causes all comparisons to be ignored.
This value is intended to be combined with FilterPath or FilterValues.
It is an error to pass an unfiltered Ignore option to Equal.
Reporter is an Option that can be passed to Equal. When Equal traverses
the value trees, it calls PushStep as it descends into each node in the
tree and PopStep as it ascend out of the node. The leaves of the tree are
either compared (determined to be equal or not equal) or ignored and reported
as such by calling the Report method.
Transformer returns an Option that applies a transformation function that
converts values of a certain type into that of another.
The transformer f must be a function "func(T) R" that converts values of
type T to those of type R and is implicitly filtered to input values
assignable to T. The transformer must not mutate T in any way.
To help prevent some cases of infinite recursive cycles applying the
same transform to the output of itself (e.g., in the case where the
input and output types are the same), an implicit filter is added such that
a transformer is applicable only if that exact transformer is not already
in the tail of the Path since the last non-Transform step.
For situations where the implicit filter is still insufficient,
consider using cmpopts.AcyclicTransformer, which adds a filter
to prevent the transformer from being recursively applied upon itself.
The name is a user provided label that is used as the Transform.Name in the
transformation PathStep (and eventually shown in the Diff output).
The name must be a valid identifier or qualified identifier in Go syntax.
If empty, an arbitrary name is used.
cleanupSurroundingIdentical scans through all unequal groups, and
moves any leading sequence of equal elements to the preceding equal group and
moves and trailing sequence of equal elements to the succeeding equal group.
This is necessary since coalesceInterveningIdentical may coalesce edit groups
together such that leading/trailing spans of equal elements becomes possible.
Note that this can occur even with an optimal diffing algorithm.
Example:
Input: [
{NumIdentical: 61},
{NumIdentical: 1 , NumRemoved: 11, NumInserted: 2}, // assume 3 leading identical elements
{NumIdentical: 67},
{NumIdentical: 7, NumRemoved: 12, NumInserted: 3}, // assume 10 trailing identical elements
{NumIdentical: 54},
]
Output: [
{NumIdentical: 64}, // incremented by 3
{NumRemoved: 9},
{NumIdentical: 67},
{NumRemoved: 9},
{NumIdentical: 64}, // incremented by 10
]
coalesceAdjacentEdits coalesces the list of edits into groups of adjacent
equal or unequal counts.
Example:
Input: "..XXY...Y"
Output: [
{NumIdentical: 2},
{NumRemoved: 2, NumInserted 1},
{NumIdentical: 3},
{NumInserted: 1},
]
coalesceAdjacentRecords coalesces the list of records into groups of
adjacent equal, or unequal counts.
coalesceInterveningIdentical coalesces sufficiently short (<= windowSize)
equal groups into adjacent unequal groups that currently result in a
dual inserted/removed printout. This acts as a high-pass filter to smooth
out high-frequency changes within the windowSize.
Example:
WindowSize: 16,
Input: [
{NumIdentical: 61}, // group 0
{NumRemoved: 3, NumInserted: 1}, // group 1
{NumIdentical: 6}, // ├── coalesce
{NumInserted: 2}, // ├── coalesce
{NumIdentical: 1}, // ├── coalesce
{NumRemoved: 9}, // └── coalesce
{NumIdentical: 64}, // group 2
{NumRemoved: 3, NumInserted: 1}, // group 3
{NumIdentical: 6}, // ├── coalesce
{NumInserted: 2}, // ├── coalesce
{NumIdentical: 1}, // ├── coalesce
{NumRemoved: 7}, // ├── coalesce
{NumIdentical: 1}, // ├── coalesce
{NumRemoved: 2}, // └── coalesce
{NumIdentical: 63}, // group 4
]
Output: [
{NumIdentical: 61},
{NumIdentical: 7, NumRemoved: 12, NumInserted: 3},
{NumIdentical: 64},
{NumIdentical: 8, NumRemoved: 12, NumInserted: 3},
{NumIdentical: 63},
]
formatString prints s as a double-quoted or backtick-quoted string.
isExported reports whether the identifier is exported.
makeAddressable returns a value that is always addressable.
It returns the input verbatim if it is already addressable,
otherwise it creates a new value and returns an addressable copy.
normalizeOption normalizes the input options such that all Options groups
are flattened and groups with a single element are reduced to that element.
Only coreOptions and Options containing coreOptions are allowed.
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.
retrieveUnexportedField uses unsafe to forcibly retrieve any field from
a struct such that the value has read-write permissions.
The parent struct, v, must be addressable, while f must be a StructField
describing the field to retrieve. If addr is false,
then the returned value will be shallowed copied to be non-addressable.
rootStep constructs the first path step. If x and y have differing types,
then they are stored within an empty interface type.
verbosityPreset modifies the verbosity settings given an index
between 0 and maxVerbosityPreset, inclusive.
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.