cuelang.org/go@v0.10.1/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: discarded cycle error 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: discarded cycle error 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 // TODO: missing error position 196 todo_v3: true, 197 198 name: "required field not present", 199 cfg: &validate.Config{Final: true}, 200 in: ` 201 Person: { 202 name!: string 203 age?: int 204 height: 1.80 205 } 206 `, 207 out: "incomplete\nPerson.name: field is required but not present:\n test:3:5", 208 }, { 209 name: "allow required fields in definitions", 210 cfg: &validate.Config{Concrete: true}, 211 in: ` 212 #Person: { 213 name!: string 214 age?: int 215 } 216 `, 217 out: "", 218 }, { 219 name: "allow required fields when not concrete", 220 in: ` 221 Person: { 222 name!: string 223 age?: int 224 } 225 `, 226 out: "", 227 }} 228 229 cuetdtest.Run(t, testCases, func(t *cuetdtest.T, tc *testCase) { 230 if tc.todo_v3 { 231 t.M.TODO_V3() 232 } 233 r := t.M.Runtime() 234 ctx := eval.NewContext(r, nil) 235 236 f, err := parser.ParseFile("test", tc.in) 237 if err != nil { 238 t.Fatal(err) 239 } 240 v, err := compile.Files(nil, r, "", f) 241 if err != nil { 242 t.Fatal(err) 243 } 244 v.Finalize(ctx) 245 if tc.lookup != "" { 246 v = v.Lookup(adt.MakeIdentLabel(r, tc.lookup, "main")) 247 } 248 249 b := validate.Validate(ctx, v, tc.cfg) 250 251 w := &strings.Builder{} 252 if b != nil { 253 fmt.Fprintln(w, b.Code) 254 errors.Print(w, b.Err, nil) 255 } 256 257 got := strings.TrimSpace(w.String()) 258 if tc.out != got { 259 t.Error(cmp.Diff(tc.out, got)) 260 } 261 }) 262 }