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