this repo has no description
at master 314 lines 6.4 kB view raw
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 15package gocodec 16 17import ( 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 28type 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 34func 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} 40func 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 76 for _, tc := range testCases { 77 t.Run(tc.name, func(t *testing.T) { 78 ctx := cuecontext.New() 79 codec := New(ctx, nil) 80 81 v, err := codec.ExtractType(tc.value) 82 if err != nil { 83 t.Fatal(err) 84 } 85 86 if tc.constraints != "" { 87 v1 := ctx.CompileString(tc.constraints, cue.Filename(tc.name)) 88 if err := v1.Err(); err != nil { 89 t.Fatal(err) 90 } 91 v = v.Unify(v1) 92 } 93 94 err = codec.Validate(v, tc.value) 95 checkErr(t, err, tc.err) 96 97 // Smoke test that it seems to work OK with deprecated *cue.Runtime argument 98 r := &cue.Runtime{} 99 codec = New(r, nil) 100 if _, err := codec.ExtractType(tc.value); err != nil { 101 t.Fatal(err) 102 } 103 }) 104 } 105} 106 107func TestComplete(t *testing.T) { 108 type sump struct { 109 A *int `cue:"C-B"` 110 B *int `cue:"C-A"` 111 C *int `cue:"A+B"` 112 } 113 one := 1 114 two := 2 115 fail := "some error" 116 _ = fail 117 _ = one 118 testCases := []struct { 119 name string 120 value interface{} 121 result interface{} 122 constraints string 123 err string 124 }{{ 125 name: "*Sum", 126 value: &Sum{A: 1, B: 4, C: 5}, 127 result: &Sum{A: 1, B: 4, C: 5}, 128 }, { 129 name: "*Sum", 130 value: &Sum{A: 1, B: 4}, 131 result: &Sum{A: 1, B: 4, C: 5}, 132 }, { 133 name: "*sump", 134 value: &sump{A: &one, B: &one}, 135 result: &sump{A: &one, B: &one, C: &two}, 136 }, { 137 name: "*Sum: backwards", 138 value: &Sum{B: 4, C: 8}, 139 result: &Sum{A: 4, B: 4, C: 8}, 140 }, { 141 name: "*Sum: sum too low", 142 value: &Sum{A: 1, B: 3}, 143 result: &Sum{A: 1, B: 3}, // Value should not be updated 144 err: fail, 145 }, { 146 name: "*Sum: sum underspecified", 147 value: &Sum{A: 1}, 148 result: &Sum{A: 1}, // Value should not be updated 149 err: fail, 150 }, { 151 name: "Sum: cannot modify", 152 value: Sum{A: 3, B: 4, C: 7}, 153 result: Sum{A: 3, B: 4, C: 7}, 154 err: fail, 155 }, { 156 name: "*Sum: cannot update nil value", 157 value: (*Sum)(nil), 158 result: (*Sum)(nil), 159 err: fail, 160 }, { 161 name: "cannot modify slice", 162 value: []string{"a", "b", "c"}, 163 result: []string{"a", "b", "c"}, 164 err: fail, 165 }} 166 for _, tc := range testCases { 167 t.Run(tc.name, func(t *testing.T) { 168 ctx := cuecontext.New() 169 codec := New(ctx, nil) 170 171 v, err := codec.ExtractType(tc.value) 172 if err != nil { 173 t.Fatal(err) 174 } 175 176 if tc.constraints != "" { 177 c := ctx.CompileString(tc.constraints, cue.Filename(tc.name)) 178 if err := c.Err(); err != nil { 179 t.Fatal(err) 180 } 181 v = v.Unify(c) 182 } 183 184 err = codec.Complete(v, tc.value) 185 checkErr(t, err, tc.err) 186 if diff := cmp.Diff(tc.value, tc.result); diff != "" { 187 t.Error(diff) 188 } 189 }) 190 } 191} 192 193func TestEncode(t *testing.T) { 194 testCases := []struct { 195 in string 196 dst interface{} 197 want interface{} 198 }{{ 199 in: "4", 200 dst: new(int), 201 want: 4, 202 }} 203 ctx := cuecontext.New() 204 c := New(ctx, nil) 205 206 for _, tc := range testCases { 207 t.Run("", func(t *testing.T) { 208 in := ctx.CompileString(tc.in, cue.Filename("test")) 209 if err := in.Err(); err != nil { 210 t.Fatal(err) 211 } 212 213 err := c.Encode(in, tc.dst) 214 if err != nil { 215 t.Fatal(err) 216 } 217 218 got := reflect.ValueOf(tc.dst).Elem().Interface() 219 if diff := cmp.Diff(got, tc.want); diff != "" { 220 t.Error(diff) 221 } 222 }) 223 } 224} 225 226func TestDecode(t *testing.T) { 227 testCases := []struct { 228 in interface{} 229 want string 230 }{{ 231 in: "str", 232 want: `"str"`, 233 }, { 234 in: func() interface{} { 235 type T struct { 236 B int 237 } 238 type S struct { 239 A string 240 T 241 } 242 return S{} 243 }(), 244 want: `{ 245 A: "" 246 B: 0 247}`, 248 }, { 249 in: func() interface{} { 250 type T struct { 251 B int 252 } 253 type S struct { 254 A string 255 T `json:"t"` 256 } 257 return S{} 258 }(), 259 want: `{ 260 A: "" 261 t: { 262 B: 0 263 } 264}`, 265 }} 266 c := New(cuecontext.New(), nil) 267 268 for _, tc := range testCases { 269 t.Run("", func(t *testing.T) { 270 v, err := c.Decode(tc.in) 271 if err != nil { 272 t.Fatal(err) 273 } 274 275 got := fmt.Sprint(v) 276 if got != tc.want { 277 t.Errorf("got %v; want %v", got, tc.want) 278 } 279 }) 280 } 281} 282 283// For debugging purposes, do not remove. 284func TestX(t *testing.T) { 285 t.Skip() 286 287 fail := "some error" 288 // Not a typical constraint, but it is possible. 289 var ( 290 name = "string list incompatible lengths" 291 value = []string{"a", "b", "c"} 292 constraints = `4*[string]` 293 wantErr = fail 294 ) 295 296 ctx := cuecontext.New() 297 codec := New(ctx, nil) 298 299 v, err := codec.ExtractType(value) 300 if err != nil { 301 t.Fatal(err) 302 } 303 304 if constraints != "" { 305 c := ctx.CompileString(constraints, cue.Filename(name)) 306 if err := c.Err(); err != nil { 307 t.Fatal(err) 308 } 309 v = v.Unify(c) 310 } 311 312 err = codec.Validate(v, value) 313 checkErr(t, err, wantErr) 314}