From 37e48f8aa8a7e398eeea8b2f2b1f2d2a6fee216b Mon Sep 17 00:00:00 2001
From: Xavier Coulon <xcoulon@redhat.com>
Date: Mon, 30 Sep 2019 08:43:03 +0200
Subject: [PATCH] refactor(test): improve failure messages in custom matchers
 (#422)

display a readable diff of the "processed actual" vs "expected"
values

Fixes #421

Signed-off-by: Xavier Coulon <xcoulon@redhat.com>
---
 testsupport/comparison.go                     | 32 +++++++++++++++++
 testsupport/comparison_test.go                | 20 +++++++++++
 testsupport/console_matcher.go                |  8 ++---
 testsupport/document_block_matcher.go         | 24 +++++--------
 testsupport/document_block_matcher_test.go    |  4 +--
 testsupport/document_matcher.go               | 17 ++++-----
 testsupport/document_matcher_test.go          |  4 +--
 testsupport/document_metadata_matcher.go      | 17 ++++-----
 testsupport/document_metadata_matcher_test.go | 10 ++++--
 testsupport/document_preamble_matcher.go      | 17 ++++-----
 testsupport/document_preamble_matcher_test.go | 18 +++++-----
 testsupport/document_toc_matcher.go           | 17 ++++-----
 testsupport/document_toc_matcher_test.go      |  4 +--
 testsupport/html5_rendering_matcher.go        | 35 +++++++++----------
 testsupport/html5_rendering_matcher_test.go   |  4 +--
 testsupport/preflight_document_matcher.go     | 16 ++++-----
 .../preflight_document_matcher_test.go        |  9 +++--
 .../replace_non_alphanumeric_matcher_test.go  |  4 +--
 .../replace_non_alphanumerics_matcher.go      | 16 +++++----
 19 files changed, 164 insertions(+), 112 deletions(-)
 create mode 100644 testsupport/comparison.go
 create mode 100644 testsupport/comparison_test.go

diff --git a/testsupport/comparison.go b/testsupport/comparison.go
new file mode 100644
index 00000000..7efd8a26
--- /dev/null
+++ b/testsupport/comparison.go
@@ -0,0 +1,32 @@
+package testsupport
+
+import (
+	"reflect"
+
+	"github.com/davecgh/go-spew/spew"
+	. "github.com/onsi/ginkgo" // nolint: go-lint
+	"github.com/sergi/go-diff/diffmatchpatch"
+)
+
+type comparison struct {
+	actual   string
+	expected string
+	diffs    string
+}
+
+// compare compares the 'actual' vs 'expected' values and produces a 'comparison' report
+func compare(actual interface{}, expected interface{}) comparison {
+	c := comparison{
+		actual:   spew.Sdump(actual),
+		expected: spew.Sdump(expected),
+	}
+	if !reflect.DeepEqual(actual, expected) {
+		dmp := diffmatchpatch.New()
+		diffs := dmp.DiffMain(c.actual, c.expected, true)
+		c.diffs = dmp.DiffPrettyText(diffs)
+	}
+	GinkgoT().Logf("actual:\n%s", c.actual)
+	GinkgoT().Logf("expected:\n%s", c.expected)
+	GinkgoT().Logf("diff:\n%s", c.diffs)
+	return c
+}
diff --git a/testsupport/comparison_test.go b/testsupport/comparison_test.go
new file mode 100644
index 00000000..3dc2392d
--- /dev/null
+++ b/testsupport/comparison_test.go
@@ -0,0 +1,20 @@
+package testsupport_test
+
+import (
+	"reflect"
+
+	"github.com/davecgh/go-spew/spew"
+	"github.com/sergi/go-diff/diffmatchpatch"
+)
+
+// compare compares the 'actual' vs 'expected' values and produces a 'comparison' report
+func compare(actual interface{}, expected interface{}) string {
+	a := spew.Sdump(actual)
+	e := spew.Sdump(expected)
+	if reflect.DeepEqual(actual, expected) {
+		return ""
+	}
+	dmp := diffmatchpatch.New()
+	diffs := dmp.DiffMain(a, e, true)
+	return dmp.DiffPrettyText(diffs)
+}
diff --git a/testsupport/console_matcher.go b/testsupport/console_matcher.go
index 5ce984b6..a8b42f4f 100644
--- a/testsupport/console_matcher.go
+++ b/testsupport/console_matcher.go
@@ -74,11 +74,11 @@ func (m *containMessageMatcher) Match(actual interface{}) (success bool, err err
 	return false, nil
 }
 
-func (m *containMessageMatcher) FailureMessage(actual interface{}) (message string) {
+func (m *containMessageMatcher) FailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected console to contain message '%s' with level '%v'", m.msg, m.level)
 }
 
-func (m *containMessageMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+func (m *containMessageMatcher) NegatedFailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected console not to contain message '%s' with level '%v'", m.msg, m.level)
 }
 
@@ -121,10 +121,10 @@ func (m *containAnyMessageMatcher) Match(actual interface{}) (success bool, err
 	return false, nil
 }
 
-func (m *containAnyMessageMatcher) FailureMessage(actual interface{}) (message string) {
+func (m *containAnyMessageMatcher) FailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected console to contain a message at level '%v'", m.levels)
 }
 
-func (m *containAnyMessageMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+func (m *containAnyMessageMatcher) NegatedFailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected console not to contain a message at level '%v'", m.levels)
 }
diff --git a/testsupport/document_block_matcher.go b/testsupport/document_block_matcher.go
index 0c9d379a..847e1b64 100644
--- a/testsupport/document_block_matcher.go
+++ b/testsupport/document_block_matcher.go
@@ -2,16 +2,12 @@ package testsupport
 
 import (
 	"fmt"
-	"reflect"
 	"strings"
 
 	"github.com/bytesparadise/libasciidoc/pkg/parser"
-	"github.com/davecgh/go-spew/spew"
 
-	. "github.com/onsi/ginkgo"
 	"github.com/onsi/gomega/types"
 	"github.com/pkg/errors"
-	log "github.com/sirupsen/logrus"
 )
 
 // EqualDocumentBlock a custom matcher to verify that a document block matches the expectation
@@ -22,8 +18,9 @@ func EqualDocumentBlock(expected interface{}) types.GomegaMatcher {
 }
 
 type documentBlockMatcher struct {
-	expected interface{}
-	actual   interface{}
+	expected   interface{}
+	actual     interface{}
+	comparison comparison
 }
 
 func (m *documentBlockMatcher) Match(actual interface{}) (success bool, err error) {
@@ -40,17 +37,14 @@ func (m *documentBlockMatcher) Match(actual interface{}) (success bool, err erro
 	if err != nil {
 		return false, err
 	}
-	if log.IsLevelEnabled(log.DebugLevel) {
-		GinkgoT().Logf("actual:\n%v", spew.Sdump(m.actual))
-		GinkgoT().Logf("expected:\n%v", spew.Sdump(m.expected))
-	}
-	return reflect.DeepEqual(m.expected, m.actual), nil
+	m.comparison = compare(m.actual, m.expected)
+	return m.comparison.diffs == "", nil
 }
 
-func (m *documentBlockMatcher) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected document blocks to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *documentBlockMatcher) FailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected document blocks to match:\n%s", m.comparison.diffs)
 }
 
