2
Gomega's format package pretty-prints objects. It explores input objects recursively and generates formatted, indented output with type information.
18
// Use MaxDepth to set the maximum recursion depth when printing deeply nested objects
19
var MaxDepth = uint(10)
21
// MaxLength of the string representation of an object.
22
// If MaxLength is set to 0, the Object will not be truncated.
26
By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output.
28
Set UseStringerRepresentation = true to use GoString (for fmt.GoStringers) or String (for fmt.Stringer) instead.
30
Note that GoString and String don't always have all the information you need to understand why a test failed!
32
var UseStringerRepresentation = false
35
Print the content of context objects. By default it will be suppressed.
37
Set PrintContextObjects = true to enable printing of the context internals.
39
var PrintContextObjects = false
41
// TruncatedDiff choose if we should display a truncated pretty diff or not
42
var TruncatedDiff = true
44
// TruncateThreshold (default 50) specifies the maximum length string to print in string comparison assertion error
46
var TruncateThreshold uint = 50
48
// CharactersAroundMismatchToInclude (default 5) specifies how many contextual characters should be printed before and
49
// after the first diff location in a truncated string assertion error message.
50
var CharactersAroundMismatchToInclude uint = 5
52
var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
53
var timeType = reflect.TypeOf(time.Time{})
55
// The default indentation string emitted by the format package
58
var longFormThreshold = 20
60
// GomegaStringer allows for custom formating of objects for gomega.
61
type GomegaStringer interface {
62
// GomegaString will be used to custom format an object.
63
// It does not follow UseStringerRepresentation value and will always be called regardless.
64
// It also ignores the MaxLength value.
69
CustomFormatters can be registered with Gomega via RegisterCustomFormatter()
70
Any value to be rendered by Gomega is passed to each registered CustomFormatters.
71
The CustomFormatter signals that it will handle formatting the value by returning (formatted-string, true)
72
If the CustomFormatter does not want to handle the object it should return ("", false)
74
Strings returned by CustomFormatters are not truncated
76
type CustomFormatter func(value interface{}) (string, bool)
77
type CustomFormatterKey uint
79
var customFormatterKey CustomFormatterKey = 1
81
type customFormatterKeyPair struct {
87
RegisterCustomFormatter registers a CustomFormatter and returns a CustomFormatterKey
89
You can call UnregisterCustomFormatter with the returned key to unregister the associated CustomFormatter
91
func RegisterCustomFormatter(customFormatter CustomFormatter) CustomFormatterKey {
92
key := customFormatterKey
93
customFormatterKey += 1
94
customFormatters = append(customFormatters, customFormatterKeyPair{customFormatter, key})
99
UnregisterCustomFormatter unregisters a previously registered CustomFormatter. You should pass in the key returned by RegisterCustomFormatter
101
func UnregisterCustomFormatter(key CustomFormatterKey) {
102
formatters := []customFormatterKeyPair{}
103
for _, f := range customFormatters {
104
if f.CustomFormatterKey == key {
107
formatters = append(formatters, f)
109
customFormatters = formatters
112
var customFormatters = []customFormatterKeyPair{}
115
Generates a formatted matcher success/failure message of the form:
118
<pretty printed actual>
120
<pretty printed expected>
122
If expected is omitted, then the message looks like:
125
<pretty printed actual>
128
func Message(actual interface{}, message string, expected ...interface{}) string {
129
if len(expected) == 0 {
130
return fmt.Sprintf("Expected\n%s\n%s", Object(actual, 1), message)
132
return fmt.Sprintf("Expected\n%s\n%s\n%s", Object(actual, 1), message, Object(expected[0], 1))
137
Generates a nicely formatted matcher success / failure message
139
Much like Message(...), but it attempts to pretty print diffs in strings
142
<string>: "...aaaaabaaaaa..."
144
<string>: "...aaaaazaaaaa..."
148
func MessageWithDiff(actual, message, expected string) string {
149
if TruncatedDiff && len(actual) >= int(TruncateThreshold) && len(expected) >= int(TruncateThreshold) {
150
diffPoint := findFirstMismatch(actual, expected)
151
formattedActual := truncateAndFormat(actual, diffPoint)
152
formattedExpected := truncateAndFormat(expected, diffPoint)
154
spacesBeforeFormattedMismatch := findFirstMismatch(formattedActual, formattedExpected)
157
spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)
159
paddingCount := spaceFromMessageToActual + spacesBeforeFormattedMismatch
160
if paddingCount < 0 {
161
return Message(formattedActual, message, formattedExpected)
164
padding := strings.Repeat(" ", paddingCount) + "|"
165
return Message(formattedActual, message+padding, formattedExpected)
168
actual = escapedWithGoSyntax(actual)
169
expected = escapedWithGoSyntax(expected)
171
return Message(actual, message, expected)
174
func escapedWithGoSyntax(str string) string {
175
withQuotes := fmt.Sprintf("%q", str)
176
return withQuotes[1 : len(withQuotes)-1]
179
func truncateAndFormat(str string, index int) string {
181
rightPadding := `...`
183
start := index - int(CharactersAroundMismatchToInclude)
189
// slice index must include the mis-matched character
190
lengthOfMismatchedCharacter := 1
191
end := index + int(CharactersAroundMismatchToInclude) + lengthOfMismatchedCharacter
197
return fmt.Sprintf("\"%s\"", leftPadding+str[start:end]+rightPadding)
200
func findFirstMismatch(a, b string) int {
201
aSlice := strings.Split(a, "")
202
bSlice := strings.Split(b, "")
204
for index, str := range aSlice {
205
if index > len(bSlice)-1 {
208
if str != bSlice[index] {
220
const truncateHelpText = `
221
Gomega truncated this representation as it exceeds 'format.MaxLength'.
222
Consider having the object provide a custom 'GomegaStringer' representation
223
or adjust the parameters in Gomega's 'format' package.
225
Learn more here: https://onsi.github.io/gomega/#adjusting-output
228
func truncateLongStrings(s string) string {
229
if MaxLength > 0 && len(s) > MaxLength {
230
var sb strings.Builder
231
for i, r := range s {
239
sb.WriteString("...\n")
240
sb.WriteString(truncateHelpText)
248
Pretty prints the passed in object at the passed in indentation level.
250
Object recurses into deeply nested objects emitting pretty-printed representations of their components.
252
Modify format.MaxDepth to control how deep the recursion is allowed to go
253
Set format.UseStringerRepresentation to true to return object.GoString() or object.String() when available instead of
254
recursing into the object.
256
Set PrintContextObjects to true to print the content of objects implementing context.Context
258
func Object(object interface{}, indentation uint) string {
259
indent := strings.Repeat(Indent, int(indentation))
260
value := reflect.ValueOf(object)
261
commonRepresentation := ""
262
if err, ok := object.(error); ok && !isNilValue(value) { // isNilValue check needed here to avoid nil deref due to boxed nil
263
commonRepresentation += "\n" + IndentString(err.Error(), indentation) + "\n" + indent
265
return fmt.Sprintf("%s<%s>: %s%s", indent, formatType(value), commonRepresentation, formatValue(value, indentation))
269
IndentString takes a string and indents each line by the specified amount.
271
func IndentString(s string, indentation uint) string {
272
return indentString(s, indentation, true)
275
func indentString(s string, indentation uint, indentFirstLine bool) string {
276
result := &strings.Builder{}
277
components := strings.Split(s, "\n")
278
indent := strings.Repeat(Indent, int(indentation))
279
for i, component := range components {
280
if i > 0 || indentFirstLine {
281
result.WriteString(indent)
283
result.WriteString(component)
284
if i < len(components)-1 {
285
result.WriteString("\n")
289
return result.String()
292
func formatType(v reflect.Value) string {
294
case reflect.Invalid:
297
return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
299
return fmt.Sprintf("%s | 0x%x", v.Type(), v.Pointer())
301
return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
303
return fmt.Sprintf("%s | len:%d", v.Type(), v.Len())
305
return v.Type().String()
309
func formatValue(value reflect.Value, indentation uint) string {
310
if indentation > MaxDepth {
314
if isNilValue(value) {
318
if value.CanInterface() {
319
obj := value.Interface()
321
// if a CustomFormatter handles this values, we'll go with that
322
for _, customFormatter := range customFormatters {
323
formatted, handled := customFormatter.CustomFormatter(obj)
324
// do not truncate a user-provided CustomFormatter()
326
return indentString(formatted, indentation+1, false)
330
// GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation
331
if x, ok := obj.(GomegaStringer); ok {
332
// do not truncate a user-defined GomegaString() value
333
return indentString(x.GomegaString(), indentation+1, false)
336
if UseStringerRepresentation {
337
switch x := obj.(type) {
339
return indentString(truncateLongStrings(x.GoString()), indentation+1, false)
341
return indentString(truncateLongStrings(x.String()), indentation+1, false)
346
if !PrintContextObjects {
347
if value.Type().Implements(contextType) && indentation > 1 {
348
return "<suppressed context>"
352
switch value.Kind() {
354
return fmt.Sprintf("%v", value.Bool())
355
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
356
return fmt.Sprintf("%v", value.Int())
357
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
358
return fmt.Sprintf("%v", value.Uint())
359
case reflect.Uintptr:
360
return fmt.Sprintf("0x%x", value.Uint())
361
case reflect.Float32, reflect.Float64:
362
return fmt.Sprintf("%v", value.Float())
363
case reflect.Complex64, reflect.Complex128:
364
return fmt.Sprintf("%v", value.Complex())
366
return fmt.Sprintf("0x%x", value.Pointer())
368
return fmt.Sprintf("0x%x", value.Pointer())
370
return formatValue(value.Elem(), indentation)
372
return truncateLongStrings(formatSlice(value, indentation))
374
return truncateLongStrings(formatString(value.String(), indentation))
376
return truncateLongStrings(formatSlice(value, indentation))
378
return truncateLongStrings(formatMap(value, indentation))
380
if value.Type() == timeType && value.CanInterface() {
381
t, _ := value.Interface().(time.Time)
382
return t.Format(time.RFC3339Nano)
384
return truncateLongStrings(formatStruct(value, indentation))
385
case reflect.Interface:
386
return formatInterface(value, indentation)
388
if value.CanInterface() {
389
return truncateLongStrings(fmt.Sprintf("%#v", value.Interface()))
391
return truncateLongStrings(fmt.Sprintf("%#v", value))
395
func formatString(object interface{}, indentation uint) string {
396
if indentation == 1 {
397
s := fmt.Sprintf("%s", object)
398
components := strings.Split(s, "\n")
400
for i, component := range components {
404
result += Indent + component
406
if i < len(components)-1 {
413
return fmt.Sprintf("%q", object)
417
func formatSlice(v reflect.Value, indentation uint) string {
418
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && isPrintableString(string(v.Bytes())) {
419
return formatString(v.Bytes(), indentation)
423
result := make([]string, l)
425
for i := 0; i < l; i++ {
426
result[i] = formatValue(v.Index(i), indentation+1)
427
if len(result[i]) > longest {
428
longest = len(result[i])
432
if longest > longFormThreshold {
433
indenter := strings.Repeat(Indent, int(indentation))
434
return fmt.Sprintf("[\n%s%s,\n%s]", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
436
return fmt.Sprintf("[%s]", strings.Join(result, ", "))
439
func formatMap(v reflect.Value, indentation uint) string {
441
result := make([]string, l)
444
for i, key := range v.MapKeys() {
445
value := v.MapIndex(key)
446
result[i] = fmt.Sprintf("%s: %s", formatValue(key, indentation+1), formatValue(value, indentation+1))
447
if len(result[i]) > longest {
448
longest = len(result[i])
452
if longest > longFormThreshold {
453
indenter := strings.Repeat(Indent, int(indentation))
454
return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
456
return fmt.Sprintf("{%s}", strings.Join(result, ", "))
459
func formatStruct(v reflect.Value, indentation uint) string {
465
for i := 0; i < l; i++ {
466
structField := t.Field(i)
467
fieldEntry := v.Field(i)
468
representation := fmt.Sprintf("%s: %s", structField.Name, formatValue(fieldEntry, indentation+1))
469
result = append(result, representation)
470
if len(representation) > longest {
471
longest = len(representation)
474
if longest > longFormThreshold {
475
indenter := strings.Repeat(Indent, int(indentation))
476
return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
478
return fmt.Sprintf("{%s}", strings.Join(result, ", "))
481
func formatInterface(v reflect.Value, indentation uint) string {
482
return fmt.Sprintf("<%s>%s", formatType(v.Elem()), formatValue(v.Elem(), indentation))
485
func isNilValue(a reflect.Value) bool {
487
case reflect.Invalid:
489
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
497
Returns true when the string is entirely made of printable runes, false otherwise.
499
func isPrintableString(str string) bool {
500
for _, runeValue := range str {
501
if !strconv.IsPrint(runeValue) {