1//
2// Copyright (c) 2011-2019 Canonical Ltd
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16package yaml_test
17
18import (
19	"bytes"
20	"fmt"
21	"math"
22	"strconv"
23	"strings"
24	"time"
25
26	"net"
27	"os"
28
29	. "gopkg.in/check.v1"
30	"gopkg.in/yaml.v3"
31)
32
33var marshalIntTest = 123
34
35var marshalTests = []struct {
36	value interface{}
37	data  string
38}{
39	{
40		nil,
41		"null\n",
42	}, {
43		(*marshalerType)(nil),
44		"null\n",
45	}, {
46		&struct{}{},
47		"{}\n",
48	}, {
49		map[string]string{"v": "hi"},
50		"v: hi\n",
51	}, {
52		map[string]interface{}{"v": "hi"},
53		"v: hi\n",
54	}, {
55		map[string]string{"v": "true"},
56		"v: \"true\"\n",
57	}, {
58		map[string]string{"v": "false"},
59		"v: \"false\"\n",
60	}, {
61		map[string]interface{}{"v": true},
62		"v: true\n",
63	}, {
64		map[string]interface{}{"v": false},
65		"v: false\n",
66	}, {
67		map[string]interface{}{"v": 10},
68		"v: 10\n",
69	}, {
70		map[string]interface{}{"v": -10},
71		"v: -10\n",
72	}, {
73		map[string]uint{"v": 42},
74		"v: 42\n",
75	}, {
76		map[string]interface{}{"v": int64(4294967296)},
77		"v: 4294967296\n",
78	}, {
79		map[string]int64{"v": int64(4294967296)},
80		"v: 4294967296\n",
81	}, {
82		map[string]uint64{"v": 4294967296},
83		"v: 4294967296\n",
84	}, {
85		map[string]interface{}{"v": "10"},
86		"v: \"10\"\n",
87	}, {
88		map[string]interface{}{"v": 0.1},
89		"v: 0.1\n",
90	}, {
91		map[string]interface{}{"v": float64(0.1)},
92		"v: 0.1\n",
93	}, {
94		map[string]interface{}{"v": float32(0.99)},
95		"v: 0.99\n",
96	}, {
97		map[string]interface{}{"v": -0.1},
98		"v: -0.1\n",
99	}, {
100		map[string]interface{}{"v": math.Inf(+1)},
101		"v: .inf\n",
102	}, {
103		map[string]interface{}{"v": math.Inf(-1)},
104		"v: -.inf\n",
105	}, {
106		map[string]interface{}{"v": math.NaN()},
107		"v: .nan\n",
108	}, {
109		map[string]interface{}{"v": nil},
110		"v: null\n",
111	}, {
112		map[string]interface{}{"v": ""},
113		"v: \"\"\n",
114	}, {
115		map[string][]string{"v": []string{"A", "B"}},
116		"v:\n    - A\n    - B\n",
117	}, {
118		map[string][]string{"v": []string{"A", "B\nC"}},
119		"v:\n    - A\n    - |-\n      B\n      C\n",
120	}, {
121		map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}},
122		"v:\n    - A\n    - 1\n    - B:\n        - 2\n        - 3\n",
123	}, {
124		map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}},
125		"a:\n    b: c\n",
126	}, {
127		map[string]interface{}{"a": "-"},
128		"a: '-'\n",
129	},
130
131	// Simple values.
132	{
133		&marshalIntTest,
134		"123\n",
135	},
136
137	// Structures
138	{
139		&struct{ Hello string }{"world"},
140		"hello: world\n",
141	}, {
142		&struct {
143			A struct {
144				B string
145			}
146		}{struct{ B string }{"c"}},
147		"a:\n    b: c\n",
148	}, {
149		&struct {
150			A *struct {
151				B string
152			}
153		}{&struct{ B string }{"c"}},
154		"a:\n    b: c\n",
155	}, {
156		&struct {
157			A *struct {
158				B string
159			}
160		}{},
161		"a: null\n",
162	}, {
163		&struct{ A int }{1},
164		"a: 1\n",
165	}, {
166		&struct{ A []int }{[]int{1, 2}},
167		"a:\n    - 1\n    - 2\n",
168	}, {
169		&struct{ A [2]int }{[2]int{1, 2}},
170		"a:\n    - 1\n    - 2\n",
171	}, {
172		&struct {
173			B int "a"
174		}{1},
175		"a: 1\n",
176	}, {
177		&struct{ A bool }{true},
178		"a: true\n",
179	}, {
180		&struct{ A string }{"true"},
181		"a: \"true\"\n",
182	}, {
183		&struct{ A string }{"off"},
184		"a: \"off\"\n",
185	},
186
187	// Conditional flag
188	{
189		&struct {
190			A int "a,omitempty"
191			B int "b,omitempty"
192		}{1, 0},
193		"a: 1\n",
194	}, {
195		&struct {
196			A int "a,omitempty"
197			B int "b,omitempty"
198		}{0, 0},
199		"{}\n",
200	}, {
201		&struct {
202			A *struct{ X, y int } "a,omitempty,flow"
203		}{&struct{ X, y int }{1, 2}},
204		"a: {x: 1}\n",
205	}, {
206		&struct {
207			A *struct{ X, y int } "a,omitempty,flow"
208		}{nil},
209		"{}\n",
210	}, {
211		&struct {
212			A *struct{ X, y int } "a,omitempty,flow"
213		}{&struct{ X, y int }{}},
214		"a: {x: 0}\n",
215	}, {
216		&struct {
217			A struct{ X, y int } "a,omitempty,flow"
218		}{struct{ X, y int }{1, 2}},
219		"a: {x: 1}\n",
220	}, {
221		&struct {
222			A struct{ X, y int } "a,omitempty,flow"
223		}{struct{ X, y int }{0, 1}},
224		"{}\n",
225	}, {
226		&struct {
227			A float64 "a,omitempty"
228			B float64 "b,omitempty"
229		}{1, 0},
230		"a: 1\n",
231	},
232	{
233		&struct {
234			T1 time.Time  "t1,omitempty"
235			T2 time.Time  "t2,omitempty"
236			T3 *time.Time "t3,omitempty"
237			T4 *time.Time "t4,omitempty"
238		}{
239			T2: time.Date(2018, 1, 9, 10, 40, 47, 0, time.UTC),
240			T4: newTime(time.Date(2098, 1, 9, 10, 40, 47, 0, time.UTC)),
241		},
242		"t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n",
243	},
244	// Nil interface that implements Marshaler.
245	{
246		map[string]yaml.Marshaler{
247			"a": nil,
248		},
249		"a: null\n",
250	},
251
252	// Flow flag
253	{
254		&struct {
255			A []int "a,flow"
256		}{[]int{1, 2}},
257		"a: [1, 2]\n",
258	}, {
259		&struct {
260			A map[string]string "a,flow"
261		}{map[string]string{"b": "c", "d": "e"}},
262		"a: {b: c, d: e}\n",
263	}, {
264		&struct {
265			A struct {
266				B, D string
267			} "a,flow"
268		}{struct{ B, D string }{"c", "e"}},
269		"a: {b: c, d: e}\n",
270	}, {
271		&struct {
272			A string "a,flow"
273		}{"b\nc"},
274		"a: \"b\\nc\"\n",
275	},
276
277	// Unexported field
278	{
279		&struct {
280			u int
281			A int
282		}{0, 1},
283		"a: 1\n",
284	},
285
286	// Ignored field
287	{
288		&struct {
289			A int
290			B int "-"
291		}{1, 2},
292		"a: 1\n",
293	},
294
295	// Struct inlining
296	{
297		&struct {
298			A int
299			C inlineB `yaml:",inline"`
300		}{1, inlineB{2, inlineC{3}}},
301		"a: 1\nb: 2\nc: 3\n",
302	},
303	// Struct inlining as a pointer
304	{
305		&struct {
306			A int
307			C *inlineB `yaml:",inline"`
308		}{1, &inlineB{2, inlineC{3}}},
309		"a: 1\nb: 2\nc: 3\n",
310	}, {
311		&struct {
312			A int
313			C *inlineB `yaml:",inline"`
314		}{1, nil},
315		"a: 1\n",
316	}, {
317		&struct {
318			A int
319			D *inlineD `yaml:",inline"`
320		}{1, &inlineD{&inlineC{3}, 4}},
321		"a: 1\nc: 3\nd: 4\n",
322	},
323
324	// Map inlining
325	{
326		&struct {
327			A int
328			C map[string]int `yaml:",inline"`
329		}{1, map[string]int{"b": 2, "c": 3}},
330		"a: 1\nb: 2\nc: 3\n",
331	},
332
333	// Duration
334	{
335		map[string]time.Duration{"a": 3 * time.Second},
336		"a: 3s\n",
337	},
338
339	// Issue #24: bug in map merging logic.
340	{
341		map[string]string{"a": "<foo>"},
342		"a: <foo>\n",
343	},
344
345	// Issue #34: marshal unsupported base 60 floats quoted for compatibility
346	// with old YAML 1.1 parsers.
347	{
348		map[string]string{"a": "1:1"},
349		"a: \"1:1\"\n",
350	},
351
352	// Binary data.
353	{
354		map[string]string{"a": "\x00"},
355		"a: \"\\0\"\n",
356	}, {
357		map[string]string{"a": "\x80\x81\x82"},
358		"a: !!binary gIGC\n",
359	}, {
360		map[string]string{"a": strings.Repeat("\x90", 54)},
361		"a: !!binary |\n    " + strings.Repeat("kJCQ", 17) + "kJ\n    CQ\n",
362	},
363
364	// Encode unicode as utf-8 rather than in escaped form.
365	{
366		map[string]string{"a": "你好"},
367		"a: 你好\n",
368	},
369
370	// Support encoding.TextMarshaler.
371	{
372		map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)},
373		"a: 1.2.3.4\n",
374	},
375	// time.Time gets a timestamp tag.
376	{
377		map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)},
378		"a: 2015-02-24T18:19:39Z\n",
379	},
380	{
381		map[string]*time.Time{"a": newTime(time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC))},
382		"a: 2015-02-24T18:19:39Z\n",
383	},
384	{
385		// This is confirmed to be properly decoded in Python (libyaml) without a timestamp tag.
386		map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 123456789, time.FixedZone("FOO", -3*60*60))},
387		"a: 2015-02-24T18:19:39.123456789-03:00\n",
388	},
389	// Ensure timestamp-like strings are quoted.
390	{
391		map[string]string{"a": "2015-02-24T18:19:39Z"},
392		"a: \"2015-02-24T18:19:39Z\"\n",
393	},
394
395	// Ensure strings containing ": " are quoted (reported as PR #43, but not reproducible).
396	{
397		map[string]string{"a": "b: c"},
398		"a: 'b: c'\n",
399	},
400
401	// Containing hash mark ('#') in string should be quoted
402	{
403		map[string]string{"a": "Hello #comment"},
404		"a: 'Hello #comment'\n",
405	},
406	{
407		map[string]string{"a": "你好 #comment"},
408		"a: '你好 #comment'\n",
409	},
410
411	// Ensure MarshalYAML also gets called on the result of MarshalYAML itself.
412	{
413		&marshalerType{marshalerType{true}},
414		"true\n",
415	}, {
416		&marshalerType{&marshalerType{true}},
417		"true\n",
418	},
419
420	// Check indentation of maps inside sequences inside maps.
421	{
422		map[string]interface{}{"a": map[string]interface{}{"b": []map[string]int{{"c": 1, "d": 2}}}},
423		"a:\n    b:\n        - c: 1\n          d: 2\n",
424	},
425
426	// Strings with tabs were disallowed as literals (issue #471).
427	{
428		map[string]string{"a": "\tB\n\tC\n"},
429		"a: |\n    \tB\n    \tC\n",
430	},
431
432	// Ensure that strings do not wrap
433	{
434		map[string]string{"a": "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 "},
435		"a: 'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 '\n",
436	},
437
438	// yaml.Node
439	{
440		&struct {
441			Value yaml.Node
442		}{
443			yaml.Node{
444				Kind:  yaml.ScalarNode,
445				Tag:   "!!str",
446				Value: "foo",
447				Style: yaml.SingleQuotedStyle,
448			},
449		},
450		"value: 'foo'\n",
451	}, {
452		yaml.Node{
453			Kind:  yaml.ScalarNode,
454			Tag:   "!!str",
455			Value: "foo",
456			Style: yaml.SingleQuotedStyle,
457		},
458		"'foo'\n",
459	},
460
461	// Enforced tagging with shorthand notation (issue #616).
462	{
463		&struct {
464			Value yaml.Node
465		}{
466			yaml.Node{
467				Kind:  yaml.ScalarNode,
468				Style: yaml.TaggedStyle,
469				Value: "foo",
470				Tag:   "!!str",
471			},
472		},
473		"value: !!str foo\n",
474	}, {
475		&struct {
476			Value yaml.Node
477		}{
478			yaml.Node{
479				Kind:  yaml.MappingNode,
480				Style: yaml.TaggedStyle,
481				Tag:   "!!map",
482			},
483		},
484		"value: !!map {}\n",
485	}, {
486		&struct {
487			Value yaml.Node
488		}{
489			yaml.Node{
490				Kind:  yaml.SequenceNode,
491				Style: yaml.TaggedStyle,
492				Tag:   "!!seq",
493			},
494		},
495		"value: !!seq []\n",
496	},
497}
498
499func (s *S) TestMarshal(c *C) {
500	defer os.Setenv("TZ", os.Getenv("TZ"))
501	os.Setenv("TZ", "UTC")
502	for i, item := range marshalTests {
503		c.Logf("test %d: %q", i, item.data)
504		data, err := yaml.Marshal(item.value)
505		c.Assert(err, IsNil)
506		c.Assert(string(data), Equals, item.data)
507	}
508}
509
510func (s *S) TestEncoderSingleDocument(c *C) {
511	for i, item := range marshalTests {
512		c.Logf("test %d. %q", i, item.data)
513		var buf bytes.Buffer
514		enc := yaml.NewEncoder(&buf)
515		err := enc.Encode(item.value)
516		c.Assert(err, Equals, nil)
517		err = enc.Close()
518		c.Assert(err, Equals, nil)
519		c.Assert(buf.String(), Equals, item.data)
520	}
521}
522
523func (s *S) TestEncoderMultipleDocuments(c *C) {
524	var buf bytes.Buffer
525	enc := yaml.NewEncoder(&buf)
526	err := enc.Encode(map[string]string{"a": "b"})
527	c.Assert(err, Equals, nil)
528	err = enc.Encode(map[string]string{"c": "d"})
529	c.Assert(err, Equals, nil)
530	err = enc.Close()
531	c.Assert(err, Equals, nil)
532	c.Assert(buf.String(), Equals, "a: b\n---\nc: d\n")
533}
534
535func (s *S) TestEncoderWriteError(c *C) {
536	enc := yaml.NewEncoder(errorWriter{})
537	err := enc.Encode(map[string]string{"a": "b"})
538	c.Assert(err, ErrorMatches, `yaml: write error: some write error`) // Data not flushed yet
539}
540
541type errorWriter struct{}
542
543func (errorWriter) Write([]byte) (int, error) {
544	return 0, fmt.Errorf("some write error")
545}
546
547var marshalErrorTests = []struct {
548	value interface{}
549	error string
550	panic string
551}{{
552	value: &struct {
553		B       int
554		inlineB ",inline"
555	}{1, inlineB{2, inlineC{3}}},
556	panic: `duplicated key 'b' in struct struct \{ B int; .*`,
557}, {
558	value: &struct {
559		A int
560		B map[string]int ",inline"
561	}{1, map[string]int{"a": 2}},
562	panic: `cannot have key "a" in inlined map: conflicts with struct field`,
563}}
564
565func (s *S) TestMarshalErrors(c *C) {
566	for _, item := range marshalErrorTests {
567		if item.panic != "" {
568			c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic)
569		} else {
570			_, err := yaml.Marshal(item.value)
571			c.Assert(err, ErrorMatches, item.error)
572		}
573	}
574}
575
576func (s *S) TestMarshalTypeCache(c *C) {
577	var data []byte
578	var err error
579	func() {
580		type T struct{ A int }
581		data, err = yaml.Marshal(&T{})
582		c.Assert(err, IsNil)
583	}()
584	func() {
585		type T struct{ B int }
586		data, err = yaml.Marshal(&T{})
587		c.Assert(err, IsNil)
588	}()
589	c.Assert(string(data), Equals, "b: 0\n")
590}
591
592var marshalerTests = []struct {
593	data  string
594	value interface{}
595}{
596	{"_:\n    hi: there\n", map[interface{}]interface{}{"hi": "there"}},
597	{"_:\n    - 1\n    - A\n", []interface{}{1, "A"}},
598	{"_: 10\n", 10},
599	{"_: null\n", nil},
600	{"_: BAR!\n", "BAR!"},
601}
602
603type marshalerType struct {
604	value interface{}
605}
606
607func (o marshalerType) MarshalText() ([]byte, error) {
608	panic("MarshalText called on type with MarshalYAML")
609}
610
611func (o marshalerType) MarshalYAML() (interface{}, error) {
612	return o.value, nil
613}
614
615type marshalerValue struct {
616	Field marshalerType "_"
617}
618
619func (s *S) TestMarshaler(c *C) {
620	for _, item := range marshalerTests {
621		obj := &marshalerValue{}
622		obj.Field.value = item.value
623		data, err := yaml.Marshal(obj)
624		c.Assert(err, IsNil)
625		c.Assert(string(data), Equals, string(item.data))
626	}
627}
628
629func (s *S) TestMarshalerWholeDocument(c *C) {
630	obj := &marshalerType{}
631	obj.value = map[string]string{"hello": "world!"}
632	data, err := yaml.Marshal(obj)
633	c.Assert(err, IsNil)
634	c.Assert(string(data), Equals, "hello: world!\n")
635}
636
637type failingMarshaler struct{}
638
639func (ft *failingMarshaler) MarshalYAML() (interface{}, error) {
640	return nil, failingErr
641}
642
643func (s *S) TestMarshalerError(c *C) {
644	_, err := yaml.Marshal(&failingMarshaler{})
645	c.Assert(err, Equals, failingErr)
646}
647
648func (s *S) TestSetIndent(c *C) {
649	var buf bytes.Buffer
650	enc := yaml.NewEncoder(&buf)
651	enc.SetIndent(8)
652	err := enc.Encode(map[string]interface{}{"a": map[string]interface{}{"b": map[string]string{"c": "d"}}})
653	c.Assert(err, Equals, nil)
654	err = enc.Close()
655	c.Assert(err, Equals, nil)
656	c.Assert(buf.String(), Equals, "a:\n        b:\n                c: d\n")
657}
658
659func (s *S) TestSortedOutput(c *C) {
660	order := []interface{}{
661		false,
662		true,
663		1,
664		uint(1),
665		1.0,
666		1.1,
667		1.2,
668		2,
669		uint(2),
670		2.0,
671		2.1,
672		"",
673		".1",
674		".2",
675		".a",
676		"1",
677		"2",
678		"a!10",
679		"a/0001",
680		"a/002",
681		"a/3",
682		"a/10",
683		"a/11",
684		"a/0012",
685		"a/100",
686		"a~10",
687		"ab/1",
688		"b/1",
689		"b/01",
690		"b/2",
691		"b/02",
692		"b/3",
693		"b/03",
694		"b1",
695		"b01",
696		"b3",
697		"c2.10",
698		"c10.2",
699		"d1",
700		"d7",
701		"d7abc",
702		"d12",
703		"d12a",
704		"e2b",
705		"e4b",
706		"e21a",
707	}
708	m := make(map[interface{}]int)
709	for _, k := range order {
710		m[k] = 1
711	}
712	data, err := yaml.Marshal(m)
713	c.Assert(err, IsNil)
714	out := "\n" + string(data)
715	last := 0
716	for i, k := range order {
717		repr := fmt.Sprint(k)
718		if s, ok := k.(string); ok {
719			if _, err = strconv.ParseFloat(repr, 32); s == "" || err == nil {
720				repr = `"` + repr + `"`
721			}
722		}
723		index := strings.Index(out, "\n"+repr+":")
724		if index == -1 {
725			c.Fatalf("%#v is not in the output: %#v", k, out)
726		}
727		if index < last {
728			c.Fatalf("%#v was generated before %#v: %q", k, order[i-1], out)
729		}
730		last = index
731	}
732}
733
734func newTime(t time.Time) *time.Time {
735	return &t
736}
737