1
16
17 package yaml
18
19 import (
20 "bytes"
21 "encoding/json"
22 "fmt"
23 "math"
24 "reflect"
25 "sort"
26 "strconv"
27 "testing"
28
29 "github.com/google/go-cmp/cmp"
30 yamlv2 "sigs.k8s.io/yaml/goyaml.v2"
31 yamlv3 "sigs.k8s.io/yaml/goyaml.v3"
32 )
33
34
35
36 func strPtr(str string) *string {
37 return &str
38 }
39
40 type errorType int
41
42 const (
43 noErrorsType errorType = 0
44 fatalErrorsType errorType = 1 << iota
45 )
46
47 type unmarshalTestCase struct {
48 encoded []byte
49 decodeInto interface{}
50 decoded interface{}
51 err errorType
52 }
53
54 type testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) error
55
56 var (
57 funcUnmarshal testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) error {
58 return Unmarshal(yamlBytes, obj)
59 }
60
61 funcUnmarshalStrict testUnmarshalFunc = func(yamlBytes []byte, obj interface{}) error {
62 return UnmarshalStrict(yamlBytes, obj)
63 }
64 )
65
66 func testUnmarshal(t *testing.T, f testUnmarshalFunc, tests map[string]unmarshalTestCase) {
67 for testName, test := range tests {
68 t.Run(testName, func(t *testing.T) {
69 typ := reflect.TypeOf(test.decodeInto)
70 if typ.Kind() != reflect.Ptr {
71 t.Errorf("unmarshalTest.ptr %T is not a pointer type", test.decodeInto)
72 }
73
74 value := reflect.New(typ.Elem())
75
76 if !reflect.DeepEqual(test.decodeInto, value.Interface()) {
77
78
79
80
81
82
83 t.Errorf("unmarshalTest.ptr %#v is not a pointer to a zero value", test.decodeInto)
84 }
85
86 err := f(test.encoded, value.Interface())
87 if err != nil && test.err == noErrorsType {
88 t.Errorf("error unmarshaling YAML: %v", err)
89 }
90 if err == nil && test.err&fatalErrorsType != 0 {
91 t.Errorf("expected a fatal error, but no fatal error was returned, yaml: `%s`", test.encoded)
92 }
93
94 if test.err&fatalErrorsType != 0 {
95
96 return
97 }
98
99 if !reflect.DeepEqual(value.Elem().Interface(), test.decoded) {
100 t.Errorf("unmarshal YAML was unsuccessful, expected: %+#v, got: %+#v", test.decoded, value.Elem().Interface())
101 }
102 })
103 }
104 }
105
106 type yamlToJSONTestcase struct {
107 yaml string
108 json string
109
110
111 yamlReverseOverwrite *string
112 err errorType
113 }
114
115 type testYAMLToJSONFunc = func(yamlBytes []byte) ([]byte, error)
116
117 var (
118 funcYAMLToJSON testYAMLToJSONFunc = func(yamlBytes []byte) ([]byte, error) {
119 return YAMLToJSON(yamlBytes)
120 }
121
122 funcYAMLToJSONStrict testYAMLToJSONFunc = func(yamlBytes []byte) ([]byte, error) {
123 return YAMLToJSONStrict(yamlBytes)
124 }
125 )
126
127 func testYAMLToJSON(t *testing.T, f testYAMLToJSONFunc, tests map[string]yamlToJSONTestcase) {
128 for testName, test := range tests {
129 t.Run(fmt.Sprintf("%s_YAMLToJSON", testName), func(t *testing.T) {
130
131 jsonBytes, err := f([]byte(test.yaml))
132 if err != nil && test.err == noErrorsType {
133 t.Errorf("Failed to convert YAML to JSON, yamlv2: `%s`, err: %v", test.yaml, err)
134 }
135 if err == nil && test.err&fatalErrorsType != 0 {
136 t.Errorf("expected a fatal error, but no fatal error was returned, yaml: `%s`", test.yaml)
137 }
138
139 if test.err&fatalErrorsType != 0 {
140
141 return
142 }
143
144
145 if string(jsonBytes) != test.json {
146 t.Errorf("Failed to convert YAML to JSON, yaml: `%s`, expected json `%s`, got `%s`", test.yaml, test.json, string(jsonBytes))
147 }
148 })
149
150 t.Run(fmt.Sprintf("%s_JSONToYAML", testName), func(t *testing.T) {
151
152 yamlBytes, err := JSONToYAML([]byte(test.json))
153 if err != nil {
154 t.Errorf("Failed to convert JSON to YAML, json: `%s`, err: %v", test.json, err)
155 }
156
157
158 correctYamlString := test.yaml
159
160
161 if test.yamlReverseOverwrite != nil {
162 correctYamlString = *test.yamlReverseOverwrite
163 }
164
165
166 if string(yamlBytes) != correctYamlString {
167 t.Errorf("Failed to convert JSON to YAML, json: `%s`, expected yaml `%s`, got `%s`", test.json, correctYamlString, string(yamlBytes))
168 }
169 })
170 }
171 }
172
173
174
175 type MarshalTest struct {
176 A string
177 B int64
178
179
180 C float32
181 }
182
183 func TestMarshal(t *testing.T) {
184 f32String := strconv.FormatFloat(math.MaxFloat32, 'g', -1, 32)
185 s := MarshalTest{"a", math.MaxInt64, math.MaxFloat32}
186 e := []byte(fmt.Sprintf("A: a\nB: %d\nC: %s\n", math.MaxInt64, f32String))
187
188 y, err := Marshal(s)
189 if err != nil {
190 t.Errorf("error marshaling YAML: %v", err)
191 }
192
193 if !reflect.DeepEqual(y, e) {
194 t.Errorf("marshal YAML was unsuccessful, expected: %#v, got: %#v",
195 string(e), string(y))
196 }
197 }
198
199 type UnmarshalUntaggedStruct struct {
200 A string
201 True string
202 }
203
204 type UnmarshalTaggedStruct struct {
205 AUpper string `json:"A"`
206 ALower string `json:"a"`
207 TrueUpper string `json:"True"`
208 TrueLower string `json:"true"`
209 YesUpper string `json:"Yes"`
210 YesLower string `json:"yes"`
211 Int3 string `json:"3"`
212 IntBig1 string `json:"9007199254740993"`
213 IntBig2 string `json:"1000000000000000000000000000000000000"`
214 IntBig2Scientific string `json:"1e+36"`
215 Float3dot3 string `json:"3.3"`
216 }
217
218 type UnmarshalStruct struct {
219 A string `json:"a"`
220 B *string `json:"b"`
221 C string `json:"c"`
222 }
223
224 type UnmarshalStringMap struct {
225 A map[string]string `json:"a"`
226 }
227
228 type UnmarshalNestedStruct struct {
229 A UnmarshalStruct `json:"a"`
230 }
231
232 type UnmarshalSlice struct {
233 A []UnmarshalStruct `json:"a"`
234 }
235
236 type UnmarshalEmbedStruct struct {
237 UnmarshalStruct
238 B string `json:"b"`
239 }
240
241 type UnmarshalEmbedStructPointer struct {
242 *UnmarshalStruct
243 B string `json:"b"`
244 }
245
246 type UnmarshalEmbedRecursiveStruct struct {
247 *UnmarshalEmbedRecursiveStruct `json:"a"`
248 B string `json:"b"`
249 }
250
251 func TestUnmarshal(t *testing.T) {
252 tests := map[string]unmarshalTestCase{
253
254 "untagged casematched string key": {
255 encoded: []byte("A: test"),
256 decodeInto: new(UnmarshalUntaggedStruct),
257 decoded: UnmarshalUntaggedStruct{A: "test"},
258 },
259 "untagged non-casematched string key": {
260 encoded: []byte("a: test"),
261 decodeInto: new(UnmarshalUntaggedStruct),
262 decoded: UnmarshalUntaggedStruct{A: "test"},
263 },
264 "untagged casematched boolean key": {
265 encoded: []byte("True: test"),
266 decodeInto: new(UnmarshalUntaggedStruct),
267 decoded: UnmarshalUntaggedStruct{True: "test"},
268 },
269 "untagged non-casematched boolean key": {
270 encoded: []byte("true: test"),
271 decodeInto: new(UnmarshalUntaggedStruct),
272 decoded: UnmarshalUntaggedStruct{True: "test"},
273 },
274
275
276 "tagged casematched string key": {
277 encoded: []byte("A: test"),
278 decodeInto: new(UnmarshalTaggedStruct),
279 decoded: UnmarshalTaggedStruct{AUpper: "test"},
280 },
281 "tagged non-casematched string key": {
282 encoded: []byte("a: test"),
283 decodeInto: new(UnmarshalTaggedStruct),
284 decoded: UnmarshalTaggedStruct{ALower: "test"},
285 },
286 "tagged casematched boolean key": {
287 encoded: []byte("True: test"),
288 decodeInto: new(UnmarshalTaggedStruct),
289 decoded: UnmarshalTaggedStruct{TrueLower: "test"},
290 },
291 "tagged non-casematched boolean key": {
292 encoded: []byte("true: test"),
293 decodeInto: new(UnmarshalTaggedStruct),
294 decoded: UnmarshalTaggedStruct{TrueLower: "test"},
295 },
296 "tagged casematched boolean key (yes)": {
297 encoded: []byte("Yes: test"),
298 decodeInto: new(UnmarshalTaggedStruct),
299 decoded: UnmarshalTaggedStruct{TrueLower: "test"},
300 },
301 "tagged non-casematched boolean key (yes)": {
302 encoded: []byte("yes: test"),
303 decodeInto: new(UnmarshalTaggedStruct),
304 decoded: UnmarshalTaggedStruct{TrueLower: "test"},
305 },
306 "tagged integer key": {
307 encoded: []byte("3: test"),
308 decodeInto: new(UnmarshalTaggedStruct),
309 decoded: UnmarshalTaggedStruct{Int3: "test"},
310 },
311 "tagged big integer key 2^53 + 1": {
312 encoded: []byte("9007199254740993: test"),
313 decodeInto: new(UnmarshalTaggedStruct),
314 decoded: UnmarshalTaggedStruct{IntBig1: "test"},
315 },
316 "tagged big integer key 1000000000000000000000000000000000000": {
317 encoded: []byte("1000000000000000000000000000000000000: test"),
318 decodeInto: new(UnmarshalTaggedStruct),
319 decoded: UnmarshalTaggedStruct{IntBig2Scientific: "test"},
320 },
321 "tagged float key": {
322 encoded: []byte("3.3: test"),
323 decodeInto: new(UnmarshalTaggedStruct),
324 decoded: UnmarshalTaggedStruct{Float3dot3: "test"},
325 },
326
327
328 "string value into string field": {
329 encoded: []byte("a: test"),
330 decodeInto: new(UnmarshalStruct),
331 decoded: UnmarshalStruct{A: "test"},
332 },
333 "integer value into string field": {
334 encoded: []byte("a: 1"),
335 decodeInto: new(UnmarshalStruct),
336 decoded: UnmarshalStruct{A: "1"},
337 },
338 "boolean value into string field": {
339 encoded: []byte("a: true"),
340 decodeInto: new(UnmarshalStruct),
341 decoded: UnmarshalStruct{A: "true"},
342 },
343 "boolean value (no) into string field": {
344 encoded: []byte("a: no"),
345 decodeInto: new(UnmarshalStruct),
346 decoded: UnmarshalStruct{A: "false"},
347 },
348
349
350 "decode into nested struct": {
351 encoded: []byte("a:\n a: 1"),
352 decodeInto: new(UnmarshalNestedStruct),
353 decoded: UnmarshalNestedStruct{UnmarshalStruct{A: "1"}},
354 },
355 "decode into slice": {
356 encoded: []byte("a:\n - a: abc\n b: def\n - a: 123"),
357 decodeInto: new(UnmarshalSlice),
358 decoded: UnmarshalSlice{[]UnmarshalStruct{{A: "abc", B: strPtr("def")}, {A: "123"}}},
359 },
360 "decode into string map": {
361 encoded: []byte("a:\n b: 1"),
362 decodeInto: new(UnmarshalStringMap),
363 decoded: UnmarshalStringMap{map[string]string{"b": "1"}},
364 },
365 "decode into struct pointer map": {
366 encoded: []byte("a:\n a: TestA\nb:\n a: TestB\n b: TestC"),
367 decodeInto: new(map[string]*UnmarshalStruct),
368 decoded: map[string]*UnmarshalStruct{
369 "a": {A: "TestA"},
370 "b": {A: "TestB", B: strPtr("TestC")},
371 },
372 },
373
374
375 "string map: decode string key": {
376 encoded: []byte("a:"),
377 decodeInto: new(map[string]struct{}),
378 decoded: map[string]struct{}{
379 "a": {},
380 },
381 },
382 "string map: decode boolean key": {
383 encoded: []byte("True:"),
384 decodeInto: new(map[string]struct{}),
385 decoded: map[string]struct{}{
386 "true": {},
387 },
388 },
389 "string map: decode boolean key (yes)": {
390 encoded: []byte("Yes:"),
391 decodeInto: new(map[string]struct{}),
392 decoded: map[string]struct{}{
393 "true": {},
394 },
395 },
396 "string map: decode integer key": {
397 encoded: []byte("44:"),
398 decodeInto: new(map[string]struct{}),
399 decoded: map[string]struct{}{
400 "44": {},
401 },
402 },
403 "string map: decode float key": {
404 encoded: []byte("444.444:"),
405 decodeInto: new(map[string]struct{}),
406 decoded: map[string]struct{}{
407 "444.444": {},
408 },
409 },
410
411
412 "decode 2^53 + 1 into int": {
413 encoded: []byte("9007199254740993"),
414 decodeInto: new(int),
415 decoded: 9007199254740993,
416 },
417 "decode 2^53 + 1 into interface": {
418 encoded: []byte("9007199254740993"),
419 decodeInto: new(interface{}),
420 decoded: 9.007199254740992e+15,
421 },
422
423
424 "float into interface": {
425 encoded: []byte("3.0"),
426 decodeInto: new(interface{}),
427 decoded: float64(3),
428 },
429 "integer into interface": {
430 encoded: []byte("3"),
431 decodeInto: new(interface{}),
432 decoded: float64(3),
433 },
434 "empty vs empty string into interface": {
435 encoded: []byte("a: \"\"\nb: \n"),
436 decodeInto: new(interface{}),
437 decoded: map[string]interface{}{
438 "a": "",
439 "b": nil,
440 },
441 },
442
443
444 "decode duplicate (non-casematched) into nested struct 1": {
445 encoded: []byte("a:\n a: 1\n b: 1\n c: test\n\nA:\n a: 2"),
446 decodeInto: new(UnmarshalNestedStruct),
447 decoded: UnmarshalNestedStruct{A: UnmarshalStruct{A: "1", B: strPtr("1"), C: "test"}},
448 },
449 "decode duplicate (non-casematched) into nested struct 2": {
450 encoded: []byte("A:\n a: 1\n b: 1\n c: test\na:\n a: 2"),
451 decodeInto: new(UnmarshalNestedStruct),
452 decoded: UnmarshalNestedStruct{A: UnmarshalStruct{A: "2", B: strPtr("1"), C: "test"}},
453 },
454 "decode duplicate (non-casematched) into nested slice 1": {
455 encoded: []byte("a:\n - a: abc\n b: def\nA:\n - a: 123"),
456 decodeInto: new(UnmarshalSlice),
457 decoded: UnmarshalSlice{[]UnmarshalStruct{{A: "abc", B: strPtr("def")}}},
458 },
459 "decode duplicate (non-casematched) into nested slice 2": {
460 encoded: []byte("A:\n - a: abc\n b: def\na:\n - a: 123"),
461 decodeInto: new(UnmarshalSlice),
462 decoded: UnmarshalSlice{[]UnmarshalStruct{{A: "123", B: strPtr("def")}}},
463 },
464 "decode duplicate (non-casematched) into nested string map 1": {
465 encoded: []byte("a:\n b: 1\nA:\n c: 1"),
466 decodeInto: new(UnmarshalStringMap),
467 decoded: UnmarshalStringMap{map[string]string{"b": "1", "c": "1"}},
468 },
469 "decode duplicate (non-casematched) into nested string map 2": {
470 encoded: []byte("A:\n b: 1\na:\n c: 1"),
471 decodeInto: new(UnmarshalStringMap),
472 decoded: UnmarshalStringMap{map[string]string{"b": "1", "c": "1"}},
473 },
474 "decode duplicate (non-casematched) into string map": {
475 encoded: []byte("a: test\nb: test\nA: test2"),
476 decodeInto: new(map[string]string),
477 decoded: map[string]string{
478 "a": "test",
479 "A": "test2",
480 "b": "test",
481 },
482 },
483
484
485 "decode embeded struct": {
486 encoded: []byte("a: testA\nb: testB"),
487 decodeInto: new(UnmarshalEmbedStruct),
488 decoded: UnmarshalEmbedStruct{
489 UnmarshalStruct: UnmarshalStruct{
490 A: "testA",
491 },
492 B: "testB",
493 },
494 },
495 "decode embeded structpointer": {
496 encoded: []byte("a: testA\nb: testB"),
497 decodeInto: new(UnmarshalEmbedStructPointer),
498 decoded: UnmarshalEmbedStructPointer{
499 UnmarshalStruct: &UnmarshalStruct{
500 A: "testA",
501 },
502 B: "testB",
503 },
504 },
505 "decode recursive embeded structpointer": {
506 encoded: []byte("b: testB\na:\n b: testA"),
507 decodeInto: new(UnmarshalEmbedRecursiveStruct),
508 decoded: UnmarshalEmbedRecursiveStruct{
509 UnmarshalEmbedRecursiveStruct: &UnmarshalEmbedRecursiveStruct{
510 B: "testA",
511 },
512 B: "testB",
513 },
514 },
515
516
517 "decode embeded struct and cast integer to string": {
518 encoded: []byte("a: 11\nb: testB"),
519 decodeInto: new(UnmarshalEmbedStruct),
520 decoded: UnmarshalEmbedStruct{
521 UnmarshalStruct: UnmarshalStruct{
522 A: "11",
523 },
524 B: "testB",
525 },
526 err: fatalErrorsType,
527 },
528 "decode embeded structpointer and cast integer to string": {
529 encoded: []byte("a: 11\nb: testB"),
530 decodeInto: new(UnmarshalEmbedStructPointer),
531 decoded: UnmarshalEmbedStructPointer{
532 UnmarshalStruct: &UnmarshalStruct{
533 A: "11",
534 },
535 B: "testB",
536 },
537 err: fatalErrorsType,
538 },
539
540
541 "decode into stringmap with incompatible type": {
542 encoded: []byte("a:\n a:\n a: 3"),
543 decodeInto: new(UnmarshalStringMap),
544 err: fatalErrorsType,
545 },
546 }
547
548 t.Run("Unmarshal", func(t *testing.T) {
549 testUnmarshal(t, funcUnmarshal, tests)
550 })
551
552 t.Run("UnmarshalStrict", func(t *testing.T) {
553 testUnmarshal(t, funcUnmarshalStrict, tests)
554 })
555 }
556
557 func TestUnmarshalStrictFails(t *testing.T) {
558 tests := map[string]unmarshalTestCase{
559
560 "decode into struct pointer map with duplicate string value": {
561 encoded: []byte("a:\n a: TestA\n b: ID-A\n b: ID-1"),
562 decodeInto: new(map[string]*UnmarshalStruct),
563 decoded: map[string]*UnmarshalStruct{
564 "a": {A: "TestA", B: strPtr("ID-1")},
565 },
566 },
567 "decode into string field with duplicate boolean value": {
568 encoded: []byte("a: true\na: false"),
569 decodeInto: new(UnmarshalStruct),
570 decoded: UnmarshalStruct{A: "false"},
571 },
572 "decode into slice with duplicate string-boolean value": {
573 encoded: []byte("a:\n- b: abc\n a: 32\n b: 123"),
574 decodeInto: new(UnmarshalSlice),
575 decoded: UnmarshalSlice{[]UnmarshalStruct{{A: "32", B: strPtr("123")}}},
576 },
577
578
579 "decode into struct with unknown field": {
580 encoded: []byte("a: TestB\nb: ID-B\nunknown: Some-Value"),
581 decodeInto: new(UnmarshalStruct),
582 decoded: UnmarshalStruct{A: "TestB", B: strPtr("ID-B")},
583 },
584
585
586 "decode duplicate into nested struct": {
587 encoded: []byte("a:\n a: 1\na:\n a: 2"),
588 decodeInto: new(UnmarshalNestedStruct),
589 decoded: UnmarshalNestedStruct{A: UnmarshalStruct{A: "2"}},
590 },
591 "decode duplicate into nested slice": {
592 encoded: []byte("a:\n - a: abc\n b: def\na:\n - a: 123"),
593 decodeInto: new(UnmarshalSlice),
594 decoded: UnmarshalSlice{[]UnmarshalStruct{{A: "123"}}},
595 },
596 "decode duplicate into nested string map": {
597 encoded: []byte("a:\n b: 1\na:\n c: 1"),
598 decodeInto: new(UnmarshalStringMap),
599 decoded: UnmarshalStringMap{map[string]string{"c": "1"}},
600 },
601 "decode duplicate into string map": {
602 encoded: []byte("a: test\nb: test\na: test2"),
603 decodeInto: new(map[string]string),
604 decoded: map[string]string{
605 "a": "test2",
606 "b": "test",
607 },
608 },
609 }
610
611 t.Run("Unmarshal", func(t *testing.T) {
612 testUnmarshal(t, funcUnmarshal, tests)
613 })
614
615 t.Run("UnmarshalStrict", func(t *testing.T) {
616 failTests := map[string]unmarshalTestCase{}
617 for name, test := range tests {
618 test.err = fatalErrorsType
619 failTests[name] = test
620 }
621 testUnmarshal(t, funcUnmarshalStrict, failTests)
622 })
623 }
624
625 func TestYAMLToJSON(t *testing.T) {
626 tests := map[string]yamlToJSONTestcase{
627 "string value": {
628 yaml: "t: a\n",
629 json: `{"t":"a"}`,
630 },
631 "null value": {
632 yaml: "t: null\n",
633 json: `{"t":null}`,
634 },
635 "boolean value": {
636 yaml: "t: True\n",
637 json: `{"t":true}`,
638 yamlReverseOverwrite: strPtr("t: true\n"),
639 },
640 "boolean value (no)": {
641 yaml: "t: no\n",
642 json: `{"t":false}`,
643 yamlReverseOverwrite: strPtr("t: false\n"),
644 },
645 "integer value (2^53 + 1)": {
646 yaml: "t: 9007199254740993\n",
647 json: `{"t":9007199254740993}`,
648 yamlReverseOverwrite: strPtr("t: 9007199254740993\n"),
649 },
650 "integer value (1000000000000000000000000000000000000)": {
651 yaml: "t: 1000000000000000000000000000000000000\n",
652 json: `{"t":1e+36}`,
653 yamlReverseOverwrite: strPtr("t: 1e+36\n"),
654 },
655 "line-wrapped string value": {
656 yaml: "t: this is very long line with spaces and it must be longer than 80 so we will repeat\n that it must be longer that 80\n",
657 json: `{"t":"this is very long line with spaces and it must be longer than 80 so we will repeat that it must be longer that 80"}`,
658 },
659 "empty yaml value": {
660 yaml: "t: ",
661 json: `{"t":null}`,
662 yamlReverseOverwrite: strPtr("t: null\n"),
663 },
664 "boolean key": {
665 yaml: "True: a",
666 json: `{"true":"a"}`,
667 yamlReverseOverwrite: strPtr("\"true\": a\n"),
668 },
669 "boolean key (no)": {
670 yaml: "no: a",
671 json: `{"false":"a"}`,
672 yamlReverseOverwrite: strPtr("\"false\": a\n"),
673 },
674 "integer key": {
675 yaml: "1: a",
676 json: `{"1":"a"}`,
677 yamlReverseOverwrite: strPtr("\"1\": a\n"),
678 },
679 "float key": {
680 yaml: "1.2: a",
681 json: `{"1.2":"a"}`,
682 yamlReverseOverwrite: strPtr("\"1.2\": a\n"),
683 },
684 "large integer key": {
685 yaml: "1000000000000000000000000000000000000: a",
686 json: `{"1e+36":"a"}`,
687 yamlReverseOverwrite: strPtr("\"1e+36\": a\n"),
688 },
689 "large integer key (scientific notation)": {
690 yaml: "1e+36: a",
691 json: `{"1e+36":"a"}`,
692 yamlReverseOverwrite: strPtr("\"1e+36\": a\n"),
693 },
694 "string key (large integer as string)": {
695 yaml: "\"1e+36\": a\n",
696 json: `{"1e+36":"a"}`,
697 },
698 "string key (float as string)": {
699 yaml: "\"1.2\": a\n",
700 json: `{"1.2":"a"}`,
701 },
702 "array": {
703 yaml: "- t: a\n",
704 json: `[{"t":"a"}]`,
705 },
706 "nested struct array": {
707 yaml: "- t: a\n- t:\n b: 1\n c: 2\n",
708 json: `[{"t":"a"},{"t":{"b":1,"c":2}}]`,
709 },
710 "nested struct array (json notation)": {
711 yaml: `[{t: a}, {t: {b: 1, c: 2}}]`,
712 json: `[{"t":"a"},{"t":{"b":1,"c":2}}]`,
713 yamlReverseOverwrite: strPtr("- t: a\n- t:\n b: 1\n c: 2\n"),
714 },
715 "empty struct value": {
716 yaml: "- t: ",
717 json: `[{"t":null}]`,
718 yamlReverseOverwrite: strPtr("- t: null\n"),
719 },
720 "null struct value": {
721 yaml: "- t: null\n",
722 json: `[{"t":null}]`,
723 },
724 "binary data": {
725 yaml: "a: !!binary gIGC",
726 json: `{"a":"\ufffd\ufffd\ufffd"}`,
727 yamlReverseOverwrite: strPtr("a: \ufffd\ufffd\ufffd\n"),
728 },
729
730
731 "~ key": {
732 yaml: "~: a",
733 json: `{"null":"a"}`,
734 yamlReverseOverwrite: strPtr("\"null\": a\n"),
735 err: fatalErrorsType,
736 },
737 "null key": {
738 yaml: "null: a",
739 json: `{"null":"a"}`,
740 yamlReverseOverwrite: strPtr("\"null\": a\n"),
741 err: fatalErrorsType,
742 },
743 }
744
745 t.Run("YAMLToJSON", func(t *testing.T) {
746 testYAMLToJSON(t, funcYAMLToJSON, tests)
747 })
748
749 t.Run("YAMLToJSONStrict", func(t *testing.T) {
750 testYAMLToJSON(t, funcYAMLToJSONStrict, tests)
751 })
752 }
753
754 func TestYAMLToJSONStrictFails(t *testing.T) {
755 tests := map[string]yamlToJSONTestcase{
756
757 "duplicate struct value": {
758 yaml: "foo: bar\nfoo: baz\n",
759 json: `{"foo":"baz"}`,
760 yamlReverseOverwrite: strPtr("foo: baz\n"),
761 },
762 }
763
764 t.Run("YAMLToJSON", func(t *testing.T) {
765 testYAMLToJSON(t, funcYAMLToJSON, tests)
766 })
767
768 t.Run("YAMLToJSONStrict", func(t *testing.T) {
769 failTests := map[string]yamlToJSONTestcase{}
770 for name, test := range tests {
771 test.err = fatalErrorsType
772 failTests[name] = test
773 }
774 testYAMLToJSON(t, funcYAMLToJSONStrict, failTests)
775 })
776 }
777
778 func TestJSONObjectToYAMLObject(t *testing.T) {
779 const bigUint64 = ((uint64(1) << 63) + 500) / 1000 * 1000
780 intOrInt64 := func(i64 int64) interface{} {
781 if i := int(i64); i64 == int64(i) {
782 return i
783 }
784 return i64
785 }
786
787 tests := []struct {
788 name string
789 input map[string]interface{}
790 expected yamlv2.MapSlice
791 }{
792 {name: "nil", expected: yamlv2.MapSlice(nil)},
793 {name: "empty", input: map[string]interface{}{}, expected: yamlv2.MapSlice(nil)},
794 {
795 name: "values",
796 input: map[string]interface{}{
797 "nil slice": []interface{}(nil),
798 "nil map": map[string]interface{}(nil),
799 "empty slice": []interface{}{},
800 "empty map": map[string]interface{}{},
801 "bool": true,
802 "float64": float64(42.1),
803 "fractionless": float64(42),
804 "int": int(42),
805 "int64": int64(42),
806 "int64 big": float64(math.Pow(2, 62)),
807 "negative int64 big": -float64(math.Pow(2, 62)),
808 "map": map[string]interface{}{"foo": "bar"},
809 "slice": []interface{}{"foo", "bar"},
810 "string": string("foo"),
811 "uint64 big": bigUint64,
812 },
813 expected: yamlv2.MapSlice{
814 {Key: "nil slice"},
815 {Key: "nil map"},
816 {Key: "empty slice", Value: []interface{}{}},
817 {Key: "empty map", Value: yamlv2.MapSlice(nil)},
818 {Key: "bool", Value: true},
819 {Key: "float64", Value: float64(42.1)},
820 {Key: "fractionless", Value: int(42)},
821 {Key: "int", Value: int(42)},
822 {Key: "int64", Value: int(42)},
823 {Key: "int64 big", Value: intOrInt64(int64(1) << 62)},
824 {Key: "negative int64 big", Value: intOrInt64(-(1 << 62))},
825 {Key: "map", Value: yamlv2.MapSlice{{Key: "foo", Value: "bar"}}},
826 {Key: "slice", Value: []interface{}{"foo", "bar"}},
827 {Key: "string", Value: string("foo")},
828 {Key: "uint64 big", Value: bigUint64},
829 },
830 },
831 }
832 for _, tt := range tests {
833 t.Run(tt.name, func(t *testing.T) {
834 got := JSONObjectToYAMLObject(tt.input)
835 sortMapSlicesInPlace(tt.expected)
836 sortMapSlicesInPlace(got)
837 if !reflect.DeepEqual(tt.expected, got) {
838 t.Errorf("jsonToYAML() returned unexpected results (-want+got):\n%v", cmp.Diff(tt.expected, got))
839 }
840
841 jsonBytes, err := json.Marshal(tt.input)
842 if err != nil {
843 t.Fatalf("unexpected json.Marshal error: %v", err)
844 }
845 var gotByRoundtrip yamlv2.MapSlice
846 if err := yamlv2.Unmarshal(jsonBytes, &gotByRoundtrip); err != nil {
847 t.Fatalf("unexpected yaml.Unmarshal error: %v", err)
848 }
849
850
851
852 for i := range got {
853 switch got[i].Key {
854 case "int64 big", "uint64 big", "negative int64 big":
855 switch v := got[i].Value.(type) {
856 case int64:
857 d := int64(500)
858 if v < 0 {
859 d = -500
860 }
861 got[i].Value = int64((v+d)/1000) * 1000
862 case uint64:
863 got[i].Value = uint64((v+500)/1000) * 1000
864 case int:
865 d := int(500)
866 if v < 0 {
867 d = -500
868 }
869 got[i].Value = int((v+d)/1000) * 1000
870 default:
871 t.Fatalf("unexpected type for key %s: %v:%T", got[i].Key, v, v)
872 }
873 }
874 }
875
876 if !reflect.DeepEqual(got, gotByRoundtrip) {
877 t.Errorf("yaml.Unmarshal(json.Marshal(tt.input)) returned unexpected results (-want+got):\n%v", cmp.Diff(got, gotByRoundtrip))
878 t.Errorf("json: %s", string(jsonBytes))
879 }
880 })
881 }
882 }
883
884 func sortMapSlicesInPlace(x interface{}) {
885 switch x := x.(type) {
886 case []interface{}:
887 for i := range x {
888 sortMapSlicesInPlace(x[i])
889 }
890 case yamlv2.MapSlice:
891 sort.Slice(x, func(a, b int) bool {
892 return x[a].Key.(string) < x[b].Key.(string)
893 })
894 }
895 }
896
897 func TestPatchedYamlV3AndUpstream(t *testing.T) {
898 input := `group: apps
899 apiVersion: v1
900 kind: Deployment
901 metadata:
902 name: deploy1
903 spec:
904 template:
905 spec:
906 containers:
907 - image: nginx:1.7.9
908 name: nginx-tagged
909 - image: nginx:latest
910 name: nginx-latest
911 - image: foobar:1
912 name: replaced-with-digest
913 - image: postgres:1.8.0
914 name: postgresdb
915 initContainers:
916 - image: nginx
917 name: nginx-notag
918 - image: nginx@sha256:111111111111111111
919 name: nginx-sha256
920 - image: alpine:1.8.0
921 name: init-alpine
922 `
923
924 var v3Map map[string]interface{}
925 var v2Map map[string]interface{}
926
927
928 if err := yamlv3.Unmarshal([]byte(input), &v3Map); err != nil {
929 t.Fatal(err)
930 }
931 if err := yamlv2.Unmarshal([]byte(input), &v2Map); err != nil {
932 t.Fatal(err)
933 }
934
935
936 var buf bytes.Buffer
937 enc := yamlv3.NewEncoder(&buf)
938 enc.CompactSeqIndent()
939 enc.SetIndent(2)
940 err := enc.Encode(v3Map)
941 v3output := buf.String()
942
943
944 v2output, err := yamlv2.Marshal(v2Map)
945 if err != nil {
946 t.Fatal(err)
947 }
948 if v3output != string(v2output) {
949 t.Fatalf("expected\n%s\ngot\n%s", string(v2output), v3output)
950 }
951 }
952
View as plain text