-func (m *documentBlockMatcher) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected document blocks not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *documentBlockMatcher) NegatedFailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected document blocks not to match:\n%s", m.comparison.diffs)
 }
diff --git a/testsupport/document_block_matcher_test.go b/testsupport/document_block_matcher_test.go
index 7d13c008..a9e4f8ae 100644
--- a/testsupport/document_block_matcher_test.go
+++ b/testsupport/document_block_matcher_test.go
@@ -54,8 +54,8 @@ var _ = Describe("document block assertions", func() {
 				},
 			},
 		}
-		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document blocks to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
-		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document blocks not to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
+		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document blocks to match:\n%s", compare(obtained, expected))))
+		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document blocks not to match:\n%s", compare(obtained, expected))))
 	})
 
 	It("should return error when invalid type is input", func() {
diff --git a/testsupport/document_matcher.go b/testsupport/document_matcher.go
index e1b323ca..e3dd698d 100644
--- a/testsupport/document_matcher.go
+++ b/testsupport/document_matcher.go
@@ -2,7 +2,6 @@ package testsupport
 
 import (
 	"fmt"
-	"reflect"
 	"strings"
 
 	"github.com/bytesparadise/libasciidoc/pkg/parser"
@@ -20,8 +19,9 @@ func EqualDocument(expected types.Document) gomegatypes.GomegaMatcher {
 }
 
 type documentMatcher struct {
-	expected types.Document
-	actual   types.Document
+	expected   types.Document
+	actual     types.Document
+	comparison comparison
 }
 
 func (m *documentMatcher) Match(actual interface{}) (success bool, err error) {
@@ -34,13 +34,14 @@ func (m *documentMatcher) Match(actual interface{}) (success bool, err error) {
 	if err != nil {
 		return false, err
 	}
-	return reflect.DeepEqual(m.expected, m.actual), nil
+	m.comparison = compare(m.actual, m.expected)
+	return m.comparison.diffs == "", nil
 }
 
-func (m *documentMatcher) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected documents to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *documentMatcher) FailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected documents to match:\n%s", m.comparison.diffs)
 }
 
-func (m *documentMatcher) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected documents not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *documentMatcher) NegatedFailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected documents not to match:\n%s", m.comparison.diffs)
 }
