diff options
Diffstat (limited to 'cmp')
-rw-r--r-- | cmp/compare.go | 19 | ||||
-rw-r--r-- | cmp/example_reporter_test.go | 59 | ||||
-rw-r--r-- | cmp/options.go | 73 | ||||
-rw-r--r-- | cmp/options_test.go | 8 | ||||
-rw-r--r-- | cmp/report.go | 4 | ||||
-rw-r--r-- | cmp/report_value.go | 41 |
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 } |