package goyaml_test import ( . "launchpad.net/gocheck" "launchpad.net/goyaml" "math" "reflect" ) var unmarshalIntTest = 123 var unmarshalTests = []struct { data string value interface{} }{ {"", &struct{}{}}, {"{}", &struct{}{}}, {"v: hi", map[string]string{"v": "hi"}}, {"v: hi", map[string]interface{}{"v": "hi"}}, {"v: true", map[string]string{"v": "true"}}, {"v: true", map[string]interface{}{"v": true}}, {"v: 10", map[string]interface{}{"v": 10}}, {"v: 0b10", map[string]interface{}{"v": 2}}, {"v: 0xA", map[string]interface{}{"v": 10}}, {"v: 4294967296", map[string]interface{}{"v": int64(4294967296)}}, {"v: 4294967296", map[string]int64{"v": int64(4294967296)}}, {"v: 0.1", map[string]interface{}{"v": 0.1}}, {"v: .1", map[string]interface{}{"v": 0.1}}, {"v: .Inf", map[string]interface{}{"v": math.Inf(+1)}}, {"v: -.Inf", map[string]interface{}{"v": math.Inf(-1)}}, {"v: -10", map[string]interface{}{"v": -10}}, {"v: -.1", map[string]interface{}{"v": -0.1}}, // Simple values. {"123", &unmarshalIntTest}, // Floats from spec {"canonical: 6.8523e+5", map[string]interface{}{"canonical": 6.8523e+5}}, {"expo: 685.230_15e+03", map[string]interface{}{"expo": 685.23015e+03}}, {"fixed: 685_230.15", map[string]interface{}{"fixed": 685230.15}}, //{"sexa: 190:20:30.15", map[string]interface{}{"sexa": 0}}, // Unsupported {"neginf: -.inf", map[string]interface{}{"neginf": math.Inf(-1)}}, //{"notanum: .NaN", map[string]interface{}{"notanum": math.NaN()}}, // Equality of NaN fails. {"fixed: 685_230.15", map[string]float64{"fixed": 685230.15}}, // Bools from spec {"canonical: y", map[string]interface{}{"canonical": true}}, {"answer: NO", map[string]interface{}{"answer": false}}, {"logical: True", map[string]interface{}{"logical": true}}, {"option: on", map[string]interface{}{"option": true}}, {"option: on", map[string]bool{"option": true}}, // Ints from spec {"canonical: 685230", map[string]interface{}{"canonical": 685230}}, {"decimal: +685_230", map[string]interface{}{"decimal": 685230}}, {"octal: 02472256", map[string]interface{}{"octal": 685230}}, {"hexa: 0x_0A_74_AE", map[string]interface{}{"hexa": 685230}}, {"bin: 0b1010_0111_0100_1010_1110", map[string]interface{}{"bin": 685230}}, {"bin: -0b101010", map[string]interface{}{"bin": -42}}, //{"sexa: 190:20:30", map[string]interface{}{"sexa": 0}}, // Unsupported {"decimal: +685_230", map[string]int{"decimal": 685230}}, // Nulls from spec {"empty:", map[string]interface{}{"empty": nil}}, {"canonical: ~", map[string]interface{}{"canonical": nil}}, {"english: null", map[string]interface{}{"english": nil}}, {"~: null key", map[interface{}]string{nil: "null key"}}, {"empty:", map[string]*bool{"empty": nil}}, // Sequence {"seq: [A,B]", map[string]interface{}{"seq": []interface{}{"A", "B"}}}, {"seq: [A,B,C]", map[string][]string{"seq": []string{"A", "B", "C"}}}, {"seq: [A,1,C]", map[string][]string{"seq": []string{"A", "1", "C"}}}, {"seq: [A,1,C]", map[string][]int{"seq": []int{1}}}, {"seq: [A,1,C]", map[string]interface{}{"seq": []interface{}{"A", 1, "C"}}}, // Map inside interface with no type hints. {"a: {b: c}", map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}}}, // Structs and type conversions. {"hello: world", &struct{ Hello string }{"world"}}, {"a: {b: c}", &struct{ A struct{ B string } }{struct{ B string }{"c"}}}, {"a: {b: c}", &struct{ A *struct{ B string } }{&struct{ B string }{"c"}}}, {"a: {b: c}", &struct{ A map[string]string }{map[string]string{"b": "c"}}}, {"a: {b: c}", &struct{ A *map[string]string }{&map[string]string{"b": "c"}}}, {"a:", &struct{ A map[string]string }{}}, {"a: 1", &struct{ A int }{1}}, {"a: [1, 2]", &struct{ A []int }{[]int{1, 2}}}, {"a: 1", &struct{ B int }{0}}, {"a: 1", &struct { B int "a" }{1}}, {"a: y", &struct{ A bool }{true}}, // Some cross type conversions {"v: 42", map[string]uint{"v": 42}}, {"v: -42", map[string]uint{}}, {"v: 4294967296", map[string]uint64{"v": 4294967296}}, {"v: -4294967296", map[string]uint64{}}, // Overflow cases. {"v: 4294967297", map[string]int32{}}, {"v: 128", map[string]int8{}}, // Quoted values. {"'1': '\"2\"'", map[interface{}]interface{}{"1": "\"2\""}}, // Explicit tags. {"v: !!float '1.1'", map[string]interface{}{"v": 1.1}}, {"v: !!null ''", map[string]interface{}{"v": nil}}, {"%TAG !y! tag:yaml.org,2002:\n---\nv: !y!int '1'", map[string]interface{}{"v": 1}}, // Anchors and aliases. {"a: &x 1\nb: &y 2\nc: *x\nd: *y\n", &struct{ A, B, C, D int }{1, 2, 1, 2}}, {"a: &a {c: 1}\nb: *a", &struct { A, B struct { C int } }{struct{ C int }{1}, struct{ C int }{1}}}, {"a: &a [1, 2]\nb: *a", &struct{ B []int }{[]int{1, 2}}}, } func (s *S) TestUnmarshal(c *C) { for i, item := range unmarshalTests { t := reflect.ValueOf(item.value).Type() var value interface{} if t.Kind() == reflect.Map { value = reflect.MakeMap(t).Interface() } else { pt := reflect.ValueOf(item.value).Type() pv := reflect.New(pt.Elem()) value = pv.Interface() } err := goyaml.Unmarshal([]byte(item.data), value) c.Assert(err, IsNil, Commentf("Item #%d", i)) c.Assert(value, DeepEquals, item.value) } } func (s *S) TestUnmarshalNaN(c *C) { value := map[string]interface{}{} err := goyaml.Unmarshal([]byte("notanum: .NaN"), &value) c.Assert(err, IsNil) c.Assert(math.IsNaN(value["notanum"].(float64)), Equals, true) } var unmarshalErrorTests = []struct { data, error string }{ {"v: !!float 'error'", "YAML error: Can't decode !!str 'error' as a !!float"}, {"v: [A,", "YAML error: line 1: did not find expected node content"}, {"v:\n- [A,", "YAML error: line 2: did not find expected node content"}, {"a: *b\n", "YAML error: Unknown anchor 'b' referenced"}, {"a: &a\n b: *a\n", "YAML error: Anchor 'a' value contains itself"}, } func (s *S) TestUnmarshalErrors(c *C) { for _, item := range unmarshalErrorTests { var value interface{} err := goyaml.Unmarshal([]byte(item.data), &value) c.Assert(err, ErrorMatches, item.error, Commentf("Partial unmarshal: %#v", value)) } } var setterTests = []struct { data, tag string value interface{} }{ {"_: {hi: there}", "!!map", map[interface{}]interface{}{"hi": "there"}}, {"_: [1,A]", "!!seq", []interface{}{1, "A"}}, {"_: 10", "!!int", 10}, {"_: null", "!!null", nil}, {"_: !!foo 'BAR!'", "!!foo", "BAR!"}, } var setterResult = map[int]bool{} type typeWithSetter struct { tag string value interface{} } func (o *typeWithSetter) SetYAML(tag string, value interface{}) (ok bool) { o.tag = tag o.value = value if i, ok := value.(int); ok { if result, ok := setterResult[i]; ok { return result } } return true } type typeWithSetterField struct { Field *typeWithSetter "_" } func (s *S) TestUnmarshalWithSetter(c *C) { for _, item := range setterTests { obj := &typeWithSetterField{} err := goyaml.Unmarshal([]byte(item.data), obj) c.Assert(err, IsNil) c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value)) c.Assert(obj.Field.tag, Equals, item.tag) c.Assert(obj.Field.value, DeepEquals, item.value) } } func (s *S) TestUnmarshalWholeDocumentWithSetter(c *C) { obj := &typeWithSetter{} err := goyaml.Unmarshal([]byte(setterTests[0].data), obj) c.Assert(err, IsNil) c.Assert(obj.tag, Equals, setterTests[0].tag) value, ok := obj.value.(map[interface{}]interface{}) c.Assert(ok, Equals, true) c.Assert(value["_"], DeepEquals, setterTests[0].value) } func (s *S) TestUnmarshalWithFalseSetterIgnoresValue(c *C) { setterResult[2] = false setterResult[4] = false defer func() { delete(setterResult, 2) delete(setterResult, 4) }() m := map[string]*typeWithSetter{} data := "{abc: 1, def: 2, ghi: 3, jkl: 4}" err := goyaml.Unmarshal([]byte(data), m) c.Assert(err, IsNil) c.Assert(m["abc"], NotNil) c.Assert(m["def"], IsNil) c.Assert(m["ghi"], NotNil) c.Assert(m["jkl"], IsNil) c.Assert(m["abc"].value, Equals, 1) c.Assert(m["ghi"].value, Equals, 3) }