mirror-to-gitea/vendor/github.com/onsi/gomega/format/format.go
vendor/github.com/onsi/gomega/format/format.goRaw
1/*
2Gomega'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
7package format
8
9import (
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
19var 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.
23var MaxLength = 4000
24
25/*
26By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output.
27
28Set UseStringerRepresentation = true to use GoString (for fmt.GoStringers) or String (for fmt.Stringer) instead.
29
30Note that GoString and String don't always have all the information you need to understand why a test failed!
31*/
32var UseStringerRepresentation = false
33
34/*
35Print the content of context objects. By default it will be suppressed.
36
37Set PrintContextObjects = true to enable printing of the context internals.
38*/
39var PrintContextObjects = false
40
41// TruncatedDiff choose if we should display a truncated pretty diff or not
42var TruncatedDiff = true
43
44// TruncateThreshold (default 50) specifies the maximum length string to print in string comparison assertion error
45// messages.
46var 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.
50var CharactersAroundMismatchToInclude uint = 5
51
52var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
53var timeType = reflect.TypeOf(time.Time{})
54
55//The default indentation string emitted by the format package
56var Indent = " "
57
58var longFormThreshold = 20
59
60// GomegaStringer allows for custom formating of objects for gomega.
61type 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/*
69Generates a formatted matcher success/failure message of the form:
70
71 Expected
72 <pretty printed actual>
73 <message>
74 <pretty printed expected>
75
76If expected is omitted, then the message looks like:
77
78 Expected
79 <pretty printed actual>
80 <message>
81*/
82func Message(actual interface{}, message string, expected ...interface{}) string {
83 if len(expected) == 0 {
84 return fmt.Sprintf("Expected\n%s\n%s", Object(actual, 1), message)
85 }
86 return fmt.Sprintf("Expected\n%s\n%s\n%s", Object(actual, 1), message, Object(expected[0], 1))
87}
88
89/*
90
91Generates a nicely formatted matcher success / failure message
92
93Much like Message(...), but it attempts to pretty print diffs in strings
94
95Expected
96 <string>: "...aaaaabaaaaa..."
97to equal |
98 <string>: "...aaaaazaaaaa..."
99
100*/
101
102func MessageWithDiff(actual, message, expected string) string {
103 if TruncatedDiff && len(actual) >= int(TruncateThreshold) && len(expected) >= int(TruncateThreshold) {
104 diffPoint := findFirstMismatch(actual, expected)
105 formattedActual := truncateAndFormat(actual, diffPoint)
106 formattedExpected := truncateAndFormat(expected, diffPoint)
107
108 spacesBeforeFormattedMismatch := findFirstMismatch(formattedActual, formattedExpected)
109
110 tabLength := 4
111 spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)
112
113 paddingCount := spaceFromMessageToActual + spacesBeforeFormattedMismatch
114 if paddingCount < 0 {
115 return Message(formattedActual, message, formattedExpected)
116 }
117
118 padding := strings.Repeat(" ", paddingCount) + "|"
119 return Message(formattedActual, message+padding, formattedExpected)
120 }
121
122 actual = escapedWithGoSyntax(actual)
123 expected = escapedWithGoSyntax(expected)
124
125 return Message(actual, message, expected)
126}
127
128func escapedWithGoSyntax(str string) string {
129 withQuotes := fmt.Sprintf("%q", str)
130 return withQuotes[1 : len(withQuotes)-1]
131}
132
133func truncateAndFormat(str string, index int) string {
134 leftPadding := `...`
135 rightPadding := `...`
136
137 start := index - int(CharactersAroundMismatchToInclude)
138 if start < 0 {
139 start = 0
140 leftPadding = ""
141 }
142
143 // slice index must include the mis-matched character
144 lengthOfMismatchedCharacter := 1
145 end := index + int(CharactersAroundMismatchToInclude) + lengthOfMismatchedCharacter
146 if end > len(str) {
147 end = len(str)
148 rightPadding = ""
149
150 }
151 return fmt.Sprintf("\"%s\"", leftPadding+str[start:end]+rightPadding)
152}
153
154func findFirstMismatch(a, b string) int {
155 aSlice := strings.Split(a, "")
156 bSlice := strings.Split(b, "")
157
158 for index, str := range aSlice {
159 if index > len(bSlice)-1 {
160 return index
161 }
162 if str != bSlice[index] {
163 return index
164 }
165 }
166
167 if len(b) > len(a) {
168 return len(a) + 1
169 }
170
171 return 0
172}
173
174const truncateHelpText = `
175Gomega truncated this representation as it exceeds 'format.MaxLength'.
176Consider having the object provide a custom 'GomegaStringer' representation
177or adjust the parameters in Gomega's 'format' package.
178
179Learn more here: https://onsi.github.io/gomega/#adjusting-output
180`
181
182func truncateLongStrings(s string) string {
183 if MaxLength > 0 && len(s) > MaxLength {
184 var sb strings.Builder
185 for i, r := range s {
186 if i < MaxLength {
187 sb.WriteRune(r)
188 continue
189 }
190 break
191 }
192
193 sb.WriteString("...\n")
194 sb.WriteString(truncateHelpText)
195
196 return sb.String()
197 }
198 return s
199}
200
201/*
202Pretty prints the passed in object at the passed in indentation level.
203
204Object recurses into deeply nested objects emitting pretty-printed representations of their components.
205
206Modify format.MaxDepth to control how deep the recursion is allowed to go
207Set format.UseStringerRepresentation to true to return object.GoString() or object.String() when available instead of
208recursing into the object.
209
210Set PrintContextObjects to true to print the content of objects implementing context.Context
211*/
212func Object(object interface{}, indentation uint) string {
213 indent := strings.Repeat(Indent, int(indentation))
214 value := reflect.ValueOf(object)
215 return fmt.Sprintf("%s<%s>: %s", indent, formatType(value), formatValue(value, indentation))
216}
217
218/*
219IndentString takes a string and indents each line by the specified amount.
220*/
221func IndentString(s string, indentation uint) string {
222 components := strings.Split(s, "\n")
223 result := ""
224 indent := strings.Repeat(Indent, int(indentation))
225 for i, component := range components {
226 result += indent + component
227 if i < len(components)-1 {
228 result += "\n"
229 }
230 }
231
232 return result
233}
234
235func formatType(v reflect.Value) string {
236 switch v.Kind() {
237 case reflect.Invalid:
238 return "nil"
239 case reflect.Chan:
240 return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
241 case reflect.Ptr:
242 return fmt.Sprintf("%s | 0x%x", v.Type(), v.Pointer())
243 case reflect.Slice:
244 return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
245 case reflect.Map:
246 return fmt.Sprintf("%s | len:%d", v.Type(), v.Len())
247 default:
248 return fmt.Sprintf("%s", v.Type())
249 }
250}
251
252func formatValue(value reflect.Value, indentation uint) string {
253 if indentation > MaxDepth {
254 return "..."
255 }
256
257 if isNilValue(value) {
258 return "nil"
259 }
260
261 if value.CanInterface() {
262 obj := value.Interface()
263
264 // GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation
265 if x, ok := obj.(GomegaStringer); ok {
266 // do not truncate a user-defined GoMegaString() value
267 return x.GomegaString()
268 }
269
270 if UseStringerRepresentation {
271 switch x := obj.(type) {
272 case fmt.GoStringer:
273 return truncateLongStrings(x.GoString())
274 case fmt.Stringer:
275 return truncateLongStrings(x.String())
276 }
277 }
278 }
279
280 if !PrintContextObjects {
281 if value.Type().Implements(contextType) && indentation > 1 {
282 return "<suppressed context>"
283 }
284 }
285
286 switch value.Kind() {
287 case reflect.Bool:
288 return fmt.Sprintf("%v", value.Bool())
289 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
290 return fmt.Sprintf("%v", value.Int())
291 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
292 return fmt.Sprintf("%v", value.Uint())
293 case reflect.Uintptr:
294 return fmt.Sprintf("0x%x", value.Uint())
295 case reflect.Float32, reflect.Float64:
296 return fmt.Sprintf("%v", value.Float())
297 case reflect.Complex64, reflect.Complex128:
298 return fmt.Sprintf("%v", value.Complex())
299 case reflect.Chan:
300 return fmt.Sprintf("0x%x", value.Pointer())
301 case reflect.Func:
302 return fmt.Sprintf("0x%x", value.Pointer())
303 case reflect.Ptr:
304 return formatValue(value.Elem(), indentation)
305 case reflect.Slice:
306 return truncateLongStrings(formatSlice(value, indentation))
307 case reflect.String:
308 return truncateLongStrings(formatString(value.String(), indentation))
309 case reflect.Array:
310 return truncateLongStrings(formatSlice(value, indentation))
311 case reflect.Map:
312 return truncateLongStrings(formatMap(value, indentation))
313 case reflect.Struct:
314 if value.Type() == timeType && value.CanInterface() {
315 t, _ := value.Interface().(time.Time)
316 return t.Format(time.RFC3339Nano)
317 }
318 return truncateLongStrings(formatStruct(value, indentation))
319 case reflect.Interface:
320 return formatInterface(value, indentation)
321 default:
322 if value.CanInterface() {
323 return truncateLongStrings(fmt.Sprintf("%#v", value.Interface()))
324 }
325 return truncateLongStrings(fmt.Sprintf("%#v", value))
326 }
327}
328
329func formatString(object interface{}, indentation uint) string {
330 if indentation == 1 {
331 s := fmt.Sprintf("%s", object)
332 components := strings.Split(s, "\n")
333 result := ""
334 for i, component := range components {
335 if i == 0 {
336 result += component
337 } else {
338 result += Indent + component
339 }
340 if i < len(components)-1 {
341 result += "\n"
342 }
343 }
344
345 return result
346 } else {
347 return fmt.Sprintf("%q", object)
348 }
349}
350
351func formatSlice(v reflect.Value, indentation uint) string {
352 if v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 && isPrintableString(string(v.Bytes())) {
353 return formatString(v.Bytes(), indentation)
354 }
355
356 l := v.Len()
357 result := make([]string, l)
358 longest := 0
359 for i := 0; i < l; i++ {
360 result[i] = formatValue(v.Index(i), indentation+1)
361 if len(result[i]) > longest {
362 longest = len(result[i])
363 }
364 }
365
366 if longest > longFormThreshold {
367 indenter := strings.Repeat(Indent, int(indentation))
368 return fmt.Sprintf("[\n%s%s,\n%s]", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
369 }
370 return fmt.Sprintf("[%s]", strings.Join(result, ", "))
371}
372
373func formatMap(v reflect.Value, indentation uint) string {
374 l := v.Len()
375 result := make([]string, l)
376
377 longest := 0
378 for i, key := range v.MapKeys() {
379 value := v.MapIndex(key)
380 result[i] = fmt.Sprintf("%s: %s", formatValue(key, indentation+1), formatValue(value, indentation+1))
381 if len(result[i]) > longest {
382 longest = len(result[i])
383 }
384 }
385
386 if longest > longFormThreshold {
387 indenter := strings.Repeat(Indent, int(indentation))
388 return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
389 }
390 return fmt.Sprintf("{%s}", strings.Join(result, ", "))
391}
392
393func formatStruct(v reflect.Value, indentation uint) string {
394 t := v.Type()
395
396 l := v.NumField()
397 result := []string{}
398 longest := 0
399 for i := 0; i < l; i++ {
400 structField := t.Field(i)
401 fieldEntry := v.Field(i)
402 representation := fmt.Sprintf("%s: %s", structField.Name, formatValue(fieldEntry, indentation+1))
403 result = append(result, representation)
404 if len(representation) > longest {
405 longest = len(representation)
406 }
407 }
408 if longest > longFormThreshold {
409 indenter := strings.Repeat(Indent, int(indentation))
410 return fmt.Sprintf("{\n%s%s,\n%s}", indenter+Indent, strings.Join(result, ",\n"+indenter+Indent), indenter)
411 }
412 return fmt.Sprintf("{%s}", strings.Join(result, ", "))
413}
414
415func formatInterface(v reflect.Value, indentation uint) string {
416 return fmt.Sprintf("<%s>%s", formatType(v.Elem()), formatValue(v.Elem(), indentation))
417}
418
419func isNilValue(a reflect.Value) bool {
420 switch a.Kind() {
421 case reflect.Invalid:
422 return true
423 case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
424 return a.IsNil()
425 }
426
427 return false
428}
429
430/*
431Returns true when the string is entirely made of printable runes, false otherwise.
432*/
433func isPrintableString(str string) bool {
434 for _, runeValue := range str {
435 if !strconv.IsPrint(runeValue) {
436 return false
437 }
438 }
439 return true
440}