Skip to content
Permalink
Branch: master
Commits on Mar 12, 2019
  1. Improve clarity of compareAny (#132)

    dsnet committed Mar 12, 2019
  2. Implement specialized diffing for slices (#131)

    dsnet committed Mar 12, 2019
    Lists of primitives are a common-enough data structure that
    it is worth providing specialized diffing for.
    
    This provides significantly better readability for strings
    and byte slices. There is also a heuristic for detecting
    what a string should be diffed as a multiline string.
  3. Use concrete types for path steps (#129)

    dsnet committed Mar 12, 2019
    Rather than representing each path step as an interface,
    use concrete types instead. This provides some performance benefits as
    it reduces the amount of virtual function calls and also provides the
    ability for the compiler to inline method calls.
    
    This is technically a breaking change, but since each of the path step
    interfaces were explicitly implemented in such a way that they couldn't
    be implemented directly (due to the presence of an unexported method),
    the only way someone could have been depending on these as interfaces is
    if they embedded the interface into another interface. Static analysis of
    all code at Google and publicly available on GitHub shows that this is
    not a problem.
    
    The performance benefits of this change is significant:
    
    benchmark                               old ns/op     new ns/op     delta
    BenchmarkBytes/64KiB/EqualFilter0-4     80551394      46592605      -42.16%
    BenchmarkBytes/64KiB/EqualFilter1-4     102922132     69974509      -32.01%
    BenchmarkBytes/64KiB/EqualFilter2-4     159009935     94474812      -40.59%
    BenchmarkBytes/64KiB/EqualFilter3-4     181231264     124601102     -31.25%
    BenchmarkBytes/64KiB/EqualFilter4-4     189775228     148864070     -21.56%
    BenchmarkBytes/64KiB/EqualFilter5-4     285065469     175198907     -38.54%
    
    benchmark                               old MB/s     new MB/s     speedup
    BenchmarkBytes/64KiB/EqualFilter0-4     1.63         2.81         1.72x
    BenchmarkBytes/64KiB/EqualFilter1-4     1.27         1.87         1.47x
    BenchmarkBytes/64KiB/EqualFilter2-4     0.82         1.39         1.70x
    BenchmarkBytes/64KiB/EqualFilter3-4     0.72         1.05         1.46x
    BenchmarkBytes/64KiB/EqualFilter4-4     0.69         0.88         1.28x
    BenchmarkBytes/64KiB/EqualFilter5-4     0.46         0.75         1.63x
    
    benchmark                               old allocs     new allocs     delta
    BenchmarkBytes/64KiB/EqualFilter0-4     133            134            +0.75%
    BenchmarkBytes/64KiB/EqualFilter1-4     134            134            +0.00%
    BenchmarkBytes/64KiB/EqualFilter2-4     135            135            +0.00%
    BenchmarkBytes/64KiB/EqualFilter3-4     135            135            +0.00%
    BenchmarkBytes/64KiB/EqualFilter4-4     136            136            +0.00%
    BenchmarkBytes/64KiB/EqualFilter5-4     136            136            +0.00%
    
    benchmark                               old bytes     new bytes     delta
    BenchmarkBytes/64KiB/EqualFilter0-4     6632417       6632523       +0.00%
    BenchmarkBytes/64KiB/EqualFilter1-4     6632416       6632464       +0.00%
    BenchmarkBytes/64KiB/EqualFilter2-4     6632464       6632507       +0.00%
    BenchmarkBytes/64KiB/EqualFilter3-4     6632502       6632483       -0.00%
    BenchmarkBytes/64KiB/EqualFilter4-4     6632652       6632668       +0.00%
    BenchmarkBytes/64KiB/EqualFilter5-4     6632604       6632659       +0.00%
  4. Add IgnoreSliceElements and IgnoreMapEntries helpers (#126)

    dsnet committed Mar 12, 2019
    These helper options ignore slice elements or map entries based
    on a user-provided predicate function. These are especially useful
    for ignoring missing elements or entries.
  5. Export the Reporter API (#123)

    dsnet committed Mar 12, 2019
    The Reporter option allows users to hook in their own custom reporters
    to programatically interpret the diff structure.
    
    The Reporter API uses a push/pop mechanism, which is strictly more powerful
    than an API that only calls report on leaf nodes.
    The Reporter.Report method takes in a Result type to provide flexibility in
    what properties can be reported in the future since new properties can be added,
    but new methods cannot be easily added to the reporter interface.
  6. Implement a unified difference reporter (#124)

    dsnet committed Mar 12, 2019
    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)
You can’t perform that action at this time.