Skip to content

Commit

Permalink
assert: switch the diff library to go-diff
Browse files Browse the repository at this point in the history
  • Loading branch information
mitioshi committed Feb 22, 2024
1 parent 7c847e2 commit 31ad2a7
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 35 deletions.
91 changes: 78 additions & 13 deletions assert/assertions.go
Expand Up @@ -12,13 +12,15 @@ import (
"regexp"
"runtime"
"runtime/debug"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"

"github.com/sergi/go-diff/diffmatchpatch"

"github.com/davecgh/go-spew/spew"
"github.com/pmezard/go-difflib/difflib"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -145,8 +147,6 @@ func copyExportedFields(expected interface{}) interface{} {
// structures.
//
// This function does no assertion of any kind.
//
// Deprecated: Use [EqualExportedValues] instead.
func ObjectsExportedFieldsAreEqual(expected, actual interface{}) bool {
expectedCleaned := copyExportedFields(expected)
actualCleaned := copyExportedFields(actual)
Expand Down Expand Up @@ -1777,6 +1777,10 @@ func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) {
return t, k
}

type diffOptions struct {
ColoredOutput bool
}

// diff returns a diff of both values as long as both are of the same type and
// are a struct, map, slice, array or string. Otherwise it returns an empty string.
func diff(expected interface{}, actual interface{}) string {
Expand Down Expand Up @@ -1808,18 +1812,79 @@ func diff(expected interface{}, actual interface{}) string {
e = spewConfig.Sdump(expected)
a = spewConfig.Sdump(actual)
}
structuredDiff := structuredDiff(e, a)
prettyDiff := prettyDiff(structuredDiff)
return "\n\nDiff:\n" + prettyDiff
}

func structuredDiff(e string, a string) []diffmatchpatch.Diff {
dmp := diffmatchpatch.New()
fromRunes, toRunes, runesToLines := dmp.DiffLinesToRunes(e, a)
diffs := dmp.DiffMainRunes(fromRunes, toRunes, false)
hydrated := make([]diffmatchpatch.Diff, 0, len(diffs))
for _, aDiff := range diffs {
chars := strings.FieldsFunc(aDiff.Text, func(r rune) bool {
return string(r) == diffmatchpatch.IndexSeparator
})
text := make([]string, len(chars))

for i, char := range chars {
i1, err := strconv.Atoi(char)
if err == nil {
text[i] = runesToLines[i1]
}
}
for idx, line := range text {
if aDiff.Type == diffmatchpatch.DiffEqual && idx < len(text)-1 {
continue
}
hydrated = append(hydrated, diffmatchpatch.Diff{
Type: aDiff.Type,
Text: line,
})
}
}
return hydrated
}

diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
A: difflib.SplitLines(e),
B: difflib.SplitLines(a),
FromFile: "Expected",
FromDate: "",
ToFile: "Actual",
ToDate: "",
Context: 1,
})
func prettyDiff(diffs []diffmatchpatch.Diff) string {
var diff strings.Builder

return "\n\nDiff:\n" + diff
diff.WriteString("--- Expected\n+++ Actual\n")

for _, diffChunk := range diffs {
switch diffChunk.Type {
case diffmatchpatch.DiffInsert:
// Make sure the different parts are on separate lines for better readability
// i.e. it makes diffs like +got-expected to go as +got\n-expected\n
if !strings.HasSuffix(diffChunk.Text, "\n") {
diffChunk.Text = diffChunk.Text + "\n"
}
_, _ = fmt.Fprintf(&diff, "+%s", diffChunk.Text)
case diffmatchpatch.DiffDelete:
// Make sure the different parts are on separate lines for better readability
// i.e. it makes diffs like +got-expected to go as +got\n-expected\n
if !strings.HasSuffix(diffChunk.Text, "\n") {
diffChunk.Text = diffChunk.Text + "\n"
}
_, _ = fmt.Fprintf(&diff, "-%s", diffChunk.Text)
default:
if len(diffChunk.Text) == 0 {
continue
}
equalTextByLines := strings.SplitAfter(diffChunk.Text, "\n")
var linesTrimmed []string
for _, line := range equalTextByLines {
if len(line) == 0 {
continue
}
linesTrimmed = append(linesTrimmed, line)
}
// We're not interested in the equal parts, so only keep the last line for some context
_, _ = fmt.Fprintf(&diff, " %s", equalTextByLines[len(linesTrimmed)-1])
}
}
return diff.String()
}

