aboutsummaryrefslogtreecommitdiff
path: root/assert/equal.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-06-20 20:27:48 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-06-20 20:27:48 +0200
commit6e285f65c5a5ed8e27857a3688a7c86b37688b13 (patch)
tree3050315491ff0c53ec6eab0affaf5fbea5a8ef97 /assert/equal.go
parentc0ca4d891f08a5baef22bee16a1a4feb0089bbae (diff)
downloadgo-gg-6e285f65c5a5ed8e27857a3688a7c86b37688b13.tar.gz
assert: improve output of Equal() for slice types with only partial diffs
Diffstat (limited to 'assert/equal.go')
-rw-r--r--assert/equal.go71
1 files changed, 66 insertions, 5 deletions
diff --git a/assert/equal.go b/assert/equal.go
index 122520b..2cafb44 100644
--- a/assert/equal.go
+++ b/assert/equal.go
@@ -7,6 +7,8 @@ import (
"fmt"
"reflect"
"slices"
+ "strings"
+ "unicode/utf8"
"go.xyrillian.de/gg/internal/path"
)
@@ -108,6 +110,20 @@ func findInequalities(p path.Path, actual, expected reflect.Value) (result []ine
}
func findInequalitiesInArrayOrSlice(p path.Path, actual, expected reflect.Value) (result []inequality) {
+ // special case: for ~[]byte types (e.g. json.RawMessage) containing a valid Unicode string on both sides,
+ // a diff using string literals is likely to be vastly more readable
+ if actual.Type().Elem() == reflect.TypeFor[byte]() {
+ actualPayload := actual.Convert(reflect.TypeFor[[]byte]()).Interface().([]byte)
+ expectedPayload := expected.Convert(reflect.TypeFor[[]byte]()).Interface().([]byte)
+ if utf8.Valid(actualPayload) && utf8.Valid(expectedPayload) {
+ return []inequality{{
+ Pointer: p.AsGoExpression("actual"),
+ Actual: formatByteSliceViaString(actualPayload),
+ Expected: formatByteSliceViaString(expectedPayload),
+ }}
+ }
+ }
+
// recurse into all elements
for idx := range max(actual.Len(), expected.Len()) {
subpath := append(p, path.IndexElement(idx))
@@ -141,11 +157,7 @@ func findInequalitiesInArrayOrSlice(p path.Path, actual, expected reflect.Value)
for _, ineq := range result {
overallTextLength += len(ineq.Pointer) + len(ineq.Actual) + len(ineq.Expected)
}
- ineq := inequality{
- Pointer: p.AsGoExpression("actual"),
- Actual: formatValue(actual),
- Expected: formatValue(expected),
- }
+ ineq := buildSingleInequalityForArrayOrSlice(p, actual, expected)
if len(ineq.Pointer)+len(ineq.Actual)+len(ineq.Expected) < overallTextLength {
return []inequality{ineq}
}
@@ -154,6 +166,55 @@ func findInequalitiesInArrayOrSlice(p path.Path, actual, expected reflect.Value)
return result
}
+func formatByteSliceViaString(buf []byte) string {
+ str := string(buf)
+ if strings.Contains(str, `"`) && !strings.Contains(str, "`") {
+ return fmt.Sprintf("[]byte(`%s`)", str)
+ } else {
+ return fmt.Sprintf("[]byte(%q)", str)
+ }
+}
+
+func buildSingleInequalityForArrayOrSlice(p path.Path, actual, expected reflect.Value) inequality {
+ // This is a helper for findInequalitiesInArrayOrSlice() that reports only a single inequality for the entire thing.
+ // But it still tries to be clever, and will omit the longest common prefix and suffix to shorten the output.
+ maxTruncateableLength := min(actual.Len(), expected.Len())
+
+ commonPrefixLength := 0
+ for idx := range maxTruncateableLength {
+ actualElem := actual.Index(idx)
+ expectedElem := expected.Index(idx)
+ if reflect.DeepEqual(actualElem.Interface(), expectedElem.Interface()) {
+ commonPrefixLength = idx + 1
+ } else {
+ break
+ }
+ }
+
+ commonSuffixLength := 0
+ for idx := range max(0, maxTruncateableLength-commonPrefixLength) {
+ actualElem := actual.Index(actual.Len() - 1 - idx)
+ expectedElem := expected.Index(expected.Len() - 1 - idx)
+ if reflect.DeepEqual(actualElem.Interface(), expectedElem.Interface()) {
+ commonSuffixLength = idx + 1
+ } else {
+ break
+ }
+ }
+
+ if commonPrefixLength > 0 || commonSuffixLength > 0 {
+ p = append(p, path.SliceElement(commonPrefixLength, expected.Len()-commonSuffixLength))
+ actual = actual.Slice(commonPrefixLength, actual.Len()-commonSuffixLength)
+ expected = expected.Slice(commonPrefixLength, expected.Len()-commonSuffixLength)
+ }
+
+ return inequality{
+ Pointer: p.AsGoExpression("actual"),
+ Actual: formatValue(actual),
+ Expected: formatValue(expected),
+ }
+}
+
func findInequalitiesInMap(p path.Path, actual, expected reflect.Value) (result []inequality) {
// recurse into all keys of `actual`
iter := actual.MapRange()