podman

Форк
0
506 строк · 14.5 Кб
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 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.
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 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
/*
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 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
/*
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 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
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 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
/*
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

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.