github.com/wata727/tflint@v0.12.2-0.20191013070026-96dd0d36f385/tflint/runner_test.go (about) 1 package tflint 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/google/go-cmp/cmp/cmpopts" 12 hcl "github.com/hashicorp/hcl/v2" 13 "github.com/hashicorp/hcl/v2/hclsyntax" 14 "github.com/hashicorp/terraform/configs" 15 "github.com/hashicorp/terraform/configs/configschema" 16 "github.com/hashicorp/terraform/terraform" 17 "github.com/zclconf/go-cty/cty" 18 ) 19 20 func Test_EvaluateExpr_string(t *testing.T) { 21 cases := []struct { 22 Name string 23 Content string 24 Expected string 25 }{ 26 { 27 Name: "literal", 28 Content: ` 29 resource "null_resource" "test" { 30 key = "literal_val" 31 }`, 32 Expected: "literal_val", 33 }, 34 { 35 Name: "string interpolation", 36 Content: ` 37 variable "string_var" { 38 default = "string_val" 39 } 40 41 resource "null_resource" "test" { 42 key = "${var.string_var}" 43 }`, 44 Expected: "string_val", 45 }, 46 { 47 Name: "new style interpolation", 48 Content: ` 49 variable "string_var" { 50 default = "string_val" 51 } 52 53 resource "null_resource" "test" { 54 key = var.string_var 55 }`, 56 Expected: "string_val", 57 }, 58 { 59 Name: "list element", 60 Content: ` 61 variable "list_var" { 62 default = ["one", "two"] 63 } 64 65 resource "null_resource" "test" { 66 key = "${var.list_var[0]}" 67 }`, 68 Expected: "one", 69 }, 70 { 71 Name: "map element", 72 Content: ` 73 variable "map_var" { 74 default = { 75 one = "one" 76 two = "two" 77 } 78 } 79 80 resource "null_resource" "test" { 81 key = "${var.map_var["one"]}" 82 }`, 83 Expected: "one", 84 }, 85 { 86 Name: "object item", 87 Content: ` 88 variable "object" { 89 type = object({ foo = string }) 90 default = { foo = "bar" } 91 } 92 93 resource "null_resource" "test" { 94 key = var.object.foo 95 }`, 96 Expected: "bar", 97 }, 98 { 99 Name: "convert from integer", 100 Content: ` 101 variable "string_var" { 102 default = 10 103 } 104 105 resource "null_resource" "test" { 106 key = "${var.string_var}" 107 }`, 108 Expected: "10", 109 }, 110 { 111 Name: "conditional", 112 Content: ` 113 resource "null_resource" "test" { 114 key = "${true ? "production" : "development"}" 115 }`, 116 Expected: "production", 117 }, 118 { 119 Name: "bulit-in function", 120 Content: ` 121 resource "null_resource" "test" { 122 key = "${md5("foo")}" 123 }`, 124 Expected: "acbd18db4cc2f85cedef654fccc4a4d8", 125 }, 126 { 127 Name: "terraform workspace", 128 Content: ` 129 resource "null_resource" "test" { 130 key = "${terraform.workspace}" 131 }`, 132 Expected: "default", 133 }, 134 { 135 Name: "inside interpolation", 136 Content: ` 137 variable "string_var" { 138 default = "World" 139 } 140 141 resource "null_resource" "test" { 142 key = "Hello ${var.string_var}" 143 }`, 144 Expected: "Hello World", 145 }, 146 { 147 Name: "path.root", 148 Content: ` 149 resource "null_resource" "test" { 150 key = path.root 151 }`, 152 Expected: ".", 153 }, 154 { 155 Name: "path.module", 156 Content: ` 157 resource "null_resource" "test" { 158 key = path.module 159 }`, 160 Expected: ".", 161 }, 162 } 163 164 for _, tc := range cases { 165 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 166 167 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 168 var ret string 169 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 170 return err 171 } 172 173 if tc.Expected != ret { 174 t.Fatalf("Failed `%s` test: expected value is `%s`, but get `%s`", tc.Name, tc.Expected, ret) 175 } 176 return nil 177 }) 178 179 if err != nil { 180 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 181 } 182 } 183 } 184 185 func Test_EvaluateExpr_pathCwd(t *testing.T) { 186 cwd, err := os.Getwd() 187 if err != nil { 188 t.Fatal(err) 189 } 190 expected := filepath.ToSlash(cwd) 191 192 content := ` 193 resource "null_resource" "test" { 194 key = path.cwd 195 }` 196 runner := TestRunner(t, map[string]string{"main.tf": content}) 197 198 err = runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 199 var ret string 200 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 201 return err 202 } 203 204 if expected != ret { 205 t.Fatalf("expected value is `%s`, but get `%s`", expected, ret) 206 } 207 return nil 208 }) 209 210 if err != nil { 211 t.Fatalf("Failed: `%s` occurred", err) 212 } 213 } 214 215 func Test_EvaluateExpr_integer(t *testing.T) { 216 cases := []struct { 217 Name string 218 Content string 219 Expected int 220 }{ 221 { 222 Name: "integer interpolation", 223 Content: ` 224 variable "integer_var" { 225 default = 3 226 } 227 228 resource "null_resource" "test" { 229 key = "${var.integer_var}" 230 }`, 231 Expected: 3, 232 }, 233 { 234 Name: "convert from string", 235 Content: ` 236 variable "integer_var" { 237 default = "3" 238 } 239 240 resource "null_resource" "test" { 241 key = "${var.integer_var}" 242 }`, 243 Expected: 3, 244 }, 245 } 246 247 for _, tc := range cases { 248 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 249 250 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 251 var ret int 252 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 253 return err 254 } 255 256 if tc.Expected != ret { 257 t.Fatalf("Failed `%s` test: expected value is `%d`, but get `%d`", tc.Name, tc.Expected, ret) 258 } 259 return nil 260 }) 261 262 if err != nil { 263 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 264 } 265 } 266 } 267 268 func Test_EvaluateExpr_stringList(t *testing.T) { 269 cases := []struct { 270 Name string 271 Content string 272 Expected []string 273 }{ 274 { 275 Name: "list literal", 276 Content: ` 277 resource "null_resource" "test" { 278 key = ["one", "two", "three"] 279 }`, 280 Expected: []string{"one", "two", "three"}, 281 }, 282 } 283 284 for _, tc := range cases { 285 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 286 287 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 288 var ret []string 289 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 290 return err 291 } 292 293 if !cmp.Equal(tc.Expected, ret) { 294 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 295 } 296 return nil 297 }) 298 299 if err != nil { 300 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 301 } 302 } 303 } 304 305 func Test_EvaluateExpr_numberList(t *testing.T) { 306 cases := []struct { 307 Name string 308 Content string 309 Expected []int 310 }{ 311 { 312 Name: "list literal", 313 Content: ` 314 resource "null_resource" "test" { 315 key = [1, 2, 3] 316 }`, 317 Expected: []int{1, 2, 3}, 318 }, 319 } 320 321 for _, tc := range cases { 322 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 323 324 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 325 var ret []int 326 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 327 return err 328 } 329 330 if !cmp.Equal(tc.Expected, ret) { 331 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 332 } 333 return nil 334 }) 335 336 if err != nil { 337 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 338 } 339 } 340 } 341 342 func Test_EvaluateExpr_stringMap(t *testing.T) { 343 cases := []struct { 344 Name string 345 Content string 346 Expected map[string]string 347 }{ 348 { 349 Name: "map literal", 350 Content: ` 351 resource "null_resource" "test" { 352 key = { 353 one = 1 354 two = "2" 355 } 356 }`, 357 Expected: map[string]string{"one": "1", "two": "2"}, 358 }, 359 } 360 361 for _, tc := range cases { 362 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 363 364 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 365 var ret map[string]string 366 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 367 return err 368 } 369 370 if !cmp.Equal(tc.Expected, ret) { 371 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 372 } 373 return nil 374 }) 375 376 if err != nil { 377 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 378 } 379 } 380 } 381 382 func Test_EvaluateExpr_numberMap(t *testing.T) { 383 cases := []struct { 384 Name string 385 Content string 386 Expected map[string]int 387 }{ 388 { 389 Name: "map literal", 390 Content: ` 391 resource "null_resource" "test" { 392 key = { 393 one = 1 394 two = "2" 395 } 396 }`, 397 Expected: map[string]int{"one": 1, "two": 2}, 398 }, 399 } 400 401 for _, tc := range cases { 402 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 403 404 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 405 var ret map[string]int 406 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 407 return err 408 } 409 410 if !cmp.Equal(tc.Expected, ret) { 411 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 412 } 413 return nil 414 }) 415 416 if err != nil { 417 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 418 } 419 } 420 } 421 422 func Test_EvaluateExpr_interpolationError(t *testing.T) { 423 cases := []struct { 424 Name string 425 Content string 426 Error Error 427 }{ 428 { 429 Name: "undefined variable", 430 Content: ` 431 resource "null_resource" "test" { 432 key = "${var.undefined_var}" 433 }`, 434 Error: Error{ 435 Code: EvaluationError, 436 Level: ErrorLevel, 437 Message: "Failed to eval an expression in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined_var\" has not been declared. This variable can be declared with a variable \"undefined_var\" {} block.", 438 }, 439 }, 440 { 441 Name: "no default value", 442 Content: ` 443 variable "no_value_var" {} 444 445 resource "null_resource" "test" { 446 key = "${var.no_value_var}" 447 }`, 448 Error: Error{ 449 Code: UnknownValueError, 450 Level: WarningLevel, 451 Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value", 452 }, 453 }, 454 { 455 Name: "null value", 456 Content: ` 457 variable "null_var" { 458 type = string 459 default = null 460 } 461 462 resource "null_resource" "test" { 463 key = var.null_var 464 }`, 465 Error: Error{ 466 Code: NullValueError, 467 Level: WarningLevel, 468 Message: "Null value found in main.tf:8", 469 }, 470 }, 471 { 472 Name: "terraform env", 473 Content: ` 474 resource "null_resource" "test" { 475 key = "${terraform.env}" 476 }`, 477 Error: Error{ 478 Code: EvaluationError, 479 Level: ErrorLevel, 480 Message: "Failed to eval an expression in main.tf:3; Invalid \"terraform\" attribute: The terraform.env attribute was deprecated in v0.10 and removed in v0.12. The \"state environment\" concept was rename to \"workspace\" in v0.12, and so the workspace name can now be accessed using the terraform.workspace attribute.", 481 }, 482 }, 483 { 484 Name: "type mismatch", 485 Content: ` 486 resource "null_resource" "test" { 487 key = ["one", "two", "three"] 488 }`, 489 Error: Error{ 490 Code: TypeConversionError, 491 Level: ErrorLevel, 492 Message: "Invalid type expression in main.tf:3; string required", 493 }, 494 }, 495 { 496 Name: "unevalauble", 497 Content: ` 498 resource "null_resource" "test" { 499 key = "${module.text}" 500 }`, 501 Error: Error{ 502 Code: UnevaluableError, 503 Level: WarningLevel, 504 Message: "Unevaluable expression found in main.tf:3", 505 }, 506 }, 507 } 508 509 for _, tc := range cases { 510 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 511 512 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 513 var ret string 514 err := runner.EvaluateExpr(attribute.Expr, &ret) 515 516 AssertAppError(t, tc.Error, err) 517 return nil 518 }) 519 520 if err != nil { 521 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 522 } 523 } 524 } 525 526 func Test_EvaluateExpr_mapWithInterpolationError(t *testing.T) { 527 cases := []struct { 528 Name string 529 Content string 530 Error Error 531 }{ 532 { 533 Name: "undefined variable", 534 Content: ` 535 resource "null_resource" "test" { 536 key = { 537 value = var.undefined_var 538 } 539 }`, 540 Error: Error{ 541 Code: EvaluationError, 542 Level: ErrorLevel, 543 Message: "Failed to eval an expression in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined_var\" has not been declared. This variable can be declared with a variable \"undefined_var\" {} block.", 544 }, 545 }, 546 { 547 Name: "no default value", 548 Content: ` 549 variable "no_value_var" {} 550 551 resource "null_resource" "test" { 552 key = { 553 value = var.no_value_var 554 } 555 }`, 556 Error: Error{ 557 Code: UnknownValueError, 558 Level: WarningLevel, 559 Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value", 560 }, 561 }, 562 { 563 Name: "null value", 564 Content: ` 565 variable "null_var" { 566 type = string 567 default = null 568 } 569 570 resource "null_resource" "test" { 571 key = { 572 value = var.null_var 573 } 574 }`, 575 Error: Error{ 576 Code: NullValueError, 577 Level: WarningLevel, 578 Message: "Null value found in main.tf:8", 579 }, 580 }, 581 { 582 Name: "unevalauble", 583 Content: ` 584 resource "null_resource" "test" { 585 key = { 586 value = module.text 587 } 588 }`, 589 Error: Error{ 590 Code: UnevaluableError, 591 Level: WarningLevel, 592 Message: "Unevaluable expression found in main.tf:3", 593 }, 594 }, 595 } 596 597 for _, tc := range cases { 598 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 599 600 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 601 var ret map[string]string 602 err := runner.EvaluateExpr(attribute.Expr, &ret) 603 604 AssertAppError(t, tc.Error, err) 605 return nil 606 }) 607 608 if err != nil { 609 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 610 } 611 } 612 } 613 614 func Test_EvaluateBlock(t *testing.T) { 615 cases := []struct { 616 Name string 617 Content string 618 Expected map[string]string 619 }{ 620 { 621 Name: "map literal", 622 Content: ` 623 resource "null_resource" "test" { 624 key { 625 one = 1 626 two = "2" 627 } 628 }`, 629 Expected: map[string]string{"one": "1", "two": "2"}, 630 }, 631 { 632 Name: "variable", 633 Content: ` 634 variable "one" { 635 default = 1 636 } 637 638 resource "null_resource" "test" { 639 key { 640 one = var.one 641 two = "2" 642 } 643 }`, 644 Expected: map[string]string{"one": "1", "two": "2"}, 645 }, 646 { 647 Name: "null value", 648 Content: ` 649 variable "null_var" { 650 type = string 651 default = null 652 } 653 654 resource "null_resource" "test" { 655 key { 656 one = "1" 657 two = var.null_var 658 } 659 }`, 660 Expected: map[string]string{"one": "1", "two": ""}, 661 }, 662 } 663 664 for _, tc := range cases { 665 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 666 667 err := runner.WalkResourceBlocks("null_resource", "key", func(block *hcl.Block) error { 668 var ret map[string]string 669 schema := &configschema.Block{ 670 Attributes: map[string]*configschema.Attribute{ 671 "one": {Type: cty.String}, 672 "two": {Type: cty.String}, 673 }, 674 } 675 if err := runner.EvaluateBlock(block, schema, &ret); err != nil { 676 return err 677 } 678 679 if !cmp.Equal(tc.Expected, ret) { 680 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 681 } 682 return nil 683 }) 684 685 if err != nil { 686 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 687 } 688 } 689 } 690 691 func Test_EvaluateBlock_error(t *testing.T) { 692 cases := []struct { 693 Name string 694 Content string 695 Error Error 696 }{ 697 { 698 Name: "undefined variable", 699 Content: ` 700 resource "null_resource" "test" { 701 key { 702 one = "1" 703 two = var.undefined_var 704 } 705 }`, 706 Error: Error{ 707 Code: EvaluationError, 708 Level: ErrorLevel, 709 Message: "Failed to eval a block in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined_var\" has not been declared. This variable can be declared with a variable \"undefined_var\" {} block.", 710 }, 711 }, 712 { 713 Name: "no default value", 714 Content: ` 715 variable "no_value_var" {} 716 717 resource "null_resource" "test" { 718 key { 719 one = "1" 720 two = var.no_value_var 721 } 722 }`, 723 Error: Error{ 724 Code: UnknownValueError, 725 Level: WarningLevel, 726 Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value", 727 }, 728 }, 729 { 730 Name: "type mismatch", 731 Content: ` 732 resource "null_resource" "test" { 733 key { 734 one = "1" 735 two = { 736 three = 3 737 } 738 } 739 }`, 740 Error: Error{ 741 Code: EvaluationError, 742 Level: ErrorLevel, 743 Message: "Failed to eval a block in main.tf:3; Incorrect attribute value type: Inappropriate value for attribute \"two\": string required.", 744 }, 745 }, 746 { 747 Name: "unevalauble", 748 Content: ` 749 resource "null_resource" "test" { 750 key { 751 one = "1" 752 two = module.text 753 } 754 }`, 755 Error: Error{ 756 Code: UnevaluableError, 757 Level: WarningLevel, 758 Message: "Unevaluable block found in main.tf:3", 759 }, 760 }, 761 } 762 763 for _, tc := range cases { 764 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 765 766 err := runner.WalkResourceBlocks("null_resource", "key", func(block *hcl.Block) error { 767 var ret map[string]string 768 schema := &configschema.Block{ 769 Attributes: map[string]*configschema.Attribute{ 770 "one": {Type: cty.String}, 771 "two": {Type: cty.String}, 772 }, 773 } 774 err := runner.EvaluateBlock(block, schema, &ret) 775 776 AssertAppError(t, tc.Error, err) 777 return nil 778 }) 779 780 if err != nil { 781 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 782 } 783 } 784 } 785 786 func Test_isEvaluableExpr(t *testing.T) { 787 cases := []struct { 788 Name string 789 Content string 790 Expected bool 791 Error error 792 }{ 793 { 794 Name: "literal", 795 Content: ` 796 resource "null_resource" "test" { 797 key = "literal_val" 798 }`, 799 Expected: true, 800 }, 801 { 802 Name: "var syntax", 803 Content: ` 804 resource "null_resource" "test" { 805 key = "${var.string_var}" 806 }`, 807 Expected: true, 808 }, 809 { 810 Name: "new var syntax", 811 Content: ` 812 resource "null_resource" "test" { 813 key = var.string_var 814 }`, 815 Expected: true, 816 }, 817 { 818 Name: "conditional", 819 Content: ` 820 resource "null_resource" "test" { 821 key = "${true ? "production" : "development"}" 822 }`, 823 Expected: true, 824 }, 825 { 826 Name: "function", 827 Content: ` 828 resource "null_resource" "test" { 829 key = "${md5("foo")}" 830 }`, 831 Expected: true, 832 }, 833 { 834 Name: "terraform attributes", 835 Content: ` 836 resource "null_resource" "test" { 837 key = "${terraform.workspace}" 838 }`, 839 Expected: true, 840 }, 841 { 842 Name: "include supported syntax", 843 Content: ` 844 resource "null_resource" "test" { 845 key = "Hello ${var.string_var}" 846 }`, 847 Expected: true, 848 }, 849 { 850 Name: "list", 851 Content: ` 852 resource "null_resource" "test" { 853 key = ["one", "two", "three"] 854 }`, 855 Expected: true, 856 }, 857 { 858 Name: "map", 859 Content: ` 860 resource "null_resource" "test" { 861 key = { 862 one = 1 863 two = 2 864 } 865 }`, 866 Expected: true, 867 }, 868 { 869 Name: "module", 870 Content: ` 871 resource "null_resource" "test" { 872 key = "${module.text}" 873 }`, 874 Expected: false, 875 }, 876 { 877 Name: "resource", 878 Content: ` 879 resource "null_resource" "test" { 880 key = "${aws_subnet.app.id}" 881 }`, 882 Expected: false, 883 }, 884 { 885 Name: "include unsupported syntax", 886 Content: ` 887 resource "null_resource" "test" { 888 key = "${var.text} ${lookup(var.roles, count.index)}" 889 }`, 890 Expected: false, 891 }, 892 { 893 Name: "include unsupported syntax map", 894 Content: ` 895 resource "null_resource" "test" { 896 key = { 897 var = var.text 898 unsupported = aws_subnet.app.id 899 } 900 }`, 901 Expected: false, 902 }, 903 { 904 Name: "path attributes", 905 Content: ` 906 resource "null_resource" "test" { 907 key = path.cwd 908 }`, 909 Expected: true, 910 }, 911 { 912 Name: "invalid reference", 913 Content: ` 914 resource "null_resource" "test" { 915 key = invalid 916 }`, 917 Expected: false, 918 Error: errors.New("Invalid reference: A reference to a resource type must be followed by at least one attribute access, specifying the resource name."), 919 }, 920 } 921 922 for _, tc := range cases { 923 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 924 925 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 926 ret, err := isEvaluableExpr(attribute.Expr) 927 if err != nil && tc.Error == nil { 928 t.Fatalf("Failed `%s` test: unexpected error occurred: %s", tc.Name, err) 929 } 930 if err == nil && tc.Error != nil { 931 t.Fatalf("Failed `%s` test: expected error is %s, but no errors", tc.Name, tc.Error) 932 } 933 if err != nil && tc.Error != nil && err.Error() != tc.Error.Error() { 934 t.Fatalf("Failed `%s` test: expected error is %s, but got %s", tc.Name, tc.Error, err) 935 } 936 if ret != tc.Expected { 937 t.Fatalf("Failed `%s` test: expected value is %t, but get %t", tc.Name, tc.Expected, ret) 938 } 939 return nil 940 }) 941 942 if err != nil { 943 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 944 } 945 } 946 } 947 948 func Test_overrideVariables(t *testing.T) { 949 cases := []struct { 950 Name string 951 Content string 952 EnvVar map[string]string 953 InputValues []terraform.InputValues 954 Expected string 955 }{ 956 { 957 Name: "override default value by environment variables", 958 Content: ` 959 variable "instance_type" { 960 default = "t2.micro" 961 } 962 963 resource "null_resource" "test" { 964 key = "${var.instance_type}" 965 }`, 966 EnvVar: map[string]string{"TF_VAR_instance_type": "m4.large"}, 967 Expected: "m4.large", 968 }, 969 { 970 Name: "override environment variables by passed variables", 971 Content: ` 972 variable "instance_type" {} 973 974 resource "null_resource" "test" { 975 key = "${var.instance_type}" 976 }`, 977 EnvVar: map[string]string{"TF_VAR_instance_type": "m4.large"}, 978 InputValues: []terraform.InputValues{ 979 terraform.InputValues{ 980 "instance_type": &terraform.InputValue{ 981 Value: cty.StringVal("c5.2xlarge"), 982 SourceType: terraform.ValueFromNamedFile, 983 }, 984 }, 985 }, 986 Expected: "c5.2xlarge", 987 }, 988 { 989 Name: "override variables by variables passed later", 990 Content: ` 991 variable "instance_type" {} 992 993 resource "null_resource" "test" { 994 key = "${var.instance_type}" 995 }`, 996 InputValues: []terraform.InputValues{ 997 terraform.InputValues{ 998 "instance_type": &terraform.InputValue{ 999 Value: cty.StringVal("c5.2xlarge"), 1000 SourceType: terraform.ValueFromNamedFile, 1001 }, 1002 }, 1003 terraform.InputValues{ 1004 "instance_type": &terraform.InputValue{ 1005 Value: cty.StringVal("p3.8xlarge"), 1006 SourceType: terraform.ValueFromNamedFile, 1007 }, 1008 }, 1009 }, 1010 Expected: "p3.8xlarge", 1011 }, 1012 } 1013 1014 for _, tc := range cases { 1015 withEnvVars(t, tc.EnvVar, func() { 1016 runner := testRunnerWithInputVariables(t, map[string]string{"main.tf": tc.Content}, tc.InputValues...) 1017 1018 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 1019 var ret string 1020 err := runner.EvaluateExpr(attribute.Expr, &ret) 1021 if err != nil { 1022 return err 1023 } 1024 1025 if tc.Expected != ret { 1026 t.Fatalf("Failed `%s` test: expected value is %s, but get %s", tc.Name, tc.Expected, ret) 1027 } 1028 return nil 1029 }) 1030 1031 if err != nil { 1032 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 1033 } 1034 }) 1035 } 1036 } 1037 1038 func Test_NewModuleRunners_noModules(t *testing.T) { 1039 withinFixtureDir(t, "no_modules", func() { 1040 runner := testRunnerWithOsFs(t, moduleConfig()) 1041 1042 runners, err := NewModuleRunners(runner) 1043 if err != nil { 1044 t.Fatalf("Unexpected error occurred: %s", err) 1045 } 1046 1047 if len(runners) > 0 { 1048 t.Fatal("`NewModuleRunners` must not return runners when there is no module") 1049 } 1050 }) 1051 } 1052 1053 func Test_NewModuleRunners_nestedModules(t *testing.T) { 1054 withinFixtureDir(t, "nested_modules", func() { 1055 runner := testRunnerWithOsFs(t, moduleConfig()) 1056 1057 runners, err := NewModuleRunners(runner) 1058 if err != nil { 1059 t.Fatalf("Unexpected error occurred: %s", err) 1060 } 1061 1062 if len(runners) != 2 { 1063 t.Fatal("This function must return 2 runners because the config has 2 modules") 1064 } 1065 1066 expectedVars := map[string]map[string]*configs.Variable{ 1067 "root": { 1068 "override": { 1069 Name: "override", 1070 Default: cty.StringVal("foo"), 1071 Type: cty.DynamicPseudoType, 1072 ParsingMode: configs.VariableParseLiteral, 1073 DeclRange: hcl.Range{ 1074 Filename: filepath.Join("module", "module.tf"), 1075 Start: hcl.Pos{Line: 1, Column: 1}, 1076 End: hcl.Pos{Line: 1, Column: 20}, 1077 }, 1078 }, 1079 "no_default": { 1080 Name: "no_default", 1081 Default: cty.StringVal("bar"), 1082 Type: cty.DynamicPseudoType, 1083 ParsingMode: configs.VariableParseLiteral, 1084 DeclRange: hcl.Range{ 1085 Filename: filepath.Join("module", "module.tf"), 1086 Start: hcl.Pos{Line: 4, Column: 1}, 1087 End: hcl.Pos{Line: 4, Column: 22}, 1088 }, 1089 }, 1090 "unknown": { 1091 Name: "unknown", 1092 Default: cty.UnknownVal(cty.DynamicPseudoType), 1093 Type: cty.DynamicPseudoType, 1094 ParsingMode: configs.VariableParseLiteral, 1095 DeclRange: hcl.Range{ 1096 Filename: filepath.Join("module", "module.tf"), 1097 Start: hcl.Pos{Line: 5, Column: 1}, 1098 End: hcl.Pos{Line: 5, Column: 19}, 1099 }, 1100 }, 1101 }, 1102 "root.test": { 1103 "override": { 1104 Name: "override", 1105 Default: cty.StringVal("foo"), 1106 Type: cty.DynamicPseudoType, 1107 ParsingMode: configs.VariableParseLiteral, 1108 DeclRange: hcl.Range{ 1109 Filename: filepath.Join("module", "module1", "resource.tf"), 1110 Start: hcl.Pos{Line: 1, Column: 1}, 1111 End: hcl.Pos{Line: 1, Column: 20}, 1112 }, 1113 }, 1114 "no_default": { 1115 Name: "no_default", 1116 Default: cty.StringVal("bar"), 1117 Type: cty.DynamicPseudoType, 1118 ParsingMode: configs.VariableParseLiteral, 1119 DeclRange: hcl.Range{ 1120 Filename: filepath.Join("module", "module1", "resource.tf"), 1121 Start: hcl.Pos{Line: 4, Column: 1}, 1122 End: hcl.Pos{Line: 4, Column: 22}, 1123 }, 1124 }, 1125 "unknown": { 1126 Name: "unknown", 1127 Default: cty.UnknownVal(cty.DynamicPseudoType), 1128 Type: cty.DynamicPseudoType, 1129 ParsingMode: configs.VariableParseLiteral, 1130 DeclRange: hcl.Range{ 1131 Filename: filepath.Join("module", "module1", "resource.tf"), 1132 Start: hcl.Pos{Line: 5, Column: 1}, 1133 End: hcl.Pos{Line: 5, Column: 19}, 1134 }, 1135 }, 1136 }, 1137 } 1138 1139 for _, runner := range runners { 1140 expected, exists := expectedVars[runner.TFConfig.Path.String()] 1141 if !exists { 1142 t.Fatalf("`%s` is not found in module runners", runner.TFConfig.Path) 1143 } 1144 1145 opts := []cmp.Option{ 1146 cmpopts.IgnoreUnexported(cty.Type{}, cty.Value{}), 1147 cmpopts.IgnoreFields(hcl.Pos{}, "Byte"), 1148 } 1149 if !cmp.Equal(expected, runner.TFConfig.Module.Variables, opts...) { 1150 t.Fatalf("`%s` module variables are unmatched: Diff=%s", runner.TFConfig.Path, cmp.Diff(expected, runner.TFConfig.Module.Variables, opts...)) 1151 } 1152 } 1153 }) 1154 } 1155 1156 func Test_NewModuleRunners_modVars(t *testing.T) { 1157 withinFixtureDir(t, "nested_module_vars", func() { 1158 runner := testRunnerWithOsFs(t, moduleConfig()) 1159 1160 runners, err := NewModuleRunners(runner) 1161 if err != nil { 1162 t.Fatalf("Unexpected error occurred: %s", err) 1163 } 1164 1165 if len(runners) != 2 { 1166 t.Fatal("This function must return 2 runners because the config has 2 modules") 1167 } 1168 1169 child := runners[0] 1170 if child.TFConfig.Path.String() != "module1" { 1171 t.Fatalf("Expected child config path name is `module1`, but get `%s`", child.TFConfig.Path.String()) 1172 } 1173 1174 expected := map[string]*moduleVariable{ 1175 "foo": { 1176 Root: true, 1177 DeclRange: hcl.Range{ 1178 Filename: "main.tf", 1179 Start: hcl.Pos{Line: 4, Column: 9}, 1180 End: hcl.Pos{Line: 4, Column: 14}, 1181 }, 1182 }, 1183 "bar": { 1184 Root: true, 1185 DeclRange: hcl.Range{ 1186 Filename: "main.tf", 1187 Start: hcl.Pos{Line: 5, Column: 9}, 1188 End: hcl.Pos{Line: 5, Column: 14}, 1189 }, 1190 }, 1191 } 1192 opts := []cmp.Option{cmpopts.IgnoreFields(hcl.Pos{}, "Byte")} 1193 if !cmp.Equal(expected, child.modVars, opts...) { 1194 t.Fatalf("`%s` module variables are unmatched: Diff=%s", child.TFConfig.Path.String(), cmp.Diff(expected, child.modVars, opts...)) 1195 } 1196 1197 grandchild := runners[1] 1198 if grandchild.TFConfig.Path.String() != "module1.module2" { 1199 t.Fatalf("Expected child config path name is `module1.module2`, but get `%s`", grandchild.TFConfig.Path.String()) 1200 } 1201 1202 expected = map[string]*moduleVariable{ 1203 "red": { 1204 Root: false, 1205 Parents: []*moduleVariable{expected["foo"], expected["bar"]}, 1206 DeclRange: hcl.Range{ 1207 Filename: filepath.Join("module", "main.tf"), 1208 Start: hcl.Pos{Line: 8, Column: 11}, 1209 End: hcl.Pos{Line: 8, Column: 34}, 1210 }, 1211 }, 1212 "blue": { 1213 Root: false, 1214 Parents: []*moduleVariable{}, 1215 DeclRange: hcl.Range{ 1216 Filename: filepath.Join("module", "main.tf"), 1217 Start: hcl.Pos{Line: 9, Column: 11}, 1218 End: hcl.Pos{Line: 9, Column: 17}, 1219 }, 1220 }, 1221 "green": { 1222 Root: false, 1223 Parents: []*moduleVariable{expected["foo"]}, 1224 DeclRange: hcl.Range{ 1225 Filename: filepath.Join("module", "main.tf"), 1226 Start: hcl.Pos{Line: 10, Column: 11}, 1227 End: hcl.Pos{Line: 10, Column: 49}, 1228 }, 1229 }, 1230 } 1231 opts = []cmp.Option{cmpopts.IgnoreFields(hcl.Pos{}, "Byte")} 1232 if !cmp.Equal(expected, grandchild.modVars, opts...) { 1233 t.Fatalf("`%s` module variables are unmatched: Diff=%s", grandchild.TFConfig.Path.String(), cmp.Diff(expected, grandchild.modVars, opts...)) 1234 } 1235 }) 1236 } 1237 1238 func Test_NewModuleRunners_ignoreModules(t *testing.T) { 1239 withinFixtureDir(t, "nested_modules", func() { 1240 config := moduleConfig() 1241 config.IgnoreModules["./module"] = true 1242 runner := testRunnerWithOsFs(t, config) 1243 1244 runners, err := NewModuleRunners(runner) 1245 if err != nil { 1246 t.Fatalf("Unexpected error occurred: %s", err) 1247 } 1248 1249 if len(runners) != 0 { 1250 t.Fatalf("This function must not return runners because `ignore_module` is set. Got `%d` runner(s)", len(runners)) 1251 } 1252 }) 1253 } 1254 1255 func Test_NewModuleRunners_withInvalidExpression(t *testing.T) { 1256 withinFixtureDir(t, "invalid_module_attribute", func() { 1257 runner := testRunnerWithOsFs(t, moduleConfig()) 1258 1259 _, err := NewModuleRunners(runner) 1260 1261 expected := Error{ 1262 Code: EvaluationError, 1263 Level: ErrorLevel, 1264 Message: "Failed to eval an expression in module.tf:4; Invalid \"terraform\" attribute: The terraform.env attribute was deprecated in v0.10 and removed in v0.12. The \"state environment\" concept was rename to \"workspace\" in v0.12, and so the workspace name can now be accessed using the terraform.workspace attribute.", 1265 } 1266 AssertAppError(t, expected, err) 1267 }) 1268 } 1269 1270 func Test_NewModuleRunners_withNotAllowedAttributes(t *testing.T) { 1271 withinFixtureDir(t, "not_allowed_module_attribute", func() { 1272 runner := testRunnerWithOsFs(t, moduleConfig()) 1273 1274 _, err := NewModuleRunners(runner) 1275 1276 expected := Error{ 1277 Code: UnexpectedAttributeError, 1278 Level: ErrorLevel, 1279 Message: "Attribute of module not allowed was found in module.tf:1; module.tf:4,3-10: Unexpected \"invalid\" block; Blocks are not allowed here.", 1280 } 1281 AssertAppError(t, expected, err) 1282 }) 1283 } 1284 1285 func Test_LookupResourcesByType(t *testing.T) { 1286 content := ` 1287 resource "aws_instance" "web" { 1288 ami = "${data.aws_ami.ubuntu.id}" 1289 instance_type = "t2.micro" 1290 1291 tags { 1292 Name = "HelloWorld" 1293 } 1294 } 1295 1296 resource "aws_route53_zone" "primary" { 1297 name = "example.com" 1298 } 1299 1300 resource "aws_route" "r" { 1301 route_table_id = "rtb-4fbb3ac4" 1302 destination_cidr_block = "10.0.1.0/22" 1303 vpc_peering_connection_id = "pcx-45ff3dc1" 1304 depends_on = ["aws_route_table.testing"] 1305 }` 1306 1307 runner := TestRunner(t, map[string]string{"resource.tf": content}) 1308 resources := runner.LookupResourcesByType("aws_instance") 1309 1310 if len(resources) != 1 { 1311 t.Fatalf("Expected resources size is `1`, but get `%d`", len(resources)) 1312 } 1313 if resources[0].Type != "aws_instance" { 1314 t.Fatalf("Expected resource type is `aws_instance`, but get `%s`", resources[0].Type) 1315 } 1316 } 1317 1318 func Test_LookupIssues(t *testing.T) { 1319 runner := TestRunner(t, map[string]string{}) 1320 runner.Issues = Issues{ 1321 { 1322 Rule: &testRule{}, 1323 Message: "This is test rule", 1324 Range: hcl.Range{ 1325 Filename: "template.tf", 1326 Start: hcl.Pos{Line: 1}, 1327 }, 1328 }, 1329 { 1330 Rule: &testRule{}, 1331 Message: "This is test rule", 1332 Range: hcl.Range{ 1333 Filename: "resource.tf", 1334 Start: hcl.Pos{Line: 1}, 1335 }, 1336 }, 1337 } 1338 1339 ret := runner.LookupIssues("template.tf") 1340 expected := Issues{ 1341 { 1342 Rule: &testRule{}, 1343 Message: "This is test rule", 1344 Range: hcl.Range{ 1345 Filename: "template.tf", 1346 Start: hcl.Pos{Line: 1}, 1347 }, 1348 }, 1349 } 1350 1351 if !cmp.Equal(expected, ret) { 1352 t.Fatalf("Failed test: diff: %s", cmp.Diff(expected, ret)) 1353 } 1354 } 1355 1356 func Test_WalkResourceAttributes(t *testing.T) { 1357 cases := []struct { 1358 Name string 1359 Content string 1360 ErrorText string 1361 }{ 1362 { 1363 Name: "Resource not found", 1364 Content: ` 1365 resource "null_resource" "test" { 1366 key = "foo" 1367 }`, 1368 }, 1369 { 1370 Name: "Attribute not found", 1371 Content: ` 1372 resource "aws_instance" "test" { 1373 key = "foo" 1374 }`, 1375 }, 1376 { 1377 Name: "Block attribute", 1378 Content: ` 1379 resource "aws_instance" "test" { 1380 instance_type { 1381 name = "t2.micro" 1382 } 1383 }`, 1384 }, 1385 { 1386 Name: "walk", 1387 Content: ` 1388 resource "aws_instance" "test" { 1389 instance_type = "t2.micro" 1390 }`, 1391 ErrorText: "Walk instance_type", 1392 }, 1393 } 1394 1395 for _, tc := range cases { 1396 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 1397 1398 err := runner.WalkResourceAttributes("aws_instance", "instance_type", func(attribute *hcl.Attribute) error { 1399 return fmt.Errorf("Walk %s", attribute.Name) 1400 }) 1401 if err == nil { 1402 if tc.ErrorText != "" { 1403 t.Fatalf("Failed `%s` test: expected error is not occurred `%s`", tc.Name, tc.ErrorText) 1404 } 1405 } else if err.Error() != tc.ErrorText { 1406 t.Fatalf("Failed `%s` test: expected error is %s, but get %s", tc.Name, tc.ErrorText, err) 1407 } 1408 } 1409 } 1410 1411 func Test_WalkResourceBlocks(t *testing.T) { 1412 cases := []struct { 1413 Name string 1414 Content string 1415 ErrorText string 1416 }{ 1417 { 1418 Name: "Resource not found", 1419 Content: ` 1420 resource "null_resource" "test" { 1421 key { 1422 foo = "bar" 1423 } 1424 }`, 1425 }, 1426 { 1427 Name: "Block not found", 1428 Content: ` 1429 resource "aws_instance" "test" { 1430 key { 1431 foo = "bar" 1432 } 1433 }`, 1434 }, 1435 { 1436 Name: "Attribute", 1437 Content: ` 1438 resource "aws_instance" "test" { 1439 instance_type = "foo" 1440 }`, 1441 }, 1442 { 1443 Name: "walk", 1444 Content: ` 1445 resource "aws_instance" "test" { 1446 instance_type { 1447 foo = "bar" 1448 } 1449 }`, 1450 ErrorText: "Walk instance_type", 1451 }, 1452 { 1453 Name: "walk dynamic blocks", 1454 Content: ` 1455 resource "aws_instance" "test" { 1456 dynamic "instance_type" { 1457 for_each = ["foo", "bar"] 1458 1459 content { 1460 foo = instance_type.value 1461 } 1462 } 1463 }`, 1464 ErrorText: "Walk content", 1465 }, 1466 { 1467 Name: "Another dynamic block", 1468 Content: ` 1469 resource "aws_instance" "test" { 1470 dynamic "key" { 1471 for_each = ["foo", "bar"] 1472 1473 content { 1474 foo = key.value 1475 } 1476 } 1477 }`, 1478 }, 1479 } 1480 1481 for _, tc := range cases { 1482 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 1483 1484 err := runner.WalkResourceBlocks("aws_instance", "instance_type", func(block *hcl.Block) error { 1485 return fmt.Errorf("Walk %s", block.Type) 1486 }) 1487 if err == nil { 1488 if tc.ErrorText != "" { 1489 t.Fatalf("Failed `%s` test: expected error is not occurred `%s`", tc.Name, tc.ErrorText) 1490 } 1491 } else if err.Error() != tc.ErrorText { 1492 t.Fatalf("Failed `%s` test: expected error is %s, but get %s", tc.Name, tc.ErrorText, err) 1493 } 1494 } 1495 } 1496 1497 func Test_EnsureNoError(t *testing.T) { 1498 cases := []struct { 1499 Name string 1500 Error error 1501 ErrorText string 1502 }{ 1503 { 1504 Name: "no error", 1505 Error: nil, 1506 ErrorText: "function called", 1507 }, 1508 { 1509 Name: "native error", 1510 Error: errors.New("Error occurred"), 1511 ErrorText: "Error occurred", 1512 }, 1513 { 1514 Name: "warning error", 1515 Error: &Error{ 1516 Code: UnknownValueError, 1517 Level: WarningLevel, 1518 Message: "Warning error", 1519 }, 1520 }, 1521 { 1522 Name: "app error", 1523 Error: &Error{ 1524 Code: TypeMismatchError, 1525 Level: ErrorLevel, 1526 Message: "App error", 1527 }, 1528 ErrorText: "App error", 1529 }, 1530 } 1531 1532 for _, tc := range cases { 1533 runner := TestRunner(t, map[string]string{}) 1534 1535 err := runner.EnsureNoError(tc.Error, func() error { 1536 return errors.New("function called") 1537 }) 1538 if err == nil { 1539 if tc.ErrorText != "" { 1540 t.Fatalf("Failed `%s` test: expected error is not occurred `%s`", tc.Name, tc.ErrorText) 1541 } 1542 } else if err.Error() != tc.ErrorText { 1543 t.Fatalf("Failed `%s` test: expected error is %s, but get %s", tc.Name, tc.ErrorText, err) 1544 } 1545 } 1546 } 1547 1548 func Test_IsNullExpr(t *testing.T) { 1549 cases := []struct { 1550 Name string 1551 Content string 1552 Expected bool 1553 Error error 1554 }{ 1555 { 1556 Name: "non null literal", 1557 Content: ` 1558 resource "null_resource" "test" { 1559 key = "string" 1560 }`, 1561 Expected: false, 1562 }, 1563 { 1564 Name: "non null variable", 1565 Content: ` 1566 variable "value" { 1567 default = "string" 1568 } 1569 1570 resource "null_resource" "test" { 1571 key = var.value 1572 }`, 1573 Expected: false, 1574 }, 1575 { 1576 Name: "null literal", 1577 Content: ` 1578 resource "null_resource" "test" { 1579 key = null 1580 }`, 1581 Expected: true, 1582 }, 1583 { 1584 Name: "null variable", 1585 Content: ` 1586 variable "value" { 1587 default = null 1588 } 1589 1590 resource "null_resource" "test" { 1591 key = var.value 1592 }`, 1593 Expected: true, 1594 }, 1595 { 1596 Name: "unknown variable", 1597 Content: ` 1598 variable "value" {} 1599 1600 resource "null_resource" "test" { 1601 key = var.value 1602 }`, 1603 Expected: false, 1604 }, 1605 { 1606 Name: "unevaluable reference", 1607 Content: ` 1608 resource "null_resource" "test" { 1609 key = aws_instance.id 1610 }`, 1611 Expected: false, 1612 }, 1613 { 1614 Name: "including null literal", 1615 Content: ` 1616 resource "null_resource" "test" { 1617 key = "${null}-1" 1618 }`, 1619 Expected: false, 1620 Error: errors.New("Invalid template interpolation value: The expression result is null. Cannot include a null value in a string template."), 1621 }, 1622 { 1623 Name: "invalid references", 1624 Content: ` 1625 resource "null_resource" "test" { 1626 key = invalid 1627 }`, 1628 Expected: false, 1629 Error: errors.New("Invalid reference: A reference to a resource type must be followed by at least one attribute access, specifying the resource name."), 1630 }, 1631 } 1632 1633 for _, tc := range cases { 1634 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 1635 1636 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 1637 ret, err := runner.IsNullExpr(attribute.Expr) 1638 if err != nil && tc.Error == nil { 1639 t.Fatalf("Failed `%s` test: unexpected error occurred: %s", tc.Name, err) 1640 } 1641 if err == nil && tc.Error != nil { 1642 t.Fatalf("Failed `%s` test: expected error is %s, but no errors", tc.Name, tc.Error) 1643 } 1644 if err != nil && tc.Error != nil && err.Error() != tc.Error.Error() { 1645 t.Fatalf("Failed `%s` test: expected error is %s, but got %s", tc.Name, tc.Error, err) 1646 } 1647 if tc.Expected != ret { 1648 t.Fatalf("Failed `%s` test: expected value is %t, but get %t", tc.Name, tc.Expected, ret) 1649 } 1650 return nil 1651 }) 1652 1653 if err != nil { 1654 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 1655 } 1656 } 1657 } 1658 1659 func Test_EachStringSliceExprs(t *testing.T) { 1660 cases := []struct { 1661 Name string 1662 Content string 1663 Vals []string 1664 Lines []int 1665 }{ 1666 { 1667 Name: "literal list", 1668 Content: ` 1669 resource "null_resource" "test" { 1670 value = [ 1671 "text", 1672 "element", 1673 ] 1674 }`, 1675 Vals: []string{"text", "element"}, 1676 Lines: []int{4, 5}, 1677 }, 1678 { 1679 Name: "literal list", 1680 Content: ` 1681 variable "list" { 1682 default = [ 1683 "text", 1684 "element", 1685 ] 1686 } 1687 1688 resource "null_resource" "test" { 1689 value = var.list 1690 }`, 1691 Vals: []string{"text", "element"}, 1692 Lines: []int{10, 10}, 1693 }, 1694 { 1695 Name: "for expressions", 1696 Content: ` 1697 variable "list" { 1698 default = ["text", "element", "ignored"] 1699 } 1700 1701 resource "null_resource" "test" { 1702 value = [ 1703 for e in var.list: 1704 e 1705 if e != "ignored" 1706 ] 1707 }`, 1708 Vals: []string{"text", "element"}, 1709 Lines: []int{7, 7}, 1710 }, 1711 } 1712 1713 for _, tc := range cases { 1714 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 1715 1716 vals := []string{} 1717 lines := []int{} 1718 err := runner.WalkResourceAttributes("null_resource", "value", func(attribute *hcl.Attribute) error { 1719 return runner.EachStringSliceExprs(attribute.Expr, func(val string, expr hcl.Expression) { 1720 vals = append(vals, val) 1721 lines = append(lines, expr.Range().Start.Line) 1722 }) 1723 }) 1724 if err != nil { 1725 t.Fatalf("Failed `%s` test: %s", tc.Name, err) 1726 } 1727 1728 if !cmp.Equal(vals, tc.Vals) { 1729 t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(vals, tc.Vals)) 1730 } 1731 if !cmp.Equal(lines, tc.Lines) { 1732 t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(lines, tc.Lines)) 1733 } 1734 } 1735 } 1736 1737 type testRule struct{} 1738 1739 func (r *testRule) Name() string { 1740 return "test_rule" 1741 } 1742 func (r *testRule) Severity() string { 1743 return ERROR 1744 } 1745 func (r *testRule) Link() string { 1746 return "" 1747 } 1748 1749 func Test_EmitIssue(t *testing.T) { 1750 cases := []struct { 1751 Name string 1752 Rule Rule 1753 Message string 1754 Location hcl.Range 1755 Annotations map[string]Annotations 1756 Expected Issues 1757 }{ 1758 { 1759 Name: "basic", 1760 Rule: &testRule{}, 1761 Message: "This is test message", 1762 Location: hcl.Range{ 1763 Filename: "test.tf", 1764 Start: hcl.Pos{Line: 1}, 1765 }, 1766 Annotations: map[string]Annotations{}, 1767 Expected: Issues{ 1768 { 1769 Rule: &testRule{}, 1770 Message: "This is test message", 1771 Range: hcl.Range{ 1772 Filename: "test.tf", 1773 Start: hcl.Pos{Line: 1}, 1774 }, 1775 }, 1776 }, 1777 }, 1778 { 1779 Name: "ignore", 1780 Rule: &testRule{}, 1781 Message: "This is test message", 1782 Location: hcl.Range{ 1783 Filename: "test.tf", 1784 Start: hcl.Pos{Line: 1}, 1785 }, 1786 Annotations: map[string]Annotations{ 1787 "test.tf": { 1788 { 1789 Content: "test_rule", 1790 Token: hclsyntax.Token{ 1791 Type: hclsyntax.TokenComment, 1792 Range: hcl.Range{ 1793 Filename: "test.tf", 1794 Start: hcl.Pos{Line: 1}, 1795 }, 1796 }, 1797 }, 1798 }, 1799 }, 1800 Expected: Issues{}, 1801 }, 1802 } 1803 1804 for _, tc := range cases { 1805 runner := testRunnerWithAnnotations(t, map[string]string{}, tc.Annotations) 1806 1807 runner.EmitIssue(tc.Rule, tc.Message, tc.Location) 1808 1809 if !cmp.Equal(runner.Issues, tc.Expected) { 1810 t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(runner.Issues, tc.Expected)) 1811 } 1812 } 1813 }