diff --git a/testsupport/document_matcher_test.go b/testsupport/document_matcher_test.go
index 9defb9e0..20f0ef36 100644
--- a/testsupport/document_matcher_test.go
+++ b/testsupport/document_matcher_test.go
@@ -70,8 +70,8 @@ var _ = Describe("document assertions", func() {
 				},
 			},
 		}
-		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
-		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents not to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
+		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents to match:\n%s", compare(obtained, expected))))
+		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents not to match:\n%s", compare(obtained, expected))))
 	})
 
 	It("should return error when invalid type is input", func() {
diff --git a/testsupport/document_metadata_matcher.go b/testsupport/document_metadata_matcher.go
index 3e5ffa9e..2b0c9e28 100644
--- a/testsupport/document_metadata_matcher.go
+++ b/testsupport/document_metadata_matcher.go
@@ -3,7 +3,6 @@ package testsupport
 import (
 	"context"
 	"fmt"
-	"reflect"
 
 	"github.com/bytesparadise/libasciidoc/pkg/renderer"
 	"github.com/bytesparadise/libasciidoc/pkg/types"
@@ -20,8 +19,9 @@ func HaveMetadata(expected interface{}) gomegatypes.GomegaMatcher {
 }
 
 type metadataMatcher struct {
-	expected interface{}
-	actual   types.DocumentAttributes
+	expected   interface{}
+	actual     interface{}
+	comparison comparison
 }
 
 func (m *metadataMatcher) Match(actual interface{}) (success bool, err error) {
@@ -32,13 +32,14 @@ func (m *metadataMatcher) Match(actual interface{}) (success bool, err error) {
 	ctx := renderer.Wrap(context.Background(), source)
 	renderer.ProcessDocumentHeader(ctx)
 	m.actual = ctx.Document.Attributes
-	return reflect.DeepEqual(m.expected, m.actual), nil
+	m.comparison = compare(m.actual, m.expected)
+	return m.comparison.diffs == "", nil
 }
 
-func (m *metadataMatcher) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected document metadata to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *metadataMatcher) FailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected document metadata to match:\n%s", m.comparison.diffs)
 }
 
-func (m *metadataMatcher) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected document metadata not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *metadataMatcher) NegatedFailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected document metadata not to match:\n%s", m.comparison.diffs)
 }
