From 1c446e85440f1da29b38cfd1f4502ac6471c81cb Mon Sep 17 00:00:00 2001 From: Khosrow Afroozeh Date: Wed, 17 Mar 2021 19:47:20 +0100 Subject: [PATCH] Truncate long object string representations --- format/format.go | 55 +++++++++++++++++++++++++++++++++++-------- format/format_test.go | 29 +++++++++++++++++++++++ format/helper_test.go | 11 +++++++++ 3 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 format/helper_test.go diff --git a/format/format.go b/format/format.go index c6e4f2783..75f9be1a3 100644 --- a/format/format.go +++ b/format/format.go @@ -18,6 +18,10 @@ import ( // Use MaxDepth to set the maximum recursion depth when printing deeply nested objects var MaxDepth = uint(10) +// MaxLength of the string representation of an object. +// If MaxLength is set to 0, the Object will not be truncted. +var MaxLength = 4000 + /* By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output. @@ -57,6 +61,7 @@ var longFormThreshold = 20 type GomegaStringer interface { // GomegaString will be used to custom format an object. // It does not follow UseStringerRepresentation value and will always be called regardless. + // It also ignores the MaxLength value. GomegaString() string } @@ -166,6 +171,34 @@ func findFirstMismatch(a, b string) int { return 0 } +const truncateHelpText = `the very very long object here that goes on forever and ever but then gets trunc.... + +Gomega truncated this representation as it exceeds 'format.MaxLength'. +Consider having the object provide a custom 'GomegaStringer' representation +or adjust the parameters in Gomega's 'format' package. + +Learn more here: https://onsi.github.io/gomega/#adjusting-output +` + +func truncateLongStrings(s string) string { + if MaxLength > 0 && len(s) > MaxLength { + var sb strings.Builder + for i, r := range s { + if i < MaxLength { + sb.WriteRune(r) + continue + } + break + } + + sb.WriteString("\n") + sb.WriteString(truncateHelpText) + + return sb.String() + } + return s +} + /* Pretty prints the passed in object at the passed in indentation level. @@ -226,19 +259,21 @@ func formatValue(value reflect.Value, indentation uint) string { return "nil" } - // GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation if value.CanInterface() { obj := value.Interface() + + // GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation if x, ok := obj.(GomegaStringer); ok { + // do not truncate a user-defined GoMegaString() value return x.GomegaString() } if UseStringerRepresentation { switch x := obj.(type) { case fmt.GoStringer: - return x.GoString() + return truncateLongStrings(x.GoString()) case fmt.Stringer: - return x.String() + return truncateLongStrings(x.String()) } } } @@ -269,26 +304,26 @@ func formatValue(value reflect.Value, indentation uint) string { case reflect.Ptr: return formatValue(value.Elem(), indentation) case reflect.Slice: - return formatSlice(value, indentation) + return truncateLongStrings(formatSlice(value, indentation)) case reflect.String: - return formatString(value.String(), indentation) + return truncateLongStrings(formatString(value.String(), indentation)) case reflect.Array: - return formatSlice(value, indentation) + return truncateLongStrings(formatSlice(value, indentation)) case reflect.Map: - return formatMap(value, indentation) + return truncateLongStrings(formatMap(value, indentation)) case reflect.Struct: if value.Type() == timeType && value.CanInterface() { t, _ := value.Interface().(time.Time) return t.Format(time.RFC3339Nano) } - return formatStruct(value, indentation) + return truncateLongStrings(formatStruct(value, indentation)) case reflect.Interface: return formatInterface(value, indentation) default: if value.CanInterface() { - return fmt.Sprintf("%#v", value.Interface()) + return truncateLongStrings(fmt.Sprintf("%#v", value.Interface())) } - return fmt.Sprintf("%#v", value) + return truncateLongStrings(fmt.Sprintf("%#v", value)) } } diff --git a/format/format_test.go b/format/format_test.go index c7e67cec2..1c0bf4d74 100644 --- a/format/format_test.go +++ b/format/format_test.go @@ -81,6 +81,13 @@ func (g gomegaStringer) GomegaString() string { return "gomegastring" } +type gomegaStringerLong struct { +} + +func (g gomegaStringerLong) GomegaString() string { + return strings.Repeat("s", MaxLength*2) +} + var _ = Describe("Format", func() { match := func(typeRepresentation string, valueRepresentation string, args ...interface{}) types.GomegaMatcher { if len(args) > 0 { @@ -107,9 +114,25 @@ var _ = Describe("Format", func() { Describe("Message", func() { Context("with only an actual value", func() { + BeforeEach(func() { + MaxLength = 4000 + }) + It("should print out an indented formatted representation of the value and the message", func() { Expect(Message(3, "to be three.")).Should(Equal("Expected\n : 3\nto be three.")) }) + + It("should print out an indented formatted representation of the value and the message, and trucate it when too long", func() { + tooLong := strings.Repeat("s", MaxLength+1) + tooLongResult := strings.Repeat("s", MaxLength) + "\n" + TruncatedHelpText() + Expect(Message(tooLong, "to be truncated")).Should(Equal("Expected\n : " + tooLongResult + "\nto be truncated")) + }) + + It("should print out an indented formatted representation of the value and the message, and not trucate it when MaxLength = 0", func() { + MaxLength = 0 + tooLong := strings.Repeat("s", MaxLength+1) + Expect(Message(tooLong, "to be truncated")).Should(Equal("Expected\n : " + tooLong + "\nto be truncated")) + }) }) Context("with an actual and an expected value", func() { @@ -668,6 +691,12 @@ var _ = Describe("Format", func() { UseStringerRepresentation = false Expect(Object(gomegaStringer{}, 1)).Should(ContainSubstring(": gomegastring")) }) + + It("should use what GomegaString() returns, disregarding MaxLength", func() { + Expect(Object(gomegaStringerLong{}, 1)).Should(Equal(" : " + strings.Repeat("s", MaxLength*2))) + UseStringerRepresentation = false + Expect(Object(gomegaStringerLong{}, 1)).Should(Equal(" : " + strings.Repeat("s", MaxLength*2))) + }) }) }) diff --git a/format/helper_test.go b/format/helper_test.go new file mode 100644 index 000000000..189f3d901 --- /dev/null +++ b/format/helper_test.go @@ -0,0 +1,11 @@ +/* +Gomega's format test helper package. +*/ + +package format + +// TruncateHelpText returns truncateHelpText. +// This function is only accessible during tests. +func TruncatedHelpText() string { + return truncateHelpText +}