github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/ast_body_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclwrite 5 6 import ( 7 "fmt" 8 "reflect" 9 "strings" 10 "testing" 11 12 "github.com/davecgh/go-spew/spew" 13 "github.com/google/go-cmp/cmp" 14 "github.com/hashicorp/hcl/v2" 15 "github.com/hashicorp/hcl/v2/hclsyntax" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 func TestBodyGetAttribute(t *testing.T) { 20 tests := []struct { 21 src string 22 name string 23 want Tokens 24 }{ 25 { 26 "", 27 "a", 28 nil, 29 }, 30 { 31 "a = 1\n", 32 "a", 33 Tokens{ 34 { 35 Type: hclsyntax.TokenIdent, 36 Bytes: []byte{'a'}, 37 SpacesBefore: 0, 38 }, 39 { 40 Type: hclsyntax.TokenEqual, 41 Bytes: []byte{'='}, 42 SpacesBefore: 1, 43 }, 44 { 45 Type: hclsyntax.TokenNumberLit, 46 Bytes: []byte{'1'}, 47 SpacesBefore: 1, 48 }, 49 { 50 Type: hclsyntax.TokenNewline, 51 Bytes: []byte{'\n'}, 52 SpacesBefore: 0, 53 }, 54 }, 55 }, 56 { 57 "a = 1\nb = 1\nc = 1\n", 58 "a", 59 Tokens{ 60 { 61 Type: hclsyntax.TokenIdent, 62 Bytes: []byte{'a'}, 63 SpacesBefore: 0, 64 }, 65 { 66 Type: hclsyntax.TokenEqual, 67 Bytes: []byte{'='}, 68 SpacesBefore: 1, 69 }, 70 { 71 Type: hclsyntax.TokenNumberLit, 72 Bytes: []byte{'1'}, 73 SpacesBefore: 1, 74 }, 75 { 76 Type: hclsyntax.TokenNewline, 77 Bytes: []byte{'\n'}, 78 SpacesBefore: 0, 79 }, 80 }, 81 }, 82 { 83 "a = 1\nb = 2\nc = 3\n", 84 "b", 85 Tokens{ 86 { 87 Type: hclsyntax.TokenIdent, 88 Bytes: []byte{'b'}, 89 SpacesBefore: 0, 90 }, 91 { 92 Type: hclsyntax.TokenEqual, 93 Bytes: []byte{'='}, 94 SpacesBefore: 1, 95 }, 96 { 97 Type: hclsyntax.TokenNumberLit, 98 Bytes: []byte{'2'}, 99 SpacesBefore: 1, 100 }, 101 { 102 Type: hclsyntax.TokenNewline, 103 Bytes: []byte{'\n'}, 104 SpacesBefore: 0, 105 }, 106 }, 107 }, 108 { 109 "a = 1\nb = 2\nc = 3\n", 110 "c", 111 Tokens{ 112 { 113 Type: hclsyntax.TokenIdent, 114 Bytes: []byte{'c'}, 115 SpacesBefore: 0, 116 }, 117 { 118 Type: hclsyntax.TokenEqual, 119 Bytes: []byte{'='}, 120 SpacesBefore: 1, 121 }, 122 { 123 Type: hclsyntax.TokenNumberLit, 124 Bytes: []byte{'3'}, 125 SpacesBefore: 1, 126 }, 127 { 128 Type: hclsyntax.TokenNewline, 129 Bytes: []byte{'\n'}, 130 SpacesBefore: 0, 131 }, 132 }, 133 }, 134 { 135 "a = 1\n# b is a b\nb = 2\nc = 3\n", 136 "b", 137 Tokens{ 138 { 139 // Recognized as a lead comment and so attached to the attribute 140 Type: hclsyntax.TokenComment, 141 Bytes: []byte("# b is a b\n"), 142 SpacesBefore: 0, 143 }, 144 { 145 Type: hclsyntax.TokenIdent, 146 Bytes: []byte{'b'}, 147 SpacesBefore: 0, 148 }, 149 { 150 Type: hclsyntax.TokenEqual, 151 Bytes: []byte{'='}, 152 SpacesBefore: 1, 153 }, 154 { 155 Type: hclsyntax.TokenNumberLit, 156 Bytes: []byte{'2'}, 157 SpacesBefore: 1, 158 }, 159 { 160 Type: hclsyntax.TokenNewline, 161 Bytes: []byte{'\n'}, 162 SpacesBefore: 0, 163 }, 164 }, 165 }, 166 { 167 "a = 1\n# not attached to a or b\n\nb = 2\nc = 3\n", 168 "b", 169 Tokens{ 170 { 171 Type: hclsyntax.TokenIdent, 172 Bytes: []byte{'b'}, 173 SpacesBefore: 0, 174 }, 175 { 176 Type: hclsyntax.TokenEqual, 177 Bytes: []byte{'='}, 178 SpacesBefore: 1, 179 }, 180 { 181 Type: hclsyntax.TokenNumberLit, 182 Bytes: []byte{'2'}, 183 SpacesBefore: 1, 184 }, 185 { 186 Type: hclsyntax.TokenNewline, 187 Bytes: []byte{'\n'}, 188 SpacesBefore: 0, 189 }, 190 }, 191 }, 192 } 193 194 for _, test := range tests { 195 t.Run(fmt.Sprintf("%s in %s", test.name, test.src), func(t *testing.T) { 196 f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) 197 if len(diags) != 0 { 198 for _, diag := range diags { 199 t.Logf("- %s", diag.Error()) 200 } 201 t.Fatalf("unexpected diagnostics") 202 } 203 204 attr := f.Body().GetAttribute(test.name) 205 if attr == nil { 206 if test.want != nil { 207 t.Fatal("attribute not found, but want it to exist") 208 } 209 } else { 210 if test.want == nil { 211 t.Fatal("attribute found, but expecting not found") 212 } 213 214 got := attr.BuildTokens(nil) 215 if !reflect.DeepEqual(got, test.want) { 216 t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.want)) 217 } 218 } 219 }) 220 } 221 } 222 223 func TestBodyFirstMatchingBlock(t *testing.T) { 224 src := `a = "b" 225 service { 226 attr0 = "val0" 227 } 228 service "label1" { 229 attr1 = "val1" 230 } 231 service "label1" "label2" { 232 attr2 = "val2" 233 } 234 parent { 235 attr3 = "val3" 236 child { 237 attr4 = "val4" 238 } 239 } 240 ` 241 242 tests := []struct { 243 src string 244 typeName string 245 labels []string 246 want string 247 }{ 248 { 249 src, 250 "service", 251 []string{}, 252 `service { 253 attr0 = "val0" 254 } 255 `, 256 }, 257 { 258 src, 259 "service", 260 []string{"label1"}, 261 `service "label1" { 262 attr1 = "val1" 263 } 264 `, 265 }, 266 { 267 src, 268 "service", 269 []string{"label1", "label2"}, 270 `service "label1" "label2" { 271 attr2 = "val2" 272 } 273 `, 274 }, 275 { 276 src, 277 "parent", 278 []string{}, 279 `parent { 280 attr3 = "val3" 281 child { 282 attr4 = "val4" 283 } 284 } 285 `, 286 }, 287 { 288 src, 289 "hoge", 290 []string{}, 291 "", 292 }, 293 { 294 src, 295 "hoge", 296 []string{"label1"}, 297 "", 298 }, 299 { 300 src, 301 "service", 302 []string{"label2"}, 303 "", 304 }, 305 { 306 src, 307 "service", 308 []string{"label2", "label1"}, 309 "", 310 }, 311 { 312 src, 313 "child", 314 []string{}, 315 "", 316 }, 317 } 318 319 for _, test := range tests { 320 t.Run(fmt.Sprintf("%s %s", test.typeName, strings.Join(test.labels, " ")), func(t *testing.T) { 321 f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) 322 if len(diags) != 0 { 323 for _, diag := range diags { 324 t.Logf("- %s", diag.Error()) 325 } 326 t.Fatalf("unexpected diagnostics") 327 } 328 329 block := f.Body().FirstMatchingBlock(test.typeName, test.labels) 330 if block == nil { 331 if test.want != "" { 332 t.Fatal("block not found, but want it to exist") 333 } 334 } else { 335 if test.want == "" { 336 t.Fatal("block found, but expecting not found") 337 } 338 339 got := string(block.BuildTokens(nil).Bytes()) 340 if got != test.want { 341 t.Errorf("wrong result\ngot: %s\nwant: %s", got, test.want) 342 } 343 } 344 }) 345 } 346 } 347 348 func TestBodySetAttributeValue(t *testing.T) { 349 tests := []struct { 350 src string 351 name string 352 val cty.Value 353 want Tokens 354 }{ 355 { 356 "", 357 "a", 358 cty.True, 359 Tokens{ 360 { 361 Type: hclsyntax.TokenIdent, 362 Bytes: []byte{'a'}, 363 SpacesBefore: 0, 364 }, 365 { 366 Type: hclsyntax.TokenEqual, 367 Bytes: []byte{'='}, 368 SpacesBefore: 1, 369 }, 370 { 371 Type: hclsyntax.TokenIdent, 372 Bytes: []byte("true"), 373 SpacesBefore: 1, 374 }, 375 { 376 Type: hclsyntax.TokenNewline, 377 Bytes: []byte{'\n'}, 378 SpacesBefore: 0, 379 }, 380 { 381 Type: hclsyntax.TokenEOF, 382 Bytes: []byte{}, 383 SpacesBefore: 0, 384 }, 385 }, 386 }, 387 { 388 "b = false\n", 389 "a", 390 cty.True, 391 Tokens{ 392 { 393 Type: hclsyntax.TokenIdent, 394 Bytes: []byte{'b'}, 395 SpacesBefore: 0, 396 }, 397 { 398 Type: hclsyntax.TokenEqual, 399 Bytes: []byte{'='}, 400 SpacesBefore: 1, 401 }, 402 { 403 Type: hclsyntax.TokenIdent, 404 Bytes: []byte("false"), 405 SpacesBefore: 1, 406 }, 407 { 408 Type: hclsyntax.TokenNewline, 409 Bytes: []byte{'\n'}, 410 SpacesBefore: 0, 411 }, 412 { 413 Type: hclsyntax.TokenIdent, 414 Bytes: []byte{'a'}, 415 SpacesBefore: 0, 416 }, 417 { 418 Type: hclsyntax.TokenEqual, 419 Bytes: []byte{'='}, 420 SpacesBefore: 1, 421 }, 422 { 423 Type: hclsyntax.TokenIdent, 424 Bytes: []byte("true"), 425 SpacesBefore: 1, 426 }, 427 { 428 Type: hclsyntax.TokenNewline, 429 Bytes: []byte{'\n'}, 430 SpacesBefore: 0, 431 }, 432 { 433 Type: hclsyntax.TokenEOF, 434 Bytes: []byte{}, 435 SpacesBefore: 0, 436 }, 437 }, 438 }, 439 { 440 "a = false\n", 441 "a", 442 cty.True, 443 Tokens{ 444 { 445 Type: hclsyntax.TokenIdent, 446 Bytes: []byte{'a'}, 447 SpacesBefore: 0, 448 }, 449 { 450 Type: hclsyntax.TokenEqual, 451 Bytes: []byte{'='}, 452 SpacesBefore: 1, 453 }, 454 { 455 Type: hclsyntax.TokenIdent, 456 Bytes: []byte("true"), 457 SpacesBefore: 1, 458 }, 459 { 460 Type: hclsyntax.TokenNewline, 461 Bytes: []byte{'\n'}, 462 SpacesBefore: 0, 463 }, 464 { 465 Type: hclsyntax.TokenEOF, 466 Bytes: []byte{}, 467 SpacesBefore: 0, 468 }, 469 }, 470 }, 471 { 472 "a = 1\nb = false\n", 473 "a", 474 cty.True, 475 Tokens{ 476 { 477 Type: hclsyntax.TokenIdent, 478 Bytes: []byte{'a'}, 479 SpacesBefore: 0, 480 }, 481 { 482 Type: hclsyntax.TokenEqual, 483 Bytes: []byte{'='}, 484 SpacesBefore: 1, 485 }, 486 { 487 Type: hclsyntax.TokenIdent, 488 Bytes: []byte("true"), 489 SpacesBefore: 1, 490 }, 491 { 492 Type: hclsyntax.TokenNewline, 493 Bytes: []byte{'\n'}, 494 SpacesBefore: 0, 495 }, 496 { 497 Type: hclsyntax.TokenIdent, 498 Bytes: []byte{'b'}, 499 SpacesBefore: 0, 500 }, 501 { 502 Type: hclsyntax.TokenEqual, 503 Bytes: []byte{'='}, 504 SpacesBefore: 1, 505 }, 506 { 507 Type: hclsyntax.TokenIdent, 508 Bytes: []byte("false"), 509 SpacesBefore: 1, 510 }, 511 { 512 Type: hclsyntax.TokenNewline, 513 Bytes: []byte{'\n'}, 514 SpacesBefore: 0, 515 }, 516 { 517 Type: hclsyntax.TokenEOF, 518 Bytes: []byte{}, 519 SpacesBefore: 0, 520 }, 521 }, 522 }, 523 } 524 525 for _, test := range tests { 526 t.Run(fmt.Sprintf("%s = %#v in %s", test.name, test.val, test.src), func(t *testing.T) { 527 f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) 528 if len(diags) != 0 { 529 for _, diag := range diags { 530 t.Logf("- %s", diag.Error()) 531 } 532 t.Fatalf("unexpected diagnostics") 533 } 534 535 f.Body().SetAttributeValue(test.name, test.val) 536 got := f.BuildTokens(nil) 537 format(got) 538 if !reflect.DeepEqual(got, test.want) { 539 diff := cmp.Diff(test.want, got) 540 t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff) 541 } 542 }) 543 } 544 } 545 546 func TestBodySetAttributeTraversal(t *testing.T) { 547 tests := []struct { 548 src string 549 name string 550 trav string 551 want Tokens 552 }{ 553 { 554 "", 555 "a", 556 `b`, 557 Tokens{ 558 { 559 Type: hclsyntax.TokenIdent, 560 Bytes: []byte{'a'}, 561 SpacesBefore: 0, 562 }, 563 { 564 Type: hclsyntax.TokenEqual, 565 Bytes: []byte{'='}, 566 SpacesBefore: 1, 567 }, 568 { 569 Type: hclsyntax.TokenIdent, 570 Bytes: []byte("b"), 571 SpacesBefore: 1, 572 }, 573 { 574 Type: hclsyntax.TokenNewline, 575 Bytes: []byte{'\n'}, 576 SpacesBefore: 0, 577 }, 578 { 579 Type: hclsyntax.TokenEOF, 580 Bytes: []byte{}, 581 SpacesBefore: 0, 582 }, 583 }, 584 }, 585 { 586 "", 587 "a", 588 `b.c.d`, 589 Tokens{ 590 { 591 Type: hclsyntax.TokenIdent, 592 Bytes: []byte{'a'}, 593 SpacesBefore: 0, 594 }, 595 { 596 Type: hclsyntax.TokenEqual, 597 Bytes: []byte{'='}, 598 SpacesBefore: 1, 599 }, 600 { 601 Type: hclsyntax.TokenIdent, 602 Bytes: []byte("b"), 603 SpacesBefore: 1, 604 }, 605 { 606 Type: hclsyntax.TokenDot, 607 Bytes: []byte("."), 608 SpacesBefore: 0, 609 }, 610 { 611 Type: hclsyntax.TokenIdent, 612 Bytes: []byte("c"), 613 SpacesBefore: 0, 614 }, 615 { 616 Type: hclsyntax.TokenDot, 617 Bytes: []byte("."), 618 SpacesBefore: 0, 619 }, 620 { 621 Type: hclsyntax.TokenIdent, 622 Bytes: []byte("d"), 623 SpacesBefore: 0, 624 }, 625 { 626 Type: hclsyntax.TokenNewline, 627 Bytes: []byte{'\n'}, 628 SpacesBefore: 0, 629 }, 630 { 631 Type: hclsyntax.TokenEOF, 632 Bytes: []byte{}, 633 SpacesBefore: 0, 634 }, 635 }, 636 }, 637 { 638 "", 639 "a", 640 `b[0]`, 641 Tokens{ 642 { 643 Type: hclsyntax.TokenIdent, 644 Bytes: []byte{'a'}, 645 SpacesBefore: 0, 646 }, 647 { 648 Type: hclsyntax.TokenEqual, 649 Bytes: []byte{'='}, 650 SpacesBefore: 1, 651 }, 652 { 653 Type: hclsyntax.TokenIdent, 654 Bytes: []byte("b"), 655 SpacesBefore: 1, 656 }, 657 { 658 Type: hclsyntax.TokenOBrack, 659 Bytes: []byte("["), 660 SpacesBefore: 0, 661 }, 662 { 663 Type: hclsyntax.TokenNumberLit, 664 Bytes: []byte("0"), 665 SpacesBefore: 0, 666 }, 667 { 668 Type: hclsyntax.TokenCBrack, 669 Bytes: []byte("]"), 670 SpacesBefore: 0, 671 }, 672 { 673 Type: hclsyntax.TokenNewline, 674 Bytes: []byte{'\n'}, 675 SpacesBefore: 0, 676 }, 677 { 678 Type: hclsyntax.TokenEOF, 679 Bytes: []byte{}, 680 SpacesBefore: 0, 681 }, 682 }, 683 }, 684 { 685 "", 686 "a", 687 `b[0].c`, 688 Tokens{ 689 { 690 Type: hclsyntax.TokenIdent, 691 Bytes: []byte{'a'}, 692 SpacesBefore: 0, 693 }, 694 { 695 Type: hclsyntax.TokenEqual, 696 Bytes: []byte{'='}, 697 SpacesBefore: 1, 698 }, 699 { 700 Type: hclsyntax.TokenIdent, 701 Bytes: []byte("b"), 702 SpacesBefore: 1, 703 }, 704 { 705 Type: hclsyntax.TokenOBrack, 706 Bytes: []byte("["), 707 SpacesBefore: 0, 708 }, 709 { 710 Type: hclsyntax.TokenNumberLit, 711 Bytes: []byte("0"), 712 SpacesBefore: 0, 713 }, 714 { 715 Type: hclsyntax.TokenCBrack, 716 Bytes: []byte("]"), 717 SpacesBefore: 0, 718 }, 719 { 720 Type: hclsyntax.TokenDot, 721 Bytes: []byte("."), 722 SpacesBefore: 0, 723 }, 724 { 725 Type: hclsyntax.TokenIdent, 726 Bytes: []byte("c"), 727 SpacesBefore: 0, 728 }, 729 { 730 Type: hclsyntax.TokenNewline, 731 Bytes: []byte{'\n'}, 732 SpacesBefore: 0, 733 }, 734 { 735 Type: hclsyntax.TokenEOF, 736 Bytes: []byte{}, 737 SpacesBefore: 0, 738 }, 739 }, 740 }, 741 } 742 743 for _, test := range tests { 744 t.Run(fmt.Sprintf("%s = %s in %s", test.name, test.trav, test.src), func(t *testing.T) { 745 f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) 746 if len(diags) != 0 { 747 for _, diag := range diags { 748 t.Logf("- %s", diag.Error()) 749 } 750 t.Fatalf("unexpected diagnostics") 751 } 752 753 traversal, diags := hclsyntax.ParseTraversalAbs([]byte(test.trav), "", hcl.Pos{Line: 1, Column: 1}) 754 if len(diags) != 0 { 755 for _, diag := range diags { 756 t.Logf("- %s", diag.Error()) 757 } 758 t.Fatalf("unexpected diagnostics from traversal") 759 } 760 761 f.Body().SetAttributeTraversal(test.name, traversal) 762 got := f.BuildTokens(nil) 763 format(got) 764 if !reflect.DeepEqual(got, test.want) { 765 diff := cmp.Diff(test.want, got) 766 t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff) 767 } 768 }) 769 } 770 } 771 772 func TestBodySetAttributeRaw(t *testing.T) { 773 tests := []struct { 774 src string 775 name string 776 tokens Tokens 777 want Tokens 778 }{ 779 { 780 "", 781 "a", 782 Tokens{ 783 { 784 Type: hclsyntax.TokenIdent, 785 Bytes: []byte(`true`), 786 SpacesBefore: 0, 787 }, 788 }, 789 Tokens{ 790 { 791 Type: hclsyntax.TokenIdent, 792 Bytes: []byte{'a'}, 793 SpacesBefore: 0, 794 }, 795 { 796 Type: hclsyntax.TokenEqual, 797 Bytes: []byte{'='}, 798 SpacesBefore: 1, 799 }, 800 { 801 Type: hclsyntax.TokenIdent, 802 Bytes: []byte("true"), 803 SpacesBefore: 1, 804 }, 805 { 806 Type: hclsyntax.TokenNewline, 807 Bytes: []byte{'\n'}, 808 SpacesBefore: 0, 809 }, 810 { 811 Type: hclsyntax.TokenEOF, 812 Bytes: []byte{}, 813 SpacesBefore: 0, 814 }, 815 }, 816 }, 817 { 818 "a = 23\n", 819 "a", 820 Tokens{ 821 { 822 Type: hclsyntax.TokenIdent, 823 Bytes: []byte(`true`), 824 SpacesBefore: 0, 825 }, 826 }, 827 Tokens{ 828 { 829 Type: hclsyntax.TokenIdent, 830 Bytes: []byte{'a'}, 831 SpacesBefore: 0, 832 }, 833 { 834 Type: hclsyntax.TokenEqual, 835 Bytes: []byte{'='}, 836 SpacesBefore: 1, 837 }, 838 { 839 Type: hclsyntax.TokenIdent, 840 Bytes: []byte("true"), 841 SpacesBefore: 1, 842 }, 843 { 844 Type: hclsyntax.TokenNewline, 845 Bytes: []byte{'\n'}, 846 SpacesBefore: 0, 847 }, 848 { 849 Type: hclsyntax.TokenEOF, 850 Bytes: []byte{}, 851 SpacesBefore: 0, 852 }, 853 }, 854 }, 855 { 856 "b = 23\n", 857 "a", 858 Tokens{ 859 { 860 Type: hclsyntax.TokenIdent, 861 Bytes: []byte(`true`), 862 SpacesBefore: 0, 863 }, 864 }, 865 Tokens{ 866 { 867 Type: hclsyntax.TokenIdent, 868 Bytes: []byte{'b'}, 869 SpacesBefore: 0, 870 }, 871 { 872 Type: hclsyntax.TokenEqual, 873 Bytes: []byte{'='}, 874 SpacesBefore: 1, 875 }, 876 { 877 Type: hclsyntax.TokenNumberLit, 878 Bytes: []byte("23"), 879 SpacesBefore: 1, 880 }, 881 { 882 Type: hclsyntax.TokenNewline, 883 Bytes: []byte{'\n'}, 884 SpacesBefore: 0, 885 }, 886 { 887 Type: hclsyntax.TokenIdent, 888 Bytes: []byte{'a'}, 889 SpacesBefore: 0, 890 }, 891 { 892 Type: hclsyntax.TokenEqual, 893 Bytes: []byte{'='}, 894 SpacesBefore: 1, 895 }, 896 { 897 Type: hclsyntax.TokenIdent, 898 Bytes: []byte("true"), 899 SpacesBefore: 1, 900 }, 901 { 902 Type: hclsyntax.TokenNewline, 903 Bytes: []byte{'\n'}, 904 SpacesBefore: 0, 905 }, 906 { 907 Type: hclsyntax.TokenEOF, 908 Bytes: []byte{}, 909 SpacesBefore: 0, 910 }, 911 }, 912 }, 913 } 914 915 for _, test := range tests { 916 t.Run(fmt.Sprintf("%s = %s in %s", test.name, test.tokens.Bytes(), test.src), func(t *testing.T) { 917 f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) 918 if len(diags) != 0 { 919 for _, diag := range diags { 920 t.Logf("- %s", diag.Error()) 921 } 922 t.Fatalf("unexpected diagnostics") 923 } 924 925 f.Body().SetAttributeRaw(test.name, test.tokens) 926 got := f.BuildTokens(nil) 927 format(got) 928 if !reflect.DeepEqual(got, test.want) { 929 diff := cmp.Diff(test.want, got) 930 t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff) 931 } 932 }) 933 } 934 } 935 936 func TestBodySetAttributeValueInBlock(t *testing.T) { 937 src := `service "label1" { 938 attr1 = "val1" 939 } 940 ` 941 tests := []struct { 942 src string 943 typeName string 944 labels []string 945 attr string 946 val cty.Value 947 want string 948 }{ 949 { 950 src, 951 "service", 952 []string{"label1"}, 953 "attr1", 954 cty.StringVal("updated1"), 955 `service "label1" { 956 attr1 = "updated1" 957 } 958 `, 959 }, 960 } 961 962 for _, test := range tests { 963 t.Run(fmt.Sprintf("%s = %#v in %s %s", test.attr, test.val, test.typeName, strings.Join(test.labels, " ")), func(t *testing.T) { 964 f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) 965 if len(diags) != 0 { 966 for _, diag := range diags { 967 t.Logf("- %s", diag.Error()) 968 } 969 t.Fatalf("unexpected diagnostics") 970 } 971 972 b := f.Body().FirstMatchingBlock(test.typeName, test.labels) 973 b.Body().SetAttributeValue(test.attr, test.val) 974 tokens := f.BuildTokens(nil) 975 format(tokens) 976 got := string(tokens.Bytes()) 977 if got != test.want { 978 t.Errorf("wrong result\ngot: %s\nwant: %s\n", got, test.want) 979 } 980 }) 981 } 982 } 983 984 func TestBodySetAttributeValueInNestedBlock(t *testing.T) { 985 src := `parent { 986 attr1 = "val1" 987 child { 988 attr2 = "val2" 989 } 990 } 991 ` 992 tests := []struct { 993 src string 994 parentTypeName string 995 childTypeName string 996 attr string 997 val cty.Value 998 want string 999 }{ 1000 { 1001 src, 1002 "parent", 1003 "child", 1004 "attr2", 1005 cty.StringVal("updated2"), 1006 `parent { 1007 attr1 = "val1" 1008 child { 1009 attr2 = "updated2" 1010 } 1011 } 1012 `, 1013 }, 1014 } 1015 1016 for _, test := range tests { 1017 t.Run(fmt.Sprintf("%s = %#v in %s in %s", test.attr, test.val, test.childTypeName, test.parentTypeName), func(t *testing.T) { 1018 f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) 1019 if len(diags) != 0 { 1020 for _, diag := range diags { 1021 t.Logf("- %s", diag.Error()) 1022 } 1023 t.Fatalf("unexpected diagnostics") 1024 } 1025 1026 parent := f.Body().FirstMatchingBlock(test.parentTypeName, []string{}) 1027 child := parent.Body().FirstMatchingBlock(test.childTypeName, []string{}) 1028 child.Body().SetAttributeValue(test.attr, test.val) 1029 tokens := f.BuildTokens(nil) 1030 format(tokens) 1031 got := string(tokens.Bytes()) 1032 if got != test.want { 1033 t.Errorf("wrong result\ngot: %s\nwant: %s\n", got, test.want) 1034 } 1035 }) 1036 } 1037 } 1038 1039 func TestBodyRemoveAttribute(t *testing.T) { 1040 tests := []struct { 1041 src string 1042 name string 1043 want Tokens 1044 }{ 1045 { 1046 "", 1047 "a", 1048 Tokens{ 1049 { 1050 Type: hclsyntax.TokenEOF, 1051 Bytes: []byte{}, 1052 SpacesBefore: 0, 1053 }, 1054 }, 1055 }, 1056 { 1057 "b = false\n", 1058 "a", 1059 Tokens{ 1060 { 1061 Type: hclsyntax.TokenIdent, 1062 Bytes: []byte{'b'}, 1063 SpacesBefore: 0, 1064 }, 1065 { 1066 Type: hclsyntax.TokenEqual, 1067 Bytes: []byte{'='}, 1068 SpacesBefore: 1, 1069 }, 1070 { 1071 Type: hclsyntax.TokenIdent, 1072 Bytes: []byte("false"), 1073 SpacesBefore: 1, 1074 }, 1075 { 1076 Type: hclsyntax.TokenNewline, 1077 Bytes: []byte{'\n'}, 1078 SpacesBefore: 0, 1079 }, 1080 { 1081 Type: hclsyntax.TokenEOF, 1082 Bytes: []byte{}, 1083 SpacesBefore: 0, 1084 }, 1085 }, 1086 }, 1087 { 1088 "a = false\n", 1089 "a", 1090 Tokens{ 1091 { 1092 Type: hclsyntax.TokenEOF, 1093 Bytes: []byte{}, 1094 SpacesBefore: 0, 1095 }, 1096 }, 1097 }, 1098 { 1099 "a = 1\nb = false\n", 1100 "a", 1101 Tokens{ 1102 { 1103 Type: hclsyntax.TokenIdent, 1104 Bytes: []byte{'b'}, 1105 SpacesBefore: 0, 1106 }, 1107 { 1108 Type: hclsyntax.TokenEqual, 1109 Bytes: []byte{'='}, 1110 SpacesBefore: 1, 1111 }, 1112 { 1113 Type: hclsyntax.TokenIdent, 1114 Bytes: []byte("false"), 1115 SpacesBefore: 1, 1116 }, 1117 { 1118 Type: hclsyntax.TokenNewline, 1119 Bytes: []byte{'\n'}, 1120 SpacesBefore: 0, 1121 }, 1122 { 1123 Type: hclsyntax.TokenEOF, 1124 Bytes: []byte{}, 1125 SpacesBefore: 0, 1126 }, 1127 }, 1128 }, 1129 } 1130 1131 for _, test := range tests { 1132 t.Run(fmt.Sprintf("%s in %s", test.name, test.src), func(t *testing.T) { 1133 f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) 1134 if len(diags) != 0 { 1135 for _, diag := range diags { 1136 t.Logf("- %s", diag.Error()) 1137 } 1138 t.Fatalf("unexpected diagnostics") 1139 } 1140 1141 f.Body().RemoveAttribute(test.name) 1142 got := f.BuildTokens(nil) 1143 format(got) 1144 if !reflect.DeepEqual(got, test.want) { 1145 diff := cmp.Diff(test.want, got) 1146 t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff) 1147 } 1148 }) 1149 } 1150 } 1151 1152 func TestBodyAppendBlock(t *testing.T) { 1153 tests := []struct { 1154 src string 1155 blockType string 1156 labels []string 1157 want Tokens 1158 }{ 1159 { 1160 "", 1161 "foo", 1162 nil, 1163 Tokens{ 1164 { 1165 Type: hclsyntax.TokenIdent, 1166 Bytes: []byte(`foo`), 1167 SpacesBefore: 0, 1168 }, 1169 { 1170 Type: hclsyntax.TokenOBrace, 1171 Bytes: []byte{'{'}, 1172 SpacesBefore: 1, 1173 }, 1174 { 1175 Type: hclsyntax.TokenNewline, 1176 Bytes: []byte{'\n'}, 1177 SpacesBefore: 0, 1178 }, 1179 { 1180 Type: hclsyntax.TokenCBrace, 1181 Bytes: []byte{'}'}, 1182 SpacesBefore: 0, 1183 }, 1184 { 1185 Type: hclsyntax.TokenNewline, 1186 Bytes: []byte{'\n'}, 1187 SpacesBefore: 0, 1188 }, 1189 { 1190 Type: hclsyntax.TokenEOF, 1191 Bytes: []byte{}, 1192 SpacesBefore: 0, 1193 }, 1194 }, 1195 }, 1196 { 1197 "", 1198 "foo", 1199 []string{"bar"}, 1200 Tokens{ 1201 { 1202 Type: hclsyntax.TokenIdent, 1203 Bytes: []byte(`foo`), 1204 SpacesBefore: 0, 1205 }, 1206 { 1207 Type: hclsyntax.TokenOQuote, 1208 Bytes: []byte(`"`), 1209 SpacesBefore: 1, 1210 }, 1211 { 1212 Type: hclsyntax.TokenQuotedLit, 1213 Bytes: []byte(`bar`), 1214 SpacesBefore: 0, 1215 }, 1216 { 1217 Type: hclsyntax.TokenCQuote, 1218 Bytes: []byte(`"`), 1219 SpacesBefore: 0, 1220 }, 1221 { 1222 Type: hclsyntax.TokenOBrace, 1223 Bytes: []byte{'{'}, 1224 SpacesBefore: 1, 1225 }, 1226 { 1227 Type: hclsyntax.TokenNewline, 1228 Bytes: []byte{'\n'}, 1229 SpacesBefore: 0, 1230 }, 1231 { 1232 Type: hclsyntax.TokenCBrace, 1233 Bytes: []byte{'}'}, 1234 SpacesBefore: 0, 1235 }, 1236 { 1237 Type: hclsyntax.TokenNewline, 1238 Bytes: []byte{'\n'}, 1239 SpacesBefore: 0, 1240 }, 1241 { 1242 Type: hclsyntax.TokenEOF, 1243 Bytes: []byte{}, 1244 SpacesBefore: 0, 1245 }, 1246 }, 1247 }, 1248 { 1249 "", 1250 "foo", 1251 []string{"bar", "baz"}, 1252 Tokens{ 1253 { 1254 Type: hclsyntax.TokenIdent, 1255 Bytes: []byte(`foo`), 1256 SpacesBefore: 0, 1257 }, 1258 { 1259 Type: hclsyntax.TokenOQuote, 1260 Bytes: []byte(`"`), 1261 SpacesBefore: 1, 1262 }, 1263 { 1264 Type: hclsyntax.TokenQuotedLit, 1265 Bytes: []byte(`bar`), 1266 SpacesBefore: 0, 1267 }, 1268 { 1269 Type: hclsyntax.TokenCQuote, 1270 Bytes: []byte(`"`), 1271 SpacesBefore: 0, 1272 }, 1273 { 1274 Type: hclsyntax.TokenOQuote, 1275 Bytes: []byte(`"`), 1276 SpacesBefore: 1, 1277 }, 1278 { 1279 Type: hclsyntax.TokenQuotedLit, 1280 Bytes: []byte(`baz`), 1281 SpacesBefore: 0, 1282 }, 1283 { 1284 Type: hclsyntax.TokenCQuote, 1285 Bytes: []byte(`"`), 1286 SpacesBefore: 0, 1287 }, 1288 { 1289 Type: hclsyntax.TokenOBrace, 1290 Bytes: []byte{'{'}, 1291 SpacesBefore: 1, 1292 }, 1293 { 1294 Type: hclsyntax.TokenNewline, 1295 Bytes: []byte{'\n'}, 1296 SpacesBefore: 0, 1297 }, 1298 { 1299 Type: hclsyntax.TokenCBrace, 1300 Bytes: []byte{'}'}, 1301 SpacesBefore: 0, 1302 }, 1303 { 1304 Type: hclsyntax.TokenNewline, 1305 Bytes: []byte{'\n'}, 1306 SpacesBefore: 0, 1307 }, 1308 { 1309 Type: hclsyntax.TokenEOF, 1310 Bytes: []byte{}, 1311 SpacesBefore: 0, 1312 }, 1313 }, 1314 }, 1315 { 1316 "bar {}\n", 1317 "foo", 1318 nil, 1319 Tokens{ 1320 { 1321 Type: hclsyntax.TokenIdent, 1322 Bytes: []byte(`bar`), 1323 SpacesBefore: 0, 1324 }, 1325 { 1326 Type: hclsyntax.TokenOBrace, 1327 Bytes: []byte{'{'}, 1328 SpacesBefore: 1, 1329 }, 1330 { 1331 Type: hclsyntax.TokenCBrace, 1332 Bytes: []byte{'}'}, 1333 SpacesBefore: 0, 1334 }, 1335 { 1336 Type: hclsyntax.TokenNewline, 1337 Bytes: []byte{'\n'}, 1338 SpacesBefore: 0, 1339 }, 1340 { 1341 Type: hclsyntax.TokenIdent, 1342 Bytes: []byte(`foo`), 1343 SpacesBefore: 0, 1344 }, 1345 { 1346 Type: hclsyntax.TokenOBrace, 1347 Bytes: []byte{'{'}, 1348 SpacesBefore: 1, 1349 }, 1350 { 1351 Type: hclsyntax.TokenNewline, 1352 Bytes: []byte{'\n'}, 1353 SpacesBefore: 0, 1354 }, 1355 { 1356 Type: hclsyntax.TokenCBrace, 1357 Bytes: []byte{'}'}, 1358 SpacesBefore: 0, 1359 }, 1360 { 1361 Type: hclsyntax.TokenNewline, 1362 Bytes: []byte{'\n'}, 1363 SpacesBefore: 0, 1364 }, 1365 { 1366 Type: hclsyntax.TokenEOF, 1367 Bytes: []byte{}, 1368 SpacesBefore: 0, 1369 }, 1370 }, 1371 }, 1372 } 1373 1374 for _, test := range tests { 1375 t.Run(fmt.Sprintf("%s %#v in %s", test.blockType, test.blockType, test.src), func(t *testing.T) { 1376 f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) 1377 if len(diags) != 0 { 1378 for _, diag := range diags { 1379 t.Logf("- %s", diag.Error()) 1380 } 1381 t.Fatalf("unexpected diagnostics") 1382 } 1383 1384 f.Body().AppendNewBlock(test.blockType, test.labels) 1385 got := f.BuildTokens(nil) 1386 format(got) 1387 if !reflect.DeepEqual(got, test.want) { 1388 diff := cmp.Diff(test.want, got) 1389 t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff) 1390 } 1391 }) 1392 } 1393 } 1394 1395 func TestBodyRemoveBlock(t *testing.T) { 1396 src := strings.TrimSpace(` 1397 a = 1 1398 1399 # Foo 1400 foo { 1401 b = 1 1402 } 1403 foo { 1404 b = 2 1405 } 1406 bar {} 1407 `) 1408 f, diags := ParseConfig([]byte(src), "", hcl.Pos{Line: 1, Column: 1}) 1409 if len(diags) != 0 { 1410 for _, diag := range diags { 1411 t.Logf("- %s", diag.Error()) 1412 } 1413 t.Fatalf("unexpected diagnostics") 1414 } 1415 1416 t.Logf("Removing the first block") 1417 t.Logf("initial content:\n%s", f.Bytes()) 1418 body := f.Body() 1419 block := body.FirstMatchingBlock("foo", nil) 1420 if block == nil { 1421 t.Fatalf("didn't find a 'foo' block") 1422 } 1423 removed := body.RemoveBlock(block) 1424 if !removed { 1425 t.Fatalf("didn't remove first block") 1426 } 1427 t.Logf("updated content:\n%s", f.Bytes()) 1428 got := f.BuildTokens(nil) 1429 want := Tokens{ 1430 0: { 1431 Type: hclsyntax.TokenIdent, 1432 Bytes: []byte(`a`), 1433 SpacesBefore: 0, 1434 }, 1435 1: { 1436 Type: hclsyntax.TokenEqual, 1437 Bytes: []byte(`=`), 1438 SpacesBefore: 1, 1439 }, 1440 2: { 1441 Type: hclsyntax.TokenNumberLit, 1442 Bytes: []byte(`1`), 1443 SpacesBefore: 1, 1444 }, 1445 3: { 1446 Type: hclsyntax.TokenNewline, 1447 Bytes: []byte("\n"), 1448 SpacesBefore: 0, 1449 }, 1450 4: { 1451 Type: hclsyntax.TokenNewline, 1452 Bytes: []byte("\n"), 1453 SpacesBefore: 0, 1454 }, 1455 5: { 1456 Type: hclsyntax.TokenIdent, 1457 Bytes: []byte(`foo`), 1458 SpacesBefore: 0, 1459 }, 1460 6: { 1461 Type: hclsyntax.TokenOBrace, 1462 Bytes: []byte(`{`), 1463 SpacesBefore: 1, 1464 }, 1465 7: { 1466 Type: hclsyntax.TokenNewline, 1467 Bytes: []byte("\n"), 1468 SpacesBefore: 0, 1469 }, 1470 8: { 1471 Type: hclsyntax.TokenIdent, 1472 Bytes: []byte(`b`), 1473 SpacesBefore: 2, 1474 }, 1475 9: { 1476 Type: hclsyntax.TokenEqual, 1477 Bytes: []byte(`=`), 1478 SpacesBefore: 1, 1479 }, 1480 10: { 1481 Type: hclsyntax.TokenNumberLit, 1482 Bytes: []byte(`2`), 1483 SpacesBefore: 1, 1484 }, 1485 11: { 1486 Type: hclsyntax.TokenNewline, 1487 Bytes: []byte("\n"), 1488 SpacesBefore: 0, 1489 }, 1490 12: { 1491 Type: hclsyntax.TokenCBrace, 1492 Bytes: []byte(`}`), 1493 SpacesBefore: 0, 1494 }, 1495 13: { 1496 Type: hclsyntax.TokenNewline, 1497 Bytes: []byte("\n"), 1498 SpacesBefore: 0, 1499 }, 1500 14: { 1501 Type: hclsyntax.TokenIdent, 1502 Bytes: []byte(`bar`), 1503 SpacesBefore: 0, 1504 }, 1505 15: { 1506 Type: hclsyntax.TokenOBrace, 1507 Bytes: []byte(`{`), 1508 SpacesBefore: 1, 1509 }, 1510 16: { 1511 Type: hclsyntax.TokenCBrace, 1512 Bytes: []byte(`}`), 1513 SpacesBefore: 0, 1514 }, 1515 17: { 1516 Type: hclsyntax.TokenEOF, 1517 Bytes: []byte(""), 1518 SpacesBefore: 0, 1519 }, 1520 } 1521 format(got) 1522 if !reflect.DeepEqual(got, want) { 1523 diff := cmp.Diff(want, got) 1524 t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(want), diff) 1525 } 1526 1527 t.Logf("removing the second block") 1528 t.Logf("initial content:\n%s", f.Bytes()) 1529 block = body.FirstMatchingBlock("foo", nil) 1530 if block == nil { 1531 t.Fatalf("didn't find a 'foo' block") 1532 } 1533 removed = body.RemoveBlock(block) 1534 if !removed { 1535 t.Fatalf("didn't remove second block") 1536 } 1537 t.Logf("updated content:\n%s", f.Bytes()) 1538 got = f.BuildTokens(nil) 1539 want = Tokens{ 1540 0: { 1541 Type: hclsyntax.TokenIdent, 1542 Bytes: []byte(`a`), 1543 SpacesBefore: 0, 1544 }, 1545 1: { 1546 Type: hclsyntax.TokenEqual, 1547 Bytes: []byte(`=`), 1548 SpacesBefore: 1, 1549 }, 1550 2: { 1551 Type: hclsyntax.TokenNumberLit, 1552 Bytes: []byte(`1`), 1553 SpacesBefore: 1, 1554 }, 1555 3: { 1556 Type: hclsyntax.TokenNewline, 1557 Bytes: []byte("\n"), 1558 SpacesBefore: 0, 1559 }, 1560 4: { 1561 Type: hclsyntax.TokenNewline, 1562 Bytes: []byte("\n"), 1563 SpacesBefore: 0, 1564 }, 1565 5: { 1566 Type: hclsyntax.TokenIdent, 1567 Bytes: []byte(`bar`), 1568 SpacesBefore: 0, 1569 }, 1570 6: { 1571 Type: hclsyntax.TokenOBrace, 1572 Bytes: []byte(`{`), 1573 SpacesBefore: 1, 1574 }, 1575 7: { 1576 Type: hclsyntax.TokenCBrace, 1577 Bytes: []byte(`}`), 1578 SpacesBefore: 0, 1579 }, 1580 8: { 1581 Type: hclsyntax.TokenEOF, 1582 Bytes: []byte(""), 1583 SpacesBefore: 0, 1584 }, 1585 } 1586 format(got) 1587 if !reflect.DeepEqual(got, want) { 1588 diff := cmp.Diff(want, got) 1589 t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(want), diff) 1590 } 1591 1592 }