github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/hclext/structure_test.go (about) 1 package hclext 2 3 import ( 4 "testing" 5 6 "github.com/google/go-cmp/cmp" 7 "github.com/google/go-cmp/cmp/cmpopts" 8 "github.com/hashicorp/hcl/v2" 9 "github.com/hashicorp/hcl/v2/hclsyntax" 10 "github.com/zclconf/go-cty/cty" 11 ) 12 13 func TestContent_PartialContent(t *testing.T) { 14 tests := []struct { 15 Name string 16 Body *hclsyntax.Body 17 Schema *BodySchema 18 Partial bool 19 Want *BodyContent 20 DiagCount int 21 }{ 22 { 23 Name: "nil body with nil schema", 24 Body: nil, 25 Schema: nil, 26 Partial: false, 27 Want: &BodyContent{}, 28 DiagCount: 0, 29 }, 30 { 31 Name: "nil body with empty schema", 32 Body: nil, 33 Schema: &BodySchema{}, 34 Partial: false, 35 Want: &BodyContent{}, 36 DiagCount: 0, 37 }, 38 { 39 Name: "empty body with nil schema", 40 Body: &hclsyntax.Body{}, 41 Schema: nil, 42 Partial: false, 43 Want: &BodyContent{Attributes: Attributes{}, Blocks: Blocks{}}, 44 DiagCount: 0, 45 }, 46 { 47 Name: "empty body with empty schema", 48 Body: &hclsyntax.Body{}, 49 Schema: &BodySchema{}, 50 Partial: false, 51 Want: &BodyContent{Attributes: Attributes{}, Blocks: Blocks{}}, 52 DiagCount: 0, 53 }, 54 { 55 Name: "attributes", 56 Body: &hclsyntax.Body{ 57 Attributes: hclsyntax.Attributes{ 58 "foo": &hclsyntax.Attribute{ 59 Name: "foo", 60 }, 61 }, 62 }, 63 Schema: &BodySchema{ 64 Attributes: []AttributeSchema{ 65 { 66 Name: "foo", 67 }, 68 }, 69 }, 70 Partial: false, 71 Want: &BodyContent{ 72 Attributes: Attributes{"foo": &Attribute{Name: "foo"}}, 73 Blocks: Blocks{}, 74 }, 75 DiagCount: 0, 76 }, 77 { 78 Name: "attributes with empty schema", 79 Body: &hclsyntax.Body{ 80 Attributes: hclsyntax.Attributes{ 81 "foo": &hclsyntax.Attribute{ 82 Name: "foo", 83 }, 84 }, 85 }, 86 Schema: &BodySchema{}, 87 Partial: false, 88 Want: &BodyContent{ 89 Attributes: Attributes{}, 90 Blocks: Blocks{}, 91 }, 92 DiagCount: 1, // extra attribute is not allowed 93 }, 94 { 95 Name: "attributes with partial empty schema", 96 Body: &hclsyntax.Body{ 97 Attributes: hclsyntax.Attributes{ 98 "foo": &hclsyntax.Attribute{ 99 Name: "foo", 100 }, 101 }, 102 }, 103 Schema: &BodySchema{}, 104 Partial: true, 105 Want: &BodyContent{ 106 Attributes: Attributes{}, 107 Blocks: Blocks{}, 108 }, 109 DiagCount: 0, // extra attribute is allowed in partial schema 110 }, 111 { 112 Name: "empty body with attribute schema", 113 Body: &hclsyntax.Body{ 114 Attributes: hclsyntax.Attributes{}, 115 }, 116 Schema: &BodySchema{ 117 Attributes: []AttributeSchema{ 118 { 119 Name: "foo", 120 }, 121 }, 122 }, 123 Partial: false, 124 Want: &BodyContent{ 125 Attributes: Attributes{}, 126 Blocks: Blocks{}, 127 }, 128 DiagCount: 0, // attribute is not required by default 129 }, 130 { 131 Name: "empty body with required attribute schema", 132 Body: &hclsyntax.Body{ 133 Attributes: hclsyntax.Attributes{}, 134 }, 135 Schema: &BodySchema{ 136 Attributes: []AttributeSchema{ 137 { 138 Name: "foo", 139 Required: true, 140 }, 141 }, 142 }, 143 Partial: false, 144 Want: &BodyContent{ 145 Attributes: Attributes{}, 146 Blocks: Blocks{}, 147 }, 148 DiagCount: 1, // attribute is required 149 }, 150 { 151 Name: "attributes with block schema", 152 Body: &hclsyntax.Body{ 153 Attributes: hclsyntax.Attributes{ 154 "foo": &hclsyntax.Attribute{ 155 Name: "foo", 156 }, 157 }, 158 }, 159 Schema: &BodySchema{ 160 Blocks: []BlockSchema{ 161 { 162 Type: "foo", 163 }, 164 }, 165 }, 166 Partial: false, 167 Want: &BodyContent{ 168 Attributes: Attributes{}, 169 Blocks: Blocks{}, 170 }, 171 DiagCount: 1, // "foo" is defined as attribute, but should be defined as block 172 }, 173 { 174 Name: "blocks", 175 Body: &hclsyntax.Body{ 176 Blocks: hclsyntax.Blocks{ 177 &hclsyntax.Block{ 178 Type: "foo", 179 }, 180 }, 181 }, 182 Schema: &BodySchema{ 183 Blocks: []BlockSchema{ 184 { 185 Type: "foo", 186 }, 187 }, 188 }, 189 Partial: false, 190 Want: &BodyContent{ 191 Attributes: Attributes{}, 192 Blocks: Blocks{ 193 { 194 Type: "foo", 195 Body: &BodyContent{}, 196 }, 197 }, 198 }, 199 DiagCount: 0, 200 }, 201 { 202 Name: "multiple blocks", 203 Body: &hclsyntax.Body{ 204 Blocks: hclsyntax.Blocks{ 205 &hclsyntax.Block{ 206 Type: "foo", 207 }, 208 &hclsyntax.Block{ 209 Type: "foo", 210 }, 211 }, 212 }, 213 Schema: &BodySchema{ 214 Blocks: []BlockSchema{ 215 { 216 Type: "foo", 217 }, 218 }, 219 }, 220 Partial: false, 221 Want: &BodyContent{ 222 Attributes: Attributes{}, 223 Blocks: Blocks{ 224 { 225 Type: "foo", 226 Body: &BodyContent{}, 227 }, 228 { 229 Type: "foo", 230 Body: &BodyContent{}, 231 }, 232 }, 233 }, 234 DiagCount: 0, 235 }, 236 { 237 Name: "multiple blocks which including unexpected schema", 238 Body: &hclsyntax.Body{ 239 Blocks: hclsyntax.Blocks{ 240 &hclsyntax.Block{ 241 Type: "foo", 242 }, 243 &hclsyntax.Block{ 244 Type: "bar", 245 }, 246 }, 247 }, 248 Schema: &BodySchema{ 249 Blocks: []BlockSchema{ 250 { 251 Type: "foo", 252 }, 253 }, 254 }, 255 Partial: false, 256 Want: &BodyContent{ 257 Attributes: Attributes{}, 258 Blocks: Blocks{ 259 { 260 Type: "foo", 261 Body: &BodyContent{}, 262 }, 263 }, 264 }, 265 DiagCount: 1, // "bar" is not expected 266 }, 267 { 268 Name: "multiple blocks which including unexpected schema with partial schema", 269 Body: &hclsyntax.Body{ 270 Blocks: hclsyntax.Blocks{ 271 &hclsyntax.Block{ 272 Type: "foo", 273 }, 274 &hclsyntax.Block{ 275 Type: "bar", 276 }, 277 }, 278 }, 279 Schema: &BodySchema{ 280 Blocks: []BlockSchema{ 281 { 282 Type: "foo", 283 }, 284 }, 285 }, 286 Partial: true, 287 Want: &BodyContent{ 288 Attributes: Attributes{}, 289 Blocks: Blocks{ 290 { 291 Type: "foo", 292 Body: &BodyContent{}, 293 }, 294 }, 295 }, 296 DiagCount: 0, // extra schema block is allowed in partial schema 297 }, 298 { 299 Name: "labeled block", 300 Body: &hclsyntax.Body{ 301 Blocks: hclsyntax.Blocks{ 302 &hclsyntax.Block{ 303 Type: "foo", 304 Labels: []string{"bar"}, 305 }, 306 }, 307 }, 308 Schema: &BodySchema{ 309 Blocks: []BlockSchema{ 310 { 311 Type: "foo", 312 LabelNames: []string{"name"}, 313 }, 314 }, 315 }, 316 Partial: false, 317 Want: &BodyContent{ 318 Attributes: Attributes{}, 319 Blocks: Blocks{ 320 { 321 Type: "foo", 322 Labels: []string{"bar"}, 323 Body: &BodyContent{}, 324 }, 325 }, 326 }, 327 DiagCount: 0, 328 }, 329 { 330 Name: "non-labeled block with labeled block schema", 331 Body: &hclsyntax.Body{ 332 Blocks: hclsyntax.Blocks{ 333 &hclsyntax.Block{ 334 Type: "foo", 335 }, 336 }, 337 }, 338 Schema: &BodySchema{ 339 Blocks: []BlockSchema{ 340 { 341 Type: "foo", 342 LabelNames: []string{"name"}, 343 }, 344 }, 345 }, 346 Partial: false, 347 Want: &BodyContent{ 348 Attributes: Attributes{}, 349 Blocks: Blocks{}, 350 }, 351 DiagCount: 1, // missing label is not allowed 352 }, 353 { 354 Name: "labeled block with non-labeled block schema", 355 Body: &hclsyntax.Body{ 356 Blocks: hclsyntax.Blocks{ 357 &hclsyntax.Block{ 358 Type: "foo", 359 Labels: []string{"bar"}, 360 LabelRanges: []hcl.Range{{}}, 361 }, 362 }, 363 }, 364 Schema: &BodySchema{ 365 Blocks: []BlockSchema{ 366 { 367 Type: "foo", 368 }, 369 }, 370 }, 371 Partial: false, 372 Want: &BodyContent{ 373 Attributes: Attributes{}, 374 Blocks: Blocks{}, 375 }, 376 DiagCount: 1, // extraneous label is not allowed 377 }, 378 { 379 Name: "multi-labeled block with single-labeled block schema", 380 Body: &hclsyntax.Body{ 381 Blocks: hclsyntax.Blocks{ 382 &hclsyntax.Block{ 383 Type: "foo", 384 Labels: []string{"bar", "baz"}, 385 LabelRanges: []hcl.Range{{}, {}}, 386 }, 387 }, 388 }, 389 Schema: &BodySchema{ 390 Blocks: []BlockSchema{ 391 { 392 Type: "foo", 393 LabelNames: []string{"name"}, 394 }, 395 }, 396 }, 397 Partial: false, 398 Want: &BodyContent{ 399 Attributes: Attributes{}, 400 Blocks: Blocks{}, 401 }, 402 DiagCount: 1, // extraneous label is not allowed 403 }, 404 { 405 Name: "block with attribute schema", 406 Body: &hclsyntax.Body{ 407 Blocks: hclsyntax.Blocks{ 408 &hclsyntax.Block{ 409 Type: "foo", 410 }, 411 }, 412 }, 413 Schema: &BodySchema{ 414 Attributes: []AttributeSchema{ 415 { 416 Name: "foo", 417 }, 418 }, 419 }, 420 Partial: false, 421 Want: &BodyContent{ 422 Attributes: Attributes{}, 423 Blocks: Blocks{}, 424 }, 425 DiagCount: 1, // "foo" is defined as block, but should be defined as attribute 426 }, 427 { 428 Name: "nested blocks", 429 Body: &hclsyntax.Body{ 430 Attributes: hclsyntax.Attributes{ 431 "foo": &hclsyntax.Attribute{Name: "foo"}, 432 }, 433 Blocks: hclsyntax.Blocks{ 434 &hclsyntax.Block{ 435 Type: "bar", 436 Body: &hclsyntax.Body{ 437 Attributes: hclsyntax.Attributes{ 438 "baz": &hclsyntax.Attribute{Name: "baz"}, 439 }, 440 }, 441 }, 442 }, 443 }, 444 Schema: &BodySchema{ 445 Attributes: []AttributeSchema{ 446 { 447 Name: "foo", 448 }, 449 }, 450 Blocks: []BlockSchema{ 451 { 452 Type: "bar", 453 Body: &BodySchema{ 454 Attributes: []AttributeSchema{ 455 { 456 Name: "baz", 457 }, 458 }, 459 }, 460 }, 461 }, 462 }, 463 Partial: false, 464 Want: &BodyContent{ 465 Attributes: Attributes{ 466 "foo": &Attribute{Name: "foo"}, 467 }, 468 Blocks: Blocks{ 469 { 470 Type: "bar", 471 Body: &BodyContent{ 472 Attributes: Attributes{ 473 "baz": &Attribute{Name: "baz"}, 474 }, 475 Blocks: Blocks{}, 476 }, 477 }, 478 }, 479 }, 480 DiagCount: 0, 481 }, 482 { 483 Name: "attributes with empty schema in nested blocks", 484 Body: &hclsyntax.Body{ 485 Attributes: hclsyntax.Attributes{ 486 "foo": &hclsyntax.Attribute{Name: "foo"}, 487 }, 488 Blocks: hclsyntax.Blocks{ 489 &hclsyntax.Block{ 490 Type: "bar", 491 Body: &hclsyntax.Body{ 492 Attributes: hclsyntax.Attributes{ 493 "baz": &hclsyntax.Attribute{Name: "baz"}, 494 }, 495 }, 496 }, 497 }, 498 }, 499 Schema: &BodySchema{ 500 Attributes: []AttributeSchema{ 501 { 502 Name: "foo", 503 }, 504 }, 505 Blocks: []BlockSchema{ 506 { 507 Type: "bar", 508 Body: &BodySchema{}, 509 }, 510 }, 511 }, 512 Partial: false, 513 Want: &BodyContent{ 514 Attributes: Attributes{ 515 "foo": &Attribute{Name: "foo"}, 516 }, 517 Blocks: Blocks{ 518 { 519 Type: "bar", 520 Body: &BodyContent{ 521 Attributes: Attributes{}, 522 Blocks: Blocks{}, 523 }, 524 }, 525 }, 526 }, 527 DiagCount: 1, // extra attribute in nested blocks is not allowed 528 }, 529 { 530 Name: "attributes with partial empty schema in nested blocks", 531 Body: &hclsyntax.Body{ 532 Attributes: hclsyntax.Attributes{ 533 "foo": &hclsyntax.Attribute{Name: "foo"}, 534 }, 535 Blocks: hclsyntax.Blocks{ 536 &hclsyntax.Block{ 537 Type: "bar", 538 Body: &hclsyntax.Body{ 539 Attributes: hclsyntax.Attributes{ 540 "baz": &hclsyntax.Attribute{Name: "baz"}, 541 }, 542 }, 543 }, 544 }, 545 }, 546 Schema: &BodySchema{ 547 Attributes: []AttributeSchema{ 548 { 549 Name: "foo", 550 }, 551 }, 552 Blocks: []BlockSchema{ 553 { 554 Type: "bar", 555 Body: &BodySchema{}, 556 }, 557 }, 558 }, 559 Partial: true, 560 Want: &BodyContent{ 561 Attributes: Attributes{ 562 "foo": &Attribute{Name: "foo"}, 563 }, 564 Blocks: Blocks{ 565 { 566 Type: "bar", 567 Body: &BodyContent{ 568 Attributes: Attributes{}, 569 Blocks: Blocks{}, 570 }, 571 }, 572 }, 573 }, 574 DiagCount: 0, // extra attribute in nested blocks is allowed in partial schema 575 }, 576 { 577 Name: "empty body with attribute schema in nested blocks", 578 Body: &hclsyntax.Body{ 579 Attributes: hclsyntax.Attributes{ 580 "foo": &hclsyntax.Attribute{Name: "foo"}, 581 }, 582 Blocks: hclsyntax.Blocks{ 583 &hclsyntax.Block{ 584 Type: "bar", 585 Body: &hclsyntax.Body{}, 586 }, 587 }, 588 }, 589 Schema: &BodySchema{ 590 Attributes: []AttributeSchema{ 591 { 592 Name: "foo", 593 }, 594 }, 595 Blocks: []BlockSchema{ 596 { 597 Type: "bar", 598 Body: &BodySchema{ 599 Attributes: []AttributeSchema{ 600 { 601 Name: "baz", 602 }, 603 }, 604 }, 605 }, 606 }, 607 }, 608 Partial: true, 609 Want: &BodyContent{ 610 Attributes: Attributes{ 611 "foo": &Attribute{Name: "foo"}, 612 }, 613 Blocks: Blocks{ 614 { 615 Type: "bar", 616 Body: &BodyContent{ 617 Attributes: Attributes{}, 618 Blocks: Blocks{}, 619 }, 620 }, 621 }, 622 }, 623 DiagCount: 0, // attribute in nested blocks is not required by default 624 }, 625 { 626 Name: "empty body with required attribute schema in nested blocks", 627 Body: &hclsyntax.Body{ 628 Attributes: hclsyntax.Attributes{ 629 "foo": &hclsyntax.Attribute{Name: "foo"}, 630 }, 631 Blocks: hclsyntax.Blocks{ 632 &hclsyntax.Block{ 633 Type: "bar", 634 Body: &hclsyntax.Body{}, 635 }, 636 }, 637 }, 638 Schema: &BodySchema{ 639 Attributes: []AttributeSchema{ 640 { 641 Name: "foo", 642 }, 643 }, 644 Blocks: []BlockSchema{ 645 { 646 Type: "bar", 647 Body: &BodySchema{ 648 Attributes: []AttributeSchema{ 649 { 650 Name: "baz", 651 Required: true, 652 }, 653 }, 654 }, 655 }, 656 }, 657 }, 658 Partial: true, 659 Want: &BodyContent{ 660 Attributes: Attributes{ 661 "foo": &Attribute{Name: "foo"}, 662 }, 663 Blocks: Blocks{ 664 { 665 Type: "bar", 666 Body: &BodyContent{ 667 Attributes: Attributes{}, 668 Blocks: Blocks{}, 669 }, 670 }, 671 }, 672 }, 673 DiagCount: 1, // attribute in nested blocks is required 674 }, 675 } 676 677 for _, test := range tests { 678 t.Run(test.Name, func(t *testing.T) { 679 var got *BodyContent 680 var diags hcl.Diagnostics 681 if test.Partial { 682 got, diags = PartialContent(test.Body, test.Schema) 683 } else { 684 got, diags = Content(test.Body, test.Schema) 685 } 686 687 if len(diags) != test.DiagCount { 688 t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount) 689 for _, diag := range diags { 690 t.Logf(" - %s", diag.Error()) 691 } 692 } 693 694 if diff := cmp.Diff(test.Want, got); diff != "" { 695 t.Errorf("wrong result\ndiff: %s", diff) 696 } 697 }) 698 } 699 } 700 701 func TestContent_JustAttributes(t *testing.T) { 702 tests := []struct { 703 Name string 704 Body *hclsyntax.Body 705 Schema *BodySchema 706 Partial bool 707 Want *BodyContent 708 DiagCount int 709 }{ 710 { 711 Name: "just attributes in the top level", 712 Body: &hclsyntax.Body{ 713 Attributes: hclsyntax.Attributes{ 714 "foo": &hclsyntax.Attribute{Name: "foo"}, 715 "bar": &hclsyntax.Attribute{Name: "bar"}, 716 "baz": &hclsyntax.Attribute{Name: "baz"}, 717 }, 718 }, 719 Schema: &BodySchema{Mode: SchemaJustAttributesMode}, 720 Want: &BodyContent{ 721 Attributes: Attributes{ 722 "foo": &Attribute{Name: "foo"}, 723 "bar": &Attribute{Name: "bar"}, 724 "baz": &Attribute{Name: "baz"}, 725 }, 726 Blocks: Blocks{}, 727 }, 728 }, 729 { 730 Name: "just attributes in nested blocks", 731 Body: &hclsyntax.Body{ 732 Blocks: hclsyntax.Blocks{ 733 &hclsyntax.Block{ 734 Type: "bar", 735 Body: &hclsyntax.Body{ 736 Attributes: hclsyntax.Attributes{ 737 "foo": &hclsyntax.Attribute{Name: "foo"}, 738 "bar": &hclsyntax.Attribute{Name: "bar"}, 739 "baz": &hclsyntax.Attribute{Name: "baz"}, 740 }, 741 }, 742 }, 743 }, 744 }, 745 Schema: &BodySchema{ 746 Blocks: []BlockSchema{ 747 { 748 Type: "bar", 749 Body: &BodySchema{Mode: SchemaJustAttributesMode}, 750 }, 751 }, 752 }, 753 Want: &BodyContent{ 754 Attributes: Attributes{}, 755 Blocks: Blocks{ 756 { 757 Type: "bar", 758 Body: &BodyContent{ 759 Attributes: Attributes{ 760 "foo": &Attribute{Name: "foo"}, 761 "bar": &Attribute{Name: "bar"}, 762 "baz": &Attribute{Name: "baz"}, 763 }, 764 Blocks: Blocks{}, 765 }, 766 }, 767 }, 768 }, 769 }, 770 { 771 Name: "just attributes in body with blocks", 772 Body: &hclsyntax.Body{ 773 Attributes: hclsyntax.Attributes{ 774 "foo": &hclsyntax.Attribute{Name: "foo"}, 775 "bar": &hclsyntax.Attribute{Name: "bar"}, 776 "baz": &hclsyntax.Attribute{Name: "baz"}, 777 }, 778 Blocks: hclsyntax.Blocks{ 779 &hclsyntax.Block{ 780 Type: "bar", 781 Body: &hclsyntax.Body{}, 782 }, 783 }, 784 }, 785 Schema: &BodySchema{Mode: SchemaJustAttributesMode}, 786 Want: &BodyContent{ 787 Attributes: Attributes{ 788 "foo": &Attribute{Name: "foo"}, 789 "bar": &Attribute{Name: "bar"}, 790 "baz": &Attribute{Name: "baz"}, 791 }, 792 Blocks: Blocks{}, 793 }, 794 DiagCount: 1, // Unexpected "bar" block; Blocks are not allowed here. 795 }, 796 } 797 798 for _, test := range tests { 799 t.Run(test.Name, func(t *testing.T) { 800 var got *BodyContent 801 var diags hcl.Diagnostics 802 if test.Partial { 803 got, diags = PartialContent(test.Body, test.Schema) 804 } else { 805 got, diags = Content(test.Body, test.Schema) 806 } 807 808 if len(diags) != test.DiagCount { 809 t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount) 810 for _, diag := range diags { 811 t.Logf(" - %s", diag.Error()) 812 } 813 } 814 815 if diff := cmp.Diff(test.Want, got); diff != "" { 816 t.Errorf("wrong result\ndiff: %s", diff) 817 } 818 }) 819 } 820 } 821 822 func Test_IsEmpty(t *testing.T) { 823 tests := []struct { 824 name string 825 body *BodyContent 826 want bool 827 }{ 828 { 829 name: "body is not empty", 830 body: &BodyContent{ 831 Attributes: Attributes{ 832 "foo": &Attribute{Name: "foo"}, 833 }, 834 }, 835 want: false, 836 }, 837 { 838 name: "body has empty attributes and empty blocks", 839 body: &BodyContent{Attributes: Attributes{}, Blocks: Blocks{}}, 840 want: true, 841 }, 842 { 843 name: "body has nil attributes and nil blocks", 844 body: &BodyContent{}, 845 want: true, 846 }, 847 { 848 name: "body is nil", 849 body: nil, 850 want: true, 851 }, 852 } 853 854 for _, test := range tests { 855 t.Run(test.name, func(t *testing.T) { 856 if test.body.IsEmpty() != test.want { 857 t.Errorf("%t is expected, but got %t", test.want, test.body.IsEmpty()) 858 } 859 }) 860 } 861 } 862 863 func TestCopy_BodyContent(t *testing.T) { 864 body := &BodyContent{ 865 Attributes: Attributes{ 866 "foo": {Name: "foo"}, 867 }, 868 Blocks: Blocks{ 869 { 870 Body: &BodyContent{ 871 Attributes: Attributes{ 872 "bar": {Name: "bar"}, 873 }, 874 Blocks: Blocks{ 875 { 876 Body: &BodyContent{ 877 Attributes: Attributes{ 878 "baz": {Name: "baz"}, 879 }, 880 }, 881 }, 882 }, 883 }, 884 }, 885 { 886 Body: &BodyContent{ 887 Attributes: Attributes{ 888 "aaa": {Name: "aaa"}, 889 "bbb": {Name: "bbb"}, 890 }, 891 }, 892 }, 893 }, 894 } 895 896 if diff := cmp.Diff(body.Copy(), body); diff != "" { 897 t.Error(diff) 898 } 899 } 900 901 func TestWalkAttributes(t *testing.T) { 902 body := &BodyContent{ 903 Attributes: Attributes{ 904 "foo": {Name: "foo"}, 905 }, 906 Blocks: Blocks{ 907 { 908 Body: &BodyContent{ 909 Attributes: Attributes{ 910 "bar": {Name: "bar"}, 911 }, 912 Blocks: Blocks{ 913 { 914 Body: &BodyContent{ 915 Attributes: Attributes{ 916 "baz": {Name: "baz"}, 917 }, 918 }, 919 }, 920 }, 921 }, 922 }, 923 { 924 Body: &BodyContent{ 925 Attributes: Attributes{ 926 "aaa": {Name: "aaa"}, 927 "bbb": {Name: "bbb"}, 928 }, 929 }, 930 }, 931 }, 932 } 933 934 got := []string{} 935 diags := body.WalkAttributes(func(a *Attribute) hcl.Diagnostics { 936 got = append(got, a.Name) 937 return nil 938 }) 939 if diags.HasErrors() { 940 t.Fatal(diags) 941 } 942 943 want := []string{"foo", "bar", "baz", "aaa", "bbb"} 944 945 opt := cmpopts.SortSlices(func(x, y string) bool { return x < y }) 946 if diff := cmp.Diff(got, want, opt); diff != "" { 947 t.Error(diff) 948 } 949 } 950 951 func TestCopy_Attribute(t *testing.T) { 952 attribute := &Attribute{ 953 Name: "foo", 954 Expr: hcl.StaticExpr(cty.StringVal("foo"), hcl.Range{}), 955 Range: hcl.Range{Start: hcl.Pos{Line: 2}}, 956 NameRange: hcl.Range{Start: hcl.Pos{Line: 1}}, 957 } 958 959 must := func(v cty.Value, diags hcl.Diagnostics) cty.Value { 960 if diags.HasErrors() { 961 t.Fatal(diags) 962 } 963 return v 964 } 965 opts := cmp.Options{ 966 cmp.Comparer(func(x, y hcl.Expression) bool { 967 return must(x.Value(nil)) == must(y.Value(nil)) 968 }), 969 } 970 if diff := cmp.Diff(attribute.Copy(), attribute, opts); diff != "" { 971 t.Error(diff) 972 } 973 } 974 975 func TestCopy_Block(t *testing.T) { 976 block := &Block{ 977 Type: "foo", 978 Labels: []string{"bar", "baz"}, 979 Body: &BodyContent{ 980 Attributes: Attributes{ 981 "foo": {Name: "foo"}, 982 }, 983 Blocks: Blocks{}, 984 }, 985 DefRange: hcl.Range{Start: hcl.Pos{Line: 1}}, 986 TypeRange: hcl.Range{Start: hcl.Pos{Line: 2}}, 987 LabelRanges: []hcl.Range{ 988 {Start: hcl.Pos{Line: 3}}, 989 {Start: hcl.Pos{Line: 4}}, 990 }, 991 } 992 993 if diff := cmp.Diff(block.Copy(), block); diff != "" { 994 t.Error(diff) 995 } 996 }