cuelang.org/go@v0.10.1/cue/types_test.go (about) 1 // Copyright 2018 The 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 "bytes" 19 "fmt" 20 "io" 21 "math" 22 "math/big" 23 "reflect" 24 "strconv" 25 "strings" 26 "testing" 27 28 "github.com/google/go-cmp/cmp" 29 30 "cuelang.org/go/cue" 31 "cuelang.org/go/cue/ast" 32 "cuelang.org/go/cue/cuecontext" 33 "cuelang.org/go/internal/astinternal" 34 "cuelang.org/go/internal/core/adt" 35 "cuelang.org/go/internal/core/debug" 36 "cuelang.org/go/internal/cuetdtest" 37 "cuelang.org/go/internal/cuetest" 38 "cuelang.org/go/internal/tdtest" 39 ) 40 41 func getValue(t *cuetdtest.M, body string) cue.Value { 42 t.Helper() 43 44 ctx := t.Context() 45 return ctx.CompileString(body, cue.Filename("test")) 46 } 47 48 func mustCompile(t testing.TB, ctx *cue.Context, body string) cue.Value { 49 t.Helper() 50 51 val := ctx.CompileString(body, cue.Filename("test")) 52 if err := val.Err(); err != nil { 53 t.Fatal(err) 54 } 55 return val 56 } 57 58 func TestAPI(t *testing.T) { 59 testCases := []struct { 60 input string 61 fun func(i cue.Value) cue.Value 62 want string 63 skip bool 64 }{{ 65 // Issue #567 66 input: ` 67 #runSpec: {action: foo: int} 68 69 v: {ction: foo: 1} 70 `, 71 fun: func(i cue.Value) cue.Value { 72 runSpec := i.LookupDef("#runSpec") 73 v := i.Lookup("v") 74 res := runSpec.Unify(v) 75 return res 76 }, 77 want: "_|_ // #runSpec.ction: field not allowed", 78 79 skip: true, 80 }, { 81 // Issue #567 82 input: ` 83 #runSpec: {action: foo: int} 84 85 v: {action: Foo: 1} 86 `, 87 fun: func(i cue.Value) cue.Value { 88 runSpec := i.LookupDef("#runSpec") 89 v := i.Lookup("v") 90 res := runSpec.Unify(v) 91 return res 92 }, 93 want: "_|_ // #runSpec.action.Foo: field not allowed", 94 }, { 95 input: ` 96 #runSpec: v: {action: foo: int} 97 98 w: {ction: foo: 1} 99 `, 100 fun: func(i cue.Value) cue.Value { 101 runSpec := i.LookupDef("#runSpec") 102 v := runSpec.Lookup("v") 103 w := i.Lookup("w") 104 res := w.Unify(v) 105 return res 106 }, 107 want: "_|_ // w.ction: field not allowed", 108 }, { 109 // Issue #1879 110 input: ` 111 #Steps: { 112 ... 113 } 114 115 test: #Steps & { 116 if true { 117 test1: "test1" 118 } 119 if false { 120 test2: "test2" 121 } 122 } 123 `, 124 125 fun: func(v cue.Value) (val cue.Value) { 126 sub := v.LookupPath(cue.ParsePath("test")) 127 st, err := sub.Struct() 128 if err != nil { 129 panic(err) 130 } 131 132 for i := 0; i < st.Len(); i++ { 133 val = st.Field(i).Value 134 } 135 136 return val 137 }, 138 want: `"test1"`, 139 }} 140 for _, tc := range testCases { 141 if tc.skip { 142 continue 143 } 144 cuetdtest.FullMatrix.Run(t, "", func(t *cuetdtest.M) { 145 t.TODO_V3() 146 147 ctx := t.Context() 148 149 valIn := mustCompile(t, ctx, tc.input) 150 valOut := tc.fun(valIn) 151 got := fmt.Sprintf("%+v", valOut) 152 if got != tc.want { 153 t.Errorf("got:\n%s\nwant:\n%s", got, tc.want) 154 } 155 }) 156 } 157 } 158 159 func TestValueType(t *testing.T) { 160 testCases := []struct { 161 value string 162 kind cue.Kind 163 incompleteKind cue.Kind 164 json string 165 valid bool 166 concrete bool 167 closed bool 168 // pos token.Pos 169 }{{ // Not a concrete value. 170 value: `v: _`, 171 kind: cue.BottomKind, 172 incompleteKind: cue.TopKind, 173 }, { 174 value: `v: _|_`, 175 kind: cue.BottomKind, 176 incompleteKind: cue.BottomKind, 177 concrete: true, 178 }, { 179 value: `v: 1&2`, 180 kind: cue.BottomKind, 181 incompleteKind: cue.BottomKind, 182 concrete: true, 183 }, { 184 value: `v: b, b: 1&2`, 185 kind: cue.BottomKind, 186 incompleteKind: cue.BottomKind, 187 concrete: true, 188 }, { 189 value: `v: (b[a]), b: 1, a: 1`, 190 kind: cue.BottomKind, 191 incompleteKind: cue.BottomKind, 192 concrete: true, 193 }, { // TODO: should be error{ 194 value: `v: (b) 195 b: bool`, 196 kind: cue.BottomKind, 197 incompleteKind: cue.BoolKind, 198 }, { 199 value: `v: ([][b]), b: "d"`, 200 kind: cue.BottomKind, 201 incompleteKind: cue.BottomKind, 202 concrete: true, 203 }, { 204 value: `v: null`, 205 kind: cue.NullKind, 206 incompleteKind: cue.NullKind, 207 concrete: true, 208 }, { 209 value: `v: true`, 210 kind: cue.BoolKind, 211 incompleteKind: cue.BoolKind, 212 concrete: true, 213 }, { 214 value: `v: false`, 215 kind: cue.BoolKind, 216 incompleteKind: cue.BoolKind, 217 concrete: true, 218 }, { 219 value: `v: bool`, 220 kind: cue.BottomKind, 221 incompleteKind: cue.BoolKind, 222 }, { 223 value: `v: 2`, 224 kind: cue.IntKind, 225 incompleteKind: cue.IntKind, 226 concrete: true, 227 }, { 228 value: `v: 2.0`, 229 kind: cue.FloatKind, 230 incompleteKind: cue.FloatKind, 231 concrete: true, 232 }, { 233 value: `v: 2.0Mi`, 234 kind: cue.IntKind, 235 incompleteKind: cue.IntKind, 236 concrete: true, 237 }, { 238 value: `v: 14_000`, 239 kind: cue.IntKind, 240 incompleteKind: cue.IntKind, 241 concrete: true, 242 }, { 243 value: `v: >=0 & <5`, 244 kind: cue.BottomKind, 245 incompleteKind: cue.NumberKind, 246 }, { 247 value: `v: float`, 248 kind: cue.BottomKind, 249 incompleteKind: cue.FloatKind, 250 }, { 251 value: `v: "str"`, 252 kind: cue.StringKind, 253 incompleteKind: cue.StringKind, 254 concrete: true, 255 }, { 256 value: "v: '''\n'''", 257 kind: cue.BytesKind, 258 incompleteKind: cue.BytesKind, 259 concrete: true, 260 }, { 261 value: "v: string", 262 kind: cue.BottomKind, 263 incompleteKind: cue.StringKind, 264 }, { 265 value: `v: {}`, 266 kind: cue.StructKind, 267 incompleteKind: cue.StructKind, 268 concrete: true, 269 }, { 270 value: `v: close({})`, 271 kind: cue.StructKind, 272 incompleteKind: cue.StructKind, 273 concrete: true, 274 closed: true, 275 }, { 276 value: `v: []`, 277 kind: cue.ListKind, 278 incompleteKind: cue.ListKind, 279 concrete: true, 280 closed: true, 281 }, { 282 value: `v: [...int]`, 283 kind: cue.BottomKind, 284 incompleteKind: cue.ListKind, 285 concrete: false, 286 }, { 287 value: `v: {a: int, b: [1][a]}.b`, 288 kind: cue.BottomKind, 289 concrete: false, 290 }, { 291 value: `import "time" 292 v: time.Time`, 293 kind: cue.BottomKind, 294 incompleteKind: cue.StringKind, 295 concrete: false, 296 }, { 297 value: `import "time" 298 v: {a: time.Time}.a`, 299 kind: cue.BottomKind, 300 incompleteKind: cue.StringKind, 301 concrete: false, 302 }, { 303 value: `import "time" 304 v: {a: time.Time & string}.a`, 305 kind: cue.BottomKind, 306 incompleteKind: cue.StringKind, 307 concrete: false, 308 }, { 309 value: `import "strings" 310 v: {a: strings.ContainsAny("D")}.a`, 311 kind: cue.BottomKind, 312 incompleteKind: cue.StringKind, 313 concrete: false, 314 }, { 315 value: `import "struct" 316 v: {a: struct.MaxFields(2) & {}}.a`, 317 kind: cue.StructKind, // Can determine a valid struct already. 318 incompleteKind: cue.StructKind, 319 concrete: true, 320 }, { 321 value: `v: #Foo 322 #Foo: { 323 name: string, 324 ... 325 }`, 326 kind: cue.StructKind, 327 incompleteKind: cue.StructKind, 328 concrete: true, 329 }, { 330 value: `v: #Foo 331 #Foo: { 332 name: string, 333 }`, 334 kind: cue.StructKind, 335 incompleteKind: cue.StructKind, 336 concrete: true, 337 closed: true, 338 }, { 339 value: `v: #Foo | int 340 #Foo: { 341 name: string, 342 }`, 343 incompleteKind: cue.StructKind | cue.IntKind, 344 // Hard to tell what is correct here, but For backwards compatibility, 345 // this is false. 346 closed: false, 347 }} 348 for _, tc := range testCases { 349 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 350 val := getValue(t, tc.value) 351 v := val.Lookup("v") 352 if got := v.Kind(); got != tc.kind { 353 t.Errorf("Kind: got %x; want %x", int(got), int(tc.kind)) 354 } 355 want := tc.incompleteKind | cue.BottomKind 356 if got := v.IncompleteKind(); got != want { 357 t.Errorf("IncompleteKind: got %x; want %x", int(got), int(want)) 358 } 359 if got := v.IsConcrete(); got != tc.concrete { 360 t.Errorf("IsConcrete: got %v; want %v", got, tc.concrete) 361 } 362 if got := v.IsClosed(); got != tc.closed { 363 t.Errorf("IsClosed: got %v; want %v", got, tc.closed) 364 } 365 }) 366 } 367 } 368 369 func TestInt(t *testing.T) { 370 testCases := []struct { 371 value string 372 int int64 373 uint uint64 374 base int 375 err string 376 errU string 377 notInt bool 378 }{{ 379 value: "1", 380 int: 1, 381 uint: 1, 382 }, { 383 value: "-1", 384 int: -1, 385 uint: 0, 386 errU: cue.ErrAbove.Error(), 387 }, { 388 value: "-111222333444555666777888999000", 389 int: math.MinInt64, 390 uint: 0, 391 err: cue.ErrAbove.Error(), 392 errU: cue.ErrAbove.Error(), 393 }, { 394 value: "111222333444555666777888999000", 395 int: math.MaxInt64, 396 uint: math.MaxUint64, 397 err: cue.ErrBelow.Error(), 398 errU: cue.ErrBelow.Error(), 399 }, { 400 value: "1.0", 401 err: "cannot use value 1.0 (type float) as int", 402 errU: "cannot use value 1.0 (type float) as int", 403 notInt: true, 404 }, { 405 value: "int", 406 err: "non-concrete value int", 407 errU: "non-concrete value int", 408 notInt: true, 409 }, { 410 value: "_|_", 411 err: "explicit error (_|_ literal) in source", 412 errU: "explicit error (_|_ literal) in source", 413 notInt: true, 414 }} 415 for _, tc := range testCases { 416 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 417 n := getValue(t, tc.value) 418 base := 10 419 if tc.base > 0 { 420 base = tc.base 421 } 422 b, err := n.AppendInt(nil, base) 423 if checkFailed(t.T, err, tc.err, "append") { 424 want := tc.value 425 if got := string(b); got != want { 426 t.Errorf("append: got %v; want %v", got, want) 427 } 428 } 429 430 vi, err := n.Int64() 431 checkErr(t.T, err, tc.err, "Int64") 432 if vi != tc.int { 433 t.Errorf("Int64: got %v; want %v", vi, tc.int) 434 } 435 436 vu, err := n.Uint64() 437 checkErr(t.T, err, tc.errU, "Uint64") 438 if vu != uint64(tc.uint) { 439 t.Errorf("Uint64: got %v; want %v", vu, tc.uint) 440 } 441 }) 442 } 443 } 444 445 func TestFloat(t *testing.T) { 446 testCases := []struct { 447 value string 448 float string 449 float64 float64 450 mant string 451 exp int 452 fmt byte 453 prec int 454 kind cue.Kind 455 err string 456 }{{ 457 value: "1", 458 float: "1", 459 mant: "1", 460 exp: 0, 461 float64: 1, 462 fmt: 'g', 463 kind: cue.IntKind, 464 }, { 465 value: "-1", 466 float: "-1", 467 mant: "-1", 468 exp: 0, 469 float64: -1, 470 fmt: 'g', 471 kind: cue.IntKind, 472 }, { 473 value: "0.0", 474 float: "0.0", 475 mant: "0", 476 exp: -1, 477 float64: 0.0, 478 fmt: 'g', 479 kind: cue.FloatKind, 480 }, { 481 value: "1.0", 482 float: "1.0", 483 mant: "10", 484 exp: -1, 485 float64: 1.0, 486 fmt: 'g', 487 kind: cue.FloatKind, 488 }, { 489 value: "2.6", 490 float: "2.6", 491 mant: "26", 492 exp: -1, 493 float64: 2.6, 494 fmt: 'g', 495 kind: cue.FloatKind, 496 }, { 497 value: "20.600", 498 float: "20.60", 499 mant: "20600", 500 exp: -3, 501 float64: 20.60, 502 prec: 2, 503 fmt: 'f', 504 kind: cue.FloatKind, 505 }, { 506 value: "1/0", 507 float: "", 508 float64: 0, 509 prec: 2, 510 fmt: 'f', 511 err: "division by zero", 512 kind: cue.BottomKind, 513 }, { 514 value: "1.797693134862315708145274237317043567982e+308", 515 float: "1.8e+308", 516 mant: "1797693134862315708145274237317043567982", 517 exp: 269, 518 float64: math.Inf(1), 519 prec: 2, 520 fmt: 'g', 521 err: cue.ErrAbove.Error(), 522 kind: cue.FloatKind, 523 }, { 524 value: "-1.797693134862315708145274237317043567982e+308", 525 float: "-1.8e+308", 526 mant: "-1797693134862315708145274237317043567982", 527 exp: 269, 528 float64: math.Inf(-1), 529 prec: 2, 530 fmt: 'g', 531 kind: cue.FloatKind, 532 err: cue.ErrBelow.Error(), 533 }, { 534 value: "4.940656458412465441765687928682213723650e-324", 535 float: "4.941e-324", 536 mant: "4940656458412465441765687928682213723650", 537 exp: -363, 538 float64: 0, 539 prec: 4, 540 fmt: 'g', 541 kind: cue.FloatKind, 542 err: cue.ErrBelow.Error(), 543 }, { 544 value: "-4.940656458412465441765687928682213723650e-324", 545 float: "-4.940656458412465441765687928682213723650e-324", 546 mant: "-4940656458412465441765687928682213723650", 547 exp: -363, 548 float64: 0, 549 prec: -1, 550 fmt: 'g', 551 kind: cue.FloatKind, 552 err: cue.ErrAbove.Error(), 553 }} 554 for _, tc := range testCases { 555 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 556 n := getValue(t, tc.value) 557 if n.Kind() != tc.kind { 558 t.Fatal("Not a number") 559 } 560 561 var mant big.Int 562 exp, err := n.MantExp(&mant) 563 mstr := "" 564 if err == nil { 565 mstr = mant.String() 566 } 567 if exp != tc.exp || mstr != tc.mant { 568 t.Errorf("mantExp: got %s %d; want %s %d", mstr, exp, tc.mant, tc.exp) 569 } 570 571 b, _ := n.AppendFloat(nil, tc.fmt, tc.prec) 572 want := tc.float 573 if got := string(b); got != want { 574 t.Errorf("append: got %v; want %v", got, want) 575 } 576 577 f, err := n.Float64() 578 checkErr(t.T, err, tc.err, "Float64") 579 if f != tc.float64 { 580 t.Errorf("Float64: got %v; want %v", f, tc.float64) 581 } 582 }) 583 } 584 } 585 586 func TestString(t *testing.T) { 587 testCases := []struct { 588 value string 589 str string 590 err string 591 }{{ 592 value: `""`, 593 str: ``, 594 }, { 595 value: `"Hello world!"`, 596 str: `Hello world!`, 597 }, { 598 value: `"Hello \(#world)!" 599 #world: "world"`, 600 str: `Hello world!`, 601 }, { 602 value: `string`, 603 err: "non-concrete value string", 604 }} 605 for _, tc := range testCases { 606 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 607 str, err := getValue(t, tc.value).String() 608 checkFatal(t.T, err, tc.err, "init") 609 if str != tc.str { 610 t.Errorf("String: got %q; want %q", str, tc.str) 611 } 612 613 b, err := getValue(t, tc.value).Bytes() 614 checkFatal(t.T, err, tc.err, "init") 615 if got := string(b); got != tc.str { 616 t.Errorf("Bytes: got %q; want %q", got, tc.str) 617 } 618 619 r, err := getValue(t, tc.value).Reader() 620 checkFatal(t.T, err, tc.err, "init") 621 b, _ = io.ReadAll(r) 622 if got := string(b); got != tc.str { 623 t.Errorf("Reader: got %q; want %q", got, tc.str) 624 } 625 }) 626 } 627 } 628 629 func TestError(t *testing.T) { 630 testCases := []struct { 631 value string 632 err string 633 }{{ 634 value: `_|_`, 635 err: "explicit error (_|_ literal) in source", 636 }, { 637 value: `"Hello world!"`, 638 }, { 639 value: `string`, 640 err: "", 641 }} 642 for _, tc := range testCases { 643 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 644 err := getValue(t, tc.value).Err() 645 checkErr(t.T, err, tc.err, "init") 646 }) 647 } 648 } 649 650 func TestNull(t *testing.T) { 651 testCases := []struct { 652 value string 653 err string 654 }{{ 655 value: `v: _|_`, 656 err: "explicit error (_|_ literal) in source", 657 }, { 658 value: `v: "str"`, 659 err: "cannot use value \"str\" (type string) as null", 660 }, { 661 value: `v: null`, 662 }, { 663 value: `v: _`, 664 err: "non-concrete value _", 665 }} 666 for _, tc := range testCases { 667 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 668 v := getValue(t, tc.value).Lookup("v") 669 err := v.Null() 670 checkErr(t.T, err, tc.err, "init") 671 wantBool := err == nil 672 gotBool := v.IsNull() 673 if wantBool != gotBool { 674 t.Fatalf("IsNull reported %v, but Null reported: %v", gotBool, err) 675 } 676 }) 677 } 678 } 679 680 func TestBool(t *testing.T) { 681 testCases := []struct { 682 value string 683 bool bool 684 err string 685 }{{ 686 value: `_|_`, 687 err: "explicit error (_|_ literal) in source", 688 }, { 689 value: `"str"`, 690 err: "cannot use value \"str\" (type string) as bool", 691 }, { 692 value: `true`, 693 bool: true, 694 }, { 695 value: `false`, 696 }, { 697 value: `bool`, 698 err: "non-concrete value bool", 699 }} 700 for _, tc := range testCases { 701 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 702 got, err := getValue(t, tc.value).Bool() 703 if checkErr(t.T, err, tc.err, "init") { 704 if got != tc.bool { 705 t.Errorf("got %v; want %v", got, tc.bool) 706 } 707 } 708 }) 709 } 710 } 711 712 func TestList(t *testing.T) { 713 testCases := []struct { 714 value string 715 res string 716 err string 717 }{{ 718 value: `_|_`, 719 err: "explicit error (_|_ literal) in source", 720 }, { 721 value: `"str"`, 722 err: "cannot use value \"str\" (type string) as list", 723 }, { 724 value: `[]`, 725 res: "[]", 726 }, { 727 value: `[1,2,3]`, 728 res: "[1,2,3,]", 729 }, { 730 value: `[for x in #y if x > 1 { x }] 731 #y: [1,2,3]`, 732 res: "[2,3,]", 733 }, { 734 value: `[int]`, 735 err: "cannot convert incomplete value", 736 }} 737 for _, tc := range testCases { 738 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 739 l, err := getValue(t, tc.value).List() 740 checkFatal(t.T, err, tc.err, "init") 741 742 buf := []byte{'['} 743 for wantIdx := 0; l.Next(); wantIdx++ { 744 // Ensure that we can get each index as well. 745 if got := l.Selector().Index(); got != wantIdx { 746 t.Errorf("Index got %v; want %v", got, wantIdx) 747 } 748 b, err := l.Value().MarshalJSON() 749 checkFatal(t.T, err, tc.err, "list.Value") 750 buf = append(buf, b...) 751 buf = append(buf, ',') 752 } 753 buf = append(buf, ']') 754 if got := string(buf); got != tc.res { 755 t.Errorf("got %v; want %v", got, tc.res) 756 } 757 }) 758 } 759 } 760 761 func TestFields(t *testing.T) { 762 testCases := []struct { 763 value string 764 res string 765 err string 766 opts []cue.Option 767 768 todoV3 bool 769 }{{ 770 value: `{ #def: 1, _hidden: 2, opt?: 3, reg: 4 }`, 771 res: "{reg:4,}", 772 }, { 773 value: `_|_`, 774 err: "explicit error (_|_ literal) in source", 775 }, { 776 value: `"str"`, 777 err: "cannot use value \"str\" (type string) as struct", 778 }, { 779 value: `{}`, 780 res: "{}", 781 }, { 782 value: `{a:1,b:2,c:3}`, 783 res: "{a:1,b:2,c:3,}", 784 }, { 785 value: `{a:1,"_b":2,c:3,_d:4}`, 786 res: `{a:1,"_b":2,c:3,}`, 787 }, { 788 value: `{_a:"a"}`, 789 res: "{}", 790 }, { 791 value: `{ for k, v in #y if v > 1 {"\(k)": v} } 792 #y: {a:1,b:2,c:3}`, 793 res: "{b:2,c:3,}", 794 }, { 795 value: `{ #def: 1, _hidden: 2, opt?: 3, reg: 4 }`, 796 res: "{reg:4,}", 797 }, { 798 value: `{a:1,b:2,c:int}`, 799 err: "cannot convert incomplete value", 800 }, { 801 value: ` 802 step1: {} 803 step2: {prefix: 3} 804 if step2.value > 100 { 805 step3: {prefix: step2.value} 806 } 807 _hidden: 3`, 808 res: `{step1:{},step2:{"prefix":3},}`, 809 }, { 810 opts: []cue.Option{cue.Final()}, 811 value: ` 812 step1: {} 813 if step1.value > 100 { 814 }`, 815 err: "undefined field: value", 816 }, { 817 opts: []cue.Option{cue.Concrete(true)}, 818 value: ` 819 step1: {} 820 if step1.value > 100 { 821 }`, 822 err: "undefined field: value", 823 }, { 824 value: `{a!: 1, b?: 2, c: 3}`, 825 err: "a: field is required but not present", 826 }, { 827 opts: []cue.Option{cue.Hidden(true)}, 828 value: `1, _a: 2`, 829 res: `{_a:2,}`, 830 }, { 831 opts: []cue.Option{cue.Definitions(true)}, 832 value: `1, #a: 2`, 833 res: `{#a:2,}`, 834 }, { 835 opts: []cue.Option{cue.Optional(true)}, 836 value: `1, a?: 2`, 837 err: "cannot use value 1 (type int) as struct", 838 }} 839 for _, tc := range testCases { 840 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 841 obj := getValue(t, tc.value) 842 843 iter, err := obj.Fields(tc.opts...) 844 checkFatal(t.T, err, tc.err, "init") 845 846 buf := []byte{'{'} 847 for iter.Next() { 848 buf = append(buf, iter.Selector().String()...) 849 buf = append(buf, ':') 850 b, err := iter.Value().MarshalJSON() 851 checkFatal(t.T, err, tc.err, "Obj.At") 852 buf = append(buf, b...) 853 buf = append(buf, ',') 854 } 855 buf = append(buf, '}') 856 if got := string(buf); got != tc.res { 857 t.Errorf("got %v; want %v", got, tc.res) 858 } 859 860 iter, _ = obj.Fields(tc.opts...) 861 for iter.Next() { 862 want, err := iter.Value().MarshalJSON() 863 checkFatal(t.T, err, tc.err, "Obj.At2") 864 865 got, err := obj.LookupPath(cue.MakePath(iter.Selector())).MarshalJSON() 866 checkFatal(t.T, err, tc.err, "Obj.At2") 867 868 if !bytes.Equal(got, want) { 869 t.Errorf("Lookup: got %q; want %q", got, want) 870 } 871 } 872 v := obj.LookupPath(cue.MakePath(cue.Str("non-existing"))) 873 checkErr(t.T, v.Err(), "not found", "non-existing") 874 }) 875 } 876 } 877 878 func TestAllFields(t *testing.T) { 879 testCases := []struct { 880 value string 881 res string 882 err string 883 }{{ 884 value: `{a:1,"_b":2,c:3,_d:4}`, 885 res: `{a:1,"_b":2,c:3,_d:4,}`, 886 }, { 887 value: `{_a:"a"}`, 888 res: `{_a:"a",}`, 889 }, { 890 value: `{_a:"a", b?: "b", #c: 3}`, 891 res: `{_a:"a",b?:"b",#c:3,}`, 892 }, { 893 // Issue #1879 894 value: `{a: 1, if false { b: 2 }}`, 895 res: `{a:1,}`, 896 }, { 897 value: `{a!:1,b?:2,c:3}`, 898 res: `{a!:1,b?:2,c:3,}`, 899 }} 900 for _, tc := range testCases { 901 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 902 obj := getValue(t, tc.value) 903 904 var iter *cue.Iterator // Verify that the returned iterator is a pointer. 905 iter, err := obj.Fields(cue.All()) 906 checkFatal(t.T, err, tc.err, "init") 907 908 buf := []byte{'{'} 909 for iter.Next() { 910 buf = append(buf, iter.Selector().String()...) 911 buf = append(buf, ':') 912 b, err := iter.Value().MarshalJSON() 913 checkFatal(t.T, err, tc.err, "Obj.At") 914 buf = append(buf, b...) 915 buf = append(buf, ',') 916 } 917 buf = append(buf, '}') 918 if got := string(buf); got != tc.res { 919 t.Errorf("got %v; want %v", got, tc.res) 920 } 921 }) 922 } 923 } 924 925 func TestFieldType(t *testing.T) { 926 testCases := []struct { 927 value string 928 want string 929 }{{ 930 value: `{a:1,"_b":2,c:3,_d:4,#def: 1}`, 931 want: ` 932 StringLabel 933 StringLabel 934 StringLabel 935 HiddenLabel 936 DefinitionLabel`, 937 }, { 938 value: `{a!:1,b?:2,c:3}`, 939 want: ` 940 StringLabel|RequiredConstraint 941 StringLabel|OptionalConstraint 942 StringLabel`, 943 }} 944 for _, tc := range testCases { 945 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 946 obj := getValue(t, tc.value) 947 948 iter, err := obj.Fields(cue.All()) 949 if err != nil { 950 t.Fatal(err) 951 } 952 953 b := &strings.Builder{} 954 for iter.Next() { 955 fmt.Fprint(b, "\n\t\t", iter.FieldType()) 956 } 957 if got := b.String(); got != tc.want { 958 t.Errorf("got:%v\nwant:%v", got, tc.want) 959 } 960 }) 961 } 962 } 963 964 func TestLookup(t *testing.T) { 965 cuetdtest.FullMatrix.Do(t, func(t *cuetdtest.M) { 966 ctx := t.Context() 967 val := mustCompile(t, ctx, ` 968 #V: { 969 x: int 970 } 971 #X: { 972 [string]: int64 973 } & #V 974 v: #X 975 976 a: { 977 b!: 1 978 c: 2 979 }`) 980 // expr, err := parser.ParseExpr("lookup.cue", `v`, parser.DeclarationErrors, parser.AllErrors) 981 // if err != nil { 982 // log.Fatalf("parseExpr: %v", err) 983 // } 984 // v := inst.Eval(expr) 985 986 type testCase struct { 987 ref []string 988 result string 989 syntax string 990 } 991 testCases := []testCase{{ 992 ref: []string{"a"}, 993 result: `{ 994 b!: 1 995 c: 2 996 }`, 997 syntax: "{b!: 1, c: 2}", 998 }, { 999 // Allow descending into structs even if it has a required field error. 1000 ref: []string{"a", "c"}, 1001 result: "2", 1002 syntax: "2", 1003 }, { 1004 ref: []string{"a", "b"}, 1005 result: "_|_ // a.b: field is required but not present", 1006 syntax: "1", 1007 }, { 1008 ref: []string{"v", "x"}, 1009 result: "int64", 1010 syntax: "int64", 1011 }} 1012 for _, tc := range testCases { 1013 t.Run("", func(t *testing.T) { 1014 v := val.Lookup(tc.ref...) 1015 1016 if got := fmt.Sprintf("%+v", v); got != tc.result { 1017 t.Errorf("got %v; want %v", got, tc.result) 1018 } 1019 1020 got := fmt.Sprint(astinternal.DebugStr(v.Eval().Syntax())) 1021 if got != tc.syntax { 1022 t.Errorf("got %v; want %v", got, tc.syntax) 1023 } 1024 1025 v = val.Lookup() 1026 for _, ref := range tc.ref { 1027 s, err := v.Struct() 1028 if err != nil { 1029 t.Fatal(err) 1030 } 1031 fi, err := s.FieldByName(ref, false) 1032 if err != nil { 1033 t.Fatal(err) 1034 } 1035 v = fi.Value 1036 1037 // Struct gets all fields. Skip tests with optional fields, 1038 // as the result will differ. 1039 if cue.ValueVertex(v).ArcType != adt.ArcMember { 1040 return 1041 } 1042 } 1043 1044 if got := fmt.Sprintf("%+v", v); got != tc.result { 1045 t.Errorf("got %v; want %v", got, tc.result) 1046 } 1047 1048 got = fmt.Sprint(astinternal.DebugStr(v.Eval().Syntax())) 1049 if got != tc.syntax { 1050 t.Errorf("got %v; want %v", got, tc.syntax) 1051 } 1052 }) 1053 } 1054 }) 1055 } 1056 1057 func goValue(v cue.Value) interface{} { 1058 var x interface{} 1059 err := v.Decode(&x) 1060 if err != nil { 1061 return err 1062 } 1063 return x 1064 } 1065 1066 // TODO: Exporting of Vertex as Conjunct 1067 func TestFill(t *testing.T) { 1068 // TODO: run with matrix. 1069 1070 ctx := cuecontext.New() 1071 1072 val := ctx.BuildExpr(ast.NewStruct("bar", ast.NewString("baz"))) 1073 if err := val.Err(); err != nil { 1074 t.Fatal(err) 1075 } 1076 1077 testCases := []struct { 1078 in string 1079 x interface{} 1080 path string // comma-separated path 1081 out string 1082 }{{ 1083 in: ` 1084 foo: int 1085 bar: foo 1086 `, 1087 x: 3, 1088 path: "foo", 1089 out: ` 1090 foo: 3 1091 bar: 3 1092 `, 1093 }, { 1094 in: ` 1095 string 1096 `, 1097 x: "foo", 1098 path: "", 1099 out: ` 1100 "foo" 1101 `, 1102 }, { 1103 in: ` 1104 foo: _ 1105 `, 1106 x: val, 1107 path: "foo", 1108 out: ` 1109 {foo: {bar: "baz"}} 1110 `, 1111 }} 1112 1113 for _, tc := range testCases { 1114 var path []string 1115 if tc.path != "" { 1116 path = strings.Split(tc.path, ",") 1117 } 1118 1119 v := mustCompile(t, ctx, tc.in) 1120 v = v.Fill(tc.x, path...) 1121 1122 w := mustCompile(t, ctx, tc.out) 1123 1124 if diff := cmp.Diff(goValue(v), goValue(w)); diff != "" { 1125 t.Error(diff) 1126 t.Errorf("\ngot: %s\nwant: %s", v, w) 1127 } 1128 } 1129 } 1130 1131 func TestFill2(t *testing.T) { 1132 // TODO: run with matrix. 1133 1134 ctx := cuecontext.New() 1135 1136 root := mustCompile(t, ctx, ` 1137 #Provider: { 1138 ID: string 1139 notConcrete: bool 1140 a: int 1141 b: int 1142 } 1143 `) 1144 1145 spec := root.LookupDef("#Provider") 1146 providerInstance := spec.Fill("12345", "ID") 1147 root = root.Fill(providerInstance, "providers", "myprovider") 1148 1149 got := fmt.Sprintf("%#v", root) 1150 want := `#Provider: { 1151 ID: string 1152 notConcrete: bool 1153 a: int 1154 b: int 1155 } 1156 providers: { 1157 myprovider: { 1158 ID: "12345" 1159 notConcrete: bool 1160 a: int 1161 b: int 1162 } 1163 }` 1164 if got != want { 1165 t.Errorf("got: %s\nwant: %s", got, want) 1166 } 1167 } 1168 1169 func TestFillPath(t *testing.T) { 1170 cuetdtest.FullMatrix.Do(t, func(t *cuetdtest.M) { 1171 ctx := t.Context() 1172 1173 val := ctx.BuildExpr(ast.NewStruct("bar", ast.NewString("baz"))) 1174 if err := val.Err(); err != nil { 1175 t.Fatal(err) 1176 } 1177 1178 testCases := []struct { 1179 in string 1180 x interface{} 1181 path cue.Path 1182 out string 1183 }{{ 1184 in: ` 1185 foo: int 1186 bar: foo 1187 `, 1188 x: 3, 1189 path: cue.ParsePath("foo"), 1190 out: ` 1191 foo: 3 1192 bar: 3 1193 `, 1194 }, { 1195 in: ` 1196 X="#foo": int 1197 bar: X 1198 `, 1199 x: 3, 1200 path: cue.ParsePath(`"#foo"`), 1201 out: ` 1202 "#foo": 3 1203 bar: 3 1204 `, 1205 }, { 1206 in: ` 1207 X="#foo": foo: int 1208 bar: X.foo 1209 `, 1210 x: 3, 1211 path: cue.ParsePath(`"#foo".foo`), 1212 out: ` 1213 "#foo": foo: 3 1214 bar: 3 1215 `, 1216 }, { 1217 in: ` 1218 foo: #foo: int 1219 bar: foo.#foo 1220 `, 1221 x: 3, 1222 path: cue.ParsePath("foo.#foo"), 1223 out: ` 1224 foo: { 1225 #foo: 3 1226 } 1227 bar: 3 1228 `, 1229 }, { 1230 in: ` 1231 foo: _foo: int 1232 bar: foo._foo 1233 `, 1234 x: 3, 1235 path: cue.MakePath(cue.Str("foo"), cue.Hid("_foo", "_")), 1236 out: ` 1237 foo: { 1238 _foo: 3 1239 } 1240 bar: 3 1241 `, 1242 }, { 1243 in: ` 1244 string 1245 `, 1246 x: "foo", 1247 path: cue.ParsePath(""), 1248 out: ` 1249 "foo" 1250 `, 1251 }, { 1252 in: ` 1253 foo: _ 1254 `, 1255 x: val, 1256 path: cue.ParsePath("foo"), 1257 out: ` 1258 {foo: {bar: "baz"}} 1259 `, 1260 }, { 1261 // Resolve to enclosing 1262 in: ` 1263 foo: _ 1264 x: 1 1265 `, 1266 x: ast.NewIdent("x"), 1267 path: cue.ParsePath("foo"), 1268 out: ` 1269 {foo: 1, x: 1} 1270 `, 1271 }, { 1272 in: ` 1273 foo: { 1274 bar: _ 1275 x: 1 1276 } 1277 `, 1278 x: ast.NewIdent("x"), 1279 path: cue.ParsePath("foo.bar"), 1280 out: ` 1281 {foo: {bar: 1, x: 1}} 1282 `, 1283 }, { 1284 // Resolve one scope up 1285 in: ` 1286 x: 1 1287 foo: { 1288 bar: _ 1289 } 1290 `, 1291 x: ast.NewIdent("x"), 1292 path: cue.ParsePath("foo.bar"), 1293 out: ` 1294 {foo: {bar: 1}, x: 1} 1295 `, 1296 }, { 1297 // Resolve within ast expression 1298 in: ` 1299 foo: { 1300 bar: _ 1301 } 1302 `, 1303 x: ast.NewStruct( 1304 ast.NewIdent("x"), ast.NewString("1"), 1305 ast.NewIdent("y"), ast.NewIdent("x"), 1306 ), 1307 path: cue.ParsePath("foo.bar"), 1308 out: ` 1309 {foo: {bar: {x: "1", y: "1"}}} 1310 `, 1311 }, { 1312 // Resolve in non-existing 1313 in: ` 1314 foo: x: 1 1315 `, 1316 x: ast.NewIdent("x"), 1317 path: cue.ParsePath("foo.bar.baz"), 1318 out: ` 1319 {foo: {x: 1, bar: baz: 1}} 1320 `, 1321 }, { 1322 // empty path 1323 in: ` 1324 _ 1325 #foo: 1 1326 `, 1327 x: ast.NewIdent("#foo"), 1328 out: `{1, #foo: 1}`, 1329 }, { 1330 in: `[...int]`, 1331 x: 1, 1332 path: cue.ParsePath("0"), 1333 out: `[1]`, 1334 }, { 1335 in: `[1, ...int]`, 1336 x: 1, 1337 path: cue.ParsePath("1"), 1338 out: `[1, 1]`, 1339 }, { 1340 in: `a: {b: v: int, c: v: int}`, 1341 x: 1, 1342 path: cue.MakePath(cue.Str("a"), cue.AnyString, cue.Str("v")), 1343 out: `{ 1344 a: { 1345 b: { 1346 v: 1 1347 } 1348 c: { 1349 v: 1 1350 } 1351 } 1352 }`, 1353 }, { 1354 in: `a: [_]`, 1355 x: 1, 1356 path: cue.MakePath(cue.Str("a"), cue.AnyIndex, cue.Str("b")), 1357 out: `{ 1358 a: [{ 1359 b: 1 1360 }] 1361 }`, 1362 }, { 1363 in: `a: 1`, 1364 x: 1, 1365 path: cue.MakePath(cue.Str("b").Optional()), 1366 out: `{a: 1}`, 1367 }, { 1368 in: `b: int`, 1369 x: 1, 1370 path: cue.MakePath(cue.Str("b").Optional()), 1371 out: `{b: 1}`, 1372 }} 1373 1374 for _, tc := range testCases { 1375 t.Run("", func(t *testing.T) { 1376 v := mustCompile(t, ctx, tc.in) 1377 v = v.FillPath(tc.path, tc.x) 1378 1379 w := mustCompile(t, ctx, tc.out) 1380 1381 if diff := cmp.Diff(goValue(v), goValue(w)); diff != "" { 1382 t.Error(diff) 1383 t.Error(cmp.Diff(goValue(v), goValue(w))) 1384 t.Errorf("\ngot: %s\nwant: %s", v, w) 1385 } 1386 }) 1387 } 1388 }) 1389 } 1390 1391 func TestFillPathError(t *testing.T) { 1392 testCases := []struct { 1393 in string 1394 x interface{} 1395 path cue.Path 1396 err string 1397 }{{ 1398 // unsupported type. 1399 in: `_`, 1400 x: make(chan int), 1401 err: "unsupported Go type (chan int)", 1402 }} 1403 1404 for _, tc := range testCases { 1405 cuetdtest.FullMatrix.Run(t, "", func(t *cuetdtest.M) { 1406 ctx := t.Context() 1407 v := mustCompile(t, ctx, tc.in) 1408 v = v.FillPath(tc.path, tc.x) 1409 1410 err := v.Err() 1411 if err == nil { 1412 t.Errorf("unexpected success") 1413 1414 } else if got := err.Error(); !strings.Contains(got, tc.err) { 1415 t.Errorf("\ngot: %s\nwant: %s", got, tc.err) 1416 } 1417 }) 1418 } 1419 } 1420 1421 func TestAllows(t *testing.T) { 1422 testCases := []struct { 1423 desc string 1424 in string 1425 sel cue.Selector 1426 allow bool 1427 1428 todo_nosharing bool 1429 }{{ 1430 desc: "allow new field in open struct", 1431 in: ` 1432 x: { 1433 a: int 1434 } 1435 `, 1436 sel: cue.Str("b"), 1437 allow: true, 1438 }, { 1439 desc: "disallow new field in definition", 1440 in: ` 1441 x: #Def 1442 #Def: { 1443 a: int 1444 } 1445 `, 1446 sel: cue.Str("b"), 1447 }, { 1448 desc: "disallow new field in explicitly closed struct", 1449 in: ` 1450 x: close({ 1451 a: int 1452 }) 1453 `, 1454 sel: cue.Str("b"), 1455 }, { 1456 desc: "allow field in pattern", 1457 in: ` 1458 x: #X 1459 #X: [>"a"]: 1 1460 `, 1461 sel: cue.Str("b"), 1462 allow: true, 1463 }, { 1464 desc: "allow index in open list", 1465 in: ` 1466 x: [...int] 1467 `, 1468 sel: cue.Index(100), 1469 allow: true, 1470 }, { 1471 desc: "disallow index in closed list", 1472 in: ` 1473 x: [] 1474 `, 1475 sel: cue.Index(0), 1476 }, { 1477 desc: "allow existing index in closed list", 1478 in: ` 1479 x: [1] 1480 `, 1481 sel: cue.Index(0), 1482 allow: true, 1483 }, { 1484 desc: "definition in non-def closed list", 1485 in: ` 1486 x: [1] 1487 `, 1488 sel: cue.Def("#foo"), 1489 allow: true, 1490 }, { 1491 // TODO(disallow) 1492 desc: "definition in def open list", 1493 in: ` 1494 x: #Def 1495 x: [1] 1496 #Def: [...int] 1497 `, 1498 sel: cue.Def("#foo"), 1499 allow: true, 1500 }, { 1501 desc: "field in def open list", 1502 in: ` 1503 x: #Def 1504 x: [1] 1505 #Def: [...int] 1506 `, 1507 sel: cue.Str("foo"), 1508 }, { 1509 desc: "definition in open scalar", 1510 in: ` 1511 x: 1 1512 `, 1513 sel: cue.Def("#foo"), 1514 allow: true, 1515 }, { 1516 desc: "field in scalar", 1517 in: ` 1518 x: #Def 1519 x: 1 1520 #Def: int 1521 `, 1522 sel: cue.Str("foo"), 1523 }, { 1524 desc: "any index in closed list", 1525 in: ` 1526 x: [1] 1527 `, 1528 sel: cue.AnyIndex, 1529 }, { 1530 desc: "any index in open list", 1531 in: ` 1532 x: [...int] 1533 `, 1534 sel: cue.AnyIndex, 1535 allow: true, 1536 }, { 1537 desc: "definition in open scalar", 1538 in: ` 1539 x: 1 1540 `, 1541 sel: cue.AnyDefinition, 1542 allow: true, 1543 }, { 1544 desc: "field in open scalar", 1545 in: ` 1546 x: 1 1547 `, 1548 sel: cue.AnyString, 1549 1550 // TODO(v0.6.0) 1551 // }, { 1552 // desc: "definition in closed scalar", 1553 // in: ` 1554 // x: #Def 1555 // x: 1 1556 // #Def: int 1557 // `, 1558 // sel: cue.AnyDefinition, 1559 // allow: true, 1560 }, { 1561 desc: "allow field in any", 1562 in: ` 1563 x: _ 1564 `, 1565 sel: cue.AnyString, 1566 allow: true, 1567 }, { 1568 desc: "allow index in any", 1569 in: ` 1570 x: _ 1571 `, 1572 sel: cue.AnyIndex, 1573 allow: true, 1574 }, { 1575 desc: "allow index in disjunction", 1576 in: ` 1577 x: [...int] | 1 1578 `, 1579 sel: cue.AnyIndex, 1580 allow: true, 1581 }, { 1582 desc: "allow index in disjunction", 1583 in: ` 1584 x: [] | [...int] 1585 `, 1586 sel: cue.AnyIndex, 1587 allow: true, 1588 }, { 1589 desc: "disallow index in disjunction", 1590 in: ` 1591 x: [1, 2] | [3, 2] 1592 `, 1593 sel: cue.AnyIndex, 1594 }, { 1595 desc: "disallow index in non-list disjunction", 1596 in: ` 1597 x: "foo" | 1 1598 `, 1599 sel: cue.AnyIndex, 1600 }, { 1601 desc: "allow label in disjunction", 1602 in: ` 1603 x: {} | 1 1604 `, 1605 sel: cue.AnyString, 1606 allow: true, 1607 }, { 1608 desc: "allow label in disjunction", 1609 in: ` 1610 x: #Def 1611 #Def: { a: 1 } | { b: 1, ... } 1612 `, 1613 sel: cue.AnyString, 1614 allow: true, 1615 1616 todo_nosharing: true, 1617 }, { 1618 desc: "disallow label in disjunction", 1619 in: ` 1620 x: #Def 1621 #Def: { a: 1 } | { b: 1 } 1622 `, 1623 sel: cue.AnyString, 1624 }, { 1625 desc: "pattern constraint", 1626 in: ` 1627 x: #PC 1628 #PC: [>"m"]: int 1629 `, 1630 sel: cue.Str(""), 1631 }, { 1632 desc: "pattern constraint", 1633 in: ` 1634 x: #PC 1635 #PC: [>"m"]: int 1636 `, 1637 sel: cue.Str("z"), 1638 allow: true, 1639 }, { 1640 desc: "any in pattern constraint", 1641 in: ` 1642 x: #PC 1643 #PC: [>"m"]: int 1644 `, 1645 sel: cue.AnyString, 1646 }, { 1647 desc: "any in pattern constraint", 1648 in: ` 1649 x: #PC 1650 #PC: [>" "]: int 1651 `, 1652 sel: cue.AnyString, 1653 }} 1654 1655 path := cue.ParsePath("x") 1656 1657 for _, tc := range testCases { 1658 cuetdtest.FullMatrix.Run(t, tc.desc, func(t *cuetdtest.M) { 1659 if tc.todo_nosharing { 1660 t.TODO_NoSharing() 1661 } 1662 ctx := t.Context() 1663 v := mustCompile(t, ctx, tc.in) 1664 v = v.LookupPath(path) 1665 1666 got := v.Allows(tc.sel) 1667 if got != tc.allow { 1668 t.Errorf("got %v; want %v", got, tc.allow) 1669 } 1670 }) 1671 } 1672 } 1673 1674 func TestFillFloat(t *testing.T) { 1675 // This tests panics for issue #749 1676 1677 want := `{ 1678 x: 3.14 1679 }` 1680 1681 filltest := func(x interface{}) { 1682 ctx := cuecontext.New() 1683 val := mustCompile(t, ctx, ` 1684 x: number 1685 `) 1686 val = val.Fill(x, "x") 1687 1688 got := fmt.Sprint(val) 1689 if got != want { 1690 t.Errorf("got: %s\nwant: %s", got, want) 1691 } 1692 } 1693 1694 filltest(float32(3.14)) 1695 filltest(float64(3.14)) 1696 filltest(big.NewFloat(3.14)) 1697 } 1698 1699 func TestValue_LookupDef(t *testing.T) { 1700 testCases := []struct { 1701 in string 1702 def string // comma-separated path 1703 exists bool 1704 out string 1705 }{{ 1706 in: `#foo: 3`, 1707 def: "#foo", 1708 out: `3`, 1709 }, { 1710 in: `_foo: 3`, 1711 def: "_foo", 1712 out: `_|_ // field not found: #_foo`, 1713 }, { 1714 in: `_#foo: 3`, 1715 def: "_#foo", 1716 out: `_|_ // field not found: _#foo`, 1717 }, { 1718 in: `"foo", #foo: 3`, 1719 def: "#foo", 1720 out: `3`, 1721 }} 1722 1723 for _, tc := range testCases { 1724 cuetdtest.FullMatrix.Run(t, tc.def, func(t *cuetdtest.M) { 1725 ctx := t.Context() 1726 v := mustCompile(t, ctx, tc.in) 1727 v = v.LookupDef(tc.def) 1728 got := fmt.Sprint(v) 1729 1730 if got != tc.out { 1731 t.Errorf("\ngot: %s\nwant: %s", got, tc.out) 1732 } 1733 }) 1734 } 1735 } 1736 1737 // TODO: trim down to individual defaults? 1738 func TestDefaults(t *testing.T) { 1739 testCases := []struct { 1740 value string 1741 def string 1742 val string 1743 ok bool 1744 }{{ 1745 value: `number | *1`, 1746 def: "1", 1747 val: "number", 1748 ok: true, 1749 }, { 1750 value: `1 | 2 | *3`, 1751 def: "3", 1752 val: "1|2|3", 1753 ok: true, 1754 }, { 1755 value: `*{a:1,b:2}|{a:1}|{b:2}`, 1756 def: "{a:1,b:2}", 1757 val: "{a: 1}|{b: 2}", 1758 ok: true, 1759 }, { 1760 value: `{a:1}&{b:2}`, 1761 def: `{a:1,b:2}`, 1762 val: ``, 1763 ok: false, 1764 }, { 1765 value: `*_|_ | (*"x" | string)`, 1766 def: `"x" | string`, 1767 val: `"x"|string`, 1768 ok: false, 1769 }} 1770 for _, tc := range testCases { 1771 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 1772 v := getValue(t, "a: "+tc.value).Lookup("a") 1773 1774 v = v.Eval() 1775 d, ok := v.Default() 1776 if ok != tc.ok { 1777 t.Errorf("hasDefault: got %v; want %v", ok, tc.ok) 1778 } 1779 1780 if got := compactRawStr(d); got != tc.def { 1781 t.Errorf("default: got %v; want %v", got, tc.def) 1782 } 1783 1784 op, val := d.Expr() 1785 if op != cue.OrOp { 1786 return 1787 } 1788 vars := []string{} 1789 for _, v := range val { 1790 vars = append(vars, fmt.Sprint(v)) 1791 } 1792 if got := strings.Join(vars, "|"); got != tc.val { 1793 t.Errorf("value: got %v; want %v", got, tc.val) 1794 } 1795 }) 1796 } 1797 } 1798 1799 func TestLen(t *testing.T) { 1800 testCases := []struct { 1801 input string 1802 length string 1803 }{{ 1804 input: "[1, 3]", 1805 length: "2", 1806 }, { 1807 input: "[1, 3, ...]", 1808 length: "int & >=2", 1809 }, { 1810 input: `"foo"`, 1811 length: "3", 1812 }, { 1813 input: `'foo'`, 1814 length: "3", 1815 // TODO: Currently not supported. 1816 // }, { 1817 // input: "{a:1, b:3, a:1, c?: 3, _hidden: 4}", 1818 // length: "2", 1819 }, { 1820 input: "3", 1821 length: "_|_ // len not supported for type int", 1822 }} 1823 for _, tc := range testCases { 1824 cuetdtest.FullMatrix.Run(t, tc.input, func(t *cuetdtest.M) { 1825 v := getValue(t, "a: "+tc.input).Lookup("a") 1826 1827 length := v.Len() 1828 if got := fmt.Sprint(length); got != tc.length { 1829 t.Errorf("length: got %v; want %v", got, tc.length) 1830 } 1831 }) 1832 } 1833 } 1834 1835 func TestTemplate(t *testing.T) { 1836 testCases := []struct { 1837 value string 1838 path []string 1839 want string 1840 1841 skip bool 1842 }{{ 1843 value: ` 1844 a: [Name=string]: Name 1845 `, 1846 path: []string{"a", ""}, 1847 want: `"label"`, 1848 }, { 1849 value: ` 1850 [Name=string]: { a: Name } 1851 `, 1852 path: []string{"", "a"}, 1853 want: `"label"`, 1854 }, { 1855 value: ` 1856 [Name=string]: { a: Name } 1857 `, 1858 path: []string{""}, 1859 want: `{"a":"label"}`, 1860 }, { 1861 value: ` 1862 a: [Foo=string]: [Bar=string]: { b: Foo+Bar } 1863 `, 1864 path: []string{"a", "", ""}, 1865 want: `{"b":"labellabel"}`, 1866 }, { 1867 value: ` 1868 a: [Foo=string]: b: [Bar=string]: { c: Foo+Bar } 1869 a: foo: b: [Bar=string]: { d: Bar } 1870 `, 1871 path: []string{"a", "foo", "b", ""}, 1872 want: `{"c":"foolabel","d":"label"}`, 1873 1874 skip: true, // TODO: reordering 1875 }} 1876 for _, tc := range testCases { 1877 cuetdtest.FullMatrix.Run(t, "", func(t *cuetdtest.M) { 1878 if tc.skip { 1879 t.TODO_V3() 1880 } 1881 1882 v := getValue(t, tc.value) 1883 for _, p := range tc.path { 1884 if p == "" { 1885 v = v.Template()("label") 1886 } else { 1887 v = v.Lookup(p) 1888 } 1889 } 1890 b, err := v.MarshalJSON() 1891 if err != nil { 1892 t.Fatal(err) 1893 } 1894 if got := string(b); got != tc.want { 1895 t.Errorf("\n got: %q\nwant: %q", got, tc.want) 1896 } 1897 }) 1898 } 1899 } 1900 1901 func TestElem(t *testing.T) { 1902 testCases := []struct { 1903 value string 1904 path []string 1905 want string 1906 skip bool 1907 }{{ 1908 value: ` 1909 a: [...int] 1910 `, 1911 path: []string{"a", ""}, 1912 want: `int`, 1913 }, { 1914 value: ` 1915 [Name=string]: { a: Name } 1916 `, 1917 path: []string{"", "a"}, 1918 want: `string`, 1919 }, { 1920 value: ` 1921 [Name=string]: { a: Name } 1922 `, 1923 path: []string{""}, 1924 want: "{\n\ta: string\n}", 1925 }, { 1926 value: ` 1927 a: [Foo=string]: [Bar=string]: { b: Foo+Bar } 1928 `, 1929 path: []string{"a", "", ""}, 1930 want: "{\n\tb: string + string\n}", 1931 }, { 1932 value: ` 1933 a: [Foo=string]: b: [Bar=string]: { c: Foo+Bar } 1934 a: foo: b: [Bar=string]: { d: Bar } 1935 `, 1936 path: []string{"a", "foo", "b", ""}, 1937 want: "{\n\tc: \"foo\" + string\n\td: string\n}", 1938 skip: true, // TODO(p3): Skip because this is just a reordering. 1939 }} 1940 for _, tc := range testCases { 1941 cuetdtest.FullMatrix.Run(t, "", func(t *cuetdtest.M) { 1942 if tc.skip { 1943 t.TODO_V3() 1944 } 1945 1946 v := getValue(t, tc.value) 1947 cue.ValueVertex(v).Finalize(cue.ValueCtx(v)) // TODO: do in instance. 1948 for _, p := range tc.path { 1949 if p == "" { 1950 var ok bool 1951 v, ok = v.Elem() 1952 if !ok { 1953 t.Fatal("expected element") 1954 } 1955 } else { 1956 v = v.Lookup(p) 1957 } 1958 } 1959 got := fmt.Sprint(v) 1960 // got := debug.NodeString(v.ctx(), v.v, &debug.Config{Compact: true}) 1961 if got != tc.want { 1962 t.Errorf("\n got: %q\nwant: %q", got, tc.want) 1963 } 1964 }) 1965 } 1966 } 1967 1968 func TestSubsume(t *testing.T) { 1969 a := cue.ParsePath("a") 1970 b := cue.ParsePath("b") 1971 testCases := []struct { 1972 value string 1973 pathA cue.Path 1974 pathB cue.Path 1975 options []cue.Option 1976 want bool 1977 }{{ 1978 value: `4`, 1979 want: true, 1980 }, { 1981 value: `a: string, b: "foo"`, 1982 pathA: a, 1983 pathB: b, 1984 want: true, 1985 }, { 1986 value: `a: string, b: "foo"`, 1987 pathA: b, 1988 pathB: a, 1989 want: false, 1990 }, { 1991 value: `a: {a: string, b: 4}, b: {a: "foo", b: 4}`, 1992 pathA: a, 1993 pathB: b, 1994 want: true, 1995 }, { 1996 value: `a: [string, 4], b: ["foo", 4]`, 1997 pathA: a, 1998 pathB: b, 1999 want: true, 2000 }, { 2001 value: `a: [...string], b: ["foo"]`, 2002 pathA: a, 2003 pathB: b, 2004 want: true, 2005 }, { 2006 value: `a: [...int], b: ["foo"]`, 2007 pathA: a, 2008 pathB: b, 2009 want: false, 2010 }, { 2011 // Issue #566 2012 // Closed struct subsuming open struct. 2013 value: ` 2014 #Run: { action: "run", command: [...string] } 2015 b: { action: "run", command: ["echo", "hello"] } 2016 `, 2017 pathA: cue.ParsePath("#Run"), 2018 pathB: b, 2019 2020 // NOTE: this is for v0.2 compatibility. Logically a closed struct 2021 // does not subsume an open struct. One could argue that the default 2022 // of an open struct is the closed struct with the minimal number 2023 // of fields that is an instance of it, though. 2024 want: true, // open struct is not subsumed by closed if not final. 2025 }, { 2026 // Issue #566 2027 // Closed struct subsuming open struct. 2028 value: ` 2029 #Run: { action: "run", command: [...string] } 2030 b: { action: "run", command: ["echo", "hello"] } 2031 `, 2032 pathA: cue.ParsePath("#Run"), 2033 pathB: b, 2034 options: []cue.Option{cue.Final()}, 2035 want: true, 2036 }, { 2037 // default 2038 value: ` 2039 a: <5 2040 b: *3 | int 2041 `, 2042 pathA: a, 2043 pathB: b, 2044 want: true, 2045 }, { 2046 // Disable default elimination. 2047 value: ` 2048 a: <5 2049 b: *3 | int 2050 `, 2051 pathA: a, 2052 pathB: b, 2053 options: []cue.Option{cue.Raw()}, 2054 want: false, 2055 }, { 2056 value: ` 2057 #A: { 2058 exact: string 2059 } | { 2060 regex: string 2061 } 2062 #B: { 2063 exact: string 2064 } | { 2065 regex: string 2066 } 2067 `, 2068 pathA: cue.ParsePath("#A"), 2069 pathB: cue.ParsePath("#B"), 2070 options: []cue.Option{}, 2071 want: true, 2072 }, { 2073 value: ` 2074 import "time" 2075 a: time.Format(time.ANSIC) 2076 b: 1 2077 `, 2078 pathA: a, 2079 pathB: b, 2080 want: false, 2081 }} 2082 for _, tc := range testCases { 2083 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 2084 v := getValue(t, tc.value) 2085 a := v.LookupPath(tc.pathA) 2086 b := v.LookupPath(tc.pathB) 2087 got := a.Subsume(b, tc.options...) == nil 2088 if got != tc.want { 2089 t.Errorf("got %v (%v); want %v (%v)", got, a, tc.want, b) 2090 } 2091 }) 2092 } 2093 } 2094 2095 func TestSubsumes(t *testing.T) { 2096 a := []string{"a"} 2097 b := []string{"b"} 2098 testCases := []struct { 2099 value string 2100 pathA []string 2101 pathB []string 2102 want bool 2103 }{{ 2104 value: `4`, 2105 want: true, 2106 }, { 2107 value: `a: string, b: "foo"`, 2108 pathA: a, 2109 pathB: b, 2110 want: true, 2111 }, { 2112 value: `a: string, b: "foo"`, 2113 pathA: b, 2114 pathB: a, 2115 want: false, 2116 }, { 2117 value: `a: {a: string, b: 4}, b: {a: "foo", b: 4}`, 2118 pathA: a, 2119 pathB: b, 2120 want: true, 2121 }, { 2122 value: `a: [string, 4], b: ["foo", 4]`, 2123 pathA: a, 2124 pathB: b, 2125 want: true, 2126 }, { 2127 value: `a: [...string], b: ["foo"]`, 2128 pathA: a, 2129 pathB: b, 2130 want: true, 2131 }, { 2132 value: `a: [...int], b: ["foo"]`, 2133 pathA: a, 2134 pathB: b, 2135 want: false, 2136 }, { 2137 value: ` 2138 a: { action: "run", command: [...string] } 2139 b: { action: "run", command: ["echo", "hello"] } 2140 `, 2141 pathA: a, 2142 pathB: b, 2143 want: true, 2144 }} 2145 for _, tc := range testCases { 2146 cuetdtest.FullMatrix.Run(t, tc.value, func(t *cuetdtest.M) { 2147 v := getValue(t, tc.value) 2148 a := v.Lookup(tc.pathA...) 2149 b := v.Lookup(tc.pathB...) 2150 got := a.Subsumes(b) 2151 if got != tc.want { 2152 t.Errorf("got %v (%v); want %v (%v)", got, a, tc.want, b) 2153 } 2154 }) 2155 } 2156 } 2157 2158 func TestUnify(t *testing.T) { 2159 a := "a" 2160 b := "b" 2161 type testCase struct { 2162 value string 2163 pathA string 2164 pathB string 2165 want string 2166 } 2167 testCases := []testCase{{ 2168 value: `4`, 2169 want: `4`, 2170 }, { 2171 value: `a: string, b: "foo"`, 2172 pathA: a, 2173 pathB: b, 2174 want: `"foo"`, 2175 }, { 2176 value: `a: string, b: "foo"`, 2177 pathA: b, 2178 pathB: a, 2179 want: `"foo"`, 2180 }, { 2181 value: `a: {a: string, b: 4}, b: {a: "foo", b: 4}`, 2182 pathA: a, 2183 pathB: b, 2184 want: `{"a":"foo","b":4}`, 2185 }, { 2186 value: `a: [string, 4], b: ["foo", 4]`, 2187 pathA: a, 2188 pathB: b, 2189 want: `["foo",4]`, 2190 }, { 2191 value: `a: {a: string, _hidden: int, _#hidden: int}, b: close({a: "foo"})`, 2192 pathA: a, 2193 pathB: b, 2194 want: `{"a":"foo"}`, 2195 }, { 2196 // Issue #2325: let should not result in a closedness error. 2197 value: `#T: { 2198 ... 2199 } 2200 b: { 2201 let foobar = {} 2202 _fb: foobar 2203 }`, 2204 pathA: "#T", 2205 pathB: b, 2206 want: `{}`, 2207 }, { 2208 value: ` 2209 a: #A: "foo" 2210 #B: {...} 2211 `, 2212 pathA: a, 2213 pathB: "#B", 2214 want: `{}`, 2215 }} 2216 // TODO(tdtest): use cuetest.Run when supported. 2217 cuetdtest.FullMatrix.Do(t, func(m *cuetdtest.M) { 2218 tdtest.Run(t, testCases, func(t *cuetest.T, tc *testCase) { 2219 v := getValue(m, tc.value) 2220 x := v.LookupPath(cue.ParsePath(tc.pathA)) 2221 y := v.LookupPath(cue.ParsePath(tc.pathB)) 2222 b, err := x.Unify(y).MarshalJSON() 2223 if err != nil { 2224 t.Fatal(err) 2225 } 2226 t.Equal(string(b), tc.want) 2227 }) 2228 }) 2229 } 2230 2231 func TestUnifyAccept(t *testing.T) { 2232 type testCase struct { 2233 value string 2234 want string 2235 } 2236 testCases := []testCase{{ 2237 value: `#v: 4, #w: 4, #accept: int`, 2238 want: `4`, 2239 }, { 2240 value: `#v: string, #w: "foo", #accept: string`, 2241 want: `"foo"`, 2242 }, { 2243 value: `#v: {a: "foo"}, #w: {b: 4}, #accept: {a: string, b: int}`, 2244 want: `{"a":"foo","b":4}`, 2245 }, { 2246 value: `#v: [string, 4], #w: ["foo", 4], #accept: [string, int, ...]`, 2247 want: `["foo",4]`, 2248 }, { 2249 value: `#v: {a: string, b: 1, _#hidden: int}, #w: {a: "foo"}, #accept: {...}`, 2250 want: `{"a":"foo","b":1}`, 2251 }, { 2252 // Issue #2325: let should not result in a closedness error. 2253 value: `#accept: { 2254 ... 2255 } 2256 #v: {} 2257 #w: { 2258 let foobar = {} 2259 _fb: foobar 2260 }`, 2261 want: `{}`, 2262 }, { 2263 value: ` 2264 #v: #v: "foo" 2265 #w: {b:1} 2266 #accept: {...} 2267 `, 2268 want: `{"b":1}`, 2269 }} 2270 // TODO(tdtest): use cuetest.Run when supported. 2271 cuetdtest.FullMatrix.Do(t, func(m *cuetdtest.M) { 2272 tdtest.Run(t, testCases, func(t *cuetest.T, tc *testCase) { 2273 v := getValue(m, tc.value) 2274 x := v.LookupPath(cue.ParsePath("#v")) 2275 y := v.LookupPath(cue.ParsePath("#w")) 2276 a := v.LookupPath(cue.ParsePath("#accept")) 2277 b, err := x.UnifyAccept(y, a).MarshalJSON() 2278 if err != nil { 2279 t.Fatal(err) 2280 } 2281 t.Equal(string(b), tc.want) 2282 }) 2283 }) 2284 } 2285 2286 func TestEquals(t *testing.T) { 2287 testCases := []struct { 2288 a, b string 2289 want bool 2290 }{{ 2291 `4`, `4`, true, 2292 }, { 2293 `"str"`, `2`, false, 2294 }, { 2295 `2`, `3`, false, 2296 }, { 2297 `[1]`, `[3]`, false, 2298 }, { 2299 `[{a: 1,...}]`, `[{a: 1,...}]`, true, 2300 }, { 2301 `[]`, `[]`, true, 2302 }, { 2303 `{ 2304 a: b, 2305 b: a, 2306 }`, 2307 `{ 2308 a: b, 2309 b: a, 2310 }`, 2311 true, 2312 }, { 2313 `{ 2314 a: "foo", 2315 b: "bar", 2316 }`, 2317 `{ 2318 a: "foo", 2319 }`, 2320 false, 2321 }, { 2322 // Ignore closedness 2323 `{ #Foo: { k: 1 }, a: #Foo }`, 2324 `{ #Foo: { k: 1 }, a: { k: 1 } }`, 2325 true, 2326 }, { 2327 // Ignore optional fields 2328 `{ #Foo: { k: 1 }, a: #Foo }`, 2329 `{ #Foo: { k: 1 }, a: { #Foo, i?: 1 } }`, 2330 true, 2331 }, { 2332 // Treat embedding as equal 2333 `{ a: 2, b: { 3 } }`, 2334 `{ a: { 2 }, b: 3 }`, 2335 true, 2336 }} 2337 for _, tc := range testCases { 2338 cuetdtest.FullMatrix.Run(t, "", func(t *cuetdtest.M) { 2339 ctx := t.Context() 2340 2341 a := mustCompile(t, ctx, tc.a) 2342 b := mustCompile(t, ctx, tc.b) 2343 got := a.Equals(b) 2344 if got != tc.want { 2345 t.Errorf("got %v; want %v", got, tc.want) 2346 } 2347 }) 2348 } 2349 } 2350 2351 // TODO: options: disallow cycles. 2352 func TestValidate(t *testing.T) { 2353 testCases := []struct { 2354 desc string 2355 in string 2356 err bool 2357 opts []cue.Option 2358 2359 skip bool 2360 }{{ 2361 desc: "issue #51", 2362 in: ` 2363 a: [string]: foo 2364 a: b: {} 2365 `, 2366 err: true, 2367 }, { 2368 desc: "concrete", 2369 in: ` 2370 a: 1 2371 b: { c: 2, d: 3 } 2372 c: d: e: f: 5 2373 g?: int 2374 `, 2375 opts: []cue.Option{cue.Concrete(true)}, 2376 }, { 2377 desc: "definition error", 2378 in: ` 2379 #b: 1 & 2 2380 `, 2381 opts: []cue.Option{}, 2382 err: true, 2383 }, { 2384 desc: "definition error okay if optional", 2385 in: ` 2386 #b?: 1 & 2 2387 `, 2388 opts: []cue.Option{}, 2389 }, { 2390 desc: "definition with optional", 2391 in: ` 2392 #b: { 2393 a: int 2394 b?: >=0 2395 } 2396 `, 2397 opts: []cue.Option{cue.Concrete(true)}, 2398 }, { 2399 desc: "disjunction", 2400 in: `a: 1 | 2`, 2401 }, { 2402 desc: "disjunction concrete", 2403 in: `a: 1 | 2`, 2404 opts: []cue.Option{cue.Concrete(true)}, 2405 err: true, 2406 }, { 2407 desc: "incomplete concrete", 2408 in: `a: string`, 2409 }, { 2410 desc: "incomplete", 2411 in: `a: string`, 2412 opts: []cue.Option{cue.Concrete(true)}, 2413 err: true, 2414 }, { 2415 desc: "list", 2416 in: `a: [{b: string}, 3]`, 2417 }, { 2418 desc: "list concrete", 2419 in: `a: [{b: string}, 3]`, 2420 opts: []cue.Option{cue.Concrete(true)}, 2421 err: true, 2422 }, { 2423 desc: "allow cycles", 2424 in: ` 2425 a: b - 100 2426 b: a + 100 2427 c: [c[1], c[0]] 2428 `, 2429 }, { 2430 desc: "disallow cycles", 2431 in: ` 2432 a: b - 100 2433 b: a + 100 2434 c: [c[1], c[0]] 2435 `, 2436 opts: []cue.Option{cue.DisallowCycles(true)}, 2437 err: true, 2438 2439 // TODO: in the new evaluator these are not considered to be cycles 2440 // but rather incomplete errors. This is actually the correct behavior 2441 // and the old evaluator treats errors like this by default. 2442 // The option tested in this test was added for backwards compatibility 2443 // when the old evaluator was made to treat these kinds of errors 2444 // equally. With the new evaluator we can no longer distinguish these 2445 // errors. For now, at least. Consider what to do with this option. 2446 skip: true, 2447 }, { 2448 desc: "builtins are okay", 2449 in: ` 2450 import "time" 2451 2452 a: { b: time.Duration } | { c: time.Duration } 2453 `, 2454 }, { 2455 desc: "comprehension error", 2456 in: ` 2457 a: { if b == "foo" { field: 2 } } 2458 `, 2459 err: true, 2460 }, { 2461 desc: "ignore optional in schema", 2462 in: ` 2463 #Schema1: { 2464 a?: int 2465 } 2466 instance1: #Schema1 2467 `, 2468 opts: []cue.Option{cue.Concrete(true)}, 2469 }, { 2470 desc: "issue324", 2471 in: ` 2472 import "encoding/yaml" 2473 2474 x: string 2475 a: b: c: *["\(x)"] | _ 2476 d: yaml.Marshal(a.b) 2477 `, 2478 }, { 2479 desc: "allow non-concrete values for definitions", 2480 in: ` 2481 variables: #variables 2482 2483 {[!~"^[.]"]: #job} 2484 2485 #variables: [string]: int | string 2486 2487 #job: ({a: int} | {b: int}) & { 2488 "variables"?: #variables 2489 } 2490 `, 2491 }} 2492 for _, tc := range testCases { 2493 cuetdtest.FullMatrix.Run(t, tc.desc, func(t *cuetdtest.M) { 2494 if tc.skip { 2495 t.TODO_V3() 2496 } 2497 2498 ctx := t.Context() 2499 val := ctx.CompileString(tc.in, cue.Filename("validate")) 2500 err := val.Validate(tc.opts...) 2501 if gotErr := err != nil; gotErr != tc.err { 2502 t.Errorf("got %v; want %v", err, tc.err) 2503 } 2504 }) 2505 } 2506 } 2507 2508 func TestPath(t *testing.T) { 2509 config := ` 2510 a: b: c: 5 2511 b: { 2512 b1: 3 2513 b2: 4 2514 "b 3": 5 2515 "4b": 6 2516 l: [ 2517 {a: 2}, 2518 {c: 2}, 2519 ] 2520 } 2521 ` 2522 mkpath := func(p ...string) []string { return p } 2523 testCases := [][]string{ 2524 mkpath("a", "b", "c"), 2525 mkpath("b", "l", "1", "c"), 2526 mkpath("b", `"b 3"`), 2527 mkpath("b", `"4b"`), 2528 } 2529 for _, tc := range testCases { 2530 cuetdtest.FullMatrix.Run(t, strings.Join(tc, "."), func(t *cuetdtest.M) { 2531 ctx := t.Context() 2532 val := mustCompile(t, ctx, config) 2533 2534 v := val.Lookup(tc[0]) 2535 for _, e := range tc[1:] { 2536 if '0' <= e[0] && e[0] <= '9' { 2537 i, err := strconv.Atoi(e) 2538 if err != nil { 2539 t.Fatal(err) 2540 } 2541 iter, err := v.List() 2542 if err != nil { 2543 t.Fatal(err) 2544 } 2545 for c := 0; iter.Next(); c++ { 2546 if c == i { 2547 v = iter.Value() 2548 break 2549 } 2550 } 2551 } else if e[0] == '"' { 2552 v = v.Lookup(e[1 : len(e)-1]) 2553 } else { 2554 v = v.Lookup(e) 2555 } 2556 } 2557 got := cue.PathToStrings(v.Path()) 2558 if !reflect.DeepEqual(got, tc) { 2559 t.Errorf("got %v; want %v", got, tc) 2560 } 2561 }) 2562 } 2563 } 2564 2565 func TestValueLookup(t *testing.T) { 2566 config := ` 2567 a: { 2568 a: 0 2569 b: 1 2570 c: 2 2571 } 2572 b: { 2573 d: a.a 2574 e: int 2575 } 2576 ` 2577 2578 strList := func(s ...string) []string { return s } 2579 2580 testCases := []struct { 2581 config string 2582 path []string 2583 str string 2584 notExists bool 2585 }{{ 2586 config: "_|_", 2587 path: strList(""), 2588 str: "explicit error (_|_ literal) in source", 2589 }, { 2590 config: "_|_", 2591 path: strList("a"), 2592 str: "explicit error (_|_ literal) in source", 2593 }, { 2594 config: config, 2595 path: strList(), 2596 str: "{a:{a:0,b:1,c:2},b:{d:0,e:int}", 2597 }, { 2598 config: config, 2599 path: strList("a", "a"), 2600 str: "0", 2601 }, { 2602 config: config, 2603 path: strList("a"), 2604 str: "{a:0,b:1,c:2}", 2605 }, { 2606 config: config, 2607 path: strList("b", "d"), 2608 str: "0", 2609 }, { 2610 config: config, 2611 path: strList("c", "non-existing"), 2612 str: "not found", 2613 notExists: true, 2614 }, { 2615 config: config, 2616 path: strList("b", "d", "lookup in non-struct"), 2617 str: "cannot use value 0 (type int) as struct", 2618 }} 2619 for _, tc := range testCases { 2620 cuetdtest.FullMatrix.Run(t, tc.str, func(t *cuetdtest.M) { 2621 v := getValue(t, tc.config).Lookup(tc.path...) 2622 if got := !v.Exists(); got != tc.notExists { 2623 t.Errorf("exists: got %v; want %v", got, tc.notExists) 2624 } 2625 2626 got := cue.ValueCtx(v).Str(cue.ValueVertex(v)) 2627 if tc.str == "" { 2628 t.Fatalf("str empty, got %q", got) 2629 } 2630 if !strings.Contains(got, tc.str) { 2631 t.Errorf("\n got %v\nwant %v", got, tc.str) 2632 } 2633 }) 2634 } 2635 } 2636 2637 func cmpError(a, b error) bool { 2638 if a == nil { 2639 return b == nil 2640 } 2641 if b == nil { 2642 return a == nil 2643 } 2644 return a.Error() == b.Error() 2645 } 2646 2647 // TODO: duplicate docs. 2648 func TestValueDoc(t *testing.T) { 2649 const config = ` 2650 // foobar defines at least foo. 2651 package foobar 2652 2653 // A Foo fooses stuff. 2654 Foo: { 2655 // field1 is an int. 2656 field1: int 2657 2658 field2: int 2659 2660 // duplicate field comment 2661 dup3: int 2662 } 2663 2664 // foos are instances of Foo. 2665 foos: [string]: Foo 2666 2667 // My first little foo. 2668 foos: MyFoo: { 2669 // local field comment. 2670 field1: 0 2671 2672 // Dangling comment. 2673 2674 // other field comment. 2675 field2: 1 2676 2677 // duplicate field comment 2678 dup3: int 2679 } 2680 2681 bar: { 2682 // comment from bar on field 1 2683 field1: int 2684 // comment from bar on field 2 2685 field2: int // don't include this 2686 } 2687 2688 baz: bar & { 2689 // comment from baz on field 1 2690 field1: int 2691 field2: int 2692 } 2693 ` 2694 config2 := ` 2695 // Another Foo. 2696 Foo: {} 2697 ` 2698 2699 cuetdtest.FullMatrix.Do(t, func(t *cuetdtest.M) { 2700 ctx := t.Context() 2701 v1 := mustCompile(t, ctx, config) 2702 v2 := mustCompile(t, ctx, config2) 2703 both := v1.Unify(v2) 2704 2705 testCases := []struct { 2706 val cue.Value 2707 path string 2708 doc string 2709 skip bool 2710 }{{ 2711 val: v1, 2712 path: "foos", 2713 doc: "foos are instances of Foo.\n", 2714 }, { 2715 val: v1, 2716 path: "foos MyFoo", 2717 doc: "My first little foo.\n", 2718 }, { 2719 val: v1, 2720 path: "foos MyFoo field1", 2721 doc: `local field comment. 2722 2723 field1 is an int. 2724 `, 2725 }, { 2726 val: v1, 2727 path: "foos MyFoo field2", 2728 doc: "other field comment.\n", 2729 }, { 2730 // Duplicates are now removed. 2731 val: v1, 2732 path: "foos MyFoo dup3", 2733 doc: "duplicate field comment\n", 2734 }, { 2735 val: v1, 2736 path: "bar field1", 2737 doc: "comment from bar on field 1\n", 2738 }, { 2739 val: v1, 2740 path: "baz field1", 2741 doc: `comment from bar on field 1 2742 2743 comment from baz on field 1 2744 `, 2745 // New evaluaotor orders the comments differently (arguably better). 2746 skip: true, 2747 }, { 2748 val: v1, 2749 path: "baz field2", 2750 doc: "comment from bar on field 2\n", 2751 }, { 2752 val: v2, 2753 path: "Foo", 2754 doc: `Another Foo. 2755 `, 2756 }, { 2757 val: both, 2758 path: "Foo", 2759 doc: `A Foo fooses stuff. 2760 2761 Another Foo. 2762 `, 2763 }} 2764 for _, tc := range testCases { 2765 if tc.skip { 2766 t.TODO_V3() 2767 } 2768 t.Run("field:"+tc.path, func(t *testing.T) { 2769 v := tc.val.Lookup(strings.Split(tc.path, " ")...) 2770 doc := docStr(v.Doc()) 2771 if doc != tc.doc { 2772 t.Errorf("doc: got:\n%vwant:\n%v", doc, tc.doc) 2773 } 2774 }) 2775 } 2776 want := "foobar defines at least foo.\n" 2777 if got := docStr(v1.Doc()); got != want { 2778 t.Errorf("pkg: got:\n%vwant:\n%v", got, want) 2779 } 2780 }) 2781 } 2782 2783 func docStr(docs []*ast.CommentGroup) string { 2784 doc := "" 2785 for _, d := range docs { 2786 if doc != "" { 2787 doc += "\n" 2788 } 2789 doc += d.Text() 2790 } 2791 return doc 2792 } 2793 2794 // TODO: unwrap marshal error 2795 // TODO: improve error messages 2796 func TestMarshalJSON(t *testing.T) { 2797 type testCase struct { 2798 value string 2799 json string 2800 err string 2801 } 2802 testCases := []testCase{{ 2803 value: `""`, 2804 json: `""`, 2805 }, { 2806 value: `null`, 2807 json: `null`, 2808 }, { 2809 value: `_|_`, 2810 err: "explicit error (_|_ literal) in source", 2811 }, { 2812 value: `(a.b) 2813 a: {}`, 2814 err: "undefined field", 2815 }, { 2816 value: `true`, 2817 json: `true`, 2818 }, { 2819 value: `false`, 2820 json: `false`, 2821 }, { 2822 value: `bool`, 2823 err: "cannot convert incomplete value", 2824 }, { 2825 value: `"str"`, 2826 json: `"str"`, 2827 }, { 2828 value: `12_000`, 2829 json: `12000`, 2830 }, { 2831 value: `12.000`, 2832 json: `12.000`, 2833 }, { 2834 value: `12M`, 2835 json: `12000000`, 2836 }, { 2837 value: `3.0e100`, 2838 json: `3.0E+100`, 2839 }, { 2840 value: `0/0`, 2841 err: "division undefined", 2842 }, { 2843 value: `[]`, 2844 json: `[]`, 2845 }, { 2846 value: `[1, 2, 3]`, 2847 json: `[1,2,3]`, 2848 }, { 2849 value: `[int]`, 2850 err: `0: cannot convert incomplete value`, 2851 }, { 2852 value: `{}`, 2853 json: `{}`, 2854 }, { 2855 value: `{a: 2, b: 3, c: ["A", "B"]}`, 2856 json: `{"a":2,"b":3,"c":["A","B"]}`, 2857 }, { 2858 value: `{a: 2, b: 3, c: [string, "B"]}`, 2859 err: `c.0: cannot convert incomplete value`, 2860 }, { 2861 value: `{a: [{b: [0, {c: string}] }] }`, 2862 err: `a.0.b.1.c: cannot convert incomplete value`, 2863 }, { 2864 value: `{foo?: 1, bar?: 2, baz: 3}`, 2865 json: `{"baz":3}`, 2866 }, { 2867 value: `{foo!: 1, bar: 2}`, 2868 err: "cue: marshal error: foo: field is required but not present", 2869 }, { 2870 // Has an unresolved cycle, but should not matter as all fields involved 2871 // are optional 2872 value: `{foo?: bar, bar?: foo, baz: 3}`, 2873 json: `{"baz":3}`, 2874 }, { 2875 // Issue #107 2876 value: `a: 1.0/1`, 2877 json: `{"a":1.0}`, 2878 }, { 2879 // Issue #108 2880 value: ` 2881 a: int 2882 a: >0 2883 a: <2 2884 2885 b: int 2886 b: >=0.9 2887 b: <1.1 2888 2889 c: int 2890 c: >1 2891 c: <=2 2892 2893 d: int 2894 d: >=1 2895 d: <=1.5 2896 2897 e: int 2898 e: >=1 2899 e: <=1.32 2900 2901 f: >=1.1 & <=1.1 2902 `, 2903 json: `{"a":1,"b":1,"c":2,"d":1,"e":1,"f":1.1}`, 2904 }, { 2905 value: ` 2906 #Task: { 2907 { 2908 op: "pull" 2909 tag: *"latest" | string 2910 tagInString: tag + "dd" 2911 } | { 2912 op: "scratch" 2913 } 2914 } 2915 2916 foo: #Task & {"op": "pull"} 2917 `, 2918 json: `{"foo":{"op":"pull","tag":"latest","tagInString":"latestdd"}}`, 2919 }, { 2920 // Issue #326 2921 value: `x: "\(string)": "v"`, 2922 err: `x: invalid interpolation`, 2923 }, { 2924 // Issue #326 2925 value: `x: "\(bool)": "v"`, 2926 err: `invalid interpolation`, 2927 }, { 2928 // Issue #326 2929 value: ` 2930 x: { 2931 for k, v in y { 2932 "\(k)": v 2933 } 2934 } 2935 y: {} 2936 `, 2937 json: `{"x":{},"y":{}}`, 2938 }, { 2939 // Issue #326 2940 value: ` 2941 x: { 2942 for k, v in y { 2943 "\(k)": v 2944 } 2945 } 2946 y: _ 2947 `, 2948 err: `x: cannot range over y (incomplete type _)`, 2949 }, { 2950 value: ` 2951 package foo 2952 2953 #SomeBaseType: { 2954 "a" | "b" 2955 #AUTO: "z" 2956 } 2957 2958 V1: ("x" | "y") | *"z" 2959 V2: ("x" | "y") | *#SomeBaseType.#AUTO 2960 `, 2961 err: "cue: marshal error: V2: cannot convert incomplete value \"|((string){ \\\"x\\\" }, (string){ \\\"y\\\" })\" to JSON", 2962 }} 2963 for i, tc := range testCases { 2964 cuetdtest.FullMatrix.Run(t, fmt.Sprintf("%d/%v", i, tc.value), func(t *cuetdtest.M) { 2965 t.TODO_V3() 2966 2967 val := getValue(t, tc.value) 2968 b, err := val.MarshalJSON() 2969 checkFatal(t.T, err, tc.err, "init") 2970 2971 if got := string(b); got != tc.json { 2972 t.Errorf("\n got %v;\nwant %v", got, tc.json) 2973 } 2974 }) 2975 } 2976 } 2977 2978 func TestWalk(t *testing.T) { 2979 testCases := []struct { 2980 value string 2981 out string 2982 }{{ 2983 value: `""`, 2984 out: `""`, 2985 }, { 2986 value: `null`, 2987 out: `null`, 2988 }, { 2989 value: `_|_`, 2990 out: "_|_(explicit error (_|_ literal) in source)", 2991 }, { 2992 value: `(a.b) 2993 a: {}`, 2994 out: `_|_(undefined field: b)`, 2995 }, { 2996 value: `true`, 2997 out: `true`, 2998 }, { 2999 value: `false`, 3000 out: `false`, 3001 }, { 3002 value: `bool`, 3003 out: "bool", 3004 }, { 3005 value: `"str"`, 3006 out: `"str"`, 3007 }, { 3008 value: `12_000`, 3009 out: `12000`, 3010 // out: `12_000`, 3011 }, { 3012 value: `12.000`, 3013 out: `12.000`, 3014 }, { 3015 value: `12M`, 3016 out: `12000000`, 3017 // out: `12M`, 3018 }, { 3019 value: `3.0e100`, 3020 out: `3.0e+100`, 3021 // out: `3.0e100`, 3022 }, { 3023 value: `[]`, 3024 out: `[]`, 3025 }, { 3026 value: `[1, 2, 3]`, 3027 out: `[1,2,3]`, 3028 }, { 3029 value: `[int]`, 3030 out: `[int]`, 3031 }, { 3032 value: `3 * [1, 2]`, 3033 out: `[1,2,1,2,1,2]`, 3034 }, { 3035 value: `{}`, 3036 out: `{}`, 3037 }, { 3038 value: `{a: 2, b: 3, c: ["A", "B"]}`, 3039 out: `{a:2,b:3,c:["A","B"]}`, 3040 }} 3041 for i, tc := range testCases { 3042 cuetdtest.FullMatrix.Run(t, fmt.Sprintf("%d/%v", i, tc.value), func(t *cuetdtest.M) { 3043 t.TODO_V3() 3044 3045 val := getValue(t, tc.value) 3046 buf := []byte{} 3047 stripComma := func() { 3048 if n := len(buf) - 1; buf[n] == ',' { 3049 buf = buf[:n] 3050 } 3051 } 3052 val.Walk(func(v cue.Value) bool { 3053 v = v.Eval() 3054 if !cue.ValueVertex(v).Label.IsInt() { 3055 if k, ok := v.Label(); ok { 3056 buf = append(buf, k+":"...) 3057 } 3058 } 3059 switch v.Kind() { 3060 case cue.StructKind: 3061 buf = append(buf, '{') 3062 case cue.ListKind: 3063 buf = append(buf, '[') 3064 default: 3065 if b := cue.ValueVertex(v).Bottom(); b != nil { 3066 s := cue.DebugStr(cue.ValueCtx(v), b) 3067 buf = append(buf, fmt.Sprint(s, ",")...) 3068 return true 3069 } 3070 buf = append(buf, fmt.Sprint(v, ",")...) 3071 } 3072 return true 3073 }, func(v cue.Value) { 3074 switch v.Kind() { 3075 case cue.StructKind: 3076 stripComma() 3077 buf = append(buf, "},"...) 3078 case cue.ListKind: 3079 stripComma() 3080 buf = append(buf, "],"...) 3081 } 3082 }) 3083 stripComma() 3084 if got := string(buf); got != tc.out { 3085 t.Errorf("\n got %v;\nwant %v", got, tc.out) 3086 } 3087 }) 3088 } 3089 } 3090 3091 func TestReferencePath(t *testing.T) { 3092 testCases := []struct { 3093 input string 3094 want string 3095 wantImportPath string 3096 alt string 3097 }{{ 3098 input: "v: w: x: _|_", 3099 want: "", 3100 }, { 3101 input: "v: w: x: 2", 3102 want: "", 3103 }, { 3104 input: "v: w: x: a, a: 1", 3105 want: "a", 3106 }, { 3107 input: "v: w: x: a.b.c, a: b: c: 1", 3108 want: "a.b.c", 3109 }, { 3110 input: "if true { v: w: x: a, a: 1 }", 3111 want: "a", 3112 }, { 3113 input: "v: w: x: w.a.b.c, v: w: a: b: c: 1", 3114 want: "v.w.a.b.c", 3115 }, { 3116 input: `v: w: x: w.a.b.c, v: w: a: b: c: 1, #D: 3, opt?: 3, "v\(#D)": 3, X: {a: 3}, X`, 3117 want: "v.w.a.b.c", 3118 }, { 3119 input: ` 3120 v: w: x: w.a[bb]["c"] 3121 v: w: a: b: c: 1 3122 bb: "b"`, 3123 want: "v.w.a.b.c", 3124 }, { 3125 input: ` 3126 X="\(y)": 1 3127 v: w: x: X // TODO: Move up for crash 3128 y: "foo"`, 3129 want: "foo", 3130 }, { 3131 input: ` 3132 v: w: _ 3133 v: [X=string]: x: a[X] 3134 a: w: 1`, 3135 want: "a.w", 3136 }, { 3137 input: `v: { 3138 for t in src { 3139 w: "t\(t)": 1 3140 w: "\(t)": w["t\(t)"] 3141 } 3142 }, 3143 src: ["x", "y"]`, 3144 want: "v.w.tx", 3145 }, { 3146 input: ` 3147 v: w: x: a 3148 a: 1 3149 for i in [] { 3150 } 3151 `, 3152 want: "a", 3153 }, { 3154 input: ` 3155 v: w: close({x: a}) 3156 a: 1 3157 `, 3158 want: "a", 3159 }, { 3160 input: ` 3161 import "math" 3162 3163 v: w: x: math.Pi 3164 `, 3165 want: "Pi", 3166 wantImportPath: "math", 3167 alt: "3.14159265358979323846264338327950288419716939937510582097494459", 3168 }} 3169 for _, tc := range testCases { 3170 cuetdtest.FullMatrix.Run(t, "", func(t *cuetdtest.M) { 3171 ctx := t.Context() 3172 3173 val := ctx.CompileString(tc.input, cue.Filename("in")) 3174 v := val.Lookup("v", "w", "x") 3175 3176 root, path := v.ReferencePath() 3177 if got := path.String(); got != tc.want { 3178 t.Errorf("\n got %v;\nwant %v", got, tc.want) 3179 } 3180 if tc.want != "" { 3181 want := "1" 3182 if tc.alt != "" { 3183 want = tc.alt 3184 } 3185 v := fmt.Sprint(root.LookupPath(path)) 3186 if v != want { 3187 t.Errorf("path resolved to %s; want %s", v, want) 3188 } 3189 buildInst := root.BuildInstance() 3190 if buildInst == nil { 3191 t.Fatalf("no build instance found for reference path root") 3192 } 3193 if got, want := buildInst.ImportPath, tc.wantImportPath; got != want { 3194 t.Errorf("unexpected import path; got %q want %q", got, want) 3195 } 3196 } 3197 3198 inst, a := v.Reference() 3199 if got := strings.Join(a, "."); got != tc.want { 3200 t.Errorf("\n got %v;\nwant %v", got, tc.want) 3201 } 3202 3203 if tc.want != "" { 3204 want := "1" 3205 if tc.alt != "" { 3206 want = tc.alt 3207 } 3208 v := fmt.Sprint(inst.Lookup(a...)) 3209 if v != want { 3210 t.Errorf("path resolved to %s; want %s", v, want) 3211 } 3212 } 3213 }) 3214 } 3215 } 3216 3217 func TestZeroValueBuildInstance(t *testing.T) { 3218 inst := cue.Value{}.BuildInstance() 3219 if inst != nil { 3220 t.Error("unexpected non-nil instance") 3221 } 3222 } 3223 3224 func TestPos(t *testing.T) { 3225 testCases := []struct { 3226 value string 3227 pos string 3228 skip bool 3229 }{{ 3230 value: ` 3231 a: string 3232 a: "foo"`, 3233 pos: "3:4", 3234 }, { 3235 value: ` 3236 a: x: string 3237 a: x: "x"`, 3238 pos: "2:4", 3239 3240 // the position of the new evaluator is also correct, and actually 3241 // better. 3242 skip: true, 3243 }, { 3244 // Prefer struct conjuncts with actual fields. 3245 value: ` 3246 a: [string]: string 3247 a: x: "x"`, 3248 pos: "3:4", 3249 }, { 3250 value: ` 3251 a: [string]: [string]: string 3252 a: x: y: "x"`, 3253 pos: "3:4", 3254 }, { 3255 value: ` 3256 a: [string]: [string]: [string]: string 3257 a: x: y: z: "x"`, 3258 pos: "3:4", 3259 }} 3260 for _, tc := range testCases { 3261 cuetdtest.FullMatrix.Run(t, "", func(t *cuetdtest.M) { 3262 if tc.skip { 3263 t.TODO_V3() 3264 } 3265 3266 c := t.Context() 3267 v := c.CompileString(tc.value) 3268 v = v.LookupPath(cue.ParsePath("a")) 3269 pos := v.Pos().String() 3270 if pos != tc.pos { 3271 t.Errorf("got %v; want %v", pos, tc.pos) 3272 } 3273 }) 3274 } 3275 } 3276 3277 func TestPathCorrection(t *testing.T) { 3278 testCases := []struct { 3279 input string 3280 lookup func(i cue.Value) cue.Value 3281 want string 3282 skip bool 3283 }{{ 3284 input: ` 3285 a: b: { 3286 c: d: b 3287 } 3288 `, 3289 lookup: func(i cue.Value) cue.Value { 3290 op, a := i.Lookup("a", "b", "c", "d").Expr() 3291 _ = op 3292 return a[0] // structural cycle errors. 3293 }, 3294 want: "a", 3295 }, { 3296 3297 // TODO: embedding: have field operators. 3298 input: ` 3299 a: { 3300 {x: c} 3301 c: 3 3302 } 3303 `, 3304 lookup: func(i cue.Value) cue.Value { 3305 op, a := i.Lookup("a").Expr() 3306 _ = op 3307 return a[0].Lookup("x") 3308 }, 3309 want: "a.c", 3310 }, { 3311 3312 // TODO: implement proper Elem() 3313 input: ` 3314 a: b: [...T] 3315 a: b: [...T] 3316 T: int 3317 `, 3318 lookup: func(i cue.Value) cue.Value { 3319 v, _ := i.Lookup("a", "b").Elem() 3320 _, a := v.Expr() 3321 return a[0] 3322 }, 3323 want: "T", 3324 }, { 3325 input: ` 3326 #S: { 3327 b?: [...#T] 3328 b?: [...#T] 3329 } 3330 #T: int 3331 `, 3332 lookup: func(i cue.Value) cue.Value { 3333 v := i.LookupDef("#S") 3334 f, _ := v.LookupField("b") 3335 v, _ = f.Value.Elem() 3336 _, a := v.Expr() 3337 return a[0] 3338 }, 3339 want: "#T", 3340 }, { 3341 input: ` 3342 #S: { 3343 a?: [...#T] 3344 b?: [...#T] 3345 } 3346 #T: int 3347 `, 3348 lookup: func(i cue.Value) cue.Value { 3349 v := i.LookupDef("#S") 3350 f, _ := v.LookupField("a") 3351 x := f.Value 3352 f, _ = v.LookupField("b") 3353 y := f.Value 3354 u := x.Unify(y) 3355 v, _ = u.Elem() 3356 _, a := v.Expr() 3357 return a[0] 3358 }, 3359 want: "#T", 3360 }, { 3361 input: ` 3362 #a: { 3363 close({}) | close({c: #T}) | close({d: string}) 3364 #T: {b: 3} 3365 } 3366 `, 3367 lookup: func(i cue.Value) cue.Value { 3368 f, _ := i.LookupField("#a") 3369 _, a := f.Value.Expr() // & 3370 _, a = a[0].Expr() // | 3371 return a[1].Lookup("c") 3372 }, 3373 want: "#a.#T", 3374 }, { 3375 input: ` 3376 package foo 3377 3378 #Struct: { 3379 #T: int 3380 3381 {b?: #T} 3382 }`, 3383 want: "#Struct.#T", 3384 lookup: func(val cue.Value) cue.Value { 3385 // Locate Struct 3386 i, _ := val.Fields(cue.Definitions(true)) 3387 if !i.Next() { 3388 t.Fatal("no fields") 3389 } 3390 // Locate b 3391 i, _ = i.Value().Fields(cue.Definitions(true), cue.Optional(true)) 3392 if !(i.Next() && i.Next()) { 3393 t.Fatal("no fields") 3394 } 3395 v := i.Value() 3396 return v 3397 }, 3398 }, { 3399 3400 input: ` 3401 package foo 3402 3403 #A: #B: #T 3404 3405 #T: { 3406 a: #S.#U 3407 #S: #U: {} 3408 } 3409 `, 3410 want: "#T.#S.#U", 3411 lookup: func(val cue.Value) cue.Value { 3412 f, _ := val.LookupField("#A") 3413 f, _ = f.Value.LookupField("#B") 3414 v := f.Value 3415 v = cue.Dereference(v) 3416 v = v.Lookup("a") 3417 return v 3418 }, 3419 }, { 3420 3421 // TODO: record additionalItems in list 3422 input: ` 3423 package foo 3424 3425 #A: #B: #T 3426 3427 #T: { 3428 a: [...#S] 3429 #S: {} 3430 } 3431 `, 3432 want: "#T.#S", 3433 lookup: func(val cue.Value) cue.Value { 3434 f, _ := val.LookupField("#A") 3435 f, _ = f.Value.LookupField("#B") 3436 v := f.Value 3437 v = cue.Dereference(v) 3438 v, _ = v.Lookup("a").Elem() 3439 return v 3440 }, 3441 }, { 3442 input: ` 3443 #A: { 3444 b: #T 3445 } 3446 3447 #T: { 3448 a: #S 3449 #S: {} 3450 } 3451 `, 3452 want: "#T.#S", 3453 lookup: func(val cue.Value) cue.Value { 3454 f, _ := val.LookupField("#A") 3455 v := f.Value.Lookup("b") 3456 v = cue.Dereference(v) 3457 v = v.Lookup("a") 3458 return v 3459 }, 3460 }, { 3461 input: ` 3462 #Tracing: { 3463 #T: { address?: string } 3464 #S: { ip?: string } 3465 3466 close({}) | close({ 3467 t: #T 3468 }) | close({ 3469 s: #S 3470 }) 3471 } 3472 #X: {} 3473 #X // Disconnect top-level struct from the one visible by close. 3474 `, 3475 want: "#Tracing.#T", 3476 lookup: func(val cue.Value) cue.Value { 3477 f, _ := val.LookupField("#Tracing") 3478 v := f.Value.Eval() 3479 _, args := v.Expr() 3480 v = args[1] 3481 v = v.Lookup("t") 3482 return v 3483 }, 3484 }, { 3485 input: ` 3486 x: { if true { v: a } } 3487 a: b 3488 b: 2 3489 `, 3490 want: "b", 3491 lookup: func(val cue.Value) cue.Value { 3492 v := val.LookupPath(cue.ParsePath("x.v")) 3493 v = cue.Dereference(v) 3494 return v 3495 }, 3496 }, { 3497 input: ` 3498 package foo 3499 3500 #A:{ if true { #B: #T } } 3501 3502 #T: { 3503 a: #S.#U 3504 #S: #U: {} 3505 } 3506 `, 3507 want: "#T.#S.#U", 3508 lookup: func(val cue.Value) cue.Value { 3509 f, _ := val.LookupField("#A") 3510 f, _ = f.Value.LookupField("#B") 3511 v := f.Value 3512 v = cue.Dereference(v) 3513 v = v.Lookup("a") 3514 return v 3515 }, 3516 }} 3517 for _, tc := range testCases { 3518 if tc.skip { 3519 continue 3520 } 3521 cuetdtest.FullMatrix.Run(t, "", func(t *cuetdtest.M) { 3522 ctx := t.Context() 3523 3524 // don't use mustCompile because some test cases here need to 3525 // inspect the value error. 3526 val := ctx.CompileString(tc.input, cue.Filename("in")) 3527 v := tc.lookup(val) 3528 gotVal, ref := v.ReferencePath() 3529 if gotVal != val { 3530 t.Error("reference not in original instance") 3531 } 3532 gotPath := strings.Join(cue.PathToStrings(ref), ".") 3533 if gotPath != tc.want { 3534 t.Errorf("got path %s; want %s", gotPath, tc.want) 3535 } 3536 3537 x, p := v.ReferencePath() 3538 if x != val { 3539 t.Error("reference not in original instance") 3540 } 3541 gotPath = p.String() 3542 if gotPath != tc.want { 3543 t.Errorf("got path %s; want %s", gotPath, tc.want) 3544 } 3545 3546 }) 3547 } 3548 } 3549 3550 // func TestReferences(t *testing.T) { 3551 // config1 := ` 3552 // a: { 3553 // b: 3 3554 // } 3555 // c: { 3556 // d: a.b 3557 // e: c.d 3558 // f: a 3559 // } 3560 // ` 3561 // config2 := ` 3562 // a: { c: 3 } 3563 // b: { c: int, d: 4 } 3564 // r: (a & b).c 3565 // c: {args: s1 + s2}.args 3566 // s1: string 3567 // s2: string 3568 // d: ({arg: b}).arg.c 3569 // e: f.arg.c 3570 // f: {arg: b} 3571 // ` 3572 // testCases := []struct { 3573 // config string 3574 // in string 3575 // out string 3576 // }{ 3577 // {config1, "c.d", "a.b"}, 3578 // {config1, "c.e", "c.d"}, 3579 // {config1, "c.f", "a"}, 3580 3581 // {config2, "r", "a.c b.c"}, 3582 // {config2, "c", "s1 s2"}, 3583 // // {config2, "d", "b.c"}, // TODO: make this work as well. 3584 // {config2, "e", "f.arg.c"}, // TODO: should also report b.c. 3585 // } 3586 // for _, tc := range testCases { 3587 // t.Run(tc.in, func(t *testing.T) { 3588 // ctx, st := compileFile(t, tc.config) 3589 // v := newValueRoot(ctx, st) 3590 // for _, k := range strings.Split(tc.in, ".") { 3591 // obj, err := v.structValFull(ctx) 3592 // if err != nil { 3593 // t.Fatal(err) 3594 // } 3595 // v = obj.Lookup(k) 3596 // } 3597 // got := []string{} 3598 // for _, r := range v.References() { 3599 // got = append(got, strings.Join(r, ".")) 3600 // } 3601 // want := strings.Split(tc.out, " ") 3602 // if !reflect.DeepEqual(got, want) { 3603 // t.Errorf("got %v; want %v", got, want) 3604 // } 3605 // }) 3606 // } 3607 // } 3608 3609 func checkErr(t *testing.T, err error, str, name string) bool { 3610 t.Helper() 3611 if err == nil { 3612 if str != "" { 3613 t.Errorf(`err:%s: got ""; want %q`, name, str) 3614 } 3615 return true 3616 } 3617 return checkFailed(t, err, str, name) 3618 } 3619 3620 func checkFatal(t *testing.T, err error, str, name string) { 3621 t.Helper() 3622 if !checkFailed(t, err, str, name) { 3623 t.SkipNow() 3624 } 3625 } 3626 3627 func checkFailed(t *testing.T, err error, str, name string) bool { 3628 t.Helper() 3629 if err != nil { 3630 got := err.Error() 3631 if str == "" { 3632 t.Fatalf(`err:%s: got %q; want ""`, name, got) 3633 } 3634 if !strings.Contains(got, str) { 3635 t.Errorf(`err:%s: got %q; want %q`, name, got, str) 3636 } 3637 return false 3638 } 3639 return true 3640 } 3641 3642 func TestExpr(t *testing.T) { 3643 testCases := []struct { 3644 input string 3645 want string 3646 }{{ 3647 input: "v: 3", 3648 want: "3", 3649 }, { 3650 input: "v: 3 + 4", 3651 want: "+(3 4)", 3652 }, { 3653 input: "v: !a, a: bool", 3654 want: `!(.(〈〉 "a"))`, 3655 }, { 3656 input: "v: !a, a: 3", // TODO: Should still look up. 3657 want: `!(.(〈〉 "a"))`, 3658 }, { 3659 input: "v: 1 | 2 | 3 | *4", 3660 want: "|(1 2 3 4)", 3661 }, { 3662 input: "v: 2 & 5", // Allow even with error. 3663 want: "&(2 5)", 3664 }, { 3665 input: "v: 2 | 5", 3666 want: "|(2 5)", 3667 }, { 3668 input: "v: 2 && 5", 3669 want: "&&(2 5)", 3670 }, { 3671 input: "v: 2 || 5", 3672 want: "||(2 5)", 3673 }, { 3674 input: "v: 2 == 5", 3675 want: "==(2 5)", 3676 }, { 3677 input: "v: !b, b: true", 3678 want: `!(.(〈〉 "b"))`, 3679 }, { 3680 input: "v: 2 != 5", 3681 want: "!=(2 5)", 3682 }, { 3683 input: "v: <5", 3684 want: "<(5)", 3685 }, { 3686 input: "v: 2 <= 5", 3687 want: "<=(2 5)", 3688 }, { 3689 input: "v: 2 > 5", 3690 want: ">(2 5)", 3691 }, { 3692 input: "v: 2 >= 5", 3693 want: ">=(2 5)", 3694 }, { 3695 input: "v: 2 =~ 5", 3696 want: "=~(2 5)", 3697 }, { 3698 input: "v: 2 !~ 5", 3699 want: "!~(2 5)", 3700 }, { 3701 input: "v: 2 + 5", 3702 want: "+(2 5)", 3703 }, { 3704 input: "v: 2 - 5", 3705 want: "-(2 5)", 3706 }, { 3707 input: "v: 2 * 5", 3708 want: "*(2 5)", 3709 }, { 3710 input: "v: 2 / 5", 3711 want: "/(2 5)", 3712 }, { 3713 input: "v: 2 quo 5", 3714 want: "quo(2 5)", 3715 }, { 3716 input: "v: 2 rem 5", 3717 want: "rem(2 5)", 3718 }, { 3719 input: "v: 2 div 5", 3720 want: "div(2 5)", 3721 }, { 3722 input: "v: 2 mod 5", 3723 want: "mod(2 5)", 3724 }, { 3725 input: "v: a.b, a: b: 4", 3726 want: `.(.(〈〉 "a") "b")`, 3727 }, { 3728 input: `v: a["b"], a: b: 3 `, 3729 want: `[](.(〈〉 "a") "b")`, 3730 }, { 3731 input: "v: a[2:5], a: [1, 2, 3, 4, 5]", 3732 want: `[:](.(〈〉 "a") 2 5)`, 3733 }, { 3734 input: "v: len([])", 3735 want: "()(len [])", 3736 }, { 3737 input: "v: a.b, a: { b: string }", 3738 want: `.(.(〈〉 "a") "b")`, 3739 }, { 3740 input: `v: "Hello, \(x)! Welcome to \(place)", place: string, x: string`, 3741 want: `\()("Hello, " .(〈〉 "x") "! Welcome to " .(〈〉 "place") "")`, 3742 }, { 3743 // Split out the reference, but ensure the split-off outer struct 3744 // remains valid. 3745 input: `v: { a, #b: 1 }, a: 2`, 3746 want: `&(.(〈〉 "a") {int,#b:1})`, 3747 }, { 3748 // Result is an error, no need to split off. 3749 input: `v: { a, b: 1 }, a: 2`, 3750 want: `&(.(〈〉 "a") {b:1})`, 3751 }, { 3752 // Don't split of concrete values. 3753 input: `v: { "foo", #def: 1 }`, 3754 want: `{"foo",#def:1}`, 3755 }, { 3756 input: `v: { {} | { a: #A, b: #B}, #A: {} | { c: int} }, #B: int | bool`, 3757 want: `&(|({} {a:#A,b:#B}) {#A:({}|{c:int})})`, 3758 }, { 3759 input: `v: { {c: a}, b: a }, a: int`, 3760 want: `&({c:a} {b:a})`, 3761 }, { 3762 input: `v: [...number] | *[1, 2, 3]`, 3763 // Filter defaults that are subsumed by another value. 3764 want: `[...number]`, 3765 }, { 3766 input: `v: or([1, 2, 3])`, 3767 want: `|(1 2 3)`, 3768 }, { 3769 input: `v: or([])`, 3770 want: `_|_(empty list in call to or)`, 3771 }, { 3772 input: `v: and([1, 2, 3])`, 3773 want: `&(1 2 3)`, 3774 }, { 3775 input: `v: and([])`, 3776 want: `_`, 3777 }, { 3778 //Issue #1245 3779 input: ` 3780 x: *4 | int 3781 v: x | *7 3782 `, 3783 want: `|(.(〈〉 "x") 7)`, 3784 }, { 3785 // Issue #1119 3786 // Unwrap single embedded values. 3787 input: `v: {>30}`, 3788 want: `>(30)`, 3789 }, { 3790 input: `v: {>30, <40}`, 3791 want: `&(>(30) <(40))`, 3792 }, { 3793 input: `a: string, if true { v: a }`, 3794 want: `.(〈〉 "a")`, 3795 }} 3796 for _, tc := range testCases { 3797 cuetdtest.FullMatrix.Run(t, tc.input, func(t *cuetdtest.M) { 3798 v := getValue(t, tc.input).Lookup("v") 3799 got := exprStr(v) 3800 if got != tc.want { 3801 t.Errorf("\n got %v;\nwant %v", got, tc.want) 3802 } 3803 }) 3804 } 3805 } 3806 3807 func exprStr(v cue.Value) string { 3808 op, operands := v.Expr() 3809 if op == cue.NoOp { 3810 return compactRawStr(operands[0]) 3811 } 3812 s := op.String() 3813 s += "(" 3814 for i, v := range operands { 3815 if i > 0 { 3816 s += " " 3817 } 3818 s += exprStr(v) 3819 } 3820 s += ")" 3821 return s 3822 } 3823 3824 func compactRawStr(v cue.Value) string { 3825 ctx := cue.ValueCtx(v) 3826 cfg := &debug.Config{Compact: true, Raw: true} 3827 return debug.NodeString(ctx, cue.ValueVertex(v), cfg) 3828 }