Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/vendor/github.com/onsi/gomega/format/format.go
2880 views
1
/*
2
Gomega's format package pretty-prints objects. It explores input objects recursively and generates formatted, indented output with type information.
3
*/
4
5
// untested sections: 4
6
7
package format
8
9
import (
10
"context"
11
"fmt"
12
"reflect"
13
"strconv"
14
"strings"
15
"time"
16
)
17
18
// Use MaxDepth to set the maximum recursion depth when printing deeply nested objects
19
var MaxDepth = uint(10)
20
21
// MaxLength of the string representation of an object.
22
// If MaxLength is set to 0, the Object will not be truncated.
23
var MaxLength = 4000
24
25
/*
26
By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output.
27
28
Set UseStringerRepresentation = true to use GoString (for fmt.GoStringers) or String (for fmt.Stringer) instead.
29
30
Note that GoString and String don't always have all the information you need to understand why a test failed!
31
*/
32
var UseStringerRepresentation = false
33
34
/*
35
Print the content of context objects. By default it will be suppressed.
36
37
Set PrintContextObjects = true to enable printing of the context internals.
38
*/
39
var PrintContextObjects = false
40
41
// TruncatedDiff choose if we should display a truncated pretty diff or not
42
var TruncatedDiff = true
43
44
// TruncateThreshold (default 50) specifies the maximum length string to print in string comparison assertion error
45
// messages.
46
var TruncateThreshold uint = 50
47
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
51
52
var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
53
var timeType = reflect.TypeOf(time.Time{})
54
55
// The default indentation string emitted by the format package
56
var Indent = " "
57
58
var longFormThreshold = 20
59
60
// GomegaStringer allows for custom formatting 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.
65
GomegaString() string
66
}
67
68
/*
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)
73
74
Strings returned by CustomFormatters are not truncated
75
*/
76
type CustomFormatter func(value any) (string, bool)
77
type CustomFormatterKey uint
78
79
var customFormatterKey CustomFormatterKey = 1
80
81
type customFormatterKeyPair struct {
82
CustomFormatter
83
CustomFormatterKey
84
}
85
86
/*
87
RegisterCustomFormatter registers a CustomFormatter and returns a CustomFormatterKey
88
89
You can call UnregisterCustomFormatter with the returned key to unregister the associated CustomFormatter
90
*/
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
/*
99
UnregisterCustomFormatter unregisters a previously registered CustomFormatter. You should pass in the key returned by RegisterCustomFormatter
100
*/
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
/*
115
Generates a formatted matcher success/failure message of the form:
116
117
Expected
118
<pretty printed actual>
119
<message>
120
<pretty printed expected>
121
122
If expected is omitted, then the message looks like:
123
124
Expected
125
<pretty printed actual>
126
<message>
127
*/
128
func Message(actual any, message string, expected ...any) 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
/*
136
137
Generates a nicely formatted matcher success / failure message
138
139
Much like Message(...), but it attempts to pretty print diffs in strings
140
141
Expected
142
<string>: "...aaaaabaaaaa..."
143
to equal |
144
<string>: "...aaaaazaaaaa..."
145
146
*/
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
// slice index must include the mis-matched character
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
/*
248
Pretty prints the passed in object at the passed in indentation level.
249
250
Object recurses into deeply nested objects emitting pretty-printed representations of their components.
251
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.
255
256
Set PrintContextObjects to true to print the content of objects implementing context.Context
257
*/
258
func Object(object any, 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
264
}
265
return fmt.Sprintf("%s<%s>: %s%s", indent, formatType(value), commonRepresentation, formatValue(value, indentation))
266
}
267
268
/*
269
IndentString takes a string and indents each line by the specified amount.
270
*/
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
// 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()
325
if handled {
326
return indentString(formatted, indentation+1, false)
327
}
328
}
329
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)
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 any, 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
/*
497
Returns true when the string is entirely made of printable runes, false otherwise.
498
*/
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
508