1package yaml_test
2
3import (
4	"bytes"
5	"fmt"
6	"math"
7	"strconv"
8	"strings"
9	"time"
10
11	"net"
12	"os"
13
14	. "gopkg.in/check.v1"
15	"gopkg.in/yaml.v2"
16)
17
18var marshalIntTest = 123
19
20var marshalTests = []struct {
21	value interface{}
22	data  string
23}{
24	{
25		nil,
26		"null\n",
27	}, {
28		(*marshalerType)(nil),
29		"null\n",
30	}, {
31		&struct{}{},
32		"{}\n",
33	}, {
34		map[string]string{"v": "hi"},
35		"v: hi\n",
36	}, {
37		map[string]interface{}{"v": "hi"},
38		"v: hi\n",
39	}, {
40		map[string]string{"v": "true"},
41		"v: \"true\"\n",
42	}, {
43		map[string]string{"v": "false"},
44		"v: \"false\"\n",
45	}, {
46		map[string]interface{}{"v": true},
47		"v: true\n",
48	}, {
49		map[string]interface{}{"v": false},
50		"v: false\n",
51	}, {
52		map[string]interface{}{"v": 10},
53		"v: 10\n",
54	}, {
55		map[string]interface{}{"v": -10},
56		"v: -10\n",
57	}, {
58		map[string]uint{"v": 42},
59		"v: 42\n",
60	}, {
61		map[string]interface{}{"v": int64(4294967296)},
62		"v: 4294967296\n",
63	}, {
64		map[string]int64{"v": int64(4294967296)},
65		"v: 4294967296\n",
66	}, {
67		map[string]uint64{"v": 4294967296},
68		"v: 4294967296\n",
69	}, {
70		map[string]interface{}{"v": "10"},
71		"v: \"10\"\n",
72	}, {
73		map[string]interface{}{"v": 0.1},
74		"v: 0.1\n",
75	}, {
76		map[string]interface{}{"v": float64(0.1)},
77		"v: 0.1\n",
78	}, {
79		map[string]interface{}{"v": -0.1},
80		"v: -0.1\n",
81	}, {
82		map[string]interface{}{"v": math.Inf(+1)},
83		"v: .inf\n",
84	}, {
85		map[string]interface{}{"v": math.Inf(-1)},
86		"v: -.inf\n",
87	}, {
88		map[string]interface{}{"v": math.NaN()},
89		"v: .nan\n",
90	}, {
91		map[string]interface{}{"v": nil},
92		"v: null\n",
93	}, {
94		map[string]interface{}{"v": ""},
95		"v: \"\"\n",
96	}, {
97		map[string][]string{"v": []string{"A", "B"}},
98		"v:\n- A\n- B\n",
99	}, {
100		map[string][]string{"v": []string{"A", "B\nC"}},
101		"v:\n- A\n- |-\n  B\n  C\n",
102	}, {
103		map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}},
104		"v:\n- A\n- 1\n- B:\n  - 2\n  - 3\n",
105	}, {
106		map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}},
107		"a:\n  b: c\n",
108	}, {
109		map[string]interface{}{"a": "-"},
110		"a: '-'\n",
111	},
112
113	// Simple values.
114	{
115		&marshalIntTest,
116		"123\n",
117	},
118
119	// Structures
120	{
121		&struct{ Hello string }{"world"},
122		"hello: world\n",
123	}, {
124		&struct {
125			A struct {
126				B string
127			}
128		}{struct{ B string }{"c"}},
129		"a:\n  b: c\n",
130	}, {
131		&struct {
132			A *struct {
133				B string
134			}
135		}{&struct{ B string }{"c"}},
136		"a:\n  b: c\n",
137	}, {
138		&struct {
139			A *struct {
140				B string
141			}
142		}{},
143		"a: null\n",
144	}, {
145		&struct{ A int }{1},
146		"a: 1\n",
147	}, {
148		&struct{ A []int }{[]int{1, 2}},
149		"a:\n- 1\n- 2\n",
150	}, {
151		&struct {
152			B int "a"
153		}{1},
154		"a: 1\n",
155	}, {
156		&struct{ A bool }{true},
157		"a: true\n",
158	},
159
160	// Conditional flag
161	{
162		&struct {
163			A int "a,omitempty"
164			B int "b,omitempty"
165		}{1, 0},
166		"a: 1\n",
167	}, {
168		&struct {
169			A int "a,omitempty"
170			B int "b,omitempty"
171		}{0, 0},
172		"{}\n",
173	}, {
174		&struct {
175			A *struct{ X, y int } "a,omitempty,flow"
176		}{&struct{ X, y int }{1, 2}},
177		"a: {x: 1}\n",
178	}, {
179		&struct {
180			A *struct{ X, y int } "a,omitempty,flow"
181		}{nil},
182		"{}\n",
183	}, {
184		&struct {
185			A *struct{ X, y int } "a,omitempty,flow"
186		}{&struct{ X, y int }{}},
187		"a: {x: 0}\n",
188	}, {
189		&struct {
190			A struct{ X, y int } "a,omitempty,flow"
191		}{struct{ X, y int }{1, 2}},
192		"a: {x: 1}\n",
193	}, {
194		&struct {
195			A struct{ X, y int } "a,omitempty,flow"
196		}{struct{ X, y int }{0, 1}},
197		"{}\n",
198	}, {
199		&struct {
200			A float64 "a,omitempty"
201			B float64 "b,omitempty"
202		}{1, 0},
203		"a: 1\n",
204	},
205	{
206		&struct {
207			T1 time.Time  "t1,omitempty"
208			T2 time.Time  "t2,omitempty"
209			T3 *time.Time "t3,omitempty"
210			T4 *time.Time "t4,omitempty"
211		}{
212			T2: time.Date(2018, 1, 9, 10, 40, 47, 0, time.UTC),
213			T4: newTime(time.Date(2098, 1, 9, 10, 40, 47, 0, time.UTC)),
214		},
215		"t2: !!timestamp 2018-01-09T10:40:47Z\nt4: !!timestamp 2098-01-09T10:40:47Z\n",
216	},
217	// Nil interface that implements Marshaler.
218	{
219		map[string]yaml.Marshaler{
220			"a": nil,
221		},
222		"a: null\n",
223	},
224
225	// Flow flag
226	{
227		&struct {
228			A []int "a,flow"
229		}{[]int{1, 2}},
230		"a: [1, 2]\n",
231	}, {
232		&struct {
233			A map[string]string "a,flow"
234		}{map[string]string{"b": "c", "d": "e"}},
235		"a: {b: c, d: e}\n",
236	}, {
237		&struct {
238			A struct {
239				B, D string
240			} "a,flow"
241		}{struct{ B, D string }{"c", "e"}},
242		"a: {b: c, d: e}\n",
243	},
244
245	// Unexported field
246	{
247		&struct {
248			u int
249			A int
250		}{0, 1},
251		"a: 1\n",
252	},
253
254	// Ignored field
255	{
256		&struct {
257			A int
258			B int "-"
259		}{1, 2},
260		"a: 1\n",
261	},
262
263	// Struct inlining
264	{
265		&struct {
266			A int
267			C inlineB `yaml:",inline"`
268		}{1, inlineB{2, inlineC{3}}},
269		"a: 1\nb: 2\nc: 3\n",
270	},
271
272	// Map inlining
273	{
274		&struct {
275			A int
276			C map[string]int `yaml:",inline"`
277		}{1, map[string]int{"b": 2, "c": 3}},
278		"a: 1\nb: 2\nc: 3\n",
279	},
280
281	// Duration
282	{
283		map[string]time.Duration{"a": 3 * time.Second},
284		"a: 3s\n",
285	},
286
287	// Issue #24: bug in map merging logic.
288	{
289		map[string]string{"a": "<foo>"},
290		"a: <foo>\n",
291	},
292
293	// Issue #34: marshal unsupported base 60 floats quoted for compatibility
294	// with old YAML 1.1 parsers.
295	{
296		map[string]string{"a": "1:1"},
297		"a: \"1:1\"\n",
298	},
299
300	// Binary data.
301	{
302		map[string]string{"a": "\x00"},
303		"a: \"\\0\"\n",
304	}, {
305		map[string]string{"a": "\x80\x81\x82"},
306		"a: !!binary gIGC\n",
307	}, {
308		map[string]string{"a": strings.Repeat("\x90", 54)},
309		"a: !!binary |\n  " + strings.Repeat("kJCQ", 17) + "kJ\n  CQ\n",
310	},
311
312	// Ordered maps.
313	{
314		&yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}},
315		"b: 2\na: 1\nd: 4\nc: 3\nsub:\n  e: 5\n",
316	},
317
318	// Encode unicode as utf-8 rather than in escaped form.
319	{
320		map[string]string{"a": "你好"},
321		"a: 你好\n",
322	},
323
324	// Support encoding.TextMarshaler.
325	{
326		map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)},
327		"a: 1.2.3.4\n",
328	},
329	// time.Time gets a timestamp tag.
330	{
331		map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)},
332		"a: !!timestamp 2015-02-24T18:19:39Z\n",
333	},
334	{
335		map[string]*time.Time{"a": newTime(time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC))},
336		"a: !!timestamp 2015-02-24T18:19:39Z\n",
337	},
338	// Ensure timestamp-like strings are quoted.
339	{
340		map[string]string{"a": "2015-02-24T18:19:39Z"},
341		"a: \"2015-02-24T18:19:39Z\"\n",
342	},
343
344	// Ensure strings containing ": " are quoted (reported as PR #43, but not reproducible).
345	{
346		map[string]string{"a": "b: c"},
347		"a: 'b: c'\n",
348	},
349
350	// Containing hash mark ('#') in string should be quoted
351	{
352		map[string]string{"a": "Hello #comment"},
353		"a: 'Hello #comment'\n",
354	},
355	{
356		map[string]string{"a": "你好 #comment"},
357		"a: '你好 #comment'\n",
358	},
359}
360
361func (s *S) TestMarshal(c *C) {
362	defer os.Setenv("TZ", os.Getenv("TZ"))
363	os.Setenv("TZ", "UTC")
364	for i, item := range marshalTests {
365		c.Logf("test %d: %q", i, item.data)
366		data, err := yaml.Marshal(item.value)
367		c.Assert(err, IsNil)
368		c.Assert(string(data), Equals, item.data)
369	}
370}
371
372func (s *S) TestEncoderSingleDocument(c *C) {
373	for i, item := range marshalTests {
374		c.Logf("test %d. %q", i, item.data)
375		var buf bytes.Buffer
376		enc := yaml.NewEncoder(&buf)
377		err := enc.Encode(item.value)
378		c.Assert(err, Equals, nil)
379		err = enc.Close()
380		c.Assert(err, Equals, nil)
381		c.Assert(buf.String(), Equals, item.data)
382	}
383}
384
385func (s *S) TestEncoderMultipleDocuments(c *C) {
386	var buf bytes.Buffer
387	enc := yaml.NewEncoder(&buf)
388	err := enc.Encode(map[string]string{"a": "b"})
389	c.Assert(err, Equals, nil)
390	err = enc.Encode(map[string]string{"c": "d"})
391	c.Assert(err, Equals, nil)
392	err = enc.Close()
393	c.Assert(err, Equals, nil)
394	c.Assert(buf.String(), Equals, "a: b\n---\nc: d\n")
395}
396
397func (s *S) TestEncoderWriteError(c *C) {
398	enc := yaml.NewEncoder(errorWriter{})
399	err := enc.Encode(map[string]string{"a": "b"})
400	c.Assert(err, ErrorMatches, `yaml: write error: some write error`) // Data not flushed yet
401}
402
403type errorWriter struct{}
404
405func (errorWriter) Write([]byte) (int, error) {
406	return 0, fmt.Errorf("some write error")
407}
408
409var marshalErrorTests = []struct {
410	value interface{}
411	error string
412	panic string
413}{{
414	value: &struct {
415		B       int
416		inlineB ",inline"
417	}{1, inlineB{2, inlineC{3}}},
418	panic: `Duplicated key 'b' in struct struct \{ B int; .*`,
419}, {
420	value: &struct {
421		A int
422		B map[string]int ",inline"
423	}{1, map[string]int{"a": 2}},
424	panic: `Can't have key "a" in inlined map; conflicts with struct field`,
425}}
426
427func (s *S) TestMarshalErrors(c *C) {
428	for _, item := range marshalErrorTests {
429		if item.panic != "" {
430			c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic)
431		} else {
432			_, err := yaml.Marshal(item.value)
433			c.Assert(err, ErrorMatches, item.error)
434		}
435	}
436}
437
438func (s *S) TestMarshalTypeCache(c *C) {
439	var data []byte
440	var err error
441	func() {
442		type T struct{ A int }
443		data, err = yaml.Marshal(&T{})
444		c.Assert(err, IsNil)
445	}()
446	func() {
447		type T struct{ B int }
448		data, err = yaml.Marshal(&T{})
449		c.Assert(err, IsNil)
450	}()
451	c.Assert(string(data), Equals, "b: 0\n")
452}
453
454var marshalerTests = []struct {
455	data  string
456	value interface{}
457}{
458	{"_:\n  hi: there\n", map[interface{}]interface{}{"hi": "there"}},
459	{"_:\n- 1\n- A\n", []interface{}{1, "A"}},
460	{"_: 10\n", 10},
461	{"_: null\n", nil},
462	{"_: BAR!\n", "BAR!"},
463}
464
465type marshalerType struct {
466	value interface{}
467}
468
469func (o marshalerType) MarshalText() ([]byte, error) {
470	panic("MarshalText called on type with MarshalYAML")
471}
472
473func (o marshalerType) MarshalYAML() (interface{}, error) {
474	return o.value, nil
475}
476
477type marshalerValue struct {
478	Field marshalerType "_"
479}
480
481func (s *S) TestMarshaler(c *C) {
482	for _, item := range marshalerTests {
483		obj := &marshalerValue{}
484		obj.Field.value = item.value
485		data, err := yaml.Marshal(obj)
486		c.Assert(err, IsNil)
487		c.Assert(string(data), Equals, string(item.data))
488	}
489}
490
491func (s *S) TestMarshalerWholeDocument(c *C) {
492	obj := &marshalerType{}
493	obj.value = map[string]string{"hello": "world!"}
494	data, err := yaml.Marshal(obj)
495	c.Assert(err, IsNil)
496	c.Assert(string(data), Equals, "hello: world!\n")
497}
498
499type failingMarshaler struct{}
500
501func (ft *failingMarshaler) MarshalYAML() (interface{}, error) {
502	return nil, failingErr
503}
504
505func (s *S) TestMarshalerError(c *C) {
506	_, err := yaml.Marshal(&failingMarshaler{})
507	c.Assert(err, Equals, failingErr)
508}
509
510func (s *S) TestSortedOutput(c *C) {
511	order := []interface{}{
512		false,
513		true,
514		1,
515		uint(1),
516		1.0,
517		1.1,
518		1.2,
519		2,
520		uint(2),
521		2.0,
522		2.1,
523		"",
524		".1",
525		".2",
526		".a",
527		"1",
528		"2",
529		"a!10",
530		"a/2",
531		"a/10",
532		"a~10",
533		"ab/1",
534		"b/1",
535		"b/01",
536		"b/2",
537		"b/02",
538		"b/3",
539		"b/03",
540		"b1",
541		"b01",
542		"b3",
543		"c2.10",
544		"c10.2",
545		"d1",
546		"d12",
547		"d12a",
548	}
549	m := make(map[interface{}]int)
550	for _, k := range order {
551		m[k] = 1
552	}
553	data, err := yaml.Marshal(m)
554	c.Assert(err, IsNil)
555	out := "\n" + string(data)
556	last := 0
557	for i, k := range order {
558		repr := fmt.Sprint(k)
559		if s, ok := k.(string); ok {
560			if _, err = strconv.ParseFloat(repr, 32); s == "" || err == nil {
561				repr = `"` + repr + `"`
562			}
563		}
564		index := strings.Index(out, "\n"+repr+":")
565		if index == -1 {
566			c.Fatalf("%#v is not in the output: %#v", k, out)
567		}
568		if index < last {
569			c.Fatalf("%#v was generated before %#v: %q", k, order[i-1], out)
570		}
571		last = index
572	}
573}
574
575func newTime(t time.Time) *time.Time {
576	return &t
577}
578