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
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
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
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
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
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 `)
929
View as plain text