func isFunction(arg interface{}) bool {
Expand Down
31 changes: 10 additions & 21 deletions assert/assertions_test.go
Expand Up @@ -385,11 +385,10 @@ func TestEqualExportedValues(t *testing.T) {
Diff:
--- Expected
+++ Actual
@@ -3,3 +3,3 @@
Exported2: (assert.Nested) {
- Exported: (int) 2,
+ Exported: (int) 1,
notExported: (interface {}) <nil>`,
}`,
},
{
value1: S3{&Nested{1, 2}, &Nested{3, 4}},
Expand All @@ -399,11 +398,10 @@ func TestEqualExportedValues(t *testing.T) {
Diff:
--- Expected
+++ Actual
@@ -2,3 +2,3 @@
Exported1: (*assert.Nested)({
- Exported: (int) 1,
+ Exported: (string) (len=1) "a",
notExported: (interface {}) <nil>`,
}`,
},
{
value1: S4{[]*Nested{
Expand All @@ -419,11 +417,10 @@ func TestEqualExportedValues(t *testing.T) {
Diff:
--- Expected
+++ Actual
@@ -7,3 +7,3 @@
(*assert.Nested)({
- Exported: (int) 3,
+ Exported: (int) 2,
notExported: (interface {}) <nil>`,
}`,
},
{
value1: S{[2]int{1, 2}, Nested{2, 3}, 4, Nested{5, 6}},
Expand Down Expand Up @@ -656,7 +653,7 @@ func TestStringEqual(t *testing.T) {
msgAndArgs []interface{}
want string
}{
{equalWant: "hi, \nmy name is", equalGot: "what,\nmy name is", want: "\tassertions.go:\\d+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"hi, \\\\nmy name is\"\n\\s+actual\\s+: \"what,\\\\nmy name is\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+@@ -1,2 \\+1,2 @@\n\\s+-hi, \n\\s+\\+what,\n\\s+my name is"},
{equalWant: "hi, \nmy name is", equalGot: "what,\nmy name is", want: "\tassertions.go:\\d+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"hi, \\\\nmy name is\"\n\\s+actual\\s+: \"what,\\\\nmy name is\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+-hi, \n\\s+\\+what,\n\\s+my name is"},
} {
mockT := &bufferT{}
Equal(mockT, currCase.equalWant, currCase.equalGot, currCase.msgAndArgs...)
Expand All @@ -671,10 +668,10 @@ func TestEqualFormatting(t *testing.T) {
msgAndArgs []interface{}
want string
}{
{equalWant: "want", equalGot: "got", want: "\tassertions.go:\\d+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"want\"\n\\s+actual\\s+: \"got\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+@@ -1 \\+1 @@\n\\s+-want\n\\s+\\+got\n"},
{equalWant: "want", equalGot: "got", msgAndArgs: []interface{}{"hello, %v!", "world"}, want: "\tassertions.go:[0-9]+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"want\"\n\\s+actual\\s+: \"got\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+@@ -1 \\+1 @@\n\\s+-want\n\\s+\\+got\n\\s+Messages:\\s+hello, world!\n"},
{equalWant: "want", equalGot: "got", msgAndArgs: []interface{}{123}, want: "\tassertions.go:[0-9]+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"want\"\n\\s+actual\\s+: \"got\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+@@ -1 \\+1 @@\n\\s+-want\n\\s+\\+got\n\\s+Messages:\\s+123\n"},
{equalWant: "want", equalGot: "got", msgAndArgs: []interface{}{struct{ a string }{"hello"}}, want: "\tassertions.go:[0-9]+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"want\"\n\\s+actual\\s+: \"got\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+@@ -1 \\+1 @@\n\\s+-want\n\\s+\\+got\n\\s+Messages:\\s+{a:hello}\n"},
{equalWant: "want", equalGot: "got", want: "\tassertions.go:\\d+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"want\"\n\\s+actual\\s+: \"got\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+-want\n\\s+\\+got\n"},
{equalWant: "want", equalGot: "got", msgAndArgs: []interface{}{"hello, %v!", "world"}, want: "\tassertions.go:[0-9]+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"want\"\n\\s+actual\\s+: \"got\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+-want\n\\s+\\+got\n\\s+Messages:\\s+hello, world!\n"},
{equalWant: "want", equalGot: "got", msgAndArgs: []interface{}{123}, want: "\tassertions.go:[0-9]+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"want\"\n\\s+actual\\s+: \"got\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+-want\n\\s+\\+got\n\\s+Messages:\\s+123\n"},
{equalWant: "want", equalGot: "got", msgAndArgs: []interface{}{struct{ a string }{"hello"}}, want: "\tassertions.go:[0-9]+: \n\t+Error Trace:\t\n\t+Error:\\s+Not equal:\\s+\n\\s+expected: \"want\"\n\\s+actual\\s+: \"got\"\n\\s+Diff:\n\\s+-+ Expected\n\\s+\\++ Actual\n\\s+-want\n\\s+\\+got\n\\s+Messages:\\s+{a:hello}\n"},
} {
mockT := &bufferT{}
Equal(mockT, currCase.equalWant, currCase.equalGot, currCase.msgAndArgs...)
Expand Down Expand Up @@ -2301,7 +2298,6 @@ func TestDiff(t *testing.T) {
Diff:
--- Expected
+++ Actual
@@ -1,3 +1,3 @@
(struct { foo string }) {
- foo: (string) (len=5) "hello"
+ foo: (string) (len=3) "bar"
Expand All @@ -2318,7 +2314,6 @@ Diff:
Diff:
--- Expected
+++ Actual
@@ -2,5 +2,5 @@
(int) 1,
- (int) 2,
(int) 3,
Expand All @@ -2338,11 +2333,10 @@ Diff:
Diff:
--- Expected
+++ Actual
@@ -2,4 +2,4 @@
(int) 1,
- (int) 2,
- (int) 3
+ (int) 3,
- (int) 3
+ (int) 5
}
`
Expand All @@ -2357,14 +2351,13 @@ Diff:
Diff:
--- Expected
+++ Actual
@@ -1,6 +1,6 @@
(map[string]int) (len=4) {
- (string) (len=4) "four": (int) 4,
+ (string) (len=4) "five": (int) 5,
(string) (len=3) "one": (int) 1,
- (string) (len=5) "three": (int) 3,
- (string) (len=3) "two": (int) 2
+ (string) (len=5) "seven": (int) 7,
- (string) (len=3) "two": (int) 2
+ (string) (len=5) "three": (int) 3
}
`
Expand All @@ -2380,7 +2373,6 @@ Diff:
Diff:
--- Expected
+++ Actual
@@ -1,3 +1,3 @@
(*errors.errorString)({
- s: (string) (len=19) "some expected error"
+ s: (string) (len=12) "actual error"
Expand All @@ -2398,7 +2390,6 @@ Diff:
Diff:
--- Expected
+++ Actual
@@ -2,3 +2,3 @@
A: (string) (len=11) "some string",
- B: (int) 10
+ B: (int) 15
Expand All @@ -2416,10 +2407,8 @@ Diff:
Diff:
--- Expected
+++ Actual
@@ -1,2 +1,2 @@
-(time.Time) 2020-09-24 00:00:00 +0000 UTC
+(time.Time) 2020-09-25 00:00:00 +0000 UTC
`

actual = diff(
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -7,6 +7,7 @@ go 1.17
require (
github.com/davecgh/go-spew v1.1.1
github.com/pmezard/go-difflib v1.0.0
github.com/sergi/go-diff v1.3.1
github.com/stretchr/objx v0.5.1
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
15 changes: 14 additions & 1 deletion go.sum
@@ -1,10 +1,23 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0=
github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit 31ad2a7

Please sign in to comment.