1
4
5
6
7 package format
8
9 import (
10 "context"
11 "fmt"
12 "reflect"
13 "strconv"
14 "strings"
15 "time"
16 )
17
18
19 var MaxDepth = uint(10)
20
21
22
23 var MaxLength = 4000
24
25
32 var UseStringerRepresentation = false
33
34
39 var PrintContextObjects = false
40
41
42 var TruncatedDiff = true
43
44
45
46 var TruncateThreshold uint = 50
47
48
49
50 var CharactersAroundMismatchToInclude uint = 5
51
52 var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
53 var timeType = reflect.TypeOf(time.Time{})
54
55
56 var Indent = " "
57
58 var longFormThreshold = 20
59
60
61 type GomegaStringer interface {
62
63
64
65 GomegaString() string
66 }
67
68
76 type CustomFormatter func(value interface{}) (string, bool)
77 type CustomFormatterKey uint
78
79 var customFormatterKey CustomFormatterKey = 1
80
81 type customFormatterKeyPair struct {
82 CustomFormatter
83 CustomFormatterKey
84 }
85
86
91 func RegisterCustomFormatter(customFormatter CustomFormatter) CustomFormatterKey {
92 key := customFormatterKey
93 customFormatterKey += 1
94 customFormatters = append(customFormatters, customFormatterKeyPair{customFormatter, key})
95 return key
96 }
97
98
101 func UnregisterCustomFormatter(key CustomFormatterKey) {
102 formatters := []customFormatterKeyPair{}
103 for _, f := range customFormatters {
104 if f.CustomFormatterKey == key {
105 continue
106 }
107 formatters = append(formatters, f)
108 }
109 customFormatters = formatters
110 }
111
112 var customFormatters = []customFormatterKeyPair{}
113
114
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)
131 }
132 return fmt.Sprintf("Expected\n%s\n%s\n%s", Object(actual, 1), message, Object(expected[0], 1))
133 }
134
135
147
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)
153
154 spacesBeforeFormattedMismatch := findFirstMismatch(formattedActual, formattedExpected)
155
156 tabLength := 4
157 spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)
158
159 paddingCount := spaceFromMessageToActual + spacesBeforeFormattedMismatch
160 if paddingCount < 0 {
161 return Message(formattedActual, message, formattedExpected)
162 }
163
164 padding := strings.Repeat(" ", paddingCount) + "|"
165 return Message(formattedActual, message+padding, formattedExpected)
166 }
167
168 actual = escapedWithGoSyntax(actual)
169 expected = escapedWithGoSyntax(expected)
170
171 return Message(actual, message, expected)
172 }
173
174 func escapedWithGoSyntax(str string) string {
175 withQuotes := fmt.Sprintf("%q", str)
176 return withQuotes[1 : len(withQuotes)-1]
177 }
178
179 func truncateAndFormat(str string, index int) string {
180 leftPadding := `...`
181 rightPadding := `...`
182
183 start := index - int(CharactersAroundMismatchToInclude)
184 if start < 0 {
185 start = 0
186 leftPadding = ""
187 }
188
189
190 lengthOfMismatchedCharacter := 1
191 end := index + int(CharactersAroundMismatchToInclude) + lengthOfMismatchedCharacter
192 if end > len(str) {
193 end = len(str)
194 rightPadding = ""
195
196 }
197 return fmt.Sprintf("\"%s\"", leftPadding+str[start:end]+rightPadding)
198 }
199
200 func findFirstMismatch(a, b string) int {
201 aSlice := strings.Split(a, "")
202 bSlice := strings.Split(b, "")
203
204 for index, str := range aSlice {
205 if index > len(bSlice)-1 {
206 return index
207 }
208 if str != bSlice[index] {
209 return index
210 }
211 }
212
213 if len(b) > len(a) {
214 return len(a) + 1
215 }
216
217 return 0
218 }
219
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.
224
225 Learn more here: https://onsi.github.io/gomega/#adjusting-output
226 `
227
228 func truncateLongStrings(s string) string {
229 if MaxLength > 0 && len(s) > MaxLength {
230 var sb strings.Builder
231 for i, r := range s {
232 if i < MaxLength {
233 sb.WriteRune(r)
234 continue
235 }
236 break
237 }
238
239 sb.WriteString("...\n")
240 sb.WriteString(truncateHelpText)
241
242 return sb.String()
243 }
244 return s
245 }
246
247
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) {
263 commonRepresentation += "\n" + IndentString(err.Error(), indentation) + "\n" + indent
264 }
265 return fmt.Sprintf("%s<%s>: %s%s", indent, formatType(value), commonRepresentation, formatValue(value, indentation))
266 }
267
268
271 func IndentString(s string, indentation uint) string {
272 return indentString(s, indentation, true)
273 }
274
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)
282 }
283 result.WriteString(component)
284 if i < len(components)-1 {
285 result.WriteString("\n")
286 }
287 }
288
289 return result.String()
290 }
291
292 func formatType(v reflect.Value) string {
293 switch v.Kind() {
294 case reflect.Invalid:
295 return "nil"
296 case reflect.Chan:
297 return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
298 case reflect.Ptr:
299 return fmt.Sprintf("%s | 0x%x", v.Type(), v.Pointer())
300 case reflect.Slice:
301 return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
302 case reflect.Map:
303 return fmt.Sprintf("%s | len:%d", v.Type(), v.Len())
304 default:
305 return v.Type().String()
306 }
307 }
308
309 func formatValue(value reflect.Value, indentation uint) string {
310 if indentation > MaxDepth {
311 return "..."
312 }
313
314 if isNilValue(value) {
315 return "nil"
316 }
317
318 if value.CanInterface() {
319 obj := value.Interface()
320
321
322 for _, customFormatter := range customFormatters {
323 formatted, handled := customFormatter.CustomFormatter(obj)
324
325 if handled {
326 return indentString(formatted, indentation+1, false)
327 }
328 }
329
330
331 if x, ok := obj.(GomegaStringer); ok {
332
333 return indentString(x.GomegaString(), indentation+1, false)
334 }
335
336 if UseStringerRepresentation {
337 switch x := obj.(type) {
338 case fmt.GoStringer:
339 return indentString(truncateLongStrings(x.GoString()), indentation+1, false)
340 case fmt.Stringer:
341 return indentString(truncateLongStrings(x.String()), indentation+1, false)
342 }
343 }
344 }
345
346 if !PrintContextObjects {
347 if value.Type().Implements(contextType) && indentation > 1 {
348 return "<suppressed context>"
349 }
350 }
351
352 switch value.Kind() {
353 case reflect.Bool:
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())
365 case reflect.Chan:
366 return fmt.Sprintf("0x%x", value.Pointer())
367 case reflect.Func:
368 return fmt.Sprintf("0x%x", value.Pointer())
369 case reflect.Ptr:
370 return formatValue(value.Elem(), indentation)
371 case reflect.Slice:
372 return truncateLongStrings(formatSlice(value, indentation))
373 case reflect.String:
374 return truncateLongStrings(formatString(value.String(), indentation))
375 case reflect.Array:
376 return truncateLongStrings(formatSlice(value, indentation))
377 case reflect.Map:
378 return truncateLongStrings(formatMap(value, indentation))
379 case reflect.Struct:
380 if value.Type() == timeType && value.CanInterface() {
381 t, _ := value.Interface().(time.Time)
382 return t.Format(time.RFC3339Nano)
383 }
384 return truncateLongStrings(formatStruct(value, indentation))
385 case reflect.Interface:
386 return formatInterface(value, indentation)
387 default:
388 if value.CanInterface() {
389 return truncateLongStrings(fmt.Sprintf("%#v", value.Interface()))
390 }
391 return truncateLongStrings(fmt.Sprintf("%#v", value))
392 }
393 }
394
395 func formatString(object interface{}, indentation uint) string {
396 if indentation == 1 {
397 s := fmt.Sprintf("%s", object)
398 components := strings.Split(s, "\n")
399 result := ""
400 for i, component := range components {
401 if i == 0 {
402 result += component
403 } else {
404 result += Indent + component
405 }
406 if i < len(components)-1 {
407 result += "\n"
408 }
409 }
410
411 return result
412 } else {
413 return fmt.Sprintf("%q", object)
414 }
415 }
416
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)
420 }
421
422 l := v.Len()
423 result := make([]string, l)
424 longest := 0
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])
429 }
430 }
431
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)
435 }
436 return fmt.Sprintf("[%s]", strings.Join(result, ", "))
437 }
438
439 func formatMap(v reflect.Value, indentation uint) string {
440 l := v.Len()
441 result := make([]string, l)
442
443 longest := 0
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])
449 }
450 }
451
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)
455 }
456 return fmt.Sprintf("{%s}", strings.Join(result, ", "))
457 }
458
459 func formatStruct(v reflect.Value, indentation uint) string {
460 t := v.Type()
461
462 l := v.NumField()
463 result := []string{}
464 longest := 0
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)
472 }
473 }
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)
477 }
478 return fmt.Sprintf("{%s}", strings.Join(result, ", "))
479 }
480
481 func formatInterface(v reflect.Value, indentation uint) string {
482 return fmt.Sprintf("<%s>%s", formatType(v.Elem()), formatValue(v.Elem(), indentation))
483 }
484
485 func isNilValue(a reflect.Value) bool {
486 switch a.Kind() {
487 case reflect.Invalid:
488 return true
489 case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
490 return a.IsNil()
491 }
492
493 return false
494 }
495
496
499 func isPrintableString(str string) bool {
500 for _, runeValue := range str {
501 if !strconv.IsPrint(runeValue) {
502 return false
503 }
504 }
505 return true
506 }
507
View as plain text