diff --git a/testsupport/document_metadata_matcher_test.go b/testsupport/document_metadata_matcher_test.go
index 9e15f960..23a3fb05 100644
--- a/testsupport/document_metadata_matcher_test.go
+++ b/testsupport/document_metadata_matcher_test.go
@@ -1,8 +1,10 @@
 package testsupport_test
 
 import (
+	"context"
 	"fmt"
 
+	"github.com/bytesparadise/libasciidoc/pkg/renderer"
 	"github.com/bytesparadise/libasciidoc/pkg/types"
 	"github.com/bytesparadise/libasciidoc/testsupport"
 
@@ -52,9 +54,11 @@ var _ = Describe("document metadata assertions", func() {
 		Expect(err).ToNot(HaveOccurred())
 		Expect(result).To(BeFalse())
 		// also verify the messages
-		obtained := types.DocumentAttributes{}
-		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
-		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata not to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
+		ctx := renderer.Wrap(context.Background(), actual)
+		renderer.ProcessDocumentHeader(ctx)
+		obtained := ctx.Document.Attributes
+		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata to match:\n%s", compare(obtained, expected))))
+		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata not to match:\n%s", compare(obtained, expected))))
 	})
 
 	It("should return error when invalid type is input", func() {
diff --git a/testsupport/document_preamble_matcher.go b/testsupport/document_preamble_matcher.go
index 71393590..73b1032b 100644
--- a/testsupport/document_preamble_matcher.go
+++ b/testsupport/document_preamble_matcher.go
@@ -3,7 +3,6 @@ package testsupport
 import (
 	"context"
 	"fmt"
-	"reflect"
 
 	"github.com/bytesparadise/libasciidoc/pkg/renderer"
 	"github.com/bytesparadise/libasciidoc/pkg/types"
@@ -20,8 +19,9 @@ func HavePreamble(expected types.Document) gomegatypes.GomegaMatcher {
 }
 
 type preambleMatcher struct {
-	expected types.Document
-	actual   types.Document
+	expected   types.Document
+	actual     types.Document
+	comparison comparison
 }
 
 func (m *preambleMatcher) Match(actual interface{}) (success bool, err error) {
@@ -32,13 +32,14 @@ func (m *preambleMatcher) Match(actual interface{}) (success bool, err error) {
 	ctx := renderer.Wrap(context.Background(), doc)
 	renderer.IncludePreamble(ctx)
 	m.actual = ctx.Document
-	return reflect.DeepEqual(m.expected, m.actual), nil
+	m.comparison = compare(m.actual, m.expected)
+	return m.comparison.diffs == "", nil
 }
 
-func (m *preambleMatcher) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected document to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *preambleMatcher) FailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected documents to match:\n%s", m.comparison.diffs)
 }
 
-func (m *preambleMatcher) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected document not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *preambleMatcher) NegatedFailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected documents not to match:\n%s", m.comparison.diffs)
 }
diff --git a/testsupport/document_preamble_matcher_test.go b/testsupport/document_preamble_matcher_test.go
index 15384be8..7aeb718a 100644
--- a/testsupport/document_preamble_matcher_test.go
+++ b/testsupport/document_preamble_matcher_test.go
@@ -1,8 +1,10 @@
 package testsupport_test
 
 import (
+	"context"
 	"fmt"
 
+	"github.com/bytesparadise/libasciidoc/pkg/renderer"
 	"github.com/bytesparadise/libasciidoc/pkg/types"
 	"github.com/bytesparadise/libasciidoc/testsupport"
 
@@ -103,15 +105,13 @@ var _ = Describe("document preamble assertions", func() {
 		Expect(err).ToNot(HaveOccurred())
 		Expect(result).To(BeFalse())
 		// also verify messages
-		obtained := types.Document{
-			Attributes:         types.DocumentAttributes{},
-			ElementReferences:  types.ElementReferences{},
-			Footnotes:          types.Footnotes{},
-			FootnoteReferences: types.FootnoteReferences{},
-			Elements:           []interface{}{},
-		}
-		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
-		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document not to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
+		ctx := renderer.Wrap(context.Background(), actual)
+		renderer.IncludePreamble(ctx)
+		obtained := ctx.Document
+		GinkgoT().Logf(matcher.FailureMessage(actual))
+		GinkgoT().Logf(fmt.Sprintf("expected documents to match:\n%s", compare(obtained, expected)))
+		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents to match:\n%s", compare(obtained, expected))))
+		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents not to match:\n%s", compare(obtained, expected))))
 	})
 
 	It("should return error when invalid type is input", func() {
diff --git a/testsupport/document_toc_matcher.go b/testsupport/document_toc_matcher.go
index 5b7e8404..b44c58f6 100644
--- a/testsupport/document_toc_matcher.go
+++ b/testsupport/document_toc_matcher.go
@@ -3,7 +3,6 @@ package testsupport
 import (
 	"context"
 	"fmt"
-	"reflect"
 
 	"github.com/bytesparadise/libasciidoc/pkg/renderer"
 	"github.com/bytesparadise/libasciidoc/pkg/types"
@@ -20,8 +19,9 @@ func HaveTableOfContents(expected types.Document) gomegatypes.GomegaMatcher {
 }
 
 type tocMatcher struct {
-	expected types.Document
-	actual   types.Document
+	expected   types.Document
+	actual     types.Document
+	comparison comparison
 }
 
 func (m *tocMatcher) Match(actual interface{}) (success bool, err error) {
@@ -32,13 +32,14 @@ func (m *tocMatcher) Match(actual interface{}) (success bool, err error) {
 	ctx := renderer.Wrap(context.Background(), doc)
 	renderer.IncludeTableOfContents(ctx)
 	m.actual = ctx.Document
-	return reflect.DeepEqual(m.expected, m.actual), nil
+	m.comparison = compare(m.actual, m.expected)
+	return m.comparison.diffs == "", nil
 }
 
-func (m *tocMatcher) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected document to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *tocMatcher) FailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected documents to match:\n%s", m.comparison.diffs)
 }
 
-func (m *tocMatcher) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected document not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *tocMatcher) NegatedFailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected documents not to match:\n%s", m.comparison.diffs)
 }
diff --git a/testsupport/document_toc_matcher_test.go b/testsupport/document_toc_matcher_test.go
index 3dd0add8..ce197929 100644
--- a/testsupport/document_toc_matcher_test.go
+++ b/testsupport/document_toc_matcher_test.go
@@ -82,8 +82,8 @@ var _ = Describe("document table of contents assertions", func() {
 		// then
 		Expect(err).ToNot(HaveOccurred())
 		Expect(result).To(BeFalse())
-		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, types.Document{})))
-		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document not to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, types.Document{})))
+		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents to match:\n%s", compare(types.Document{}, expected))))
+		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents not to match:\n%s", compare(types.Document{}, expected))))
 	})
 
 	It("should return error when invalid type is input", func() {
diff --git a/testsupport/html5_rendering_matcher.go b/testsupport/html5_rendering_matcher.go
index a2741293..93decbfd 100644
--- a/testsupport/html5_rendering_matcher.go
+++ b/testsupport/html5_rendering_matcher.go
@@ -13,10 +13,8 @@ import (
 	"github.com/bytesparadise/libasciidoc/pkg/renderer/html5"
 	"github.com/bytesparadise/libasciidoc/pkg/types"
 
-	. "github.com/onsi/ginkgo" // nolint: golint
 	gomegatypes "github.com/onsi/gomega/types"
 	"github.com/pkg/errors"
-	"github.com/sergi/go-diff/diffmatchpatch"
 )
 
 // --------------------
@@ -32,9 +30,10 @@ func RenderHTML5Element(expected string, opts ...renderer.Option) gomegatypes.Go
 }
 
 type html5ElementMatcher struct {
-	expected string
-	actual   string
-	opts     []renderer.Option
+	opts       []renderer.Option
+	expected   string
+	actual     string
+	comparison comparison
 }
 
 func (m *html5ElementMatcher) Match(actual interface{}) (success bool, err error) {
@@ -62,18 +61,16 @@ func (m *html5ElementMatcher) Match(actual interface{}) (success bool, err error
 		m.expected = strings.Replace(m.expected, "{{.LastUpdated}}", rendererCtx.LastUpdated(), 1)
 	}
 	m.actual = buff.String()
-	dmp := diffmatchpatch.New()
-	diffs := dmp.DiffMain(m.actual, m.expected, true)
-	GinkgoT().Log("%v", dmp.DiffPrettyText(diffs))
-	return m.expected == m.actual, nil
+	m.comparison = compare(m.actual, m.expected)
+	return m.comparison.diffs == "", nil
 }
 
-func (m *html5ElementMatcher) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected HTML5 elements to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *html5ElementMatcher) FailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected HTML5 elements to match:\n%s", m.comparison.diffs)
 }
 
