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) TestLineWrapping(c *C) {
403	var v = map[string]string{
404		"a": "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 ",
405	}
406	data, err := yaml.Marshal(v)
407	c.Assert(err, IsNil)
408	c.Assert(string(data), Equals,
409		"a: 'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 abcdefghijklmnopqrstuvwxyz\n" +
410		"  ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 '\n")
411
412	// The API does not allow this process to be reversed as it's intended
413	// for migration only. v3 drops this method and instead offers more
414	// control on a per encoding basis.
415	yaml.FutureLineWrap()
416
417	data, err = yaml.Marshal(v)
418	c.Assert(err, IsNil)
419	c.Assert(string(data), Equals,
420		"a: 'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 '\n")
421}
422
423func (s *S) TestMarshal(c *C) {
424	defer os.Setenv("TZ", os.Getenv("TZ"))
425	os.Setenv("TZ", "UTC")
426	for i, item := range marshalTests {
427		c.Logf("test %d: %q", i, item.data)
428		data, err := yaml.Marshal(item.value)
429		c.Assert(err, IsNil)
430		c.Assert(string(data), Equals, item.data)
431	}
432}
433
434func (s *S) TestEncoderSingleDocument(c *C) {
435	for i, item := range marshalTests {
436		c.Logf("test %d. %q", i, item.data)
437		var buf bytes.Buffer
438		enc := yaml.NewEncoder(&buf)
439		err := enc.Encode(item.value)
440		c.Assert(err, Equals, nil)
441		err = enc.Close()
442		c.Assert(err, Equals, nil)
443		c.Assert(buf.String(), Equals, item.data)
444	}
445}
446
447func (s *S) TestEncoderMultipleDocuments(c *C) {
448	var buf bytes.Buffer
449	enc := yaml.NewEncoder(&buf)
450	err := enc.Encode(map[string]string{"a": "b"})
451	c.Assert(err, Equals, nil)
452	err = enc.Encode(map[string]string{"c": "d"})
453	c.Assert(err, Equals, nil)
454	err = enc.Close()
455	c.Assert(err, Equals, nil)
456	c.Assert(buf.String(), Equals, "a: b\n---\nc: d\n")
457}
458
459func (s *S) TestEncoderWriteError(c *C) {
460	enc := yaml.NewEncoder(errorWriter{})
461	err := enc.Encode(map[string]string{"a": "b"})
462	c.Assert(err, ErrorMatches, `yaml: write error: some write error`) // Data not flushed yet
463}
464
465type errorWriter struct{}
466
467func (errorWriter) Write([]byte) (int, error) {
468	return 0, fmt.Errorf("some write error")
469}
470
471var marshalErrorTests = []struct {
472	value interface{}
473	error string
474	panic string
475}{{
476	value: &struct {
477		B       int
478		inlineB ",inline"
479	}{1, inlineB{2, inlineC{3}}},
480	panic: `Duplicated key 'b' in struct struct \{ B int; .*`,
481}, {
482	value: &struct {
483		A int
484		B map[string]int ",inline"
485	}{1, map[string]int{"a": 2}},
486	panic: `Can't have key "a" in inlined map; conflicts with struct field`,
487}}
488
489func (s *S) TestMarshalErrors(c *C) {
490	for _, item := range marshalErrorTests {
491		if item.panic != "" {
492			c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic)
493		} else {
494			_, err := yaml.Marshal(item.value)
495			c.Assert(err, ErrorMatches, item.error)
496		}
497	}
498}
499
500func (s *S) TestMarshalTypeCache(c *C) {
501	var data []byte
502	var err error
503	func() {
504		type T struct{ A int }
505		data, err = yaml.Marshal(&T{})
506		c.Assert(err, IsNil)
507	}()
508	func() {
509		type T struct{ B int }
510		data, err = yaml.Marshal(&T{})
511		c.Assert(err, IsNil)
512	}()
513	c.Assert(string(data), Equals, "b: 0\n")
514}
515
516var marshalerTests = []struct {
517	data  string
518	value interface{}
519}{
520	{"_:\n  hi: there\n", map[interface{}]interface{}{"hi": "there"}},
521	{"_:\n- 1\n- A\n", []interface{}{1, "A"}},
522	{"_: 10\n", 10},
523	{"_: null\n", nil},
524	{"_: BAR!\n", "BAR!"},
525}
526
527type marshalerType struct {
528	value interface{}
529}
530
531func (o marshalerType) MarshalText() ([]byte, error) {
532	panic("MarshalText called on type with MarshalYAML")
533}
534
535func (o marshalerType) MarshalYAML() (interface{}, error) {
536	return o.value, nil
537}
538
539type marshalerValue struct {
540	Field marshalerType "_"
541}
542
543func (s *S) TestMarshaler(c *C) {
544	for _, item := range marshalerTests {
545		obj := &marshalerValue{}
546		obj.Field.value = item.value
547		data, err := yaml.Marshal(obj)
548		c.Assert(err, IsNil)
549		c.Assert(string(data), Equals, string(item.data))
550	}
551}
552
553func (s *S) TestMarshalerWholeDocument(c *C) {
554	obj := &marshalerType{}
555	obj.value = map[string]string{"hello": "world!"}
556	data, err := yaml.Marshal(obj)
557	c.Assert(err, IsNil)
558	c.Assert(string(data), Equals, "hello: world!\n")
559}
560
561type failingMarshaler struct{}
562
563func (ft *failingMarshaler) MarshalYAML() (interface{}, error) {
564	return nil, failingErr
565}
566
567func (s *S) TestMarshalerError(c *C) {
568	_, err := yaml.Marshal(&failingMarshaler{})
569	c.Assert(err, Equals, failingErr)
570}
571
572func (s *S) TestSortedOutput(c *C) {
573	order := []interface{}{
574		false,
575		true,
576		1,
577		uint(1),
578		1.0,
579		1.1,
580		1.2,
581		2,
582		uint(2),
583		2.0,
584		2.1,
585		"",
586		".1",
587		".2",
588		".a",
589		"1",
590		"2",
591		"a!10",
592		"a/0001",
593		"a/002",
594		"a/3",
595		"a/10",
596		"a/11",
597		"a/0012",
598		"a/100",
599		"a~10",
600		"ab/1",
601		"b/1",
602		"b/01",
603		"b/2",
604		"b/02",
605		"b/3",
606		"b/03",
607		"b1",
608		"b01",
609		"b3",
610		"c2.10",
611		"c10.2",
612		"d1",
613		"d7",
614		"d7abc",
615		"d12",
616		"d12a",
617	}
618	m := make(map[interface{}]int)
619	for _, k := range order {
620		m[k] = 1
621	}
622	data, err := yaml.Marshal(m)
623	c.Assert(err, IsNil)
624	out := "\n" + string(data)
625	last := 0
626	for i, k := range order {
627		repr := fmt.Sprint(k)
628		if s, ok := k.(string); ok {
629			if _, err = strconv.ParseFloat(repr, 32); s == "" || err == nil {
630				repr = `"` + repr + `"`
631			}
632		}
633		index := strings.Index(out, "\n"+repr+":")
634		if index == -1 {
635			c.Fatalf("%#v is not in the output: %#v", k, out)
636		}
637		if index < last {
638			c.Fatalf("%#v was generated before %#v: %q", k, order[i-1], out)
639		}
640		last = index
641	}
642}
643
644func newTime(t time.Time) *time.Time {
645	return &t
646}
647