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