-func (m *html5ElementMatcher) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected HTML5 elements not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *html5ElementMatcher) NegatedFailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected HTML5 elements not to match:\n%s", m.comparison.diffs)
 }
 
 // --------------------
@@ -109,11 +106,11 @@ func (m *html5BodyMatcher) Match(actual interface{}) (success bool, err error) {
 	return m.expected == m.actual, nil
 }
 
-func (m *html5BodyMatcher) FailureMessage(actual interface{}) (message string) {
+func (m *html5BodyMatcher) FailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected HTML5 bodies to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
 }
 
-func (m *html5BodyMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+func (m *html5BodyMatcher) NegatedFailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected HTML5 bodies not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
 }
 
@@ -163,11 +160,11 @@ func (m *html5TitleMatcher) Match(actual interface{}) (success bool, err error)
 	return m.expected == m.actual, nil
 }
 
-func (m *html5TitleMatcher) FailureMessage(actual interface{}) (message string) {
+func (m *html5TitleMatcher) FailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected HTML5 titles to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
 }
 
-func (m *html5TitleMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+func (m *html5TitleMatcher) NegatedFailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected HTML5 titles not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
 }
 
@@ -206,10 +203,10 @@ func (m *html5DocumentMatcher) Match(actual interface{}) (success bool, err erro
 	return m.expected == m.actual, nil
 }
 
