github.com/solo-io/cue@v0.4.7/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  	"github.com/solo-io/cue/cue/ast"
    26  	"github.com/solo-io/cue/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 not allowed",
   187  	}, {
   188  		name: "disallowOptionals",
   189  		in:   `a?: 2`,
   190  		out:  "yaml: optional fields not allowed",
   191  	}, {
   192  		name: "disallowBulkOptionals",
   193  		in:   `[string]: 2`,
   194  		out:  "yaml: only literal labels allowed",
   195  	}, {
   196  		name: "noImports",
   197  		in: `
   198  		import "foo"
   199  
   200  		a: 1
   201  		`,
   202  		out: `yaml: unsupported node import "foo" (*ast.ImportDecl)`,
   203  	}, {
   204  		name: "disallowMultipleEmbeddings",
   205  		in: `
   206  		1
   207  		a: 2
   208  		`,
   209  		out: "yaml: embedding mixed with fields",
   210  	}, {
   211  		name: "prometheus",
   212  		in: `
   213  		{
   214  			receivers: [{
   215  				name: "pager"
   216  				slack_configs: [{
   217  					text: """
   218  						{{ range .Alerts }}{{ .Annotations.description }}
   219  						{{ end }}
   220  						"""
   221  					channel:       "#cloudmon"
   222  					send_resolved: true
   223  				}]
   224  			}]
   225  			route: {
   226  				receiver: "pager"
   227  				group_by: ["alertname", "cluster"]
   228  			}
   229  		}`,
   230  		out: `
   231  receivers:
   232  - name: pager
   233    slack_configs:
   234    - text: |-
   235        {{ range .Alerts }}{{ .Annotations.description }}
   236        {{ end }}
   237      channel: '#cloudmon'
   238      send_resolved: true
   239  route:
   240    receiver: pager
   241    group_by: [alertname, cluster]
   242  		`,
   243  	}}
   244  	for _, tc := range testCases {
   245  		t.Run(tc.name, func(t *testing.T) {
   246  			f, err := parser.ParseFile(tc.name, tc.in, parser.ParseComments)
   247  			if err != nil {
   248  				t.Fatal(err)
   249  			}
   250  			b, err := Encode(f)
   251  			var got string
   252  			if err != nil {
   253  				got = err.Error()
   254  			} else {
   255  				got = strings.TrimSpace(string(b))
   256  			}
   257  			want := strings.TrimSpace(tc.out)
   258  			if got != want {
   259  				t.Error(cmp.Diff(got, want))
   260  			}
   261  		})
   262  	}
   263  }
   264  
   265  func TestEncodeAST(t *testing.T) {
   266  	comment := func(s string) *ast.CommentGroup {
   267  		return &ast.CommentGroup{List: []*ast.Comment{
   268  			&ast.Comment{Text: "// " + s},
   269  		}}
   270  	}
   271  	testCases := []struct {
   272  		name string
   273  		in   ast.Expr
   274  		out  string
   275  	}{{
   276  		in: ast.NewStruct(
   277  			comment("foo"),
   278  			comment("bar"),
   279  			"field", ast.NewString("value"),
   280  			"field2", ast.NewString("value"),
   281  			comment("trail1"),
   282  			comment("trail2"),
   283  		),
   284  		out: `
   285  # foo
   286  
   287  # bar
   288  
   289  field: value
   290  field2: value
   291  
   292  # trail1
   293  
   294  # trail2
   295  		`,
   296  	}, {
   297  		in: &ast.StructLit{Elts: []ast.Decl{
   298  			comment("bar"),
   299  			&ast.EmbedDecl{Expr: ast.NewBool(true)},
   300  		}},
   301  		out: `
   302  # bar
   303  
   304  true
   305  		`,
   306  	}}
   307  	for _, tc := range testCases {
   308  		t.Run(tc.name, func(t *testing.T) {
   309  			n, err := encode(tc.in)
   310  			if err != nil {
   311  				t.Fatal(err)
   312  			}
   313  			b, err := yaml.Marshal(n)
   314  			if err != nil {
   315  				t.Fatal(err)
   316  			}
   317  			got := strings.TrimSpace(string(b))
   318  			want := strings.TrimSpace(tc.out)
   319  			if got != want {
   320  				t.Error(cmp.Diff(got, want))
   321  			}
   322  		})
   323  	}
   324  }
   325  
   326  // TestX is for experimentation with the YAML package to figure out the
   327  // semantics of the Node type.
   328  func TestX(t *testing.T) {
   329  	t.Skip()
   330  	var n yaml.Node
   331  	yaml.Unmarshal([]byte(`
   332  # map
   333  map: {
   334  	# doc
   335  } # line 1
   336  
   337  `), &n)
   338  
   339  	pretty.Print(n)
   340  	t.Fail()
   341  }