cuelang.org/go@v0.13.0/internal/core/validate/validate_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 validate_test 16 17 import ( 18 "fmt" 19 "strings" 20 "testing" 21 22 "cuelang.org/go/cue/errors" 23 "cuelang.org/go/cue/parser" 24 "cuelang.org/go/internal/core/adt" 25 "cuelang.org/go/internal/core/compile" 26 "cuelang.org/go/internal/core/eval" 27 "cuelang.org/go/internal/core/validate" 28 "cuelang.org/go/internal/cuetdtest" 29 "github.com/google/go-cmp/cmp" 30 ) 31 32 func TestValidate(t *testing.T) { 33 type testCase struct { 34 name string 35 in string 36 out string 37 lookup string 38 cfg *validate.Config 39 40 todo_v3 bool 41 } 42 testCases := []testCase{{ 43 name: "no error, but not concrete, even with definition label", 44 cfg: &validate.Config{Concrete: true}, 45 in: ` 46 #foo: { use: string } 47 `, 48 lookup: "#foo", 49 out: "incomplete\n#foo.use: incomplete value string:\n test:2:16", 50 }, { 51 name: "definitions not considered for completeness", 52 cfg: &validate.Config{Concrete: true}, 53 in: ` 54 #foo: { use: string } 55 `, 56 }, { 57 name: "hidden fields not considered for completeness", 58 cfg: &validate.Config{Concrete: true}, 59 in: ` 60 _foo: { use: string } 61 `, 62 }, { 63 name: "hidden fields not considered for completeness", 64 in: ` 65 _foo: { use: string } 66 `, 67 }, { 68 name: "evaluation error at top", 69 in: ` 70 1 & 2 71 `, 72 out: "eval\nconflicting values 2 and 1:\n test:2:3\n test:2:7", 73 }, { 74 name: "evaluation error in field", 75 in: ` 76 x: 1 & 2 77 `, 78 out: "eval\nx: conflicting values 2 and 1:\n test:2:6\n test:2:10", 79 }, { 80 name: "first error", 81 in: ` 82 x: 1 & 2 83 y: 2 & 4 84 `, 85 out: "eval\nx: conflicting values 2 and 1:\n test:2:6\n test:2:10", 86 }, { 87 name: "all errors", 88 cfg: &validate.Config{AllErrors: true}, 89 in: ` 90 x: 1 & 2 91 y: 2 & 4 92 `, 93 out: `eval 94 x: conflicting values 2 and 1: 95 test:2:6 96 test:2:10 97 y: conflicting values 4 and 2: 98 test:3:6 99 test:3:10`, 100 }, { 101 name: "incomplete", 102 cfg: &validate.Config{Concrete: true}, 103 in: ` 104 y: 2 + x 105 x: string 106 `, 107 out: "incomplete\ny: non-concrete value string in operand to +:\n test:2:6\n test:3:6", 108 }, { 109 name: "allowed incomplete cycle", 110 in: ` 111 y: x 112 x: y 113 `, 114 }, { 115 name: "allowed incomplete when disallowing cycles", 116 cfg: &validate.Config{DisallowCycles: true}, 117 in: ` 118 y: string 119 x: y 120 `, 121 }, { 122 // TODO: different error position 123 todo_v3: true, 124 125 name: "disallow cycle", 126 cfg: &validate.Config{DisallowCycles: true}, 127 in: ` 128 y: x + 1 129 x: y - 1 130 `, 131 out: "cycle\ncycle error:\n test:2:6", 132 }, { 133 // TODO: different error position 134 todo_v3: true, 135 136 name: "disallow cycle", 137 cfg: &validate.Config{DisallowCycles: true}, 138 in: ` 139 a: b - 100 140 b: a + 100 141 c: [c[1], c[0]] `, 142 out: "cycle\ncycle error:\n test:2:6", 143 }, { 144 name: "treat cycles as incomplete when not disallowing", 145 cfg: &validate.Config{}, 146 in: ` 147 y: x + 1 148 x: y - 1 149 `, 150 }, { 151 // Note: this is already handled by evaluation, as terminal errors 152 // are percolated up. 153 name: "catch most serious error", 154 cfg: &validate.Config{Concrete: true}, 155 in: ` 156 y: string 157 x: 1 & 2 158 `, 159 out: "eval\nx: conflicting values 2 and 1:\n test:3:6\n test:3:10", 160 }, { 161 name: "consider defaults for concreteness", 162 cfg: &validate.Config{Concrete: true}, 163 in: ` 164 x: *1 | 2 165 `, 166 }, { 167 name: "allow non-concrete in definitions in concrete mode", 168 cfg: &validate.Config{Concrete: true}, 169 in: ` 170 x: 2 171 #d: { 172 b: int 173 c: b + b 174 } 175 `, 176 }, { 177 name: "pick up non-concrete value in default", 178 cfg: &validate.Config{Concrete: true}, 179 in: ` 180 x: null | *{ 181 a: int 182 } 183 `, 184 out: "incomplete\nx.a: incomplete value int:\n test:3:7", 185 }, { 186 name: "pick up non-concrete value in default", 187 cfg: &validate.Config{Concrete: true}, 188 in: ` 189 x: null | *{ 190 a: 1 | 2 191 } 192 `, 193 out: "incomplete\nx.a: incomplete value 1 | 2", 194 }, { 195 name: "required field not present", 196 cfg: &validate.Config{Final: true}, 197 in: ` 198 Person: { 199 name!: string 200 age?: int 201 height: 1.80 202 } 203 `, 204 out: "incomplete\nPerson.name: field is required but not present:\n test:3:5", 205 }, { 206 name: "allow required fields in definitions", 207 cfg: &validate.Config{Concrete: true}, 208 in: ` 209 #Person: { 210 name!: string 211 age?: int 212 } 213 `, 214 out: "", 215 }, { 216 name: "allow required fields when not concrete", 217 in: ` 218 Person: { 219 name!: string 220 age?: int 221 } 222 `, 223 out: "", 224 }, { 225 name: "indirect resolved disjunction using matchN", 226 cfg: &validate.Config{Final: true}, 227 in: ` 228 x: {} 229 x: matchN(0, [bool | {x!: _}]) 230 `, 231 out: "", 232 }, { 233 name: "indirect resolved disjunction", 234 cfg: &validate.Config{Final: true}, 235 in: ` 236 x: {bar: 2} 237 x: string | {foo!: string} 238 `, 239 out: "incomplete\nx.foo: field is required but not present:\n test:3:18", 240 }, { 241 name: "disallow incomplete error with final", 242 cfg: &validate.Config{Final: true}, 243 in: ` 244 x: y + 1 245 y: int 246 `, 247 out: "incomplete\nx: non-concrete value int in operand to +:\n test:2:7\n test:3:7", 248 }, { 249 name: "allow incomplete error with final while in definition", 250 cfg: &validate.Config{Final: true}, 251 in: ` 252 #D: x: #D.y + 1 253 #D: y: int 254 `, 255 }, { 256 name: "allow incomplete error with final while in definition", 257 cfg: &validate.Config{Final: true}, 258 in: ` 259 #D: x: #D.y + 1 260 #D: y: int 261 `, 262 }, { 263 name: "report non-concrete value of structure shared node in correct position", 264 cfg: &validate.Config{ 265 Concrete: true, 266 Final: true, 267 }, 268 in: ` 269 #Def: a: x!: int 270 b: #Def 271 `, 272 out: "incomplete\nb.a.x: field is required but not present:\n test:2:13\n test:3:7", 273 274 todo_v3: true, // missing position 275 }, { 276 // Issue #3864: issue resulting from structure sharing. 277 name: "attribute incomplete values in definitions to concrete path", 278 cfg: &validate.Config{ 279 Concrete: true, 280 Final: true, 281 }, 282 in: ` 283 #A: y: string 284 #B: x: #A 285 #C: { 286 x: #A 287 v: #B & { x: x } // note: 'x' resolves to self. 288 } 289 config: #C & { 290 x: y: "dev" 291 } 292 `, 293 out: "incomplete\nconfig.v.x.y: incomplete value string:\n test:2:11", 294 }} 295 296 cuetdtest.Run(t, testCases, func(t *cuetdtest.T, tc *testCase) { 297 if tc.todo_v3 { 298 t.M.TODO_V3(t) // P1: cycle error missing? Other error. 299 } 300 r := t.M.Runtime() 301 ctx := eval.NewContext(r, nil) 302 303 f, err := parser.ParseFile("test", tc.in) 304 if err != nil { 305 t.Fatal(err) 306 } 307 v, err := compile.Files(nil, r, "", f) 308 if err != nil { 309 t.Fatal(err) 310 } 311 v.Finalize(ctx) 312 if tc.lookup != "" { 313 v = v.Lookup(adt.MakeIdentLabel(r, tc.lookup, "main")) 314 } 315 316 b := validate.Validate(ctx, v, tc.cfg) 317 318 w := &strings.Builder{} 319 if b != nil { 320 fmt.Fprintln(w, b.Code) 321 errors.Print(w, b.Err, nil) 322 } 323 324 got := strings.TrimSpace(w.String()) 325 if tc.out != got { 326 t.Error(cmp.Diff(tc.out, got)) 327 } 328 }) 329 }