-func (m *html5DocumentMatcher) FailureMessage(actual interface{}) (message string) {
+func (m *html5DocumentMatcher) FailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected HTML5 bodies to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
 }
 
-func (m *html5DocumentMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+func (m *html5DocumentMatcher) NegatedFailureMessage(_ interface{}) (message string) {
 	return fmt.Sprintf("expected HTML5 bodies not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
 }
diff --git a/testsupport/html5_rendering_matcher_test.go b/testsupport/html5_rendering_matcher_test.go
index d8dc499e..a24a913c 100644
--- a/testsupport/html5_rendering_matcher_test.go
+++ b/testsupport/html5_rendering_matcher_test.go
@@ -41,8 +41,8 @@ var _ = Describe("html5 rendering assertions", func() {
 			obtained := `<div class="paragraph">
 <p>foo</p>
 </div>`
-			Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected HTML5 elements to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
-			Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected HTML5 elements not to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, obtained)))
+			Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected HTML5 elements to match:\n%s", compare(obtained, expected))))
+			Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected HTML5 elements not to match:\n%s", compare(obtained, expected))))
 		})
 
 		It("should return error when invalid type is input", func() {
diff --git a/testsupport/preflight_document_matcher.go b/testsupport/preflight_document_matcher.go
index 698a3cde..6b87b797 100644
--- a/testsupport/preflight_document_matcher.go
+++ b/testsupport/preflight_document_matcher.go
@@ -2,7 +2,6 @@ package testsupport
 
 import (
 	"fmt"
-	"reflect"
 	"strings"
 
 	"github.com/bytesparadise/libasciidoc/pkg/parser"
@@ -28,9 +27,10 @@ func BecomePreflightDocumentWithoutPreprocessing(expected interface{}) types.Gom
 }
 
 type preflightDocumentMatcher struct {
+	preprocessing bool
 	expected      interface{}
 	actual        interface{}
-	preprocessing bool
+	comparison    comparison
 }
 
 func (m *preflightDocumentMatcher) Match(actual interface{}) (success bool, err error) {
@@ -41,20 +41,20 @@ func (m *preflightDocumentMatcher) Match(actual interface{}) (success bool, err
 	r := strings.NewReader(content)
 	if !m.preprocessing {
 		m.actual, err = parser.ParseReader("", r, parser.Entrypoint("PreflightDocument"))
-
 	} else {
 		m.actual, err = parser.ParsePreflightDocument("test.adoc", r)
 	}
 	if err != nil {
 		return false, err
 	}
-	return reflect.DeepEqual(m.expected, m.actual), nil
+	m.comparison = compare(m.actual, m.expected)
+	return m.comparison.diffs == "", nil
 }
 
-func (m *preflightDocumentMatcher) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected preflight documents to match:\n\texpected: '%v'\n\tactual'%v'", m.expected, m.actual)
+func (m *preflightDocumentMatcher) FailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected preflight documents to match:\n%s", m.comparison.diffs)
 }
 
-func (m *preflightDocumentMatcher) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected preflight documents not to match:\n\texpected: '%v'\n\tactual'%v'", m.expected, m.actual)
+func (m *preflightDocumentMatcher) NegatedFailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected preflight documents not to match:\n%s", m.comparison.diffs)
 }
diff --git a/testsupport/preflight_document_matcher_test.go b/testsupport/preflight_document_matcher_test.go
index b7f064a0..d7448588 100644
--- a/testsupport/preflight_document_matcher_test.go
+++ b/testsupport/preflight_document_matcher_test.go
@@ -64,8 +64,8 @@ var _ = Describe("preflight document assertions", func() {
 					},
 				},
 			}
