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