cuelang.org/go@v0.10.1/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 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 ctx := cuecontext.New() 212 codec := New(ctx, nil) 213 214 v, err := codec.ExtractType(tc.value) 215 if err != nil { 216 t.Fatal(err) 217 } 218 219 if tc.constraints != "" { 220 c := ctx.CompileString(tc.constraints, cue.Filename(tc.name)) 221 if err := c.Err(); err != nil { 222 t.Fatal(err) 223 } 224 v = v.Unify(c) 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 ctx := cuecontext.New() 247 c := New(ctx, nil) 248 249 for _, tc := range testCases { 250 t.Run("", func(t *testing.T) { 251 in := ctx.CompileString(tc.in, cue.Filename("test")) 252 if err := in.Err(); err != nil { 253 t.Fatal(err) 254 } 255 256 err := c.Encode(in, 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(cuecontext.New(), 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 ctx := cuecontext.New() 340 codec := New(ctx, nil) 341 342 v, err := codec.ExtractType(value) 343 if err != nil { 344 t.Fatal(err) 345 } 346 347 if constraints != "" { 348 c := ctx.CompileString(constraints, cue.Filename(name)) 349 if err := c.Err(); err != nil { 350 t.Fatal(err) 351 } 352 v = v.Unify(c) 353 } 354 355 err = codec.Validate(v, value) 356 checkErr(t, err, wantErr) 357 }