...

Source file src/cuelang.org/go/encoding/gocode/gocodec/codec_test.go

Documentation: cuelang.org/go/encoding/gocode/gocodec

     1  // Copyright 2019 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 gocodec
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  
    24  	"cuelang.org/go/cue"
    25  	"cuelang.org/go/cue/cuecontext"
    26  )
    27  
    28  type Sum struct {
    29  	A int `cue:"C-B" json:",omitempty"`
    30  	B int `cue:"C-A" json:",omitempty"`
    31  	C int `cue:"A+B & >=5" json:",omitempty"`
    32  }
    33  
    34  func checkErr(t *testing.T, got error, want string) {
    35  	t.Helper()
    36  	if (got == nil) != (want == "") {
    37  		t.Errorf("error: got %v; want %v", got, want)
    38  	}
    39  }
    40  func TestValidate(t *testing.T) {
    41  	fail := "some error"
    42  	testCases := []struct {
    43  		name        string
    44  		value       interface{}
    45  		constraints string
    46  		err         string
    47  	}{{
    48  		name:        "*Sum: nil disallowed by constraint",
    49  		value:       (*Sum)(nil),
    50  		constraints: "!=null",
    51  		err:         fail,
    52  	}, {
    53  		name:  "Sum",
    54  		value: Sum{A: 1, B: 4, C: 5},
    55  	}, {
    56  		name:  "*Sum",
    57  		value: &Sum{A: 1, B: 4, C: 5},
    58  	}, {
    59  		name:  "*Sum: incorrect sum",
    60  		value: &Sum{A: 1, B: 4, C: 6},
    61  		err:   fail,
    62  	}, {
    63  		name:  "*Sum: field C is too low",
    64  		value: &Sum{A: 1, B: 3, C: 4},
    65  		err:   fail,
    66  	}, {
    67  		name:  "*Sum: nil value",
    68  		value: (*Sum)(nil),
    69  	}, {
    70  		// Not a typical constraint, but it is possible.
    71  		name:        "string list",
    72  		value:       []string{"a", "b", "c"},
    73  		constraints: `[_, "b", ...]`,
    74  	}, {
    75  		// Not a typical constraint, but it is possible.
    76  		name:        "string list incompatible lengths",
    77  		value:       []string{"a", "b", "c"},
    78  		constraints: `4*[string]`,
    79  		err:         fail,
    80  	}}
    81  
    82  	for _, tc := range testCases {
    83  		t.Run(tc.name, func(t *testing.T) {
    84  			ctx := cuecontext.New()
    85  			codec := New(ctx, nil)
    86  
    87  			v, err := codec.ExtractType(tc.value)
    88  			if err != nil {
    89  				t.Fatal(err)
    90  			}
    91  
    92  			if tc.constraints != "" {
    93  				v1 := ctx.CompileString(tc.constraints, cue.Filename(tc.name))
    94  				if err := v1.Err(); err != nil {
    95  					t.Fatal(err)
    96  				}
    97  				v = v.Unify(v1)
    98  			}
    99  
   100  			err = codec.Validate(v, tc.value)
   101  			checkErr(t, err, tc.err)
   102  
   103  			// Smoke test that it seems to work OK with deprecated *cue.Runtime argument
   104  			r := &cue.Runtime{}
   105  			codec = New(r, nil)
   106  			if _, err := codec.ExtractType(tc.value); err != nil {
   107  				t.Fatal(err)
   108  			}
   109  		})
   110  	}
   111  }
   112  
   113  func TestComplete(t *testing.T) {
   114  	type updated struct {
   115  		A []*int `cue:"[...int|*1]"` // arbitrary length slice with values 1
   116  		B []int  `cue:"3*[int|*1]"`  // slice of length 3, defaults to [1,1,1]
   117  
   118  		// TODO: better errors if the user forgets to quote.
   119  		M map[string]int `cue:",opt"`
   120  	}
   121  	type sump struct {
   122  		A *int `cue:"C-B"`
   123  		B *int `cue:"C-A"`
   124  		C *int `cue:"A+B"`
   125  	}
   126  	one := 1
   127  	two := 2
   128  	fail := "some error"
   129  	_ = fail
   130  	_ = one
   131  	testCases := []struct {
   132  		name        string
   133  		value       interface{}
   134  		result      interface{}
   135  		constraints string
   136  		err         string
   137  	}{{
   138  		name:   "*Sum",
   139  		value:  &Sum{A: 1, B: 4, C: 5},
   140  		result: &Sum{A: 1, B: 4, C: 5},
   141  	}, {
   142  		name:   "*Sum",
   143  		value:  &Sum{A: 1, B: 4},
   144  		result: &Sum{A: 1, B: 4, C: 5},
   145  	}, {
   146  		name:   "*sump",
   147  		value:  &sump{A: &one, B: &one},
   148  		result: &sump{A: &one, B: &one, C: &two},
   149  	}, {
   150  		name:   "*Sum: backwards",
   151  		value:  &Sum{B: 4, C: 8},
   152  		result: &Sum{A: 4, B: 4, C: 8},
   153  	}, {
   154  		name:   "*Sum: sum too low",
   155  		value:  &Sum{A: 1, B: 3},
   156  		result: &Sum{A: 1, B: 3}, // Value should not be updated
   157  		err:    fail,
   158  	}, {
   159  		name:   "*Sum: sum underspecified",
   160  		value:  &Sum{A: 1},
   161  		result: &Sum{A: 1}, // Value should not be updated
   162  		err:    fail,
   163  	}, {
   164  		name:   "Sum: cannot modify",
   165  		value:  Sum{A: 3, B: 4, C: 7},
   166  		result: Sum{A: 3, B: 4, C: 7},
   167  		err:    fail,
   168  	}, {
   169  		name:   "*Sum: cannot update nil value",
   170  		value:  (*Sum)(nil),
   171  		result: (*Sum)(nil),
   172  		err:    fail,
   173  	}, {
   174  		name:   "cannot modify slice",
   175  		value:  []string{"a", "b", "c"},
   176  		result: []string{"a", "b", "c"},
   177  		err:    fail,
   178  	}, {
   179  		name: "composite values update",
   180  		// allocate a slice with uninitialized values and let Update fill
   181  		// out default values.
   182  		value: &updated{A: make([]*int, 3)},
   183  		result: &updated{
   184  			A: []*int{&one, &one, &one},
   185  			B: []int{1, 1, 1},
   186  			M: map[string]int(nil),
   187  		},
   188  	}, {
   189  		name:   "composite values update with unsatisfied map constraints",
   190  		value:  &updated{},
   191  		result: &updated{},
   192  
   193  		constraints: ` { M: {foo: bar, bar: foo} } `,
   194  		err:         fail, // incomplete values
   195  	}, {
   196  		name:        "composite values update with map constraints",
   197  		value:       &updated{M: map[string]int{"foo": 1}},
   198  		constraints: ` { M: {foo: bar, bar: foo} } `,
   199  		result: &updated{
   200  			// TODO: would be better if this is nil, but will not matter for
   201  			// JSON output: if omitempty is false, an empty list will be
   202  			// printed regardless, and if it is true, it will be omitted
   203  			// regardless.
   204  			A: []*int{},
   205  			B: []int{1, 1, 1},
   206  			M: map[string]int{"bar": 1, "foo": 1},
   207  		},
   208  	}}
   209  	for _, tc := range testCases {
   210  		t.Run(tc.name, func(t *testing.T) {
   211  			r := &cue.Runtime{}
   212  			codec := New(r, nil)
   213  
   214  			v, err := codec.ExtractType(tc.value)
   215  			if err != nil {
   216  				t.Fatal(err)
   217  			}
   218  
   219  			if tc.constraints != "" {
   220  				inst, err := r.Compile(tc.name, tc.constraints)
   221  				if err != nil {
   222  					t.Fatal(err)
   223  				}
   224  				v = v.Unify(inst.Value())
   225  			}
   226  
   227  			err = codec.Complete(v, tc.value)
   228  			checkErr(t, err, tc.err)
   229  			if diff := cmp.Diff(tc.value, tc.result); diff != "" {
   230  				t.Error(diff)
   231  			}
   232  		})
   233  	}
   234  }
   235  
   236  func TestEncode(t *testing.T) {
   237  	testCases := []struct {
   238  		in   string
   239  		dst  interface{}
   240  		want interface{}
   241  	}{{
   242  		in:   "4",
   243  		dst:  new(int),
   244  		want: 4,
   245  	}}
   246  	r := &cue.Runtime{}
   247  	c := New(r, nil)
   248  
   249  	for _, tc := range testCases {
   250  		t.Run("", func(t *testing.T) {
   251  			inst, err := r.Compile("test", tc.in)
   252  			if err != nil {
   253  				t.Fatal(err)
   254  			}
   255  
   256  			err = c.Encode(inst.Value(), tc.dst)
   257  			if err != nil {
   258  				t.Fatal(err)
   259  			}
   260  
   261  			got := reflect.ValueOf(tc.dst).Elem().Interface()
   262  			if diff := cmp.Diff(got, tc.want); diff != "" {
   263  				t.Error(diff)
   264  			}
   265  		})
   266  	}
   267  }
   268  
   269  func TestDecode(t *testing.T) {
   270  	testCases := []struct {
   271  		in   interface{}
   272  		want string
   273  	}{{
   274  		in:   "str",
   275  		want: `"str"`,
   276  	}, {
   277  		in: func() interface{} {
   278  			type T struct {
   279  				B int
   280  			}
   281  			type S struct {
   282  				A string
   283  				T
   284  			}
   285  			return S{}
   286  		}(),
   287  		want: `{
   288  	A: ""
   289  	B: 0
   290  }`,
   291  	}, {
   292  		in: func() interface{} {
   293  			type T struct {
   294  				B int
   295  			}
   296  			type S struct {
   297  				A string
   298  				T `json:"t"`
   299  			}
   300  			return S{}
   301  		}(),
   302  		want: `{
   303  	A: ""
   304  	t: {
   305  		B: 0
   306  	}
   307  }`,
   308  	}}
   309  	c := New(&cue.Runtime{}, nil)
   310  
   311  	for _, tc := range testCases {
   312  		t.Run("", func(t *testing.T) {
   313  			v, err := c.Decode(tc.in)
   314  			if err != nil {
   315  				t.Fatal(err)
   316  			}
   317  
   318  			got := fmt.Sprint(v)
   319  			if got != tc.want {
   320  				t.Errorf("got %v; want %v", got, tc.want)
   321  			}
   322  		})
   323  	}
   324  }
   325  
   326  // For debugging purposes, do not remove.
   327  func TestX(t *testing.T) {
   328  	t.Skip()
   329  
   330  	fail := "some error"
   331  	// Not a typical constraint, but it is possible.
   332  	var (
   333  		name        = "string list incompatible lengths"
   334  		value       = []string{"a", "b", "c"}
   335  		constraints = `4*[string]`
   336  		wantErr     = fail
   337  	)
   338  
   339  	r := &cue.Runtime{}
   340  	codec := New(r, nil)
   341  
   342  	v, err := codec.ExtractType(value)
   343  	if err != nil {
   344  		t.Fatal(err)
   345  	}
   346  
   347  	if constraints != "" {
   348  		inst, err := r.Compile(name, constraints)
   349  		if err != nil {
   350  			t.Fatal(err)
   351  		}
   352  		w := inst.Value()
   353  		v = v.Unify(w)
   354  	}
   355  
   356  	err = codec.Validate(v, value)
   357  	checkErr(t, err, wantErr)
   358  }
   359  

View as plain text