| // Copyright 2020 CUE Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package yaml |
| |
| import ( |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/kr/pretty" |
| "gopkg.in/yaml.v3" |
| |
| "cuelang.org/go/cue/ast" |
| "cuelang.org/go/cue/parser" |
| ) |
| |
| func TestEncodeFile(t *testing.T) { |
| testCases := []struct { |
| name string |
| in string |
| out string |
| }{{ |
| name: "foo", |
| in: ` |
| package test |
| |
| seq: [ |
| 1, 2, 3, { |
| a: 1 |
| b: 2 |
| } |
| ] |
| a: b: c: 3 |
| b: { |
| x: 0 |
| y: 1 |
| z: 2 |
| } |
| `, |
| out: ` |
| seq: |
| - 1 |
| - 2 |
| - 3 |
| - a: 1 |
| b: 2 |
| a: |
| b: |
| c: 3 |
| b: |
| x: 0 |
| y: 1 |
| z: 2 |
| `, |
| }, { |
| name: "oneLineFields", |
| in: ` |
| seq: [1, 2, 3] |
| map: {a: 3} |
| str: "str" |
| int: 1K |
| bin: 0b11 |
| hex: 0x11 |
| dec: .3 |
| dat: '\x80' |
| nil: null |
| yes: true |
| non: false |
| `, |
| out: ` |
| seq: [1, 2, 3] |
| map: {a: 3} |
| str: str |
| int: 1000 |
| bin: 0b11 |
| hex: 0x11 |
| dec: .3 |
| dat: !!binary gA== |
| nil: null |
| yes: true |
| non: false |
| `, |
| }, { |
| name: "comments", |
| in: ` |
| // Document |
| |
| // head 1 |
| f1: 1 |
| // foot 1 |
| |
| // head 2 |
| f2: 2 // line 2 |
| |
| // intermezzo f2 |
| // |
| // with multiline |
| |
| // head 3 |
| f3: |
| // struct doc |
| { |
| a: 1 |
| } |
| |
| f4: { |
| } // line 4 |
| |
| // Trailing |
| `, |
| out: ` |
| # Document |
| |
| # head 1 |
| f1: 1 |
| # foot 1 |
| |
| # head 2 |
| f2: 2 # line 2 |
| |
| # intermezzo f2 |
| # |
| # with multiline |
| |
| # head 3 |
| f3: |
| # struct doc |
| a: 1 |
| f4: {} # line 4 |
| |
| # Trailing |
| `, |
| }, { |
| // TODO: support this at some point |
| name: "embed", |
| in: ` |
| // hex |
| 0xabc // line |
| // trail |
| `, |
| out: ` |
| # hex |
| 0xabc # line |
| # trail |
| `, |
| }, { |
| // TODO: support this at some point |
| name: "anchors", |
| in: ` |
| a: b |
| b: 3 |
| `, |
| out: "yaml: unsupported node b (*ast.Ident)", |
| }, { |
| name: "errors", |
| in: ` |
| m: { |
| a: 1 |
| b: 3 |
| } |
| c: [1, [ x for x in m ]] |
| `, |
| out: "yaml: unsupported node [x for x in m ] (*ast.ListComprehension)", |
| }, { |
| name: "disallowMultipleEmbeddings", |
| in: ` |
| 1 |
| 1 |
| `, |
| out: "yaml: multiple embedded values", |
| }, { |
| name: "disallowDefinitions", |
| in: `a :: 2 `, |
| out: "yaml: definition not allowed", |
| }, { |
| name: "disallowOptionals", |
| in: `a?: 2`, |
| out: "yaml: optional fields not allowed", |
| }, { |
| name: "disallowBulkOptionals", |
| in: `[string]: 2`, |
| out: "yaml: only literal labels allowed", |
| }, { |
| name: "noImports", |
| in: ` |
| import "foo" |
| |
| a: 1 |
| `, |
| out: `yaml: unsupported node import "foo" (*ast.ImportDecl)`, |
| }, { |
| name: "disallowMultipleEmbeddings", |
| in: ` |
| 1 |
| a: 2 |
| `, |
| out: "yaml: embedding mixed with fields", |
| }, { |
| name: "prometheus", |
| in: ` |
| { |
| receivers: [{ |
| name: "pager" |
| slack_configs: [{ |
| text: """ |
| {{ range .Alerts }}{{ .Annotations.description }} |
| {{ end }} |
| """ |
| channel: "#cloudmon" |
| send_resolved: true |
| }] |
| }] |
| route: { |
| receiver: "pager" |
| group_by: ["alertname", "cluster"] |
| } |
| }`, |
| out: ` |
| receivers: |
| - name: pager |
| slack_configs: |
| - text: |- |
| {{ range .Alerts }}{{ .Annotations.description }} |
| {{ end }} |
| channel: '#cloudmon' |
| send_resolved: true |
| route: |
| receiver: pager |
| group_by: [alertname, cluster] |
| `, |
| }} |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| f, err := parser.ParseFile(tc.name, tc.in, parser.ParseComments) |
| if err != nil { |
| t.Fatal(err) |
| } |
| b, err := Encode(f) |
| var got string |
| if err != nil { |
| got = err.Error() |
| } else { |
| got = strings.TrimSpace(string(b)) |
| } |
| want := strings.TrimSpace(tc.out) |
| if got != want { |
| t.Error(cmp.Diff(got, want)) |
| } |
| }) |
| } |
| } |
| |
| func TestEncodeAST(t *testing.T) { |
| comment := func(s string) *ast.CommentGroup { |
| return &ast.CommentGroup{List: []*ast.Comment{ |
| &ast.Comment{Text: "// " + s}, |
| }} |
| } |
| testCases := []struct { |
| name string |
| in ast.Expr |
| out string |
| }{{ |
| in: ast.NewStruct( |
| comment("foo"), |
| comment("bar"), |
| "field", ast.NewString("value"), |
| "field2", ast.NewString("value"), |
| comment("trail1"), |
| comment("trail2"), |
| ), |
| out: ` |
| # foo |
| |
| # bar |
| |
| field: value |
| field2: value |
| |
| # trail1 |
| |
| # trail2 |
| `, |
| }, { |
| in: &ast.StructLit{Elts: []ast.Decl{ |
| comment("bar"), |
| &ast.EmbedDecl{Expr: ast.NewBool(true)}, |
| }}, |
| out: ` |
| # bar |
| |
| true |
| `, |
| }} |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| n, err := encode(tc.in) |
| if err != nil { |
| t.Fatal(err) |
| } |
| b, err := yaml.Marshal(n) |
| if err != nil { |
| t.Fatal(err) |
| } |
| got := strings.TrimSpace(string(b)) |
| want := strings.TrimSpace(tc.out) |
| if got != want { |
| t.Error(cmp.Diff(got, want)) |
| } |
| }) |
| } |
| } |
| |
| // TestX is for experimentation with the YAML package to figure out the |
| // semantics of the Node type. |
| func TestX(t *testing.T) { |
| t.Skip() |
| var n yaml.Node |
| yaml.Unmarshal([]byte(` |
| # map |
| map: { |
| # doc |
| } # line 1 |
| |
| `), &n) |
| |
| pretty.Print(n) |
| t.Fail() |
| } |