blob: d08104e6ea4ddf5224b5f6232b5a2fa346314176 [file] [log] [blame]
package yaml_test
import (
"errors"
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
"testing"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/format"
"cuelang.org/go/internal/cuetest"
"cuelang.org/go/internal/third_party/yaml"
)
var unmarshalIntTest = 123
var unmarshalTests = []struct {
data string
want string
}{
{
"",
"",
},
{
"{}",
"",
}, {
"v: hi",
`v: "hi"`,
}, {
"v: hi",
`v: "hi"`,
}, {
"v: true",
"v: true",
}, {
"v: 10",
"v: 10",
}, {
"v: 0b10",
"v: 0b10",
}, {
"v: 0xA",
"v: 0xA",
}, {
"v: 4294967296",
"v: 4294967296",
}, {
"v: 0.1",
"v: 0.1",
}, {
"v: .1",
"v: 0.1",
}, {
"v: .Inf",
"v: +Inf",
}, {
"v: -.Inf",
"v: -Inf",
}, {
"v: -10",
"v: -10",
}, {
"v: -.1",
"v: -0.1",
},
// Simple values.
{
"123",
"123",
},
// Floats from spec
{
"canonical: 6.8523e+5",
"canonical: 6.8523e+5",
}, {
"expo: 685.230_15e+03",
"expo: 685.230_15e+03",
}, {
"fixed: 685_230.15",
"fixed: 685_230.15",
}, {
"neginf: -.inf",
"neginf: -Inf",
}, {
"fixed: 685_230.15",
"fixed: 685_230.15",
},
//{"sexa: 190:20:30.15", map[string]interface{}{"sexa": 0}}, // Unsupported
//{"notanum: .NaN", map[string]interface{}{"notanum": math.NaN()}}, // Equality of NaN fails.
// Bools from spec
{
"canonical: y",
`canonical: "y"`,
}, {
"answer: n",
`answer: "n"`,
}, {
"answer: NO",
`answer: "NO"`,
}, {
"logical: True",
"logical: true",
}, {
"option: on",
`option: "on"`,
}, {
"answer: off",
`answer: "off"`,
},
// Ints from spec
{
"canonical: 685230",
"canonical: 685230",
}, {
"decimal: +685_230",
"decimal: +685_230",
}, {
"octal: 02472256",
"octal: 0o2472256",
}, {
"hexa: 0x_0A_74_AE",
"hexa: 0x_0A_74_AE",
}, {
"bin: 0b1010_0111_0100_1010_1110",
"bin: 0b1010_0111_0100_1010_1110",
}, {
"bin: -0b101010",
"bin: -0b101010",
}, {
"bin: -0b1000000000000000000000000000000000000000000000000000000000000000",
"bin: -0b1000000000000000000000000000000000000000000000000000000000000000",
}, {
"decimal: +685_230",
"decimal: +685_230",
},
//{"sexa: 190:20:30", map[string]interface{}{"sexa": 0}}, // Unsupported
// Nulls from spec
{
"empty:",
"empty: null",
}, {
"canonical: ~",
"canonical: null",
}, {
"english: null",
"english: null",
}, {
"~: null key",
`"~": "null key"`,
}, {
`empty:
apple: "newline"`,
`empty: null
apple: "newline"`,
},
// Flow sequence
{
"seq: [A,B]",
`seq: ["A", "B"]`,
}, {
"seq: [A,B,C,]",
`seq: ["A", "B", "C"]`,
}, {
"seq: [A,1,C]",
`seq: ["A", 1, "C"]`,
},
// Block sequence
{
"seq:\n - A\n - B",
`seq: [
"A",
"B",
]`,
}, {
"seq:\n - A\n - B\n - C",
`seq: [
"A",
"B",
"C",
]`,
}, {
"seq:\n - A\n - 1\n - C",
`seq: [
"A",
1,
"C",
]`,
},
// Literal block scalar
{
"scalar: | # Comment\n\n literal\n\n \ttext\n\n",
`scalar: """
literal
\ttext
"""`,
},
// Folded block scalar
{
"scalar: > # Comment\n\n folded\n line\n \n next\n line\n * one\n * two\n\n last\n line\n\n",
`scalar: """
folded line
next line
* one
* two
last line
"""`,
},
// Structs
{
"a: {b: c}",
`a: {b: "c"}`,
},
{
"hello: world",
`hello: "world"`,
}, {
"a:",
"a: null",
}, {
"a: 1",
"a: 1",
}, {
"a: 1.0",
"a: 1.0",
}, {
"a: [1, 2]",
"a: [1, 2]",
}, {
"a: y",
`a: "y"`,
}, {
"{ a: 1, b: {c: 1} }",
`a: 1, b: {c: 1}`,
},
// Some cross type conversions
{
"v: 42",
"v: 42",
}, {
"v: -42",
"v: -42",
}, {
"v: 4294967296",
"v: 4294967296",
}, {
"v: -4294967296",
"v: -4294967296",
},
// int
{
"int_max: 2147483647",
"int_max: 2147483647",
},
{
"int_min: -2147483648",
"int_min: -2147483648",
},
{
"int_overflow: 9223372036854775808", // math.MaxInt64 + 1
"int_overflow: 9223372036854775808", // math.MaxInt64 + 1
},
// int64
{
"int64_max: 9223372036854775807",
"int64_max: 9223372036854775807",
},
{
"int64_max_base2: 0b111111111111111111111111111111111111111111111111111111111111111",
"int64_max_base2: 0b111111111111111111111111111111111111111111111111111111111111111",
},
{
"int64_min: -9223372036854775808",
"int64_min: -9223372036854775808",
},
{
"int64_neg_base2: -0b111111111111111111111111111111111111111111111111111111111111111",
"int64_neg_base2: -0b111111111111111111111111111111111111111111111111111111111111111",
},
{
"int64_overflow: 9223372036854775808", // math.MaxInt64 + 1
"int64_overflow: 9223372036854775808", // math.MaxInt64 + 1
},
// uint
{
"uint_max: 4294967295",
"uint_max: 4294967295",
},
// uint64
{
"uint64_max: 18446744073709551615",
"uint64_max: 18446744073709551615",
},
{
"uint64_max_base2: 0b1111111111111111111111111111111111111111111111111111111111111111",
"uint64_max_base2: 0b1111111111111111111111111111111111111111111111111111111111111111",
},
{
"uint64_maxint64: 9223372036854775807",
"uint64_maxint64: 9223372036854775807",
},
// float32
{
"float32_max: 3.40282346638528859811704183484516925440e+38",
"float32_max: 3.40282346638528859811704183484516925440e+38",
},
{
"float32_nonzero: 1.401298464324817070923729583289916131280e-45",
"float32_nonzero: 1.401298464324817070923729583289916131280e-45",
},
{
"float32_maxuint64: 18446744073709551615",
"float32_maxuint64: 18446744073709551615",
},
{
"float32_maxuint64+1: 18446744073709551616",
`"float32_maxuint64+1": 18446744073709551616`,
},
// float64
{
"float64_max: 1.797693134862315708145274237317043567981e+308",
"float64_max: 1.797693134862315708145274237317043567981e+308",
},
{
"float64_nonzero: 4.940656458412465441765687928682213723651e-324",
"float64_nonzero: 4.940656458412465441765687928682213723651e-324",
},
{
"float64_maxuint64: 18446744073709551615",
"float64_maxuint64: 18446744073709551615",
},
{
"float64_maxuint64+1: 18446744073709551616",
`"float64_maxuint64+1": 18446744073709551616`,
},
// Overflow cases.
{
"v: 4294967297",
"v: 4294967297",
}, {
"v: 128",
"v: 128",
},
// Quoted values.
{
"'1': '\"2\"'",
`"1": "\"2\""`,
}, {
"v:\n- A\n- 'B\n\n C'\n",
`v: [
"A",
"""
B
C
""",
]`,
}, {
`"\0"`,
`"\u0000"`,
},
// Explicit tags.
{
"v: !!float '1.1'",
"v: 1.1",
}, {
"v: !!float 0",
"v: float & 0", // Should this be 0.0?
}, {
"v: !!float -1",
"v: float & -1", // Should this be -1.0?
}, {
"v: !!null ''",
"v: null",
}, {
"%TAG !y! tag:yaml.org,2002:\n---\nv: !y!int '1'",
"v: 1",
},
// Non-specific tag (Issue #75)
{
"v: ! test",
// TODO: map[string]interface{}{"v": "test"},
"",
},
// Anchors and aliases.
{
"a: &x 1\nb: &y 2\nc: *x\nd: *y\n",
`a: 1
b: 2
c: 1
d: 2`,
}, {
"a: &a {c: 1}\nb: *a",
`a: {c: 1}
b: {
c: 1
}`,
}, {
"a: &a [1, 2]\nb: *a",
"a: [1, 2]\nb: [1, 2]", // TODO: a: [1, 2], b: a
},
{
"foo: ''",
`foo: ""`,
}, {
"foo: null",
"foo: null",
},
// Support for ~
{
"foo: ~",
"foo: null",
},
// Bug #1191981
{
"" +
"%YAML 1.1\n" +
"--- !!str\n" +
`"Generic line break (no glyph)\n\` + "\n" +
` Generic line break (glyphed)\n\` + "\n" +
` Line separator\u2028\` + "\n" +
` Paragraph separator\u2029"` + "\n",
`"""
Generic line break (no glyph)
Generic line break (glyphed)
Line separator\u2028Paragraph separator\u2029
"""`,
},
// bug 1243827
{
"a: -b_c",
`a: "-b_c"`,
},
{
"a: +b_c",
`a: "+b_c"`,
},
{
"a: 50cent_of_dollar",
`a: "50cent_of_dollar"`,
},
// issue #295 (allow scalars with colons in flow mappings and sequences)
{
"a: {b: https://github.com/go-yaml/yaml}",
`a: {b: "https://github.com/go-yaml/yaml"}`,
},
{
"a: [https://github.com/go-yaml/yaml]",
`a: ["https://github.com/go-yaml/yaml"]`,
},
// Duration
{
"a: 3s",
`a: "3s"`, // for now
},
// Issue #24.
{
"a: <foo>",
`a: "<foo>"`,
},
// Base 60 floats are obsolete and unsupported.
{
"a: 1:1\n",
`a: "1:1"`,
},
// Binary data.
{
"a: !!binary gIGC\n",
`a: '\x80\x81\x82'`,
}, {
"a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
"a: '" + strings.Repeat(`\x90`, 54) + "'",
}, {
"a: !!binary |\n " + strings.Repeat("A", 70) + "\n ==\n",
"a: '" + strings.Repeat(`\x00`, 52) + "'",
},
// Ordered maps.
{
"{b: 2, a: 1, d: 4, c: 3, sub: {e: 5}}",
`b: 2, a: 1, d: 4, c: 3, sub: {e: 5}`,
},
// Spacing
{
`
a: {}
c: 1
d: [
]
e: []
`,
`a: {}
c: 1
d: [
]
e: []`,
},
{
`
a:
- { "a": 1, "b": 2 }
- { "c": 1, "d": 2 }
`,
`a: [{
a: 1, b: 2
}, {
c: 1, d: 2
}]`,
},
{
"a:\n b:\n c: d\n e: f\n",
`a: {
b: {
c: "d"
e: "f"
}
}`,
},
// Issue #39.
{
"a:\n b:\n c: d\n",
`a: {
b: {
c: "d"
}
}`,
},
// Timestamps
{
// Date only.
"a: 2015-01-01\n",
`a: "2015-01-01"`,
},
{
// RFC3339
"a: 2015-02-24T18:19:39.12Z\n",
`a: "2015-02-24T18:19:39.12Z"`,
},
{
// RFC3339 with short dates.
"a: 2015-2-3T3:4:5Z",
`a: "2015-2-3T3:4:5Z"`,
},
{
// ISO8601 lower case t
"a: 2015-02-24t18:19:39Z\n",
`a: "2015-02-24t18:19:39Z"`,
},
{
// space separate, no time zone
"a: 2015-02-24 18:19:39\n",
`a: "2015-02-24 18:19:39"`,
},
// Some cases not currently handled. Uncomment these when
// the code is fixed.
// {
// // space separated with time zone
// "a: 2001-12-14 21:59:43.10 -5",
// map[string]interface{}{"a": time.Date(2001, 12, 14, 21, 59, 43, .1e9, time.UTC)},
// },
// {
// // arbitrary whitespace between fields
// "a: 2001-12-14 \t\t \t21:59:43.10 \t Z",
// map[string]interface{}{"a": time.Date(2001, 12, 14, 21, 59, 43, .1e9, time.UTC)},
// },
{
// explicit string tag
"a: !!str 2015-01-01",
`a: "2015-01-01"`,
},
{
// explicit timestamp tag on quoted string
"a: !!timestamp \"2015-01-01\"",
`a: "2015-01-01"`,
},
{
// explicit timestamp tag on unquoted string
"a: !!timestamp 2015-01-01",
`a: "2015-01-01"`,
},
{
// quoted string that's a valid timestamp
"a: \"2015-01-01\"",
"a: \"2015-01-01\"",
},
// Empty list
{
"a: []",
"a: []",
},
// UTF-16-LE
{
"\xff\xfe\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00\n\x00",
`ñoño: "very yes"`,
},
// UTF-16-LE with surrogate.
{
"\xff\xfe\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00 \x00=\xd8\xd4\xdf\n\x00",
`ñoño: "very yes 🟔"`,
},
// UTF-16-BE
{
"\xfe\xff\x00\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00\n",
`ñoño: "very yes"`,
},
// UTF-16-BE with surrogate.
{
"\xfe\xff\x00\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00 \xd8=\xdf\xd4\x00\n",
`ñoño: "very yes 🟔"`,
},
// YAML Float regex shouldn't match this
{
"a: 123456e1\n",
`a: "123456e1"`,
}, {
"a: 123456E1\n",
`a: "123456E1"`,
},
// yaml-test-suite 3GZX: Spec Example 7.1. Alias Nodes
{
"First occurrence: &anchor Foo\nSecond occurrence: *anchor\nOverride anchor: &anchor Bar\nReuse anchor: *anchor\n",
`"First occurrence": "Foo"
"Second occurrence": "Foo"
"Override anchor": "Bar"
"Reuse anchor": "Bar"`,
},
// Single document with garbage following it.
{
"---\nhello\n...\n}not yaml",
`"hello"`,
},
}
type M map[interface{}]interface{}
type inlineB struct {
B int
inlineC `yaml:",inline"`
}
type inlineC struct {
C int
}
func cueStr(node ast.Node) string {
if s, ok := node.(*ast.StructLit); ok {
node = &ast.File{
Decls: s.Elts,
}
}
b, _ := format.Node(node)
return strings.TrimSpace(string(b))
}
func newDecoder(t *testing.T, data string) *yaml.Decoder {
dec, err := yaml.NewDecoder("test.yaml", strings.NewReader(data))
if err != nil {
t.Fatal(err)
}
return dec
}
func callUnmarshal(t *testing.T, data string) (ast.Expr, error) {
return yaml.Unmarshal("test.yaml", []byte(data))
}
func TestUnmarshal(t *testing.T) {
for i, item := range unmarshalTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Logf("test %d: %q", i, item.data)
expr, err := callUnmarshal(t, item.data)
if _, ok := err.(*yaml.TypeError); !ok && err != nil {
t.Fatal("expected error to be nil")
}
if got := cueStr(expr); got != item.want {
t.Errorf("\n got: %v;\nwant: %v", got, item.want)
}
})
}
}
// // TODO(v3): This test should also work when unmarshaling onto an interface{}.
// func (s *S) TestUnmarshalFullTimestamp(c *C) {
// // Full timestamp in same format as encoded. This is confirmed to be
// // properly decoded by Python as a timestamp as well.
// var str = "2015-02-24T18:19:39.123456789-03:00"
// expr, err := yaml.Unmarshal([]byte(str))
// c.Assert(err, IsNil)
// c.Assert(t, Equals, time.Date(2015, 2, 24, 18, 19, 39, 123456789, t.Location()))
// c.Assert(t.In(time.UTC), Equals, time.Date(2015, 2, 24, 21, 19, 39, 123456789, time.UTC))
// }
func TestDecoderSingleDocument(t *testing.T) {
// Test that Decoder.Decode works as expected on
// all the unmarshal tests.
for i, item := range unmarshalTests {
t.Run(fmt.Sprintf("test %d: %q", i, item.data), func(t *testing.T) {
if item.data == "" {
// Behaviour differs when there's no YAML.
return
}
expr, err := newDecoder(t, item.data).Decode()
if _, ok := err.(*yaml.TypeError); !ok && err != nil {
t.Errorf("err should be nil, was %v", err)
}
if got := cueStr(expr); got != item.want {
t.Errorf("\n got: %v;\nwant: %v", got, item.want)
}
})
}
}
var decoderTests = []struct {
data string
want string
}{{
"",
"",
}, {
"a: b",
`a: "b"`,
}, {
"---\na: b\n...\n",
`a: "b"`,
}, {
"---\n'hello'\n...\n---\ngoodbye\n...\n",
`"hello"` + "\n" + `"goodbye"`,
}}
func TestDecoder(t *testing.T) {
for i, item := range decoderTests {
t.Run(fmt.Sprintf("test %d: %q", i, item.data), func(t *testing.T) {
var values []string
dec := newDecoder(t, item.data)
for {
expr, err := dec.Decode()
if err == io.EOF {
break
}
if err != nil {
t.Errorf("err should be nil, was %v", err)
}
values = append(values, cueStr(expr))
}
got := strings.Join(values, "\n")
if got != item.want {
t.Errorf("\n got: %v;\nwant: %v", got, item.want)
}
})
}
}
type errReader struct{}
func (errReader) Read([]byte) (int, error) {
return 0, errors.New("some read error")
}
func TestUnmarshalNaN(t *testing.T) {
expr, err := callUnmarshal(t, "notanum: .NaN")
if err != nil {
t.Fatal("unexpected error", err)
}
got := cueStr(expr)
want := "notanum: NaN"
if got != want {
t.Errorf("got %v; want %v", got, want)
}
}
var unmarshalErrorTests = []struct {
data, error string
}{
{"\nv: !!float 'error'", "test.yaml:2: cannot decode !!str `error` as a !!float"},
{"v: [A,", "test.yaml:1: did not find expected node content"},
{"v:\n- [A,", "test.yaml:2: did not find expected node content"},
{"a:\n- b: *,", "test.yaml:2: did not find expected alphabetic or numeric character"},
{"a: *b\n", "test.yaml:1: unknown anchor 'b' referenced"},
{"a: &a\n b: *a\n", "test.yaml:2: anchor 'a' value contains itself"},
{"value: -", "test.yaml:1: block sequence entries are not allowed in this context"},
{"a: !!binary ==", "test.yaml:1: !!binary value contains invalid base64 data"},
{"{[.]}", `test.yaml:1: invalid map key: sequence`},
{"{{.}}", `test.yaml:1: invalid map key: map`},
{"b: *a\na: &a {c: 1}", `test.yaml:1: unknown anchor 'a' referenced`},
{"%TAG !%79! tag:yaml.org,2002:\n---\nv: !%79!int '1'", "test.yaml:1: did not find expected whitespace"},
}
func TestUnmarshalErrors(t *testing.T) {
for i, item := range unmarshalErrorTests {
t.Run(fmt.Sprintf("test %d: %q", i, item.data), func(t *testing.T) {
expr, err := callUnmarshal(t, item.data)
val := ""
if expr != nil {
val = cueStr(expr)
}
if err == nil || err.Error() != item.error {
t.Errorf("got %v; want %v; (value %v)", err, item.error, val)
}
})
}
}
func TestDecoderErrors(t *testing.T) {
for i, item := range unmarshalErrorTests {
t.Run(fmt.Sprintf("test %d: %q", i, item.data), func(t *testing.T) {
_, err := newDecoder(t, item.data).Decode()
if err == nil || err.Error() != item.error {
t.Errorf("got %v; want %v", err, item.error)
}
})
}
}
func TestFiles(t *testing.T) {
files := []string{"merge"}
for _, test := range files {
t.Run(test, func(t *testing.T) {
testname := fmt.Sprintf("testdata/%s.test", test)
filename := fmt.Sprintf("testdata/%s.out", test)
mergeTests, err := ioutil.ReadFile(testname)
if err != nil {
t.Fatal(err)
}
expr, err := yaml.Unmarshal("test.yaml", mergeTests)
if err != nil {
t.Fatal(err)
}
got := cueStr(expr)
if cuetest.UpdateGoldenFiles {
ioutil.WriteFile(filename, []byte(got), 0644)
return
}
b, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatal(err)
}
if want := string(b); got != want {
t.Errorf("\n got: %v;\nwant: %v", got, want)
}
})
}
}
func TestFuzzCrashers(t *testing.T) {
cases := []string{
// runtime error: index out of range
"\"\\0\\\r\n",
// should not happen
" 0: [\n] 0",
"? ? \"\n\" 0",
" - {\n000}0",
"0:\n 0: [0\n] 0",
" - \"\n000\"0",
" - \"\n000\"\"",
"0:\n - {\n000}0",
"0:\n - \"\n000\"0",
"0:\n - \"\n000\"\"",
// runtime error: index out of range
" \ufeff\n",
"? \ufeff\n",
"? \ufeff:\n",
"0: \ufeff\n",
"? \ufeff: \ufeff\n",
}
for _, data := range cases {
_, _ = callUnmarshal(t, data)
}
}
//var data []byte
//func init() {
// var err error
// data, err = ioutil.ReadFile("/tmp/file.yaml")
// if err != nil {
// panic(err)
// }
//}
//
//func (s *S) BenchmarkUnmarshal(c *C) {
// var err error
// for i := 0; i < c.N; i++ {
// var v map[string]interface{}
// err = yaml.Unmarshal(data, &v)
// }
// if err != nil {
// panic(err)
// }
//}
//
//func (s *S) BenchmarkMarshal(c *C) {
// var v map[string]interface{}
// yaml.Unmarshal(data, &v)
// c.ResetTimer()
// for i := 0; i < c.N; i++ {
// yaml.Marshal(&v)
// }
//}