cuelang.org/go@v0.13.0/internal/encoding/yaml/encode_test.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package yaml
    16  
    17  import (
    18  	"strings"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"gopkg.in/yaml.v3"
    23  
    24  	"cuelang.org/go/cue/ast"
    25  	"cuelang.org/go/cue/parser"
    26  )
    27  
    28  func TestEncodeFile(t *testing.T) {
    29  	testCases := []struct {
    30  		name string
    31  		in   string
    32  		out  string
    33  	}{{
    34  		name: "foo",
    35  		in: `
    36  		package test
    37  
    38  		seq: [
    39  			1, 2, 3, {
    40  				a: 1
    41  				b: 2
    42  			}
    43  		]
    44  		a: b: c: 3
    45  		b: {
    46  			x: 0
    47  			y: 1
    48  			z: 2
    49  		}
    50  		`,
    51  		out: `
    52  seq:
    53    - 1
    54    - 2
    55    - 3
    56    - a: 1
    57      b: 2
    58  a:
    59    b:
    60      c: 3
    61  b:
    62    x: 0
    63    "y": 1
    64    z: 2
    65  		`,
    66  	}, {
    67  		name: "oneLineFields",
    68  		in: `
    69  		seq: [1, 2, 3]
    70  		map: {a: 3}
    71  		str: "str"
    72  		int: 1K
    73  		bin: 0b11
    74  		hex: 0x11
    75  		dec: .3
    76  		dat: '\x80'
    77  		nil: null
    78  		yes: true
    79  		non: false
    80  		`,
    81  		out: `
    82  seq: [1, 2, 3]
    83  map: {a: 3}
    84  str: str
    85  int: 1000
    86  bin: 0b11
    87  hex: 0x11
    88  dec: .3
    89  dat: !!binary gA==
    90  nil: null
    91  "yes": true
    92  non: false
    93  `,
    94  	}, {
    95  		name: "comments",
    96  		in: `
    97  // Document
    98  
    99  // head 1
   100  f1: 1
   101  // foot 1
   102  
   103  // head 2
   104  f2: 2 // line 2
   105  
   106  // intermezzo f2
   107  //
   108  // with multiline
   109  
   110  // head 3
   111  f3:
   112  	// struct doc
   113  	{
   114  		a: 1
   115  	}
   116  
   117  f4: {
   118  } // line 4
   119  
   120  // Trailing
   121  `,
   122  		out: `
   123  # Document
   124  
   125  # head 1
   126  f1: 1
   127  # foot 1
   128  
   129  # head 2
   130  f2: 2 # line 2
   131  
   132  # intermezzo f2
   133  #
   134  # with multiline
   135  
   136  # head 3
   137  f3:
   138    # struct doc
   139    a: 1
   140  f4: {} # line 4
   141  
   142  # Trailing
   143  `,
   144  	}, {
   145  		// TODO: support this at some point
   146  		name: "embed",
   147  		in: `
   148  	// hex
   149  	0xabc // line
   150  	// trail
   151  	`,
   152  		out: `
   153  # hex
   154  0xabc # line
   155  # trail
   156  `,
   157  	}, {
   158  		// TODO: support this at some point
   159  		name: "anchors",
   160  		in: `
   161  		a: b
   162  		b: 3
   163  		`,
   164  		out: "yaml: unsupported node b (*ast.Ident)",
   165  	}, {
   166  		name: "errors",
   167  		in: `
   168  			m: {
   169  				a: 1
   170  				b: 3
   171  			}
   172  			c: [1, [for x in m {x}]]
   173  			`,
   174  		out: "yaml: unsupported node for x in m {x} (*ast.Comprehension)",
   175  	}, {
   176  		name: "disallowMultipleEmbeddings",
   177  		in: `
   178  		1
   179  		1
   180  		`,
   181  		out: "yaml: multiple embedded values",
   182  	}, {
   183  		name: "disallowDefinitions",
   184  		in:   `#a: 2 `,
   185  		out:  "yaml: definition or hidden fields not allowed",
   186  	}, {
   187  		name: "disallowHidden",
   188  		in:   `_a: 2 `,
   189  		out:  "yaml: definition or hidden fields not allowed",
   190  	}, {
   191  		name: "disallowOptionals",
   192  		in:   `a?: 2`,
   193  		out:  "yaml: optional fields not allowed",
   194  	}, {
   195  		name: "disallowBulkOptionals",
   196  		in:   `[string]: 2`,
   197  		out:  "yaml: only literal labels allowed",
   198  	}, {
   199  		name: "noImports",
   200  		in: `
   201  		import "foo"
   202  
   203  		a: 1
   204  		`,
   205  		out: `yaml: unsupported node import "foo" (*ast.ImportDecl)`,
   206  	}, {
   207  		name: "disallowMultipleEmbeddings",
   208  		in: `
   209  		1
   210  		a: 2
   211  		`,
   212  		out: "yaml: embedding mixed with fields",
   213  	}, {
   214  		name: "prometheus",
   215  		in: `
   216  		{
   217  			receivers: [{
   218  				name: "pager"
   219  				slack_configs: [{
   220  					text: """
   221  						{{ range .Alerts }}{{ .Annotations.description }}
   222  						{{ end }}
   223  						"""
   224  					channel:       "#cloudmon"
   225  					send_resolved: true
   226  				}]
   227  			}]
   228  			route: {
   229  				receiver: "pager"
   230  				group_by: ["alertname", "cluster"]
   231  			}
   232  		}`,
   233  		out: `
   234  receivers:
   235    - name: pager
   236      slack_configs:
   237        - text: |-
   238            {{ range .Alerts }}{{ .Annotations.description }}
   239            {{ end }}
   240          channel: '#cloudmon'
   241          send_resolved: true
   242  route:
   243    receiver: pager
   244    group_by: [alertname, cluster]
   245  		`,
   246  	}}
   247  	for _, tc := range testCases {
   248  		t.Run(tc.name, func(t *testing.T) {
   249  			f, err := parser.ParseFile(tc.name, tc.in, parser.ParseComments)
   250  			if err != nil {
   251  				t.Fatal(err)
   252  			}
   253  			b, err := Encode(f)
   254  			var got string
   255  			if err != nil {
   256  				got = err.Error()
   257  			} else {
   258  				got = strings.TrimSpace(string(b))
   259  			}
   260  			want := strings.TrimSpace(tc.out)
   261  			if got != want {
   262  				t.Error(cmp.Diff(got, want))
   263  			}
   264  		})
   265  	}
   266  }
   267  
   268  func TestEncodeAST(t *testing.T) {
   269  	comment := func(s string) *ast.CommentGroup {
   270  		return &ast.CommentGroup{List: []*ast.Comment{
   271  			{Text: "// " + s},
   272  		}}
   273  	}
   274  	testCases := []struct {
   275  		name string
   276  		in   ast.Expr
   277  		out  string
   278  	}{{
   279  		in: ast.NewStruct(
   280  			comment("foo"),
   281  			comment("bar"),
   282  			"field", ast.NewString("value"),
   283  			"field2", ast.NewString("value"),
   284  			comment("trail1"),
   285  			comment("trail2"),
   286  		),
   287  		out: `
   288  # foo
   289  
   290  # bar
   291  
   292  field: value
   293  field2: value
   294  
   295  # trail1
   296  
   297  # trail2
   298  		`,
   299  	}, {
   300  		in: &ast.StructLit{Elts: []ast.Decl{
   301  			comment("bar"),
   302  			&ast.EmbedDecl{Expr: ast.NewBool(true)},
   303  		}},
   304  		out: `
   305  # bar
   306  
   307  true
   308  		`,
   309  	}}
   310  	for _, tc := range testCases {
   311  		t.Run(tc.name, func(t *testing.T) {
   312  			n, err := encode(tc.in)
   313  			if err != nil {
   314  				t.Fatal(err)
   315  			}
   316  			b, err := yaml.Marshal(n)
   317  			if err != nil {
   318  				t.Fatal(err)
   319  			}
   320  			got := strings.TrimSpace(string(b))
   321  			want := strings.TrimSpace(tc.out)
   322  			if got != want {
   323  				t.Error(cmp.Diff(got, want))
   324  			}
   325  		})
   326  	}
   327  }