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