github.com/solo-io/cue@v0.4.7/encoding/gocode/gocodec/codec_test.go (about) 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 "github.com/kr/pretty" 24 25 "github.com/solo-io/cue/cue" 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 r := &cue.Runtime{} 85 codec := New(r, nil) 86 87 v, err := codec.ExtractType(tc.value) 88 if err != nil { 89 t.Fatal(err) 90 } 91 92 if tc.constraints != "" { 93 inst, err := r.Compile(tc.name, tc.constraints) 94 if err != nil { 95 t.Fatal(err) 96 } 97 v = v.Unify(inst.Value()) 98 } 99 100 err = codec.Validate(v, tc.value) 101 checkErr(t, err, tc.err) 102 }) 103 } 104 } 105 106 func TestComplete(t *testing.T) { 107 type updated struct { 108 A []*int `cue:"[...int|*1]"` // arbitrary length slice with values 1 109 B []int `cue:"3*[int|*1]"` // slice of length 3, defaults to [1,1,1] 110 111 // TODO: better errors if the user forgets to quote. 112 M map[string]int `cue:",opt"` 113 } 114 type sump struct { 115 A *int `cue:"C-B"` 116 B *int `cue:"C-A"` 117 C *int `cue:"A+B"` 118 } 119 one := 1 120 two := 2 121 fail := "some error" 122 _ = fail 123 _ = one 124 testCases := []struct { 125 name string 126 value interface{} 127 result interface{} 128 constraints string 129 err string 130 }{{ 131 name: "*Sum", 132 value: &Sum{A: 1, B: 4, C: 5}, 133 result: &Sum{A: 1, B: 4, C: 5}, 134 }, { 135 name: "*Sum", 136 value: &Sum{A: 1, B: 4}, 137 result: &Sum{A: 1, B: 4, C: 5}, 138 }, { 139 name: "*sump", 140 value: &sump{A: &one, B: &one}, 141 result: &sump{A: &one, B: &one, C: &two}, 142 }, { 143 name: "*Sum: backwards", 144 value: &Sum{B: 4, C: 8}, 145 result: &Sum{A: 4, B: 4, C: 8}, 146 }, { 147 name: "*Sum: sum too low", 148 value: &Sum{A: 1, B: 3}, 149 result: &Sum{A: 1, B: 3}, // Value should not be updated 150 err: fail, 151 }, { 152 name: "*Sum: sum underspecified", 153 value: &Sum{A: 1}, 154 result: &Sum{A: 1}, // Value should not be updated 155 err: fail, 156 }, { 157 name: "Sum: cannot modify", 158 value: Sum{A: 3, B: 4, C: 7}, 159 result: Sum{A: 3, B: 4, C: 7}, 160 err: fail, 161 }, { 162 name: "*Sum: cannot update nil value", 163 value: (*Sum)(nil), 164 result: (*Sum)(nil), 165 err: fail, 166 }, { 167 name: "cannot modify slice", 168 value: []string{"a", "b", "c"}, 169 result: []string{"a", "b", "c"}, 170 err: fail, 171 }, { 172 name: "composite values update", 173 // allocate a slice with uninitialized values and let Update fill 174 // out default values. 175 value: &updated{A: make([]*int, 3)}, 176 result: &updated{ 177 A: []*int{&one, &one, &one}, 178 B: []int{1, 1, 1}, 179 M: map[string]int(nil), 180 }, 181 }, { 182 name: "composite values update with unsatisfied map constraints", 183 value: &updated{}, 184 result: &updated{}, 185 186 constraints: ` { M: {foo: bar, bar: foo} } `, 187 err: fail, // incomplete values 188 }, { 189 name: "composite values update with map constraints", 190 value: &updated{M: map[string]int{"foo": 1}}, 191 constraints: ` { M: {foo: bar, bar: foo} } `, 192 result: &updated{ 193 // TODO: would be better if this is nil, but will not matter for 194 // JSON output: if omitempty is false, an empty list will be 195 // printed regardless, and if it is true, it will be omitted 196 // regardless. 197 A: []*int{}, 198 B: []int{1, 1, 1}, 199 M: map[string]int{"bar": 1, "foo": 1}, 200 }, 201 }} 202 for _, tc := range testCases { 203 t.Run(tc.name, func(t *testing.T) { 204 r := &cue.Runtime{} 205 codec := New(r, nil) 206 207 v, err := codec.ExtractType(tc.value) 208 if err != nil { 209 t.Fatal(err) 210 } 211 212 if tc.constraints != "" { 213 inst, err := r.Compile(tc.name, tc.constraints) 214 if err != nil { 215 t.Fatal(err) 216 } 217 v = v.Unify(inst.Value()) 218 } 219 220 err = codec.Complete(v, tc.value) 221 checkErr(t, err, tc.err) 222 if !reflect.DeepEqual(tc.value, tc.result) { 223 t.Error(pretty.Diff(tc.value, tc.result)) 224 } 225 }) 226 } 227 } 228 229 func TestEncode(t *testing.T) { 230 testCases := []struct { 231 in string 232 dst interface{} 233 want interface{} 234 }{{ 235 in: "4", 236 dst: new(int), 237 want: 4, 238 }} 239 r := &cue.Runtime{} 240 c := New(r, nil) 241 242 for _, tc := range testCases { 243 t.Run("", func(t *testing.T) { 244 inst, err := r.Compile("test", tc.in) 245 if err != nil { 246 t.Fatal(err) 247 } 248 249 err = c.Encode(inst.Value(), tc.dst) 250 if err != nil { 251 t.Fatal(err) 252 } 253 254 got := reflect.ValueOf(tc.dst).Elem().Interface() 255 if !cmp.Equal(got, tc.want) { 256 t.Error(cmp.Diff(got, tc.want)) 257 } 258 }) 259 } 260 } 261 262 func TestDecode(t *testing.T) { 263 testCases := []struct { 264 in interface{} 265 want string 266 }{{ 267 in: "str", 268 want: `"str"`, 269 }, { 270 in: func() interface{} { 271 type T struct { 272 B int 273 } 274 type S struct { 275 A string 276 T 277 } 278 return S{} 279 }(), 280 want: `{ 281 A: "" 282 B: 0 283 }`, 284 }, { 285 in: func() interface{} { 286 type T struct { 287 B int 288 } 289 type S struct { 290 A string 291 T `json:"t"` 292 } 293 return S{} 294 }(), 295 want: `{ 296 A: "" 297 t: { 298 B: 0 299 } 300 }`, 301 }} 302 c := New(&cue.Runtime{}, nil) 303 304 for _, tc := range testCases { 305 t.Run("", func(t *testing.T) { 306 v, err := c.Decode(tc.in) 307 if err != nil { 308 t.Fatal(err) 309 } 310 311 got := fmt.Sprint(v) 312 if got != tc.want { 313 t.Errorf("got %v; want %v", got, tc.want) 314 } 315 }) 316 } 317 } 318 319 // For debugging purposes, do not remove. 320 func TestX(t *testing.T) { 321 t.Skip() 322 323 fail := "some error" 324 // Not a typical constraint, but it is possible. 325 var ( 326 name = "string list incompatible lengths" 327 value = []string{"a", "b", "c"} 328 constraints = `4*[string]` 329 wantErr = fail 330 ) 331 332 r := &cue.Runtime{} 333 codec := New(r, nil) 334 335 v, err := codec.ExtractType(value) 336 if err != nil { 337 t.Fatal(err) 338 } 339 340 if constraints != "" { 341 inst, err := r.Compile(name, constraints) 342 if err != nil { 343 t.Fatal(err) 344 } 345 w := inst.Value() 346 v = v.Unify(w) 347 } 348 349 err = codec.Validate(v, value) 350 checkErr(t, err, wantErr) 351 }