github.com/onsi/gomega@v1.32.0/format/format.go (about) 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 }