Merge pull request #190 from ericTsiliacos/what-if-we-diff-some-strings
Attempt to diff long strings when not equal
diff --git a/format/format.go b/format/format.go
index fde341f..f070906 100644
--- a/format/format.go
+++ b/format/format.go
@@ -70,6 +70,74 @@
}
/*
+
+Generates a nicely formatted matcher success / failure message
+
+Much like Message(...), but it attempts to pretty print diffs in strings
+
+Expected
+ <string>: "...aaaaabaaaaa..."
+to equal |
+ <string>: "...aaaaazaaaaa..."
+
+*/
+
+func MessageWithDiff(actual, message, expected string) string {
+ if len(actual) >= truncateThreshold && len(expected) >= truncateThreshold {
+ diffPoint := findFirstMismatch(actual, expected)
+ formattedActual := truncateAndFormat(actual, diffPoint)
+ formattedExpected := truncateAndFormat(expected, diffPoint)
+
+ spacesBeforeFormattedMismatch := findFirstMismatch(formattedActual, formattedExpected)
+
+ tabLength := 4
+ spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)
+ padding := strings.Repeat(" ", spaceFromMessageToActual+spacesBeforeFormattedMismatch) + "|"
+ return Message(formattedActual, message+padding, formattedExpected)
+ }
+ return Message(actual, message, expected)
+}
+
+func truncateAndFormat(str string, index int) string {
+ leftPadding := `...`
+ rightPadding := `...`
+
+ start := index - charactersAroundMismatchToInclude
+ if start < 0 {
+ start = 0
+ leftPadding = ""
+ }
+
+ // slice index must include the mis-matched character
+ lengthOfMismatchedCharacter := 1
+ end := index + charactersAroundMismatchToInclude + lengthOfMismatchedCharacter
+ if end > len(str) {
+ end = len(str)
+ rightPadding = ""
+
+ }
+ return fmt.Sprintf("\"%s\"", leftPadding+str[start:end]+rightPadding)
+}
+
+func findFirstMismatch(a, b string) int {
+ aSlice := strings.Split(a, "")
+ bSlice := strings.Split(b, "")
+
+ for index, str := range aSlice {
+ if str != bSlice[index] {
+ return index
+ }
+ }
+
+ return 0
+}
+
+const (
+ truncateThreshold = 50
+ charactersAroundMismatchToInclude = 5
+)
+
+/*
Pretty prints the passed in object at the passed in indentation level.
Object recurses into deeply nested objects emitting pretty-printed representations of their components.
diff --git a/format/format_test.go b/format/format_test.go
index a27cea2..0814a2b 100644
--- a/format/format_test.go
+++ b/format/format_test.go
@@ -130,6 +130,29 @@
})
})
+ Describe("MessageWithDiff", func() {
+ It("shows the exact point where two long strings differ", func() {
+ stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+ Ω(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedLongStringFailureMessage))
+ })
+
+ It("truncates the start of long strings that differ only at their end", func() {
+ stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
+ stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz"
+
+ Ω(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedTruncatedStartStringFailureMessage))
+ })
+
+ It("truncates the end of long strings that differ only at their start", func() {
+ stringWithB := "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ stringWithZ := "zaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+ Ω(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedTruncatedEndStringFailureMessage))
+ })
+ })
+
Describe("IndentString", func() {
It("should indent the string", func() {
Ω(IndentString("foo\n bar\nbaz", 2)).Should(Equal(" foo\n bar\n baz"))
@@ -526,3 +549,28 @@
})
})
})
+
+var expectedShortStringFailureMessage = strings.TrimSpace(`
+Expected
+ <string>: tim
+to equal
+ <string>: eric
+`)
+var expectedLongStringFailureMessage = strings.TrimSpace(`
+Expected
+ <string>: "...aaaaabaaaaa..."
+to equal |
+ <string>: "...aaaaazaaaaa..."
+`)
+var expectedTruncatedEndStringFailureMessage = strings.TrimSpace(`
+Expected
+ <string>: "baaaaa..."
+to equal |
+ <string>: "zaaaaa..."
+`)
+var expectedTruncatedStartStringFailureMessage = strings.TrimSpace(`
+Expected
+ <string>: "...aaaaab"
+to equal |
+ <string>: "...aaaaaz"
+`)
diff --git a/matchers/equal_matcher.go b/matchers/equal_matcher.go
index d186597..874e6a6 100644
--- a/matchers/equal_matcher.go
+++ b/matchers/equal_matcher.go
@@ -19,6 +19,12 @@
}
func (matcher *EqualMatcher) FailureMessage(actual interface{}) (message string) {
+ actualString, actualOK := actual.(string)
+ expectedString, expectedOK := matcher.Expected.(string)
+ if actualOK && expectedOK {
+ return format.MessageWithDiff(actualString, "to equal", expectedString)
+ }
+
return format.Message(actual, "to equal", matcher.Expected)
}
diff --git a/matchers/equal_matcher_test.go b/matchers/equal_matcher_test.go
index ef0d137..2add0b7 100644
--- a/matchers/equal_matcher_test.go
+++ b/matchers/equal_matcher_test.go
@@ -2,6 +2,8 @@
import (
"errors"
+ "strings"
+
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/matchers"
@@ -41,4 +43,36 @@
Ω(myCustomType{s: "foo", n: 3, f: 2.0, arr: []string{"a", "b"}}).ShouldNot(Equal(myCustomType{s: "foo", n: 3, f: 2.0, arr: []string{"a", "b", "c"}}))
})
})
+
+ Describe("failure messages", func() {
+ It("shows the two strings simply when they are short", func() {
+ subject := EqualMatcher{Expected: "eric"}
+
+ failureMessage := subject.FailureMessage("tim")
+ Ω(failureMessage).To(BeEquivalentTo(expectedShortStringFailureMessage))
+ })
+
+ It("shows the exact point where two long strings differ", func() {
+ stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+ subject := EqualMatcher{Expected: stringWithZ}
+
+ failureMessage := subject.FailureMessage(stringWithB)
+ Ω(failureMessage).To(BeEquivalentTo(expectedLongStringFailureMessage))
+ })
+ })
})
+
+var expectedShortStringFailureMessage = strings.TrimSpace(`
+Expected
+ <string>: tim
+to equal
+ <string>: eric
+`)
+var expectedLongStringFailureMessage = strings.TrimSpace(`
+Expected
+ <string>: "...aaaaabaaaaa..."
+to equal |
+ <string>: "...aaaaazaaaaa..."
+`)