...

Source file src/sigs.k8s.io/yaml/yaml_test.go

Documentation: sigs.k8s.io/yaml

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     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  */
    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  /* Test helper functions */
    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  				// There's no reason for ptr to point to non-zero data,
    78  				// as we decode into new(right-type), so the data is
    79  				// discarded.
    80  				// This can easily mean tests that silently don't test
    81  				// what they should. To test decoding into existing
    82  				// data, see TestPrefilled.
    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  				// Don't check output if error is fatal
    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  	// By default we test that reversing the output == input. But if there is a
   110  	// difference in the reversed output, you can optionally specify it here.
   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  			// Convert Yaml to Json
   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  				// Don't check output if error is fatal
   141  				return
   142  			}
   143  
   144  			// Check it against the expected output.
   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  			// Convert JSON to YAML
   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  			// Set the string that we will compare the reversed output to.
   158  			correctYamlString := test.yaml
   159  
   160  			// If a special reverse string was specified, use that instead.
   161  			if test.yamlReverseOverwrite != nil {
   162  				correctYamlString = *test.yamlReverseOverwrite
   163  			}
   164  
   165  			// Check it against the expected output.
   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  /* Start tests */
   174  
   175  type MarshalTest struct {
   176  	A string
   177  	B int64
   178  	// Would like to test float64, but it's not supported in go-yaml.
   179  	// (See https://github.com/go-yaml/yaml/issues/83.)
   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"` // 2^53 + 1
   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  		// casematched / non-casematched untagged keys
   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  		// casematched / non-casematched tagged keys
   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  		// decode into string field
   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  		// decode into complex fields
   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  		// decoding into string map
   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  		// decoding integers
   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  		// decode into interface
   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  		// duplicate (non-casematched) keys (NOTE: this is very non-ideal behaviour!)
   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  		// decoding embeded structs
   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  		// BUG: type info gets lost (#58)
   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  		// decoding into incompatible type
   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  		// decoding with duplicate values
   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  		// decoding with unknown fields
   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  		// decoding with duplicate complex values
   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  		// Cases that should produce errors.
   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  		// expect YAMLtoJSON to pass on duplicate field names
   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  			// yamlv2.Unmarshal loses precision, it's rounding to the 4th last digit.
   851  			// Replicate this here in the test, but don't change the type.
   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  	// unmarshal the input into the two maps
   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  	// marshal using non-default settings from the yaml v3 fork
   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  	// marshal using the yaml v2 fork
   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