From 9337ead2bc2ceca82f729c852e688df4c9ac55a8 Mon Sep 17 00:00:00 2001 From: dualfroz Date: Wed, 2 Sep 2026 09:43:14 +0200 Subject: [PATCH] feat(assert): use String() for Stringer types in diff output Values whose type, or slice or array element type, implements fmt.Stringer are now rendered through their String method in the diff section of a failed assertion. Previously a named string such as peer.ID was dumped as raw escaped bytes, which is unreadable. This affects diff presentation only. Equality is still decided by ObjectsAreEqual and is unaffected. The default spew config keeps DisableMethods for every other type, matching the existing time.Time handling in diff. Refs #1215 --- assert/assertions.go | 28 ++++++++++++++++++++++++++-- assert/assertions_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 166f637..176e9dd 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1955,8 +1955,13 @@ func diff(expected interface{}, actual interface{}) string { e = spewConfigStringerEnabled.Sdump(expected) a = spewConfigStringerEnabled.Sdump(actual) default: - e = spewConfig.Sdump(expected) - a = spewConfig.Sdump(actual) + if hasStringer(et) { + e = spewConfigStringerEnabled.Sdump(expected) + a = spewConfigStringerEnabled.Sdump(actual) + } else { + e = spewConfig.Sdump(expected) + a = spewConfig.Sdump(actual) + } } diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ @@ -1972,6 +1977,25 @@ func diff(expected interface{}, actual interface{}) string { return "\n\nDiff:\n" + diff } +var stringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() + +// hasStringer reports whether t, or the element type of a slice or array, +// implements fmt.Stringer. It lets diff render such values through the +// Stringer-enabled spew config, so a named string like peer.ID is shown by its +// String output instead of its raw bytes. This influences diff presentation +// only: equality is decided by ObjectsAreEqual and is unaffected, and the +// default spew config keeps DisableMethods for every other type. +func hasStringer(t reflect.Type) bool { + if t.Implements(stringerType) { + return true + } + switch t.Kind() { + case reflect.Slice, reflect.Array: + return t.Elem().Implements(stringerType) + } + return false +} + func isFunction(arg interface{}) bool { if arg == nil { return false diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 11642e0..16b819c 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -2902,6 +2902,12 @@ func (d *diffTestingStruct) String() string { return d.A } +type diffTestingStringer string + +func (id diffTestingStringer) String() string { + return "stringer:" + string(id) +} + func TestDiff(t *testing.T) { t.Parallel() @@ -3036,6 +3042,24 @@ Diff: time.Date(2020, 9, 25, 0, 0, 0, 0, time.UTC), ) Equal(t, expected, actual) + + expected = ` + +Diff: +--- Expected ++++ Actual +@@ -2,3 +2,3 @@ + (assert.diffTestingStringer) (len=3) stringer:one, +- (assert.diffTestingStringer) (len=3) stringer:two ++ (assert.diffTestingStringer) (len=5) stringer:three + } +` + + actual = diff( + []diffTestingStringer{"one", "two"}, + []diffTestingStringer{"one", "three"}, + ) + Equal(t, expected, actual) } func TestTimeEqualityErrorFormatting(t *testing.T) {