cuelang.org/go@v0.10.1/internal/core/adt/fields_test.go (about) 1 // Copyright 2023 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 adt_test 16 17 import ( 18 "fmt" 19 "path/filepath" 20 "strings" 21 "testing" 22 23 "cuelang.org/go/cue/format" 24 "cuelang.org/go/internal/core/adt" 25 "cuelang.org/go/internal/core/debug" 26 "cuelang.org/go/internal/core/eval" 27 "cuelang.org/go/internal/core/export" 28 "cuelang.org/go/internal/core/runtime" 29 "cuelang.org/go/internal/cuetest" 30 ) 31 32 // TestCloseContext tests the intricacies of the closedness algorithm. 33 // Much of this could be tested using the existing testing framework, but the 34 // code is intricate enough that it is hard to map real-life cases onto 35 // specific edge cases. Also, changes in the higher-level order of evaluation 36 // may cause certain test cases to go untested. 37 // 38 // This test enables covering such edge cases with confidence by allowing 39 // fine control over the order of execution of conjuncts 40 // 41 // NOTE: much of the code is in export_test.go. This test needs access to 42 // higher level functionality which prevents it from being defined in 43 // package adt. The code in export_test.go provides access to the 44 // low-level functionality that this test needs. 45 func TestCloseContext(t *testing.T) { 46 r := runtime.New() 47 ctx := eval.NewContext(r, nil) 48 49 v := func(s string) adt.Value { 50 v, _ := r.Compile(nil, s) 51 if err := v.Err(ctx); err != nil { 52 t.Fatal(err.Err) 53 } 54 v.Finalize(ctx) 55 return v.Value() 56 } 57 ref := func(s string) adt.Expr { 58 f := r.StrLabel(s) 59 return &adt.FieldReference{Label: f} 60 } 61 // TODO: this may be needed once we optimize inserting scalar values. 62 // pe := func(s string) adt.Expr { 63 // x, err := parser.ParseExpr("", s) 64 // if err != nil { 65 // t.Fatal(err) 66 // } 67 // c, err := compile.Expr(nil, r, "test", x) 68 // if err != nil { 69 // t.Fatal(err) 70 // } 71 // return c.Expr() 72 // } 73 type testCase struct { 74 name string 75 run func(*adt.FieldTester) 76 77 // arcs shows a hierarchical representation of the arcs below the node. 78 arcs string 79 80 // patters shows a list of patterns and associated conjuncts 81 patterns string 82 83 // allowed is the computed allowed expression. 84 allowed string 85 86 // err holds all errors or "" if none. 87 err string 88 } 89 cases := []testCase{{ 90 name: "one", 91 run: func(x *adt.FieldTester) { 92 x.Run(x.Field("a", "foo")) 93 }, 94 arcs: `a: {"foo"}`, 95 }, { 96 name: "two", 97 run: func(x *adt.FieldTester) { 98 x.Run( 99 x.Field("a", "foo"), 100 x.Field("a", "bar"), 101 ) 102 }, 103 arcs: `a: {"foo" "bar"}`, 104 }, { 105 // This could be optimized as both nestings have a single value. 106 // This cause some provenance data to be lost, so this could be an 107 // option instead. 108 name: "double nested", 109 // a: "foo" 110 // #A 111 // 112 // where 113 // 114 // #A: { 115 // #B 116 // b: "foo" 117 // } 118 // #B: a: "foo" 119 run: func(x *adt.FieldTester) { 120 x.Run( 121 x.Field("a", "foo"), 122 x.EmbedDef( 123 x.EmbedDef(x.Field("a", "foo")), 124 x.Field("b", "foo"), 125 ), 126 ) 127 }, 128 arcs: ` 129 a: { 130 "foo" 131 [e]{ 132 [d]{ 133 [e]{ 134 [d]{"foo"} 135 } 136 } 137 } 138 } 139 b: { 140 [e]{ 141 [d]{"foo"} 142 } 143 }`, 144 }, { 145 name: "single pattern", 146 run: func(x *adt.FieldTester) { 147 x.Run(x.Pat(v(`<="foo"`), v("1"))) 148 }, 149 arcs: "", 150 patterns: `<="foo": {1}`, 151 allowed: `<="foo"`, 152 }, { 153 name: "total patterns", 154 run: func(x *adt.FieldTester) { 155 x.Run( 156 x.Pat(v(`<="foo"`), x.NewString("foo")), 157 x.Pat(v(`string`), v("1")), 158 x.Pat(v(`string`), v("1")), 159 x.Pat(v(`<="foo"`), v("2")), 160 ) 161 }, 162 163 arcs: "", 164 patterns: ` 165 <="foo": {"foo" 2} 166 string: {1 1}`, 167 168 // Should be empty or string only, as all fields match. 169 allowed: "", 170 }, { 171 name: "multi patterns", 172 run: func(x *adt.FieldTester) { 173 shared := v("100") 174 x.Run( 175 x.Pat(v(`<="foo"`), x.NewString("foo")), 176 x.Pat(v(`>"bar"`), shared), 177 x.Pat(v(`>"bar"`), shared), 178 x.Pat(v(`<="foo"`), v("1")), 179 ) 180 }, 181 182 // should have only a single 100 183 patterns: ` 184 <="foo": {"foo" 1} 185 >"bar": {100}`, 186 187 // TODO: normalize the output, Greater than first. 188 allowed: `|(<="foo", >"bar")`, 189 }, { 190 name: "pattern defined after matching field", 191 run: func(x *adt.FieldTester) { 192 x.Run( 193 x.Field("a", "foo"), 194 x.Pat(v(`string`), v(`string`)), 195 ) 196 }, 197 arcs: `a: {"foo" string}`, 198 patterns: `string: {string}`, 199 allowed: "", // all fields 200 }, { 201 name: "pattern defined before matching field", 202 run: func(x *adt.FieldTester) { 203 x.Run( 204 x.Pat(v(`string`), v(`string`)), 205 x.Field("a", "foo"), 206 ) 207 }, 208 arcs: `a: {"foo" string}`, 209 patterns: `string: {string}`, 210 allowed: "", // all fields 211 }, { 212 name: "shared on one level", 213 run: func(x *adt.FieldTester) { 214 shared := ref("shared") 215 x.Run( 216 x.Pat(v(`string`), v(`1`)), 217 x.Pat(v(`>"a"`), shared), 218 x.Pat(v(`>"a"`), shared), 219 x.Field("m", "foo"), 220 x.Pat(v(`string`), v(`2`)), 221 x.Pat(v(`>"a"`), shared), 222 x.Pat(v(`>"b"`), shared), 223 ) 224 }, 225 arcs: `m: {"foo" 1 shared 2}`, 226 patterns: ` 227 string: {1 2} 228 >"a": {shared} 229 >"b": {shared}`, 230 }, { 231 // The same conjunct in different groups could result in the different 232 // closedness rules. Hence they should not be shared. 233 name: "do not share between groups", 234 run: func(x *adt.FieldTester) { 235 notShared := ref("notShared") 236 x.Run( 237 x.Def(x.Field("m", notShared)), 238 239 // TODO(perf): since the nodes in Def have strictly more 240 // restrictive closedness requirements, this node could be 241 // eliminated from arcs. 242 x.Field("m", notShared), 243 244 // This could be shared with the first entry, but since there 245 // is no mechanism to identify equality for tis case, it is 246 // not shared. 247 x.Def(x.Field("m", notShared)), 248 249 // Under some conditions the same holds for embeddings. 250 x.Embed(x.Field("m", notShared)), 251 ) 252 }, 253 arcs: `m: { 254 [d]{notShared} 255 notShared 256 [d]{notShared} 257 [e]{notShared} 258 }`, 259 }, { 260 name: "conjunction of patterns", 261 run: func(x *adt.FieldTester) { 262 x.Run( 263 x.Def(x.Pat(v(`>"a"`), v("1"))), 264 x.Def(x.Pat(v(`<"m"`), v("2"))), 265 ) 266 }, 267 patterns: ` 268 >"a": {1} 269 <"m": {2}`, 270 // allowed reflects explicitly matched fields, even if node is not closed. 271 allowed: `&(>"a", <"m")`, 272 }, { 273 name: "pattern in definition in embedding", 274 run: func(x *adt.FieldTester) { 275 x.Run( 276 x.Embed(x.Def(x.Pat(v(`>"a"`), v("1")))), 277 ) 278 }, 279 patterns: `>"a": {1}`, 280 allowed: `>"a"`, 281 }, { 282 name: "avoid duplicate pattern entries", 283 run: func(x *adt.FieldTester) { 284 x.Run( 285 x.Field("b", "bar"), 286 x.Field("b", "baz"), 287 x.Pat(v(`string`), v("2")), 288 x.Pat(v(`string`), v("3")), 289 x.Field("c", "bar"), 290 x.Field("c", "baz"), 291 ) 292 }, 293 arcs: ` 294 b: {"bar" "baz" 2 3} 295 c: {"bar" 2 3 "baz"}`, 296 297 patterns: `string: {2 3}`, 298 }, { 299 name: "conjunction in embedding", 300 run: func(x *adt.FieldTester) { 301 x.Run( 302 x.Field("b", "foo"), 303 x.Embed( 304 x.Def(x.Pat(v(`>"a"`), v("1"))), 305 x.Def(x.Pat(v(`<"z"`), v("2"))), 306 ), 307 x.Field("c", "bar"), 308 x.Pat(v(`<"q"`), v("3")), 309 ) 310 }, 311 arcs: ` 312 b: { 313 "foo" 314 [e]{ 315 [d]{1} 316 [d]{2} 317 } 318 3 319 } 320 c: { 321 "bar" 322 [ed]{ 323 [d]{1} 324 [d]{2} 325 } 326 3 327 }`, // 328 patterns: ` 329 >"a": {1} 330 <"z": {2} 331 <"q": {3}`, 332 allowed: `|(<"q", &(>"a", <"z"))`, 333 }, { 334 // The point of this test is to see if the "allow" expression nests 335 // properly. 336 name: "conjunctions in embedding 1", 337 run: func(x *adt.FieldTester) { 338 x.Run( 339 x.Def( 340 x.Embed( 341 x.Def(x.Pat(v(`=~"^[a-s]*$"`), v("3"))), 342 x.Def(x.Pat(v(`=~"^xxx$"`), v("4"))), 343 ), 344 x.Pat(v(`=~"^b*$"`), v("4")), 345 ), 346 x.Field("b", v("4")), 347 ) 348 }, 349 arcs: `b: { 350 4 351 [d]{ 352 [ed]{ 353 [d]{3} 354 } 355 4 356 } 357 }`, 358 err: ``, 359 patterns: ` 360 =~"^[a-s]*$": {3} 361 =~"^xxx$": {4} 362 =~"^b*$": {4}`, allowed: `|(=~"^b*$", &(=~"^[a-s]*$", =~"^xxx$"))`, 363 }, { 364 name: "conjunctions in embedding 2", 365 run: func(x *adt.FieldTester) { 366 x.Run( 367 x.Embed( 368 x.Field("b", v("4")), 369 x.Embed( 370 x.Def(x.Pat(v(`>"b"`), v("3"))), 371 x.Def(x.Pat(v(`<"h"`), v("4"))), 372 ), 373 ), 374 ) 375 }, 376 arcs: `b: { 377 [e]{ 378 4 379 [ed]{ 380 [d]{4} 381 } 382 } 383 }`, 384 385 err: ``, 386 patterns: ` 387 >"b": {3} 388 <"h": {4}`, 389 }, { 390 name: "conjunctions in embedding 3", 391 run: func(x *adt.FieldTester) { 392 x.Run( 393 x.Embed( 394 x.Def( 395 x.Field("b", "foo"), 396 x.Embed( 397 x.Def(x.Pat(v(`>"a"`), v("1"))), 398 x.Def(x.Pat(v(`<"g"`), v("2"))), 399 x.Pat(v(`<"z"`), v("3")), 400 ), 401 x.Embed( 402 x.Def(x.Pat(v(`>"b"`), v("3"))), 403 x.Def(x.Pat(v(`<"h"`), v("4"))), 404 ), 405 x.Field("c", "bar"), 406 x.Pat(v(`<"q"`), v("5")), 407 ), 408 x.Def( 409 x.Embed( 410 x.Def(x.Pat(v(`>"m"`), v("6"))), 411 x.Def(x.Pat(v(`<"y"`), v("7"))), 412 x.Pat(v(`<"z"`), v("8")), 413 ), 414 x.Embed( 415 x.Def(x.Pat(v(`>"n"`), v("9"))), 416 x.Def(x.Pat(v(`<"z"`), v("10"))), 417 ), 418 x.Field("c", "bar"), 419 x.Pat(v(`<"q"`), v("11")), 420 ), 421 ), 422 x.Pat(v(`<"h"`), v("12")), 423 ) 424 }, 425 arcs: ` 426 b: { 427 [e]{ 428 [d]{ 429 "foo" 430 [e]{ 431 [d]{1} 432 [d]{2} 433 3 434 } 435 [ed]{ 436 [d]{4} 437 } 438 5 439 } 440 [d]{ 441 [ed]{ 442 [d]{7} 443 8 444 } 445 [ed]{ 446 [d]{10} 447 } 448 11 449 } 450 } 451 12 452 } 453 c: { 454 [e]{ 455 [d]{ 456 "bar" 457 [ed]{ 458 [d]{1} 459 [d]{2} 460 3 461 } 462 [ed]{ 463 [d]{3} 464 [d]{4} 465 } 466 5 467 } 468 [d]{ 469 [ed]{ 470 [d]{7} 471 8 472 } 473 [ed]{ 474 [d]{10} 475 } 476 "bar" 477 11 478 } 479 } 480 12 481 }`, 482 patterns: ` 483 >"a": {1} 484 <"g": {2} 485 <"z": {3 8 10} 486 >"b": {3} 487 <"h": {4 12} 488 <"q": {5 11} 489 >"m": {6} 490 <"y": {7} 491 >"n": {9}`, 492 allowed: `|(<"h", &(|(<"q", &(>"b", <"h"), &(>"a", <"g")), |(<"q", &(>"n", <"z"), &(>"m", <"y"))))`, 493 }, { 494 name: "dedup equal", 495 run: func(x *adt.FieldTester) { 496 shared := v("1") 497 x.Run( 498 x.FieldDedup("a", shared), 499 x.FieldDedup("a", shared), 500 ) 501 }, 502 patterns: "", 503 allowed: "", 504 arcs: `a: {1}`, 505 }, { 506 // Effectively {a: 1} & #D, where #D is {b: 1} 507 name: "disallowed before", 508 run: func(x *adt.FieldTester) { 509 x.Run( 510 x.Field("a", v("1")), 511 x.Def(x.Field("b", v("1"))), 512 ) 513 }, 514 arcs: ` 515 a: {1} 516 b: { 517 [d]{1} 518 }`, 519 err: `a: field not allowed`, 520 }, { 521 // Effectively #D & {a: 1}, where #D is {b: 1} 522 name: "disallowed after", 523 run: func(x *adt.FieldTester) { 524 x.Run( 525 x.Def(x.Field("b", v("1"))), 526 x.Field("a", v("1")), 527 ) 528 }, 529 arcs: ` 530 b: { 531 [d]{1} 532 } 533 a: {1}`, 534 err: `a: field not allowed`, 535 }, { 536 // a: {#A} 537 // a: c: 1 538 // #A: b: 1 539 name: "def embed", 540 run: func(x *adt.FieldTester) { 541 x.Run( 542 x.Group(x.EmbedDef(x.Field("b", "foo"))), 543 x.Field("c", "bar"), 544 ) 545 }, 546 arcs: ` 547 b: { 548 []{ 549 [e]{ 550 [d]{"foo"} 551 } 552 } 553 } 554 c: {"bar"}`, 555 err: `c: field not allowed`, 556 }, { 557 // a: {#A} 558 // a: c: 1 559 // #A: b: 1 560 name: "def embed", 561 run: func(x *adt.FieldTester) { 562 x.Run( 563 x.Group(x.EmbedDef(x.Field("b", "foo")), 564 x.Field("c", "foo"), 565 ), 566 x.Field("d", "bar"), 567 ) 568 }, 569 arcs: ` 570 b: { 571 []{ 572 [e]{ 573 [d]{"foo"} 574 } 575 } 576 } 577 c: { 578 [d]{"foo"} 579 } 580 d: {"bar"}`, 581 err: `d: field not allowed`, 582 }, { 583 // This test is for debugging and can be changed. 584 name: "X", 585 run: func(x *adt.FieldTester) { 586 x.Run( 587 x.Field("b", "foo"), 588 x.Embed( 589 x.Def(x.Pat(v(`>"b"`), v("3"))), 590 x.Def(x.Pat(v(`<"h"`), v("4"))), 591 ), 592 ) 593 }, 594 // TODO: could probably be b: {"foo" 4}. See TODO(constraintNode). 595 arcs: `b: { 596 "foo" 597 [ed]{ 598 [d]{4} 599 } 600 }`, 601 err: ``, 602 patterns: ` 603 >"b": {3} 604 <"h": {4}`, 605 allowed: `&(>"b", <"h")`, 606 }} 607 608 cuetest.Run(t, cases, func(t *cuetest.T, tc *testCase) { 609 // Uncomment to debug isolated test X. 610 // adt.Debug = true 611 // adt.Verbosity = 2 612 // t.Select("X") 613 614 adt.DebugDeps = true 615 showGraph := false 616 x := adt.NewFieldTester(r) 617 tc.run(x) 618 619 ctx := x.OpContext 620 621 switch graph, hasError := adt.CreateMermaidGraph(ctx, x.Root, true); { 622 case !hasError: 623 case showGraph: 624 path := filepath.Join(".debug", "TestCloseContext", tc.name) 625 adt.OpenNodeGraph(tc.name, path, "in", "out", graph) 626 fallthrough 627 default: 628 t.Errorf("imbalanced counters") 629 } 630 631 t.Equal(writeArcs(x, x.Root), tc.arcs) 632 t.Equal(x.Error(), tc.err) 633 634 var patterns, allowed string 635 if pcs := x.Root.PatternConstraints; pcs != nil { 636 patterns = writePatterns(x, pcs.Pairs) 637 if pcs.Allowed != nil { 638 allowed = debug.NodeString(r, pcs.Allowed, nil) 639 // TODO: output is nicer, but need either parenthesis or 640 // removed spaces around more tightly bound expressions. 641 // allowed = pExpr(x, pcs.Allowed) 642 } 643 } 644 645 t.Equal(patterns, tc.patterns) 646 t.Equal(allowed, tc.allowed) 647 }) 648 } 649 650 const ( 651 initialIndent = 3 652 indentString = "\t" 653 ) 654 655 func writeArcs(x adt.Runtime, v *adt.Vertex) string { 656 b := &strings.Builder{} 657 for _, a := range v.Arcs { 658 if len(v.Arcs) > 1 { 659 fmt.Fprint(b, "\n", strings.Repeat(indentString, initialIndent)) 660 } 661 fmt.Fprintf(b, "%s: ", a.Label.RawString(x)) 662 663 // TODO(perf): optimize this so that a single-element conjunct does 664 // not need a group. 665 if len(a.Conjuncts) != 1 { 666 panic("unexpected conjunct length") 667 } 668 g := a.Conjuncts[0].Elem().(*adt.ConjunctGroup) 669 vertexString(x, b, *g, initialIndent) 670 } 671 return b.String() 672 } 673 674 func writePatterns(x adt.Runtime, pairs []adt.PatternConstraint) string { 675 b := &strings.Builder{} 676 for _, pc := range pairs { 677 if len(pairs) > 1 { 678 fmt.Fprint(b, "\n", strings.Repeat(indentString, initialIndent)) 679 } 680 b.WriteString(pExpr(x, pc.Pattern)) 681 b.WriteString(": ") 682 vertexString(x, b, pc.Constraint.Conjuncts, initialIndent) 683 } 684 return b.String() 685 } 686 687 func hasVertex(a []adt.Conjunct) bool { 688 for _, c := range a { 689 switch c.Elem().(type) { 690 case *adt.ConjunctGroup: 691 return true 692 case *adt.Vertex: 693 return true 694 } 695 } 696 return false 697 } 698 699 func vertexString(x adt.Runtime, b *strings.Builder, a []adt.Conjunct, indent int) { 700 hasVertex := hasVertex(a) 701 702 b.WriteString("{") 703 for i, c := range a { 704 if g, ok := c.Elem().(*adt.ConjunctGroup); ok { 705 if hasVertex { 706 doIndent(b, indent+1) 707 } 708 b.WriteString("[") 709 if c.CloseInfo.FromEmbed { 710 b.WriteString("e") 711 } 712 if c.CloseInfo.FromDef { 713 b.WriteString("d") 714 } 715 b.WriteString("]") 716 vertexString(x, b, *g, indent+1) 717 } else { 718 if hasVertex { 719 doIndent(b, indent+1) 720 } else if i > 0 { 721 b.WriteString(" ") 722 } 723 b.WriteString(pExpr(x, c.Expr())) 724 } 725 } 726 if hasVertex { 727 doIndent(b, indent) 728 } 729 b.WriteString("}") 730 } 731 732 func doIndent(b *strings.Builder, indent int) { 733 fmt.Fprint(b, "\n", strings.Repeat(indentString, indent)) 734 } 735 736 func pExpr(x adt.Runtime, e adt.Expr) string { 737 a, _ := export.All.Expr(x, "test", e) 738 b, _ := format.Node(a) 739 return string(b) 740 }