1package format_test 2 3import ( 4 "fmt" 5 "strings" 6 "time" 7 8 . "github.com/onsi/ginkgo" 9 . "github.com/onsi/gomega" 10 . "github.com/onsi/gomega/format" 11 "github.com/onsi/gomega/types" 12) 13 14//recursive struct 15 16type StringAlias string 17type ByteAlias []byte 18type IntAlias int 19 20type AStruct struct { 21 Exported string 22} 23 24type SimpleStruct struct { 25 Name string 26 Enumeration int 27 Veritas bool 28 Data []byte 29 secret uint32 30} 31 32type ComplexStruct struct { 33 Strings []string 34 SimpleThings []*SimpleStruct 35 DataMaps map[int]ByteAlias 36} 37 38type SecretiveStruct struct { 39 boolValue bool 40 intValue int 41 uintValue uint 42 uintptrValue uintptr 43 floatValue float32 44 complexValue complex64 45 chanValue chan bool 46 funcValue func() 47 pointerValue *int 48 sliceValue []string 49 byteSliceValue []byte 50 stringValue string 51 arrValue [3]int 52 byteArrValue [3]byte 53 mapValue map[string]int 54 structValue AStruct 55 interfaceValue interface{} 56} 57 58type GoStringer struct { 59} 60 61func (g GoStringer) GoString() string { 62 return "go-string" 63} 64 65func (g GoStringer) String() string { 66 return "string" 67} 68 69type Stringer struct { 70} 71 72func (g Stringer) String() string { 73 return "string" 74} 75 76type ctx struct { 77} 78 79func (c *ctx) Deadline() (deadline time.Time, ok bool) { 80 return time.Time{}, false 81} 82 83func (c *ctx) Done() <-chan struct{} { 84 return nil 85} 86 87func (c *ctx) Err() error { 88 return nil 89} 90 91func (c *ctx) Value(key interface{}) interface{} { 92 return nil 93} 94 95var _ = Describe("Format", func() { 96 match := func(typeRepresentation string, valueRepresentation string, args ...interface{}) types.GomegaMatcher { 97 if len(args) > 0 { 98 valueRepresentation = fmt.Sprintf(valueRepresentation, args...) 99 } 100 return Equal(fmt.Sprintf("%s<%s>: %s", Indent, typeRepresentation, valueRepresentation)) 101 } 102 103 matchRegexp := func(typeRepresentation string, valueRepresentation string, args ...interface{}) types.GomegaMatcher { 104 if len(args) > 0 { 105 valueRepresentation = fmt.Sprintf(valueRepresentation, args...) 106 } 107 return MatchRegexp(fmt.Sprintf("%s<%s>: %s", Indent, typeRepresentation, valueRepresentation)) 108 } 109 110 hashMatchingRegexp := func(entries ...string) string { 111 entriesSwitch := "(" + strings.Join(entries, "|") + ")" 112 arr := make([]string, len(entries)) 113 for i := range arr { 114 arr[i] = entriesSwitch 115 } 116 return "{" + strings.Join(arr, ", ") + "}" 117 } 118 119 Describe("Message", func() { 120 Context("with only an actual value", func() { 121 It("should print out an indented formatted representation of the value and the message", func() { 122 Ω(Message(3, "to be three.")).Should(Equal("Expected\n <int>: 3\nto be three.")) 123 }) 124 }) 125 126 Context("with an actual and an expected value", func() { 127 It("should print out an indented formatted representatino of both values, and the message", func() { 128 Ω(Message(3, "to equal", 4)).Should(Equal("Expected\n <int>: 3\nto equal\n <int>: 4")) 129 }) 130 }) 131 }) 132 133 Describe("MessageWithDiff", func() { 134 It("shows the exact point where two long strings differ", func() { 135 stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 136 stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 137 138 Ω(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedLongStringFailureMessage)) 139 }) 140 141 It("truncates the start of long strings that differ only at their end", func() { 142 stringWithB := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" 143 stringWithZ := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz" 144 145 Ω(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedTruncatedStartStringFailureMessage)) 146 }) 147 148 It("truncates the start of long strings that differ only in length", func() { 149 smallString := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 150 largeString := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 151 152 Ω(MessageWithDiff(largeString, "to equal", smallString)).Should(Equal(expectedTruncatedStartSizeFailureMessage)) 153 Ω(MessageWithDiff(smallString, "to equal", largeString)).Should(Equal(expectedTruncatedStartSizeSwappedFailureMessage)) 154 }) 155 156 It("truncates the end of long strings that differ only at their start", func() { 157 stringWithB := "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 158 stringWithZ := "zaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 159 160 Ω(MessageWithDiff(stringWithB, "to equal", stringWithZ)).Should(Equal(expectedTruncatedEndStringFailureMessage)) 161 }) 162 }) 163 164 Describe("IndentString", func() { 165 It("should indent the string", func() { 166 Ω(IndentString("foo\n bar\nbaz", 2)).Should(Equal(" foo\n bar\n baz")) 167 }) 168 }) 169 170 Describe("Object", func() { 171 Describe("formatting boolean values", func() { 172 It("should give the type and format values correctly", func() { 173 Ω(Object(true, 1)).Should(match("bool", "true")) 174 Ω(Object(false, 1)).Should(match("bool", "false")) 175 }) 176 }) 177 178 Describe("formatting numbers", func() { 179 It("should give the type and format values correctly", func() { 180 Ω(Object(int(3), 1)).Should(match("int", "3")) 181 Ω(Object(int8(3), 1)).Should(match("int8", "3")) 182 Ω(Object(int16(3), 1)).Should(match("int16", "3")) 183 Ω(Object(int32(3), 1)).Should(match("int32", "3")) 184 Ω(Object(int64(3), 1)).Should(match("int64", "3")) 185 186 Ω(Object(uint(3), 1)).Should(match("uint", "3")) 187 Ω(Object(uint8(3), 1)).Should(match("uint8", "3")) 188 Ω(Object(uint16(3), 1)).Should(match("uint16", "3")) 189 Ω(Object(uint32(3), 1)).Should(match("uint32", "3")) 190 Ω(Object(uint64(3), 1)).Should(match("uint64", "3")) 191 }) 192 193 It("should handle uintptr differently", func() { 194 Ω(Object(uintptr(3), 1)).Should(match("uintptr", "0x3")) 195 }) 196 }) 197 198 Describe("formatting channels", func() { 199 It("should give the type and format values correctly", func() { 200 c := make(chan<- bool, 3) 201 c <- true 202 c <- false 203 Ω(Object(c, 1)).Should(match("chan<- bool | len:2, cap:3", "%v", c)) 204 }) 205 }) 206 207 Describe("formatting strings", func() { 208 It("should give the type and format values correctly", func() { 209 s := "a\nb\nc" 210 Ω(Object(s, 1)).Should(match("string", `a 211 b 212 c`)) 213 }) 214 }) 215 216 Describe("formatting []byte slices", func() { 217 Context("when the slice is made of printable bytes", func() { 218 It("should present it as string", func() { 219 b := []byte("a b c") 220 Ω(Object(b, 1)).Should(matchRegexp(`\[\]uint8 \| len:5, cap:\d+`, `a b c`)) 221 }) 222 }) 223 Context("when the slice contains non-printable bytes", func() { 224 It("should present it as slice", func() { 225 b := []byte("a b c\n\x01\x02\x03\xff\x1bH") 226 Ω(Object(b, 1)).Should(matchRegexp(`\[\]uint8 \| len:12, cap:\d+`, `\[97, 32, 98, 32, 99, 10, 1, 2, 3, 255, 27, 72\]`)) 227 }) 228 }) 229 }) 230 231 Describe("formatting functions", func() { 232 It("should give the type and format values correctly", func() { 233 f := func(a string, b []int) ([]byte, error) { 234 return []byte("abc"), nil 235 } 236 Ω(Object(f, 1)).Should(match("func(string, []int) ([]uint8, error)", "%v", f)) 237 }) 238 }) 239 240 Describe("formatting pointers", func() { 241 It("should give the type and dereference the value to format it correctly", func() { 242 a := 3 243 Ω(Object(&a, 1)).Should(match(fmt.Sprintf("*int | %p", &a), "3")) 244 }) 245 246 Context("when there are pointers to pointers...", func() { 247 It("should recursively deference the pointer until it gets to a value", func() { 248 a := 3 249 var b *int 250 var c **int 251 var d ***int 252 b = &a 253 c = &b 254 d = &c 255 256 Ω(Object(d, 1)).Should(match(fmt.Sprintf("***int | %p", d), "3")) 257 }) 258 }) 259 260 Context("when the pointer points to nil", func() { 261 It("should say nil and not explode", func() { 262 var a *AStruct 263 Ω(Object(a, 1)).Should(match("*format_test.AStruct | 0x0", "nil")) 264 }) 265 }) 266 }) 267 268 Describe("formatting arrays", func() { 269 It("should give the type and format values correctly", func() { 270 w := [3]string{"Jed Bartlet", "Toby Ziegler", "CJ Cregg"} 271 Ω(Object(w, 1)).Should(match("[3]string", `["Jed Bartlet", "Toby Ziegler", "CJ Cregg"]`)) 272 }) 273 274 Context("with byte arrays", func() { 275 It("should give the type and format values correctly", func() { 276 w := [3]byte{17, 28, 19} 277 Ω(Object(w, 1)).Should(match("[3]uint8", `[17, 28, 19]`)) 278 }) 279 }) 280 }) 281 282 Describe("formatting slices", func() { 283 It("should include the length and capacity in the type information", func() { 284 s := make([]bool, 3, 4) 285 Ω(Object(s, 1)).Should(match("[]bool | len:3, cap:4", "[false, false, false]")) 286 }) 287 288 Context("when the slice contains long entries", func() { 289 It("should format the entries with newlines", func() { 290 w := []string{"Josiah Edward Bartlet", "Toby Ziegler", "CJ Cregg"} 291 expected := `[ 292 "Josiah Edward Bartlet", 293 "Toby Ziegler", 294 "CJ Cregg", 295 ]` 296 Ω(Object(w, 1)).Should(match("[]string | len:3, cap:3", expected)) 297 }) 298 }) 299 }) 300 301 Describe("formatting maps", func() { 302 It("should include the length in the type information", func() { 303 m := make(map[int]bool, 5) 304 m[3] = true 305 m[4] = false 306 Ω(Object(m, 1)).Should(matchRegexp(`map\[int\]bool \| len:2`, hashMatchingRegexp("3: true", "4: false"))) 307 }) 308 309 Context("when the slice contains long entries", func() { 310 It("should format the entries with newlines", func() { 311 m := map[string][]byte{} 312 m["Josiah Edward Bartlet"] = []byte("Martin Sheen") 313 m["Toby Ziegler"] = []byte("Richard Schiff") 314 m["CJ Cregg"] = []byte("Allison Janney") 315 expected := `{ 316 ("Josiah Edward Bartlet": "Martin Sheen"|"Toby Ziegler": "Richard Schiff"|"CJ Cregg": "Allison Janney"), 317 ("Josiah Edward Bartlet": "Martin Sheen"|"Toby Ziegler": "Richard Schiff"|"CJ Cregg": "Allison Janney"), 318 ("Josiah Edward Bartlet": "Martin Sheen"|"Toby Ziegler": "Richard Schiff"|"CJ Cregg": "Allison Janney"), 319 }` 320 Ω(Object(m, 1)).Should(matchRegexp(`map\[string\]\[\]uint8 \| len:3`, expected)) 321 }) 322 }) 323 }) 324 325 Describe("formatting structs", func() { 326 It("should include the struct name and the field names", func() { 327 s := SimpleStruct{ 328 Name: "Oswald", 329 Enumeration: 17, 330 Veritas: true, 331 Data: []byte("datum"), 332 secret: 1983, 333 } 334 335 Ω(Object(s, 1)).Should(match("format_test.SimpleStruct", `{Name: "Oswald", Enumeration: 17, Veritas: true, Data: "datum", secret: 1983}`)) 336 }) 337 338 Context("when the struct contains long entries", func() { 339 It("should format the entries with new lines", func() { 340 s := &SimpleStruct{ 341 Name: "Mithrandir Gandalf Greyhame", 342 Enumeration: 2021, 343 Veritas: true, 344 Data: []byte("wizard"), 345 secret: 3, 346 } 347 348 Ω(Object(s, 1)).Should(match(fmt.Sprintf("*format_test.SimpleStruct | %p", s), `{ 349 Name: "Mithrandir Gandalf Greyhame", 350 Enumeration: 2021, 351 Veritas: true, 352 Data: "wizard", 353 secret: 3, 354 }`)) 355 }) 356 }) 357 }) 358 359 Describe("formatting nil values", func() { 360 It("should print out nil", func() { 361 Ω(Object(nil, 1)).Should(match("nil", "nil")) 362 var typedNil *AStruct 363 Ω(Object(typedNil, 1)).Should(match("*format_test.AStruct | 0x0", "nil")) 364 var c chan<- bool 365 Ω(Object(c, 1)).Should(match("chan<- bool | len:0, cap:0", "nil")) 366 var s []string 367 Ω(Object(s, 1)).Should(match("[]string | len:0, cap:0", "nil")) 368 var m map[string]bool 369 Ω(Object(m, 1)).Should(match("map[string]bool | len:0", "nil")) 370 }) 371 }) 372 373 Describe("formatting aliased types", func() { 374 It("should print out the correct alias type", func() { 375 Ω(Object(StringAlias("alias"), 1)).Should(match("format_test.StringAlias", `alias`)) 376 Ω(Object(ByteAlias("alias"), 1)).Should(matchRegexp(`format_test\.ByteAlias \| len:5, cap:\d+`, `alias`)) 377 Ω(Object(IntAlias(3), 1)).Should(match("format_test.IntAlias", "3")) 378 }) 379 }) 380 381 Describe("handling nested things", func() { 382 It("should produce a correctly nested representation", func() { 383 s := ComplexStruct{ 384 Strings: []string{"lots", "of", "short", "strings"}, 385 SimpleThings: []*SimpleStruct{ 386 {"short", 7, true, []byte("succinct"), 17}, 387 {"something longer", 427, true, []byte("designed to wrap around nicely"), 30}, 388 }, 389 DataMaps: map[int]ByteAlias{ 390 17: ByteAlias("some substantially longer chunks of data"), 391 1138: ByteAlias("that should make things wrap"), 392 }, 393 } 394 expected := `{ 395 Strings: \["lots", "of", "short", "strings"\], 396 SimpleThings: \[ 397 {Name: "short", Enumeration: 7, Veritas: true, Data: "succinct", secret: 17}, 398 { 399 Name: "something longer", 400 Enumeration: 427, 401 Veritas: true, 402 Data: "designed to wrap around nicely", 403 secret: 30, 404 }, 405 \], 406 DataMaps: { 407 (17: "some substantially longer chunks of data"|1138: "that should make things wrap"), 408 (17: "some substantially longer chunks of data"|1138: "that should make things wrap"), 409 }, 410 }` 411 Ω(Object(s, 1)).Should(matchRegexp(`format_test\.ComplexStruct`, expected)) 412 }) 413 }) 414 415 Describe("formatting times", func() { 416 It("should format time as RFC3339", func() { 417 t := time.Date(2016, 10, 31, 9, 57, 23, 12345, time.UTC) 418 Ω(Object(t, 1)).Should(match("time.Time", `2016-10-31T09:57:23.000012345Z`)) 419 }) 420 }) 421 }) 422 423 Describe("Handling unexported fields in structs", func() { 424 It("should handle all the various types correctly", func() { 425 a := int(5) 426 s := SecretiveStruct{ 427 boolValue: true, 428 intValue: 3, 429 uintValue: 4, 430 uintptrValue: 5, 431 floatValue: 6.0, 432 complexValue: complex(5.0, 3.0), 433 chanValue: make(chan bool, 2), 434 funcValue: func() {}, 435 pointerValue: &a, 436 sliceValue: []string{"string", "slice"}, 437 byteSliceValue: []byte("bytes"), 438 stringValue: "a string", 439 arrValue: [3]int{11, 12, 13}, 440 byteArrValue: [3]byte{17, 20, 32}, 441 mapValue: map[string]int{"a key": 20, "b key": 30}, 442 structValue: AStruct{"exported"}, 443 interfaceValue: map[string]int{"a key": 17}, 444 } 445 446 expected := fmt.Sprintf(`{ 447 boolValue: true, 448 intValue: 3, 449 uintValue: 4, 450 uintptrValue: 0x5, 451 floatValue: 6, 452 complexValue: \(5\+3i\), 453 chanValue: %p, 454 funcValue: %p, 455 pointerValue: 5, 456 sliceValue: \["string", "slice"\], 457 byteSliceValue: "bytes", 458 stringValue: "a string", 459 arrValue: \[11, 12, 13\], 460 byteArrValue: \[17, 20, 32\], 461 mapValue: %s, 462 structValue: {Exported: "exported"}, 463 interfaceValue: {"a key": 17}, 464 }`, s.chanValue, s.funcValue, hashMatchingRegexp(`"a key": 20`, `"b key": 30`)) 465 466 Ω(Object(s, 1)).Should(matchRegexp(`format_test\.SecretiveStruct`, expected)) 467 }) 468 }) 469 470 Describe("Handling interfaces", func() { 471 It("should unpack the interface", func() { 472 outerHash := map[string]interface{}{} 473 innerHash := map[string]int{} 474 475 innerHash["inner"] = 3 476 outerHash["integer"] = 2 477 outerHash["map"] = innerHash 478 479 expected := hashMatchingRegexp(`"integer": 2`, `"map": {"inner": 3}`) 480 Ω(Object(outerHash, 1)).Should(matchRegexp(`map\[string\]interface {} \| len:2`, expected)) 481 }) 482 }) 483 484 Describe("Handling recursive things", func() { 485 It("should not go crazy...", func() { 486 m := map[string]interface{}{} 487 m["integer"] = 2 488 m["map"] = m 489 Ω(Object(m, 1)).Should(ContainSubstring("...")) 490 }) 491 492 It("really should not go crazy...", func() { 493 type complexKey struct { 494 Value map[interface{}]int 495 } 496 497 complexObject := complexKey{} 498 complexObject.Value = make(map[interface{}]int) 499 500 complexObject.Value[&complexObject] = 2 501 Ω(Object(complexObject, 1)).Should(ContainSubstring("...")) 502 }) 503 }) 504 505 Describe("When instructed to use the Stringer representation", func() { 506 BeforeEach(func() { 507 UseStringerRepresentation = true 508 }) 509 510 AfterEach(func() { 511 UseStringerRepresentation = false 512 }) 513 514 Context("when passed a GoStringer", func() { 515 It("should use what GoString() returns", func() { 516 Ω(Object(GoStringer{}, 1)).Should(ContainSubstring("<format_test.GoStringer>: go-string")) 517 }) 518 }) 519 520 Context("when passed a stringer", func() { 521 It("should use what String() returns", func() { 522 Ω(Object(Stringer{}, 1)).Should(ContainSubstring("<format_test.Stringer>: string")) 523 }) 524 }) 525 }) 526 527 Describe("Printing a context.Context field", func() { 528 529 type structWithContext struct { 530 Context Ctx 531 Value string 532 } 533 534 context := ctx{} 535 objWithContext := structWithContext{Value: "some-value", Context: &context} 536 537 It("Suppresses the content by default", func() { 538 Ω(Object(objWithContext, 1)).Should(ContainSubstring("<suppressed context>")) 539 }) 540 541 It("Doesn't supress the context if it's the object being printed", func() { 542 Ω(Object(context, 1)).ShouldNot(MatchRegexp("^.*<suppressed context>$")) 543 }) 544 545 Context("PrintContextObjects is set", func() { 546 BeforeEach(func() { 547 PrintContextObjects = true 548 }) 549 550 AfterEach(func() { 551 PrintContextObjects = false 552 }) 553 554 It("Prints the context", func() { 555 Ω(Object(objWithContext, 1)).ShouldNot(ContainSubstring("<suppressed context>")) 556 }) 557 }) 558 }) 559}) 560 561var expectedLongStringFailureMessage = strings.TrimSpace(` 562Expected 563 <string>: "...aaaaabaaaaa..." 564to equal | 565 <string>: "...aaaaazaaaaa..." 566`) 567var expectedTruncatedEndStringFailureMessage = strings.TrimSpace(` 568Expected 569 <string>: "baaaaa..." 570to equal | 571 <string>: "zaaaaa..." 572`) 573var expectedTruncatedStartStringFailureMessage = strings.TrimSpace(` 574Expected 575 <string>: "...aaaaab" 576to equal | 577 <string>: "...aaaaaz" 578`) 579var expectedTruncatedStartSizeFailureMessage = strings.TrimSpace(` 580Expected 581 <string>: "...aaaaaa" 582to equal | 583 <string>: "...aaaaa" 584`) 585var expectedTruncatedStartSizeSwappedFailureMessage = strings.TrimSpace(` 586Expected 587 <string>: "...aaaa" 588to equal | 589 <string>: "...aaaaa" 590`) 591