aboutsummaryrefslogtreecommitdiff
path: root/cmp
diff options
context:
space:
mode:
Diffstat (limited to 'cmp')
-rw-r--r--cmp/compare.go19
-rw-r--r--cmp/example_reporter_test.go59
-rw-r--r--cmp/options.go73
-rw-r--r--cmp/options_test.go8
-rw-r--r--cmp/report.go4
-rw-r--r--cmp/report_value.go41
6 files changed, 142 insertions, 62 deletions
diff --git a/cmp/compare.go b/cmp/compare.go
index 2762733..9fe9a4c 100644
--- a/cmp/compare.go
+++ b/cmp/compare.go
@@ -120,10 +120,11 @@ func Equal(x, y interface{}, opts ...Option) bool {
// 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.
+// 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)
- opts = Options{Options(opts), reporter(r)}
+ opts = Options{Options(opts), Reporter(r)}
eq := Equal(x, y, opts...)
d := r.String()
if (d == "") != eq {
@@ -135,9 +136,9 @@ func Diff(x, y interface{}, opts ...Option) string {
type state struct {
// These fields represent the "comparison state".
// Calling statelessCompare must not result in observable changes to these.
- result diff.Result // The current result of comparison
- curPath Path // The current path in the value tree
- reporters []reporterOption // Optional reporters
+ result diff.Result // The current result of comparison
+ curPath Path // The current path in the value tree
+ reporters []reporter // Optional reporters
// recChecker checks for infinite cycles applying the same set of
// transformers upon the output of itself.
@@ -183,7 +184,7 @@ func (s *state) processOption(opt Option) {
for t := range opt {
s.exporters[t] = true
}
- case reporterOption:
+ case reporter:
s.reporters = append(s.reporters, opt)
default:
panic(fmt.Sprintf("unknown option %T", opt))
@@ -529,8 +530,8 @@ func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) {
}
}
-func (s *state) report(eq bool, rf reportFlags) {
- if rf&reportIgnored == 0 {
+func (s *state) report(eq bool, rf resultFlags) {
+ if rf&reportByIgnore == 0 {
if eq {
s.result.NumSame++
rf |= reportEqual
@@ -540,7 +541,7 @@ func (s *state) report(eq bool, rf reportFlags) {
}
}
for _, r := range s.reporters {
- r.Report(rf)
+ r.Report(Result{flags: rf})
}
}
diff --git a/cmp/example_reporter_test.go b/cmp/example_reporter_test.go
new file mode 100644
index 0000000..bc1932e
--- /dev/null
+++ b/cmp/example_reporter_test.go
@@ -0,0 +1,59 @@
+// Copyright 2019, The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package cmp_test
+
+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 ExampleReporter() {
+ x, y := MakeGatewayInfo()
+
+ var r DiffReporter
+ cmp.Equal(x, y, cmp.Reporter(&r))
+ fmt.Print(r.String())
+
+ // Output:
+ // {cmp_test.Gateway}.IPAddress:
+ // -: 192.168.0.1
+ // +: 192.168.0.2
+ //
+ // {cmp_test.Gateway}.Clients[4].IPAddress:
+ // -: 192.168.0.219
+ // +: 192.168.0.221
+ //
+ // {cmp_test.Gateway}.Clients[5->?]:
+ // -: {Hostname:americano IPAddress:192.168.0.188 LastSeen:2009-11-10 23:03:05 +0000 UTC}
+ // +: <invalid reflect.Value>
+}
diff --git a/cmp/options.go b/cmp/options.go
index a265597..6e52fee 100644
--- a/cmp/options.go
+++ b/cmp/options.go
@@ -199,7 +199,7 @@ type ignore struct{ core }
func (ignore) isFiltered() bool { return false }
func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} }
-func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportIgnored) }
+func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) }
func (ignore) String() string { return "Ignore()" }
// validator is a sentinel Option type to indicate that some options could not
@@ -407,68 +407,87 @@ func (visibleStructs) filter(_ *state, _ reflect.Type, _, _ reflect.Value) appli
panic("not implemented")
}
-// reportFlags is a bit-set representing how a comparison was determined.
-type reportFlags uint
+// Result represents the comparison result for a single node and
+// is provided by cmp when calling Result (see Reporter).
+type Result struct {
+ _ [0]func() // Make Result incomparable
+ flags resultFlags
+}
+
+// Equal reports whether the node was determined to be equal or not.
+// As a special case, ignored nodes are considered equal.
+func (r Result) Equal() bool {
+ return r.flags&(reportEqual|reportByIgnore) != 0
+}
+
+// ByIgnore reports whether the node is equal because it was ignored.
+// This never reports true if Equal reports false.
+func (r Result) ByIgnore() bool {
+ return r.flags&reportByIgnore != 0
+}
+
+// ByMethod reports whether the Equal method determined equality.
+func (r Result) ByMethod() bool {
+ return r.flags&reportByMethod != 0
+}
+
+// ByFunc reports whether a Comparer function determined equality.
+func (r Result) ByFunc() bool {
+ return r.flags&reportByFunc != 0
+}
+
+type resultFlags uint
const (
- _ reportFlags = (1 << iota) / 2
+ _ resultFlags = (1 << iota) / 2
- // reportEqual reports whether the node is equal.
- // This may be ORed with reportByMethod or reportByFunc.
reportEqual
- // reportUnequal reports whether the node is not equal.
- // This may be ORed with reportByMethod or reportByFunc.
reportUnequal
- // reportIgnored reports whether the node was ignored.
- reportIgnored
-
- // reportByMethod reports whether equality was determined by calling the
- // Equal method. This may be ORed with reportEqual or reportUnequal.
+ reportByIgnore
reportByMethod
- // reportByFunc reports whether equality was determined by calling a custom
- // Comparer function. This may be ORed with reportEqual or reportUnequal.
reportByFunc
)
-// reporter is an Option that can be passed to Equal. When Equal traverses
+// 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.
-func reporter(r interface {
- // TODO: Export this option.
-
+func Reporter(r interface {
// PushStep is called when a tree-traversal operation is performed.
// The PathStep itself is only valid until the step is popped.
- // The PathStep.Values are valid for the duration of the entire traversal.
+ // The PathStep.Values are valid for the duration of the entire traversal
+ // and must not be mutated.
//
- // Equal always call PushStep at the start to provide an operation-less
+ // Equal always calls PushStep at the start to provide an operation-less
// PathStep used to report the root values.
//
+ // Within a slice, the exact set of inserted, removed, or modified elements
+ // is unspecified and may change in future implementations.
// The entries of a map are iterated through in an unspecified order.
PushStep(PathStep)
- // Report is called at exactly once on leaf nodes to report whether the
+ // Report is called exactly once on leaf nodes to report whether the
// comparison identified the node as equal, unequal, or ignored.
// A leaf node is one that is immediately preceded by and followed by
// a pair of PushStep and PopStep calls.
- Report(reportFlags)
+ Report(Result)
// PopStep ascends back up the value tree.
// There is always a matching pop call for every push call.
PopStep()
}) Option {
- return reporterOption{r}
+ return reporter{r}
}
-type reporterOption struct{ reporterIface }
+type reporter struct{ reporterIface }
type reporterIface interface {
PushStep(PathStep)
- Report(reportFlags)
+ Report(Result)
PopStep()
}
-func (reporterOption) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
+func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
panic("not implemented")
}
diff --git a/cmp/options_test.go b/cmp/options_test.go
index f876eab..f8066c7 100644
--- a/cmp/options_test.go
+++ b/cmp/options_test.go
@@ -128,7 +128,7 @@ func TestOptionPanic(t *testing.T) {
}, {
label: "FilterPath",
fnc: FilterPath,
- args: []interface{}{func(Path) bool { return true }, reporter(&defaultReporter{})},
+ args: []interface{}{func(Path) bool { return true }, Reporter(&defaultReporter{})},
wantPanic: "invalid option type",
}, {
label: "FilterPath",
@@ -137,7 +137,7 @@ func TestOptionPanic(t *testing.T) {
}, {
label: "FilterPath",
fnc: FilterPath,
- args: []interface{}{func(Path) bool { return true }, Options{Ignore(), reporter(&defaultReporter{})}},
+ args: []interface{}{func(Path) bool { return true }, Options{Ignore(), Reporter(&defaultReporter{})}},
wantPanic: "invalid option type",
}, {
label: "FilterValues",
@@ -170,7 +170,7 @@ func TestOptionPanic(t *testing.T) {
}, {
label: "FilterValues",
fnc: FilterValues,
- args: []interface{}{func(int, int) bool { return true }, reporter(&defaultReporter{})},
+ args: []interface{}{func(int, int) bool { return true }, Reporter(&defaultReporter{})},
wantPanic: "invalid option type",
}, {
label: "FilterValues",
@@ -179,7 +179,7 @@ func TestOptionPanic(t *testing.T) {
}, {
label: "FilterValues",
fnc: FilterValues,
- args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), reporter(&defaultReporter{})}},
+ args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), Reporter(&defaultReporter{})}},
wantPanic: "invalid option type",
}}
diff --git a/cmp/report.go b/cmp/report.go
index 6810a50..6ddf299 100644
--- a/cmp/report.go
+++ b/cmp/report.go
@@ -26,8 +26,8 @@ func (r *defaultReporter) PushStep(ps PathStep) {
r.root = r.curr
}
}
-func (r *defaultReporter) Report(f reportFlags) {
- r.curr.Report(f)
+func (r *defaultReporter) Report(rs Result) {
+ r.curr.Report(rs)
}
func (r *defaultReporter) PopStep() {
r.curr = r.curr.PopStep()
diff --git a/cmp/report_value.go b/cmp/report_value.go
index fcff486..83031a7 100644
--- a/cmp/report_value.go
+++ b/cmp/report_value.go
@@ -80,41 +80,42 @@ func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) {
return child
}
-func (r *valueNode) Report(f reportFlags) {
+func (r *valueNode) Report(rs Result) {
assert(r.MaxDepth == 0) // May only be called on leaf nodes
- if f&reportEqual > 0 {
- r.NumSame++
- }
- if f&reportUnequal > 0 {
- r.NumDiff++
- }
- if f&reportIgnored > 0 {
+ if rs.ByIgnore() {
r.NumIgnored++
+ } else {
+ if rs.Equal() {
+ r.NumSame++
+ } else {
+ r.NumDiff++
+ }
}
assert(r.NumSame+r.NumDiff+r.NumIgnored == 1)
- if f&reportByMethod > 0 {
+ if rs.ByMethod() {
r.NumCompared++
}
- if f&reportByFunc > 0 {
+ if rs.ByFunc() {
r.NumCompared++
}
assert(r.NumCompared <= 1)
}
func (child *valueNode) PopStep() (parent *valueNode) {
+ if child.parent == nil {
+ return nil
+ }
parent = child.parent
- if parent != nil {
- parent.NumSame += child.NumSame
- parent.NumDiff += child.NumDiff
- parent.NumIgnored += child.NumIgnored
- parent.NumCompared += child.NumCompared
- parent.NumTransformed += child.NumTransformed
- parent.NumChildren += child.NumChildren + 1
- if parent.MaxDepth < child.MaxDepth+1 {
- parent.MaxDepth = child.MaxDepth + 1
- }
+ parent.NumSame += child.NumSame
+ parent.NumDiff += child.NumDiff
+ parent.NumIgnored += child.NumIgnored
+ parent.NumCompared += child.NumCompared
+ parent.NumTransformed += child.NumTransformed
+ parent.NumChildren += child.NumChildren + 1
+ if parent.MaxDepth < child.MaxDepth+1 {
+ parent.MaxDepth = child.MaxDepth + 1
}
return parent
}