-			Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected preflight documents to match:\n\texpected: '%v'\n\tactual'%v'", expected, obtained)))
-			Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected preflight documents not to match:\n\texpected: '%v'\n\tactual'%v'", expected, obtained)))
+			Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected preflight documents to match:\n%s", compare(obtained, expected))))
+			Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected preflight documents not to match:\n%s", compare(obtained, expected))))
 		})
 
 		It("should return error when invalid type is input", func() {
@@ -130,9 +130,8 @@ var _ = Describe("preflight document assertions", func() {
 					},
 				},
 			}
-			Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected preflight documents to match:\n\texpected: '%v'\n\tactual'%v'", expected, obtained)))
-			Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected preflight documents not to match:\n\texpected: '%v'\n\tactual'%v'", expected, obtained)))
-
+			Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected preflight documents to match:\n%s", compare(obtained, expected))))
+			Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected preflight documents not to match:\n%s", compare(obtained, expected))))
 		})
 	})
 
diff --git a/testsupport/replace_non_alphanumeric_matcher_test.go b/testsupport/replace_non_alphanumeric_matcher_test.go
index a0bf01bb..c8f26cd2 100644
--- a/testsupport/replace_non_alphanumeric_matcher_test.go
+++ b/testsupport/replace_non_alphanumeric_matcher_test.go
@@ -43,8 +43,8 @@ var _ = Describe("non-alphanumeric replacement assertions", func() {
 		Expect(err).ToNot(HaveOccurred())
 		Expect(result).To(BeFalse())
 		// also verify the messages
-		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected non alphanumeric values to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, "foobar")))
-		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected non alphanumeric values not to match:\n\texpected: '%v'\n\tactual:   '%v'", expected, "foobar")))
+		Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected non-alphanumeric values to match:\n%s", compare("foobar", expected))))
+		Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected non-alphanumeric values not to match:\n%s", compare("foobar", expected))))
 	})
 
 	It("should return error when invalid type is input", func() {
diff --git a/testsupport/replace_non_alphanumerics_matcher.go b/testsupport/replace_non_alphanumerics_matcher.go
index 9c92680b..470e8ce9 100644
--- a/testsupport/replace_non_alphanumerics_matcher.go
+++ b/testsupport/replace_non_alphanumerics_matcher.go
@@ -17,8 +17,9 @@ func EqualWithoutNonAlphanumeric(expected string) gomegatypes.GomegaMatcher {
 }
 
 type nonalphanumericMatcher struct {
-	expected string
-	actual   string
+	expected   string
+	actual     string
+	comparison comparison
 }
 
 func (m *nonalphanumericMatcher) Match(actual interface{}) (success bool, err error) {
@@ -30,13 +31,14 @@ func (m *nonalphanumericMatcher) Match(actual interface{}) (success bool, err er
 	if err != nil {
 		return false, err
 	}
-	return m.expected == m.actual, nil
+	m.comparison = compare(m.actual, m.expected)
+	return m.comparison.diffs == "", nil
 }
 
-func (m *nonalphanumericMatcher) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected non alphanumeric values to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *nonalphanumericMatcher) FailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected non-alphanumeric values to match:\n%s", m.comparison.diffs)
 }
 
-func (m *nonalphanumericMatcher) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected non alphanumeric values not to match:\n\texpected: '%v'\n\tactual:   '%v'", m.expected, m.actual)
+func (m *nonalphanumericMatcher) NegatedFailureMessage(_ interface{}) (message string) {
+	return fmt.Sprintf("expected non-alphanumeric values not to match:\n%s", m.comparison.diffs)
 }