diff options
author | Joe Tsai <joetsai@digital-static.net> | 2019-03-11 17:36:18 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-03-11 17:36:18 -0700 |
commit | 2940eda701e08ed0bd3cda4a6c69efb50af6db51 (patch) | |
tree | 2d8bd4344f8a93a8bedc62227c92c00e46db85e8 /cmp/compare.go | |
parent | c81281657ad99ba22e14fda7c4dfaaf2974c454e (diff) | |
download | go-cmp-2940eda701e08ed0bd3cda4a6c69efb50af6db51.tar.gz |
Implement a unified difference reporter (#124)
The previous implementation of the reporter simply listed all differences,
each qualified by the full path to the difference.
This method of reporting is exact, but difficult for humans to parse.
It is one of the more common sources of complaints by users and a significant
reason why cmp is not preferred over competing libraries.
This change reimplements the reporter to format the output as a
structured literal in pseudo-Go syntax. The output resembles literals that
the user would likely have in their test code. Differences between the
x and y values are denoted by a '-' or '+' prefix at the start of the line.
An overview of the new implementation is as follows:
* report.go: The defaultReporter type implements the Reporter interface.
* report_value: Through the PushStep/PopStep API, the defaultReporter is able
to contruct an in-memory valueNode tree representing the comparison of
x and y as cmp.Equal walks the sub-values.
* report_compare.go: After report_value.go constructs an AST-representation
of the compared values, report_compare.go formats the valueNode tree as a
textNode tree, which is the textual output in a tree form.
Some relevant design decisions include:
* The format logic goes through effort to avoid printing ignored nodes.
* Some number of surrounding equal (but not ignored) struct fields,
slice elements, or map entries are printed for context.
* cmp.Equal may declare two sub-reflect.Values to be equal, but are
different values when printed. In order to present a unified view on
this "equal" node, the logic formats both values and arbitrarily choses
the one with the shorter string.
* Transformed nodes are formatted with the pseudo-Go syntax of:
Inverse(TransformerName, OutputType{...})
where Inverse is some magical pseudo-function that inverts the
transformation referred to by TransformerName. The OutputType literal
is the output of the transformation.
* report_reflect.go: This contains logic to pretty-print reflect.Values and
is relied upon by report_compare.go to format the leaves of the tree.
Note that the leaves of the tree can be any arbitrary Go type and value
(including cyclic data structures).
* report_text.go: This contains logic for purely lexicographical formatting
and is depended upon by the other report_*.go files.
Advantages:
* The output is more familiar as it uses pseudo-Go syntax for literals
* It provides context about surrounding struct fields, slice elements, or
map entries that were equal
* Inserted and removed elements in a slice are easier to visualize
* Related diffs lie on the same indentation
* For diffs in a deeply nested value, the output is easier to visualize
than having a list of all the full paths to the diff.
Disadvantages:
* The implementation is drastically more complex.
* In most cases, the output is longer (though more sparse)
Diffstat (limited to 'cmp/compare.go')
-rw-r--r-- | cmp/compare.go | 21 |
1 files changed, 14 insertions, 7 deletions
diff --git a/cmp/compare.go b/cmp/compare.go index 939a3b5..2762733 100644 --- a/cmp/compare.go +++ b/cmp/compare.go @@ -32,6 +32,7 @@ import ( "strings" "github.com/google/go-cmp/cmp/internal/diff" + "github.com/google/go-cmp/cmp/internal/flags" "github.com/google/go-cmp/cmp/internal/function" "github.com/google/go-cmp/cmp/internal/value" ) @@ -109,9 +110,15 @@ func Equal(x, y interface{}, opts ...Option) bool { // 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 string will use the "-" symbol to -// indicate elements removed from x, and the "+" symbol to indicate elements -// added to y. +// 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. func Diff(x, y interface{}, opts ...Option) string { @@ -373,10 +380,10 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) { // Otherwise, it returns the input value as is. func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value { // TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/22143). - // The upstream fix landed in Go1.10, so we can remove this when drop support - // for Go1.9 and below. - if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t { - return reflect.New(t).Elem() + if !flags.AtLeastGo110 { + if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t { + return reflect.New(t).Elem() + } } return v } |