cuelang.org/go@v0.13.0/cue/path_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 cue_test 16 17 import ( 18 "fmt" 19 "reflect" 20 "testing" 21 22 "cuelang.org/go/cue" 23 "cuelang.org/go/cue/cuecontext" 24 "cuelang.org/go/internal/cuetdtest" 25 ) 26 27 func TestPaths(t *testing.T) { 28 type testCase struct { 29 path cue.Path 30 out string 31 str string 32 err bool 33 } 34 testCases := []testCase{{ 35 path: cue.MakePath(cue.Str("list"), cue.AnyIndex), 36 out: "int", 37 str: "list.[_]", 38 }, { 39 40 path: cue.MakePath(cue.Def("#Foo"), cue.Str("a"), cue.Str("b")), 41 out: "1", 42 str: "#Foo.a.b", 43 }, { 44 path: cue.ParsePath(`#Foo.a.b`), 45 out: "1", 46 str: "#Foo.a.b", 47 }, { 48 path: cue.ParsePath(`"#Foo".c.d`), 49 out: "2", 50 str: `"#Foo".c.d`, 51 }, { 52 // fallback Def(Foo) -> Def(#Foo) 53 path: cue.MakePath(cue.Def("Foo"), cue.Str("a"), cue.Str("b")), 54 out: "1", 55 str: "#Foo.a.b", 56 }, { 57 path: cue.MakePath(cue.Str("b"), cue.Index(2)), 58 out: "6", 59 str: "b[2]", // #Foo.b.2 60 }, { 61 path: cue.MakePath(cue.Str("c"), cue.Str("#Foo")), 62 out: "7", 63 str: `c."#Foo"`, 64 }, { 65 path: cue.MakePath(cue.Hid("_foo", "_"), cue.Str("b")), 66 out: "5", 67 str: `_foo.b`, 68 }, { 69 path: cue.ParsePath("#Foo.a.b"), 70 str: "#Foo.a.b", 71 out: "1", 72 }, { 73 path: cue.ParsePath("#Foo.a.c"), 74 str: "#Foo.a.c", 75 out: `_|_ // field not found: c`, 76 }, { 77 path: cue.ParsePath(`b[2]`), 78 str: `b[2]`, 79 out: "6", 80 }, { 81 path: cue.ParsePath(`c."#Foo"`), 82 str: `c."#Foo"`, 83 out: "7", 84 }, { 85 path: cue.ParsePath("foo._foo"), 86 str: "_|_", 87 err: true, 88 out: `_|_ // invalid path: hidden label _foo not allowed`, 89 }, { 90 path: cue.ParsePath(`c."#Foo`), 91 str: "_|_", 92 err: true, 93 out: `_|_ // string literal not terminated`, 94 }, { 95 path: cue.ParsePath(`b[a]`), 96 str: "_|_", 97 err: true, 98 out: `_|_ // non-constant expression a`, 99 }, { 100 path: cue.ParsePath(`b['1']`), 101 str: "_|_", 102 err: true, 103 out: `_|_ // invalid string index '1'`, 104 }, { 105 path: cue.ParsePath(`b[3T]`), 106 str: "_|_", 107 err: true, 108 out: `_|_ // int label out of range (3000000000000 not >=0 and <= 268435454)`, 109 }, { 110 path: cue.ParsePath(`b[3.3]`), 111 str: "_|_", 112 err: true, 113 out: `_|_ // invalid literal 3.3`, 114 }, { 115 path: cue.MakePath(cue.Str("map"), cue.AnyString), 116 out: "int", 117 str: "map.[_]", 118 }, { 119 path: cue.MakePath(cue.Str("list"), cue.AnyIndex), 120 out: "int", 121 str: "list.[_]", 122 }, { 123 path: cue.ParsePath("x.y"), 124 out: "{\n\tb: 0\n}", 125 str: "x.y", 126 }, { 127 path: cue.ParsePath("x.y.b"), 128 out: "0", 129 str: "x.y.b", 130 }, { 131 // Issue 3577 132 path: cue.ParsePath("pkg.y"), 133 out: `"hello"`, 134 str: "pkg.y", // show original path, not structure shared path. 135 }, { 136 // Issue 3922 137 path: cue.ParsePath("out.name"), 138 out: `"one"`, 139 str: "out.name", 140 }} 141 142 cuetdtest.Run(t, testCases, func(t *cuetdtest.T, tc *testCase) { 143 ctx := t.M.CueContext() 144 val := mustCompile(t, ctx, ` 145 #Foo: a: b: 1 146 "#Foo": c: d: 2 147 _foo: b: 5 148 a: 3 149 b: [4, 5, 6] 150 c: "#Foo": 7 151 map: [string]: int 152 list: [...int] 153 154 // Issue 2060 155 let X = {a: b: 0} 156 x: y: X.a 157 158 // Issue 3577 159 pkg: z 160 z: y: "hello" 161 162 // Issue 3922 163 out: #Output 164 #Output: name: _data.name 165 _data: name: "one" 166 `) 167 168 t.Equal(tc.path.Err() != nil, tc.err) 169 170 w := val.LookupPath(tc.path) 171 172 t.Equal(fmt.Sprint(w), tc.out) 173 174 if w.Err() != nil { 175 return 176 } 177 178 t.Equal(w.Path().String(), tc.str) 179 }) 180 } 181 182 // TestWalkPath is a more comprehensive table-driven test. 183 func TestWalkPath(t *testing.T) { 184 ctx := cuecontext.New() 185 186 testCases := []struct { 187 name string 188 cueInput string 189 wantPaths []string 190 }{ 191 { 192 name: "issue 3922", 193 cueInput: ` 194 out: #Output 195 #Output: name: _data.name 196 _data: name: "one" 197 `, 198 wantPaths: []string{ 199 "", // root 200 "out", 201 "out.name", 202 }, 203 }, 204 { 205 name: "simple struct", 206 cueInput: ` 207 b: { 208 d: 3 209 c: 2 210 } 211 a: 1 212 `, 213 wantPaths: []string{ 214 "", // root 215 "b", 216 "b.d", 217 "b.c", 218 "a", 219 }, 220 }, 221 { 222 name: "struct with list", 223 cueInput: ` 224 l: [10, {y: 30, x: 20}] 225 `, 226 wantPaths: []string{ 227 "", // root 228 "l", 229 "l[0]", 230 "l[1]", 231 "l[1].y", 232 "l[1].x", 233 }, 234 }, 235 { 236 name: "root list", 237 cueInput: `[10, {x: 20}]`, 238 wantPaths: []string{ 239 "", // root 240 "[0]", 241 "[1]", 242 "[1].x", 243 }, 244 }, 245 { 246 name: "root literal string", 247 cueInput: `"hello"`, 248 wantPaths: []string{ 249 "", // root 250 }, 251 }, 252 { 253 name: "root literal int", 254 cueInput: `123`, 255 wantPaths: []string{ 256 "", // root 257 }, 258 }, 259 { 260 name: "empty struct", 261 cueInput: `{}`, 262 wantPaths: []string{ 263 "", // root 264 }, 265 }, 266 { 267 name: "struct with various field types", 268 cueInput: ` 269 c: _h 270 _h: #D 271 #D: b: c: 3 272 a: _g 273 _g: #B 274 #B: x: "definition B" 275 `, 276 // Order: regular (a, c), definitions (#B, #D), hidden (_g, _h) 277 wantPaths: []string{ 278 "", // root 279 "c", 280 "c.b", 281 "c.b.c", 282 "a", 283 "a.x", 284 }, 285 }, 286 } 287 288 for _, tc := range testCases { 289 t.Run(tc.name, func(t *testing.T) { 290 v := ctx.CompileString(tc.cueInput) 291 if err := v.Err(); err != nil { 292 t.Fatalf("CompileString failed for input\n%s\nError: %v", tc.cueInput, err) 293 } 294 295 var gotPaths []string 296 v.Walk(func(val cue.Value) bool { 297 gotPaths = append(gotPaths, val.Path().String()) 298 return true 299 }, nil) 300 301 if !reflect.DeepEqual(gotPaths, tc.wantPaths) { 302 t.Errorf("Walk() paths mismatch for input\n%s\ngot: %#v\nwant: %#v", tc.cueInput, gotPaths, tc.wantPaths) 303 } 304 }) 305 } 306 } 307 308 var selectorTests = []struct { 309 sel cue.Selector 310 stype cue.SelectorType 311 string string 312 unquoted string 313 index int 314 isHidden bool 315 isConstraint bool 316 isDefinition bool 317 isString bool 318 pkgPath string 319 }{{ 320 sel: cue.Str("foo"), 321 stype: cue.StringLabel, 322 string: "foo", 323 unquoted: "foo", 324 isString: true, 325 }, { 326 sel: cue.Str("_foo"), 327 stype: cue.StringLabel, 328 string: `"_foo"`, 329 unquoted: "_foo", 330 isString: true, 331 }, { 332 sel: cue.Str(`a "b`), 333 stype: cue.StringLabel, 334 string: `"a \"b"`, 335 unquoted: `a "b`, 336 isString: true, 337 }, { 338 sel: cue.Index(5), 339 stype: cue.IndexLabel, 340 string: "5", 341 index: 5, 342 }, { 343 sel: cue.Def("foo"), 344 stype: cue.DefinitionLabel, 345 string: "#foo", 346 isDefinition: true, 347 }, { 348 sel: cue.Str("foo").Optional(), 349 stype: cue.StringLabel | cue.OptionalConstraint, 350 string: "foo?", 351 unquoted: "foo", 352 isString: true, 353 isConstraint: true, 354 }, { 355 sel: cue.Str("foo").Required(), 356 stype: cue.StringLabel | cue.RequiredConstraint, 357 string: "foo!", 358 unquoted: "foo", 359 isString: true, 360 isConstraint: true, 361 }, { 362 sel: cue.Def("foo").Required().Optional(), 363 stype: cue.DefinitionLabel | cue.OptionalConstraint, 364 string: "#foo?", 365 isDefinition: true, 366 isConstraint: true, 367 }, { 368 sel: cue.Def("foo").Optional().Required(), 369 stype: cue.DefinitionLabel | cue.RequiredConstraint, 370 string: "#foo!", 371 isDefinition: true, 372 isConstraint: true, 373 }, { 374 sel: cue.AnyString, 375 stype: cue.StringLabel | cue.PatternConstraint, 376 string: "[_]", 377 isConstraint: true, 378 }, { 379 sel: cue.AnyIndex, 380 stype: cue.IndexLabel | cue.PatternConstraint, 381 string: "[_]", 382 isConstraint: true, 383 }, { 384 sel: cue.Hid("_foo", "example.com"), 385 stype: cue.HiddenLabel, 386 string: "_foo", 387 isHidden: true, 388 pkgPath: "example.com", 389 }, { 390 sel: cue.Hid("_#foo", "example.com"), 391 stype: cue.HiddenDefinitionLabel, 392 string: "_#foo", 393 isHidden: true, 394 isDefinition: true, 395 pkgPath: "example.com", 396 }} 397 398 func TestSelector(t *testing.T) { 399 for _, tc := range selectorTests { 400 t.Run(tc.sel.String(), func(t *testing.T) { 401 sel := tc.sel 402 if got, want := sel.Type(), tc.stype; got != want { 403 t.Errorf("unexpected type; got %v want %v", got, want) 404 } 405 if got, want := sel.String(), tc.string; got != want { 406 t.Errorf("unexpected sel.String result; got %q want %q", got, want) 407 } 408 if tc.unquoted == "" { 409 checkPanic(t, "Selector.Unquoted invoked on non-string label", func() { 410 sel.Unquoted() 411 }) 412 } else { 413 if got, want := sel.Unquoted(), tc.unquoted; got != want { 414 t.Errorf("unexpected sel.Unquoted result; got %q want %q", got, want) 415 } 416 } 417 if sel.Type() != cue.IndexLabel { 418 checkPanic(t, "Index called on non-index selector", func() { 419 sel.Index() 420 }) 421 } else { 422 if got, want := sel.Index(), tc.index; got != want { 423 t.Errorf("unexpected sel.Index result; got %v want %v", got, want) 424 } 425 } 426 if got, want := sel.Type().IsHidden(), tc.isHidden; got != want { 427 t.Errorf("unexpected sel.IsHidden result; got %v want %v", got, want) 428 } 429 if got, want := sel.IsConstraint(), tc.isConstraint; got != want { 430 t.Errorf("unexpected sel.IsOptional result; got %v want %v", got, want) 431 } 432 if got, want := sel.IsString(), tc.isString; got != want { 433 t.Errorf("unexpected sel.IsString result; got %v want %v", got, want) 434 } 435 if got, want := sel.IsDefinition(), tc.isDefinition; got != want { 436 t.Errorf("unexpected sel.IsDefinition result; got %v want %v", got, want) 437 } 438 if got, want := sel.PkgPath(), tc.pkgPath; got != want { 439 t.Errorf("unexpected sel.PkgPath result; got %v want %v", got, want) 440 } 441 }) 442 } 443 } 444 445 func TestSelectorTypeString(t *testing.T) { 446 if got, want := cue.InvalidSelectorType.String(), "NoLabels"; got != want { 447 t.Errorf("unexpected SelectorType.String result; got %q want %q", got, want) 448 } 449 if got, want := cue.PatternConstraint.String(), "PatternConstraint"; got != want { 450 t.Errorf("unexpected SelectorType.String result; got %q want %q", got, want) 451 } 452 if got, want := (cue.StringLabel | cue.OptionalConstraint).String(), "StringLabel|OptionalConstraint"; got != want { 453 t.Errorf("unexpected SelectorType.String result; got %q want %q", got, want) 454 } 455 if got, want := cue.SelectorType(255).String(), "StringLabel|IndexLabel|DefinitionLabel|HiddenLabel|HiddenDefinitionLabel|OptionalConstraint|RequiredConstraint|PatternConstraint"; got != want { 456 t.Errorf("unexpected SelectorType.String result; got %q want %q", got, want) 457 } 458 } 459 460 func checkPanic(t *testing.T, wantPanicStr string, f func()) { 461 gotPanicStr := "" 462 func() { 463 defer func() { 464 e := recover() 465 if e == nil { 466 t.Errorf("function did not panic") 467 return 468 } 469 gotPanicStr = fmt.Sprint(e) 470 }() 471 f() 472 }() 473 if got, want := gotPanicStr, wantPanicStr; got != want { 474 t.Errorf("unexpected panic message; got %q want %q", got, want) 475 } 476 }