github.com/onsi/gomega@v1.32.0/format/format_test.go (about) 1 package format_test 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 . "github.com/onsi/ginkgo/v2" 10 11 . "github.com/onsi/gomega" 12 . "github.com/onsi/gomega/format" 13 "github.com/onsi/gomega/types" 14 ) 15 16 //recursive struct 17 18 const truncateHelpText = ` 19 Gomega truncated this representation as it exceeds 'format.MaxLength'. 20 Consider having the object provide a custom 'GomegaStringer' representation 21 or adjust the parameters in Gomega's 'format' package. 22 23 Learn more here: https://onsi.github.io/gomega/#adjusting-output 24 ` 25 26 type StringAlias string 27 type ByteAlias []byte 28 type IntAlias int 29 30 type AStruct struct { 31 Exported string 32 } 33 34 type SimpleStruct struct { 35 Name string 36 Enumeration int 37 Veritas bool 38 Data []byte 39 secret uint32 40 } 41 42 type ComplexStruct struct { 43 Strings []string 44 SimpleThings []*SimpleStruct 45 DataMaps map[int]ByteAlias 46 } 47 48 type SecretiveStruct struct { 49 boolValue bool 50 intValue int 51 uintValue uint 52 uintptrValue uintptr 53 floatValue float32 54 complexValue complex64 55 chanValue chan bool 56 funcValue func() 57 pointerValue *int 58 sliceValue []string 59 byteSliceValue []byte 60 stringValue string 61 arrValue [3]int 62 byteArrValue [3]byte 63 mapValue map[string]int 64 structValue AStruct 65 interfaceValue interface{} 66 } 67 68 type CustomFormatted struct { 69 Data string 70 Count int 71 } 72 type NotCustomFormatted struct { 73 Data string 74 Count int 75 } 76 77 type CustomError struct { 78 Details string 79 } 80 81 var _ error = &CustomError{} 82 83 func (c *CustomError) Error() string { 84 return c.Details 85 } 86 87 func customFormatter(obj interface{}) (string, bool) { 88 cf, ok := obj.(CustomFormatted) 89 if !ok { 90 return "", false 91 } 92 return fmt.Sprintf("%s (%d)", cf.Data, cf.Count), true 93 } 94 95 type GoStringer struct { 96 } 97 98 func (g GoStringer) GoString() string { 99 return "go-string" 100 } 101 102 func (g GoStringer) String() string { 103 return "string" 104 } 105 106 type Stringer struct { 107 } 108 109 func (g Stringer) String() string { 110 return "string" 111 } 112 113 type gomegaStringer struct { 114 } 115 116 func (g gomegaStringer) GomegaString() string { 117 return "gomegastring" 118 } 119 120 type gomegaStringerLong struct { 121 } 122 123 func (g gomegaStringerLong) GomegaString() string { 124 return strings.Repeat("s", MaxLength*2) 125 } 126 127 type gomegaStringerMultiline struct { 128 } 129 130 func (g gomegaStringerMultiline) GomegaString() string { 131 return "A\nB\nC" 132 } 133 134 var _ = Describe("Format", func() { 135 match := func(typeRepresentation string, valueRepresentation string, args ...interface{}) types.GomegaMatcher { 136 if len(args) > 0 { 137 valueRepresentation = fmt.Sprintf(valueRepresentation, args...) 138 } 139 return Equal(fmt.Sprintf("%s<%s>: %s", Indent, typeRepresentation, valueRepresentation)) 140 } 141 142 matchRegexp := func(typeRepresentation string, valueRepresentation string, args ...interface{}) types.GomegaMatcher { 143 if len(args) > 0 { 144 valueRepresentation = fmt.Sprintf(valueRepresentation, args...) 145 } 146 return MatchRegexp(fmt.Sprintf("%s<%s>: %s", Indent, typeRepresentation, valueRepresentation)) 147 } 148 149 hashMatchingRegexp := func(entries ...string) string { 150 entriesSwitch := "(" + strings.Join(entries, "|") + ")" 151 arr := make([]string, len(entries)) 152 for i := range arr { 153 arr[i] = entriesSwitch 154 } 155 return "{\\s*" + strings.Join(arr, ",\\s* ") + ",?\\s*}" 156 } 157 158 Describe("Message", func() { 159 Context("with only an actual value", func() { 160 BeforeEach(func() { 161 MaxLength = 4000 162 }) 163 164 It("should print out an indented formatted representation of the value and the message", func() { 165 Expect(Message(3, "to be three.")).Should(Equal("Expected\n <int>: 3\nto be three.")) 166 }) 167 168 It("should print out an indented formatted representation of the value and the message, and trucate it when too long", func() { 169 tooLong := strings.Repeat("s", MaxLength+1) 170 tooLongResult := strings.Repeat("s", MaxLength) + "...\n" + truncateHelpText 171 Expect(Message(tooLong, "to be truncated")).Should(Equal("Expected\n <string>: " + tooLongResult + "\nto be truncated")) 172 }) 173 174 It("should print out an indented formatted representation of the value and the message, and not trucate it when MaxLength = 0", func() { 175 MaxLength = 0 176 tooLong := strings.Repeat("s", MaxLength+1) 177 Expect(Message(tooLong, "to be truncated")).Should(Equal("Expected\n <string>: " + tooLong + "\nto be truncated")) 178 }) 179 }) 180 181 Context("with an actual and an expected value", func() { 182 It("should print out an indented formatted representatino of both values, and the message", func() { 183 Expect(Message(3, "to equal", 4)).Should(Equal("Expected\n <int>: 3\nto equal\n <int>: 4")) 184 }) 185 }) 186 }) 187 188 Describe("MessageWithDiff", func() { 189 It("shows the exact point where two long strings differ", func() { 190 stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 191 stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 192 193 Expect(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedLongStringFailureMessage)) 194 }) 195 196 It("truncates the start of long strings that differ only at their end", func() { 197 stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" 198 stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz" 199 200 Expect(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedTruncatedStartStringFailureMessage)) 201 }) 202 203 It("truncates the start of long strings that differ only in length", func() { 204 smallString := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 205 largeString := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 206 207 Expect(MessageWithDiff(largeString, "to equal", smallString)).Should(Equal(expectedTruncatedStartSizeFailureMessage)) 208 Expect(MessageWithDiff(smallString, "to equal", largeString)).Should(Equal(expectedTruncatedStartSizeSwappedFailureMessage)) 209 }) 210 211 It("truncates the end of long strings that differ only at their start", func() { 212 stringWithB := "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 213 stringWithZ := "zaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 214 215 Expect(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedTruncatedEndStringFailureMessage)) 216 }) 217 218 It("handles multi-byte sequences correctly", func() { 219 stringA := "• abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz1" 220 stringB := "• abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" 221 222 Expect(MessageWithDiff(stringA, "to equal", stringB)).Should(Equal(expectedTruncatedMultiByteFailureMessage)) 223 }) 224 225 It("prints special characters", func() { 226 stringA := "\n" 227 stringB := "something_else" 228 229 Expect(MessageWithDiff(stringA, "to equal", stringB)).Should(Equal(expectedSpecialCharacterFailureMessage)) 230 }) 231 232 It("handles negative padding length", func() { 233 stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 234 stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 235 longMessage := "to equal very long message" 236 237 Expect(MessageWithDiff(stringWithB, longMessage, stringWithZ)).Should(Equal(expectedDiffLongMessage)) 238 }) 239 240 Context("With truncated diff disabled", func() { 241 BeforeEach(func() { 242 TruncatedDiff = false 243 }) 244 245 AfterEach(func() { 246 TruncatedDiff = true 247 }) 248 249 It("should show the full diff", func() { 250 stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 251 stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 252 253 Expect(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedFullFailureDiff)) 254 }) 255 }) 256 257 Context("With alternate diff lengths", func() { 258 initialValue := TruncateThreshold // 50 by default 259 BeforeEach(func() { 260 TruncateThreshold = 10000 261 }) 262 263 AfterEach(func() { 264 TruncateThreshold = initialValue 265 }) 266 267 It("should show the full diff when truncate threshold is increased beyond length of strings", func() { 268 stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 269 stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 270 271 Expect(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedFullFailureDiff)) 272 }) 273 }) 274 275 Context("with alternative number of characters to include around mismatch", func() { 276 initialValue := CharactersAroundMismatchToInclude // 5 by default 277 BeforeEach(func() { 278 CharactersAroundMismatchToInclude = 10 279 }) 280 281 AfterEach(func() { 282 CharactersAroundMismatchToInclude = initialValue 283 }) 284 285 It("it shows more characters around a line length mismatch", func() { 286 smallString := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 287 largeString := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 288 289 Expect(MessageWithDiff(largeString, "to equal", smallString)).Should(Equal(expectedTruncatedStartSizeFailureMessageExtraDiff)) 290 Expect(MessageWithDiff(smallString, "to equal", largeString)).Should(Equal(expectedTruncatedStartSizeSwappedFailureMessageExtraDiff)) 291 }) 292 }) 293 294 Describe("At extremes of configurable values", func() { 295 Context("with zero-length threshold", func() { 296 initialValue := TruncateThreshold // 50 by default 297 BeforeEach(func() { 298 TruncateThreshold = 0 299 }) 300 301 AfterEach(func() { 302 TruncateThreshold = initialValue 303 }) 304 305 It("should show the full diff when truncate threshold is increased beyond length of strings", func() { 306 stringWithB := "aba" 307 stringWithZ := "aza" 308 Expect(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedDiffSmallThreshold)) 309 }) 310 }) 311 312 Context("with zero characters around mismatch", func() { 313 initialValue := CharactersAroundMismatchToInclude // 5 by default 314 BeforeEach(func() { 315 CharactersAroundMismatchToInclude = 0 316 }) 317 318 AfterEach(func() { 319 CharactersAroundMismatchToInclude = initialValue 320 }) 321 322 It("", func() { 323 stringWithB := "aba" 324 stringWithZ := "aza" 325 Expect(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedDiffZeroMismatch)) 326 }) 327 }) 328 329 Context("with zero-length threshold and zero characters around mismatch", func() { 330 initialCharactersAroundMismatch := CharactersAroundMismatchToInclude 331 initialTruncateThreshold := TruncateThreshold 332 BeforeEach(func() { 333 CharactersAroundMismatchToInclude = 0 334 TruncateThreshold = 0 335 }) 336 337 AfterEach(func() { 338 CharactersAroundMismatchToInclude = initialCharactersAroundMismatch 339 TruncateThreshold = initialTruncateThreshold 340 }) 341 342 It("", func() { 343 stringWithB := "aba" 344 stringWithZ := "aza" 345 Expect(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedDiffSmallThresholdZeroMismatch)) 346 }) 347 }) 348 }) 349 }) 350 351 Describe("IndentString", func() { 352 It("should indent the string", func() { 353 Expect(IndentString("foo\n bar\nbaz", 2)).Should(Equal(" foo\n bar\n baz")) 354 }) 355 }) 356 357 Describe("Object", func() { 358 Describe("formatting boolean values", func() { 359 It("should give the type and format values correctly", func() { 360 Expect(Object(true, 1)).Should(match("bool", "true")) 361 Expect(Object(false, 1)).Should(match("bool", "false")) 362 }) 363 }) 364 365 Describe("formatting numbers", func() { 366 It("should give the type and format values correctly", func() { 367 Expect(Object(int(3), 1)).Should(match("int", "3")) 368 Expect(Object(int8(3), 1)).Should(match("int8", "3")) 369 Expect(Object(int16(3), 1)).Should(match("int16", "3")) 370 Expect(Object(int32(3), 1)).Should(match("int32", "3")) 371 Expect(Object(int64(3), 1)).Should(match("int64", "3")) 372 373 Expect(Object(uint(3), 1)).Should(match("uint", "3")) 374 Expect(Object(uint8(3), 1)).Should(match("uint8", "3")) 375 Expect(Object(uint16(3), 1)).Should(match("uint16", "3")) 376 Expect(Object(uint32(3), 1)).Should(match("uint32", "3")) 377 Expect(Object(uint64(3), 1)).Should(match("uint64", "3")) 378 }) 379 380 It("should handle uintptr differently", func() { 381 Expect(Object(uintptr(3), 1)).Should(match("uintptr", "0x3")) 382 }) 383 }) 384 385 Describe("formatting channels", func() { 386 It("should give the type and format values correctly", func() { 387 c := make(chan<- bool, 3) 388 c <- true 389 c <- false 390 Expect(Object(c, 1)).Should(match("chan<- bool | len:2, cap:3", "%v", c)) 391 }) 392 }) 393 394 Describe("formatting strings", func() { 395 It("should give the type and format values correctly", func() { 396 s := "a\nb\nc" 397 Expect(Object(s, 1)).Should(match("string", `a 398 b 399 c`)) 400 }) 401 }) 402 403 Describe("formatting []byte slices", func() { 404 When("the slice is made of printable bytes", func() { 405 It("should present it as string", func() { 406 b := []byte("a b c") 407 Expect(Object(b, 1)).Should(matchRegexp(`\[\]uint8 \| len:5, cap:\d+`, `a b c`)) 408 }) 409 }) 410 When("the slice contains non-printable bytes", func() { 411 It("should present it as slice", func() { 412 b := []byte("a b c\n\x01\x02\x03\xff\x1bH") 413 Expect(Object(b, 1)).Should(matchRegexp(`\[\]uint8 \| len:12, cap:\d+`, `\[97, 32, 98, 32, 99, 10, 1, 2, 3, 255, 27, 72\]`)) 414 }) 415 }) 416 }) 417 418 Describe("formatting functions", func() { 419 It("should give the type and format values correctly", func() { 420 f := func(a string, b []int) ([]byte, error) { 421 return []byte("abc"), nil 422 } 423 Expect(Object(f, 1)).Should(match("func(string, []int) ([]uint8, error)", "%v", f)) 424 }) 425 }) 426 427 Describe("formatting pointers", func() { 428 It("should give the type and dereference the value to format it correctly", func() { 429 a := 3 430 Expect(Object(&a, 1)).Should(match(fmt.Sprintf("*int | %p", &a), "3")) 431 }) 432 433 When("there are pointers to pointers...", func() { 434 It("should recursively deference the pointer until it gets to a value", func() { 435 a := 3 436 var b *int 437 var c **int 438 var d ***int 439 b = &a 440 c = &b 441 d = &c 442 443 Expect(Object(d, 1)).Should(match(fmt.Sprintf("***int | %p", d), "3")) 444 }) 445 }) 446 447 When("the pointer points to nil", func() { 448 It("should say nil and not explode", func() { 449 var a *AStruct 450 Expect(Object(a, 1)).Should(match("*format_test.AStruct | 0x0", "nil")) 451 }) 452 }) 453 }) 454 455 Describe("formatting arrays", func() { 456 It("should give the type and format values correctly", func() { 457 w := [3]string{"Jed Bartlet", "Toby Ziegler", "CJ Cregg"} 458 Expect(Object(w, 1)).Should(match("[3]string", `["Jed Bartlet", "Toby Ziegler", "CJ Cregg"]`)) 459 }) 460 461 Context("with byte arrays", func() { 462 It("should give the type and format values correctly", func() { 463 w := [3]byte{17, 28, 19} 464 Expect(Object(w, 1)).Should(match("[3]uint8", `[17, 28, 19]`)) 465 }) 466 }) 467 }) 468 469 Describe("formatting slices", func() { 470 It("should include the length and capacity in the type information", func() { 471 s := make([]bool, 3, 4) 472 Expect(Object(s, 1)).Should(match("[]bool | len:3, cap:4", "[false, false, false]")) 473 }) 474 475 When("the slice contains long entries", func() { 476 It("should format the entries with newlines", func() { 477 w := []string{"Josiah Edward Bartlet", "Toby Ziegler", "CJ Cregg"} 478 expected := `[ 479 "Josiah Edward Bartlet", 480 "Toby Ziegler", 481 "CJ Cregg", 482 ]` 483 Expect(Object(w, 1)).Should(match("[]string | len:3, cap:3", expected)) 484 }) 485 }) 486 }) 487 488 Describe("formatting maps", func() { 489 It("should include the length in the type information", func() { 490 m := make(map[int]bool, 5) 491 m[3] = true 492 m[4] = false 493 Expect(Object(m, 1)).Should(matchRegexp(`map\[int\]bool \| len:2`, hashMatchingRegexp("3: true", "4: false"))) 494 }) 495 496 When("the slice contains long entries", func() { 497 It("should format the entries with newlines", func() { 498 m := map[string][]byte{} 499 m["Josiah Edward Bartlet"] = []byte("Martin Sheen") 500 m["Toby Ziegler"] = []byte("Richard Schiff") 501 m["CJ Cregg"] = []byte("Allison Janney") 502 expected := `{ 503 ("Josiah Edward Bartlet": "Martin Sheen"|"Toby Ziegler": "Richard Schiff"|"CJ Cregg": "Allison Janney"), 504 ("Josiah Edward Bartlet": "Martin Sheen"|"Toby Ziegler": "Richard Schiff"|"CJ Cregg": "Allison Janney"), 505 ("Josiah Edward Bartlet": "Martin Sheen"|"Toby Ziegler": "Richard Schiff"|"CJ Cregg": "Allison Janney"), 506 }` 507 Expect(Object(m, 1)).Should(matchRegexp(`map\[string\]\[\]uint8 \| len:3`, expected)) 508 }) 509 }) 510 }) 511 512 Describe("formatting structs", func() { 513 It("should include the struct name and the field names", func() { 514 s := SimpleStruct{ 515 Name: "Oswald", 516 Enumeration: 17, 517 Veritas: true, 518 Data: []byte("datum"), 519 secret: 1983, 520 } 521 522 Expect(Object(s, 1)).Should(match("format_test.SimpleStruct", `{Name: "Oswald", Enumeration: 17, Veritas: true, Data: "datum", secret: 1983}`)) 523 }) 524 525 When("the struct contains long entries", func() { 526 It("should format the entries with new lines", func() { 527 s := &SimpleStruct{ 528 Name: "Mithrandir Gandalf Greyhame", 529 Enumeration: 2021, 530 Veritas: true, 531 Data: []byte("wizard"), 532 secret: 3, 533 } 534 535 Expect(Object(s, 1)).Should(match(fmt.Sprintf("*format_test.SimpleStruct | %p", s), `{ 536 Name: "Mithrandir Gandalf Greyhame", 537 Enumeration: 2021, 538 Veritas: true, 539 Data: "wizard", 540 secret: 3, 541 }`)) 542 }) 543 }) 544 }) 545 546 Describe("formatting nil values", func() { 547 It("should print out nil", func() { 548 Expect(Object(nil, 1)).Should(match("nil", "nil")) 549 var typedNil *AStruct 550 Expect(Object(typedNil, 1)).Should(match("*format_test.AStruct | 0x0", "nil")) 551 var c chan<- bool 552 Expect(Object(c, 1)).Should(match("chan<- bool | len:0, cap:0", "nil")) 553 var s []string 554 Expect(Object(s, 1)).Should(match("[]string | len:0, cap:0", "nil")) 555 var m map[string]bool 556 Expect(Object(m, 1)).Should(match("map[string]bool | len:0", "nil")) 557 }) 558 }) 559 560 Describe("formatting aliased types", func() { 561 It("should print out the correct alias type", func() { 562 Expect(Object(StringAlias("alias"), 1)).Should(match("format_test.StringAlias", `alias`)) 563 Expect(Object(ByteAlias("alias"), 1)).Should(matchRegexp(`format_test\.ByteAlias \| len:5, cap:\d+`, `alias`)) 564 Expect(Object(IntAlias(3), 1)).Should(match("format_test.IntAlias", "3")) 565 }) 566 }) 567 568 Describe("handling nested things", func() { 569 It("should produce a correctly nested representation", func() { 570 s := ComplexStruct{ 571 Strings: []string{"lots", "of", "short", "strings"}, 572 SimpleThings: []*SimpleStruct{ 573 {"short", 7, true, []byte("succinct"), 17}, 574 {"something longer", 427, true, []byte("designed to wrap around nicely"), 30}, 575 }, 576 DataMaps: map[int]ByteAlias{ 577 17: ByteAlias("some substantially longer chunks of data"), 578 1138: ByteAlias("that should make things wrap"), 579 }, 580 } 581 expected := `{ 582 Strings: \["lots", "of", "short", "strings"\], 583 SimpleThings: \[ 584 {Name: "short", Enumeration: 7, Veritas: true, Data: "succinct", secret: 17}, 585 { 586 Name: "something longer", 587 Enumeration: 427, 588 Veritas: true, 589 Data: "designed to wrap around nicely", 590 secret: 30, 591 }, 592 \], 593 DataMaps: { 594 (17: "some substantially longer chunks of data"|1138: "that should make things wrap"), 595 (17: "some substantially longer chunks of data"|1138: "that should make things wrap"), 596 }, 597 }` 598 Expect(Object(s, 1)).Should(matchRegexp(`format_test\.ComplexStruct`, expected)) 599 }) 600 }) 601 602 Describe("formatting nested interface{} types", func() { 603 It("should print out the types of the container and value", func() { 604 Expect(Object([]interface{}{"foo"}, 1)). 605 To(match("[]interface {} | len:1, cap:1", `[<string>"foo"]`)) 606 607 Expect(Object(map[string]interface{}{"foo": true}, 1)). 608 To(match("map[string]interface {} | len:1", `{"foo": <bool>true}`)) 609 610 Expect(Object(struct{ A interface{} }{A: 1}, 1)). 611 To(match("struct { A interface {} }", "{A: <int>1}")) 612 613 v := struct{ A interface{} }{A: struct{ B string }{B: "foo"}} 614 Expect(Object(v, 1)).To(match(`struct { A interface {} }`, `{ 615 A: <struct { B string }>{B: "foo"}, 616 }`)) 617 }) 618 }) 619 620 Describe("formatting times", func() { 621 It("should format time as RFC3339", func() { 622 t := time.Date(2016, 10, 31, 9, 57, 23, 12345, time.UTC) 623 Expect(Object(t, 1)).Should(match("time.Time", `2016-10-31T09:57:23.000012345Z`)) 624 }) 625 }) 626 627 Describe("formatting errors", func() { 628 It("should include the error() representation", func() { 629 err := fmt.Errorf("whoops: %w", fmt.Errorf("welp: %w", fmt.Errorf("ruh roh"))) 630 Expect(Object(err, 1)).Should(MatchRegexp(` \<\*fmt\.wrapError \| 0x[0-9a-f]*\>\: 631 whoops\: welp\: ruh roh 632 \{ 633 msg\: "whoops\: welp\: ruh roh", 634 err\: \<\*fmt.wrapError \| 0x[0-9a-f]*\>\{ 635 msg\: "welp\: ruh roh", 636 err\: \<\*errors.errorString \| 0x[0-9a-f]*\>\{s\: "ruh roh"\}, 637 \}, 638 \}`)) 639 }) 640 641 It("should not panic if the error is a boxed nil", func() { 642 var err *CustomError 643 Expect(Object(err, 1)).Should(Equal(" <*format_test.CustomError | 0x0>: nil")) 644 }) 645 }) 646 }) 647 648 Describe("Handling unexported fields in structs", func() { 649 It("should handle all the various types correctly", func() { 650 a := int(5) 651 s := SecretiveStruct{ 652 boolValue: true, 653 intValue: 3, 654 uintValue: 4, 655 uintptrValue: 5, 656 floatValue: 6.0, 657 complexValue: complex(5.0, 3.0), 658 chanValue: make(chan bool, 2), 659 funcValue: func() {}, 660 pointerValue: &a, 661 sliceValue: []string{"string", "slice"}, 662 byteSliceValue: []byte("bytes"), 663 stringValue: "a string", 664 arrValue: [3]int{11, 12, 13}, 665 byteArrValue: [3]byte{17, 20, 32}, 666 mapValue: map[string]int{"a key": 20, "b key": 30}, 667 structValue: AStruct{"exported"}, 668 interfaceValue: map[string]int{"a key": 17}, 669 } 670 671 expected := fmt.Sprintf(`{ 672 boolValue: true, 673 intValue: 3, 674 uintValue: 4, 675 uintptrValue: 0x5, 676 floatValue: 6, 677 complexValue: \(5\+3i\), 678 chanValue: %p, 679 funcValue: %p, 680 pointerValue: 5, 681 sliceValue: \["string", "slice"\], 682 byteSliceValue: "bytes", 683 stringValue: "a string", 684 arrValue: \[11, 12, 13\], 685 byteArrValue: \[17, 20, 32\], 686 mapValue: %s, 687 structValue: {Exported: "exported"}, 688 interfaceValue: <map\[string\]int \| len:1>{"a key": 17}, 689 }`, s.chanValue, s.funcValue, hashMatchingRegexp(`"a key": 20`, `"b key": 30`)) 690 691 Expect(Object(s, 1)).Should(matchRegexp(`format_test\.SecretiveStruct`, expected)) 692 }) 693 }) 694 695 Describe("Handling interfaces", func() { 696 It("should unpack the interface", func() { 697 outerHash := map[string]interface{}{} 698 innerHash := map[string]int{} 699 700 innerHash["inner"] = 3 701 outerHash["integer"] = 2 702 outerHash["map"] = innerHash 703 704 expected := hashMatchingRegexp(`"integer": <int>2`, `"map": <map\[string\]int \| len:1>{"inner": 3}`) 705 Expect(Object(outerHash, 1)).Should(matchRegexp(`map\[string\]interface {} \| len:2`, expected)) 706 }) 707 }) 708 709 Describe("Handling recursive things", func() { 710 It("should not go crazy...", func() { 711 m := map[string]interface{}{} 712 m["integer"] = 2 713 m["map"] = m 714 Expect(Object(m, 1)).Should(ContainSubstring("...")) 715 }) 716 717 It("really should not go crazy...", func() { 718 type complexKey struct { 719 Value map[interface{}]int 720 } 721 722 complexObject := complexKey{} 723 complexObject.Value = make(map[interface{}]int) 724 725 complexObject.Value[&complexObject] = 2 726 Expect(Object(complexObject, 1)).Should(ContainSubstring("...")) 727 }) 728 }) 729 730 Describe("When instructed to use the Stringer representation", func() { 731 BeforeEach(func() { 732 UseStringerRepresentation = true 733 }) 734 735 AfterEach(func() { 736 UseStringerRepresentation = false 737 }) 738 739 When("passed a GoStringer", func() { 740 It("should use what GoString() returns", func() { 741 Expect(Object(GoStringer{}, 1)).Should(ContainSubstring("<format_test.GoStringer>: go-string")) 742 }) 743 }) 744 745 When("passed a stringer", func() { 746 It("should use what String() returns", func() { 747 Expect(Object(Stringer{}, 1)).Should(ContainSubstring("<format_test.Stringer>: string")) 748 }) 749 }) 750 751 When("passed a GomegaStringer", func() { 752 It("should use what GomegaString() returns", func() { 753 Expect(Object(gomegaStringer{}, 1)).Should(ContainSubstring("<format_test.gomegaStringer>: gomegastring")) 754 UseStringerRepresentation = false 755 Expect(Object(gomegaStringer{}, 1)).Should(ContainSubstring("<format_test.gomegaStringer>: gomegastring")) 756 }) 757 758 It("should use what GomegaString() returns, disregarding MaxLength", func() { 759 Expect(Object(gomegaStringerLong{}, 1)).Should(Equal(" <format_test.gomegaStringerLong>: " + strings.Repeat("s", MaxLength*2))) 760 UseStringerRepresentation = false 761 Expect(Object(gomegaStringerLong{}, 1)).Should(Equal(" <format_test.gomegaStringerLong>: " + strings.Repeat("s", MaxLength*2))) 762 }) 763 764 It("should indent what the GomegaString() returns", func() { 765 Expect(Object(gomegaStringerMultiline{}, 1)).Should(Equal(" <format_test.gomegaStringerMultiline>: A\n B\n C")) 766 }) 767 }) 768 769 Describe("when used with a registered CustomFormatter", func() { 770 It("pases objects through the custom formatter and uses the returned format, if handled", func() { 771 cf := CustomFormatted{"bob", 17} 772 ncf := NotCustomFormatted{"bob", 17} 773 Expect(Object(cf, 0)).To(Equal("<format_test.CustomFormatted>: {Data: bob, Count: 17}")) 774 Expect(Object(ncf, 0)).To(Equal("<format_test.NotCustomFormatted>: {Data: bob, Count: 17}")) 775 776 key := RegisterCustomFormatter(customFormatter) 777 Expect(Object(cf, 0)).To(Equal("<format_test.CustomFormatted>: bob (17)")) 778 Expect(Object(ncf, 0)).To(Equal("<format_test.NotCustomFormatted>: {Data: bob, Count: 17}")) 779 780 UnregisterCustomFormatter(key) 781 Expect(Object(cf, 0)).To(Equal("<format_test.CustomFormatted>: {Data: bob, Count: 17}")) 782 Expect(Object(ncf, 0)).To(Equal("<format_test.NotCustomFormatted>: {Data: bob, Count: 17}")) 783 }) 784 785 It("indents CustomFormatter output correctly", func() { 786 cf := CustomFormatted{"hey\nbob", 17} 787 DeferCleanup(UnregisterCustomFormatter, RegisterCustomFormatter(func(value interface{}) (string, bool) { 788 cf, ok := value.(CustomFormatted) 789 if !ok { 790 return "", false 791 } 792 return fmt.Sprintf("The Data:\n%s\nThe Count:%d", cf.Data, cf.Count), true 793 })) 794 795 Ω(Object(cf, 1)).Should(Equal(" <format_test.CustomFormatted>: The Data:\n hey\n bob\n The Count:17")) 796 797 type Wrapped struct { 798 MyObject CustomFormatted 799 OuterCount int 800 } 801 wrapped := Wrapped{ 802 MyObject: cf, 803 OuterCount: 10, 804 } 805 Ω(Object(wrapped, 1)).Should(Equal(" <format_test.Wrapped>: {\n MyObject: The Data:\n hey\n bob\n The Count:17,\n OuterCount: 10,\n }")) 806 807 }) 808 }) 809 }) 810 811 Describe("Printing a context.Context field", func() { 812 type structWithContext struct { 813 Context context.Context 814 Value string 815 } 816 817 objWithContext := structWithContext{Value: "some-value", Context: context.TODO()} 818 819 It("Suppresses the content by default", func() { 820 Expect(Object(objWithContext, 1)).Should(ContainSubstring("<suppressed context>")) 821 }) 822 823 It("Doesn't suppress the context if it's the object being printed", func() { 824 Expect(Object(context.TODO(), 1)).ShouldNot(MatchRegexp("^.*<suppressed context>$")) 825 }) 826 827 Context("PrintContextObjects is set", func() { 828 BeforeEach(func() { 829 PrintContextObjects = true 830 }) 831 832 AfterEach(func() { 833 PrintContextObjects = false 834 }) 835 836 It("Prints the context", func() { 837 Expect(Object(objWithContext, 1)).ShouldNot(ContainSubstring("<suppressed context>")) 838 }) 839 }) 840 }) 841 }) 842 843 var expectedLongStringFailureMessage = strings.TrimSpace(` 844 Expected 845 <string>: "...aaaaabaaaaa..." 846 to equal | 847 <string>: "...aaaaazaaaaa..." 848 `) 849 var expectedTruncatedEndStringFailureMessage = strings.TrimSpace(` 850 Expected 851 <string>: "baaaaa..." 852 to equal | 853 <string>: "zaaaaa..." 854 `) 855 var expectedTruncatedStartStringFailureMessage = strings.TrimSpace(` 856 Expected 857 <string>: "...aaaaab" 858 to equal | 859 <string>: "...aaaaaz" 860 `) 861 var expectedTruncatedStartSizeFailureMessage = strings.TrimSpace(` 862 Expected 863 <string>: "...aaaaaa" 864 to equal | 865 <string>: "...aaaaa" 866 `) 867 var expectedTruncatedStartSizeFailureMessageExtraDiff = strings.TrimSpace(` 868 Expected 869 <string>: "...aaaaaaaaaaa" 870 to equal | 871 <string>: "...aaaaaaaaaa" 872 `) 873 var expectedTruncatedStartSizeSwappedFailureMessage = strings.TrimSpace(` 874 Expected 875 <string>: "...aaaa" 876 to equal | 877 <string>: "...aaaaa" 878 `) 879 var expectedTruncatedStartSizeSwappedFailureMessageExtraDiff = strings.TrimSpace(` 880 Expected 881 <string>: "...aaaaaaaaa" 882 to equal | 883 <string>: "...aaaaaaaaaa" 884 `) 885 var expectedTruncatedMultiByteFailureMessage = strings.TrimSpace(` 886 Expected 887 <string>: "...tuvwxyz1" 888 to equal | 889 <string>: "...tuvwxyz" 890 `) 891 var expectedFullFailureDiff = strings.TrimSpace(` 892 Expected 893 <string>: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 894 to equal 895 <string>: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 896 `) 897 var expectedSpecialCharacterFailureMessage = strings.TrimSpace(` 898 Expected 899 <string>: \n 900 to equal 901 <string>: something_else 902 903 `) 904 var expectedDiffSmallThreshold = strings.TrimSpace(` 905 Expected 906 <string>: "aba" 907 to equal | 908 <string>: "aza" 909 `) 910 var expectedDiffZeroMismatch = strings.TrimSpace(` 911 Expected 912 <string>: aba 913 to equal 914 <string>: aza 915 `) 916 var expectedDiffSmallThresholdZeroMismatch = strings.TrimSpace(` 917 Expected 918 <string>: "...b..." 919 to equal | 920 <string>: "...z..." 921 `) 922 923 var expectedDiffLongMessage = strings.TrimSpace(` 924 Expected 925 <string>: "...aaaaabaaaaa..." 926 to equal very long message 927 <string>: "...aaaaazaaaaa..." 928 `)