github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/plugin/internal/host2plugin/host2plugin_test.go (about) 1 package host2plugin 2 3 import ( 4 "errors" 5 "fmt" 6 "testing" 7 8 "github.com/google/go-cmp/cmp" 9 "github.com/google/go-cmp/cmp/cmpopts" 10 "github.com/hashicorp/go-plugin" 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/hclsyntax" 13 "github.com/terraform-linters/tflint-plugin-sdk/hclext" 14 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/plugin2host" 15 "github.com/terraform-linters/tflint-plugin-sdk/tflint" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 func startTestGRPCPluginServer(t *testing.T, ruleset tflint.RuleSet) *GRPCClient { 20 client, _ := plugin.TestPluginGRPCConn(t, false, map[string]plugin.Plugin{ 21 "ruleset": &RuleSetPlugin{impl: ruleset}, 22 }) 23 raw, err := client.Dispense("ruleset") 24 if err != nil { 25 t.Fatalf("failed to dispense: %s", err) 26 } 27 return raw.(*GRPCClient) 28 } 29 30 var _ tflint.RuleSet = &mockRuleSet{} 31 32 type mockRuleSet struct { 33 tflint.BuiltinRuleSet 34 35 impl mockRuleSetImpl 36 } 37 38 type mockRuleSetImpl struct { 39 ruleNames func() []string 40 versionConstraint func() string 41 configSchema func() *hclext.BodySchema 42 applyGlobalConfig func(*tflint.Config) error 43 applyConfig func(*hclext.BodyContent) error 44 newRunner func(tflint.Runner) (tflint.Runner, error) 45 check func(tflint.Runner) error 46 } 47 48 func (r *mockRuleSet) RuleNames() []string { 49 if r.impl.ruleNames != nil { 50 return r.impl.ruleNames() 51 } 52 return []string{} 53 } 54 55 func (r *mockRuleSet) VersionConstraint() string { 56 if r.impl.versionConstraint != nil { 57 return r.impl.versionConstraint() 58 } 59 return "" 60 } 61 62 func (r *mockRuleSet) ConfigSchema() *hclext.BodySchema { 63 if r.impl.configSchema != nil { 64 return r.impl.configSchema() 65 } 66 return &hclext.BodySchema{} 67 } 68 69 func (r *mockRuleSet) ApplyGlobalConfig(config *tflint.Config) error { 70 if r.impl.applyGlobalConfig != nil { 71 return r.impl.applyGlobalConfig(config) 72 } 73 return nil 74 } 75 76 func (r *mockRuleSet) ApplyConfig(content *hclext.BodyContent) error { 77 if r.impl.applyConfig != nil { 78 return r.impl.applyConfig(content) 79 } 80 return nil 81 } 82 83 func (r *mockRuleSet) NewRunner(runner tflint.Runner) (tflint.Runner, error) { 84 if r.impl.newRunner != nil { 85 return r.impl.newRunner(runner) 86 } 87 return runner, nil 88 } 89 90 func newMockRuleSet(name, version string, impl mockRuleSetImpl) *mockRuleSet { 91 return &mockRuleSet{ 92 BuiltinRuleSet: tflint.BuiltinRuleSet{ 93 Name: name, 94 Version: version, 95 EnabledRules: []tflint.Rule{ 96 &mockRule{check: impl.check}, 97 }, 98 }, 99 impl: impl, 100 } 101 } 102 103 var _ tflint.Rule = &mockRule{} 104 105 type mockRule struct { 106 tflint.DefaultRule 107 check func(tflint.Runner) error 108 } 109 110 func (r *mockRule) Check(runner tflint.Runner) error { 111 if r.check != nil { 112 return r.check(runner) 113 } 114 return nil 115 } 116 117 func (r *mockRule) Name() string { 118 return "mock_rule" 119 } 120 121 func (r *mockRule) Severity() tflint.Severity { 122 return tflint.ERROR 123 } 124 125 func (r *mockRule) Enabled() bool { 126 return true 127 } 128 129 func TestRuleSetName(t *testing.T) { 130 // default error check helper 131 neverHappend := func(err error) bool { return err != nil } 132 133 tests := []struct { 134 Name string 135 RuleSetName string 136 Want string 137 ErrCheck func(error) bool 138 }{ 139 { 140 Name: "rule set name", 141 RuleSetName: "test_ruleset", 142 Want: "test_ruleset", 143 ErrCheck: neverHappend, 144 }, 145 } 146 147 for _, test := range tests { 148 t.Run(test.Name, func(t *testing.T) { 149 client := startTestGRPCPluginServer(t, newMockRuleSet(test.RuleSetName, "0.1.0", mockRuleSetImpl{})) 150 151 got, err := client.RuleSetName() 152 if test.ErrCheck(err) { 153 t.Fatalf("failed to call RuleSetName: %s", err) 154 } 155 156 if got != test.Want { 157 t.Errorf("expected `%s`, but got `%s`", test.Want, got) 158 } 159 }) 160 } 161 } 162 163 func TestRuleSetVersion(t *testing.T) { 164 // default error check helper 165 neverHappend := func(err error) bool { return err != nil } 166 167 tests := []struct { 168 Name string 169 RuleSetVersion string 170 Want string 171 ErrCheck func(error) bool 172 }{ 173 { 174 Name: "rule set version", 175 RuleSetVersion: "0.1.0", 176 Want: "0.1.0", 177 ErrCheck: neverHappend, 178 }, 179 } 180 181 for _, test := range tests { 182 t.Run(test.Name, func(t *testing.T) { 183 client := startTestGRPCPluginServer(t, newMockRuleSet("test_ruleset", test.RuleSetVersion, mockRuleSetImpl{})) 184 185 got, err := client.RuleSetVersion() 186 if test.ErrCheck(err) { 187 t.Fatalf("failed to call RuleSetVersion: %s", err) 188 } 189 190 if got != test.Want { 191 t.Errorf("expected `%s`, but got `%s`", test.Want, got) 192 } 193 }) 194 } 195 } 196 197 func TestRuleNames(t *testing.T) { 198 // default error check helper 199 neverHappend := func(err error) bool { return err != nil } 200 201 tests := []struct { 202 Name string 203 ServerImpl func() []string 204 Want []string 205 ErrCheck func(error) bool 206 }{ 207 { 208 Name: "rule names", 209 ServerImpl: func() []string { 210 return []string{"test1", "test2"} 211 }, 212 Want: []string{"test1", "test2"}, 213 ErrCheck: neverHappend, 214 }, 215 } 216 217 for _, test := range tests { 218 t.Run(test.Name, func(t *testing.T) { 219 client := startTestGRPCPluginServer(t, newMockRuleSet("test_ruleset", "0.1.0", mockRuleSetImpl{ruleNames: test.ServerImpl})) 220 221 got, err := client.RuleNames() 222 if test.ErrCheck(err) { 223 t.Fatalf("failed to call RuleNames: %s", err) 224 } 225 226 if diff := cmp.Diff(got, test.Want); diff != "" { 227 t.Errorf("diff: %s", diff) 228 } 229 }) 230 } 231 } 232 233 func TestVersionConstraints(t *testing.T) { 234 // default error check helper 235 neverHappend := func(err error) bool { return err != nil } 236 237 tests := []struct { 238 Name string 239 ServerImpl func() string 240 Want string 241 ErrCheck func(error) bool 242 }{ 243 { 244 Name: "default", 245 ServerImpl: func() string { 246 return "" 247 }, 248 Want: "", 249 ErrCheck: neverHappend, 250 }, 251 { 252 Name: "valid constraint", 253 ServerImpl: func() string { 254 return ">= 1.0" 255 }, 256 Want: ">= 1.0", 257 ErrCheck: neverHappend, 258 }, 259 { 260 Name: "invalid constraint", 261 ServerImpl: func() string { 262 return ">> 1.0" 263 }, 264 Want: "", 265 ErrCheck: func(err error) bool { 266 return err == nil || err.Error() != "Malformed constraint: >> 1.0" 267 }, 268 }, 269 } 270 271 for _, test := range tests { 272 t.Run(test.Name, func(t *testing.T) { 273 client := startTestGRPCPluginServer(t, newMockRuleSet("test_ruleset", "0.1.0", mockRuleSetImpl{versionConstraint: test.ServerImpl})) 274 275 got, err := client.VersionConstraints() 276 if test.ErrCheck(err) { 277 t.Fatalf("failed to call VersionConstraints: %s", err) 278 } 279 280 if got.String() != test.Want { 281 t.Errorf("want: %s, got: %s", test.Want, got) 282 } 283 }) 284 } 285 } 286 287 func TestSDKVersion(t *testing.T) { 288 client := startTestGRPCPluginServer(t, newMockRuleSet("test_ruleset", "0.1.0", mockRuleSetImpl{})) 289 290 got, err := client.SDKVersion() 291 if err != nil { 292 t.Fatalf("failed to call SDKVersion: %s", err) 293 } 294 295 if got.String() != SDKVersion { 296 t.Errorf("want: %s, got: %s", SDKVersion, got) 297 } 298 } 299 300 func TestConfigSchema(t *testing.T) { 301 // default error check helper 302 neverHappend := func(err error) bool { return err != nil } 303 304 // nested schema example 305 schema := &hclext.BodySchema{ 306 Attributes: []hclext.AttributeSchema{ 307 {Name: "foo", Required: true}, 308 }, 309 Blocks: []hclext.BlockSchema{ 310 { 311 Type: "bar", 312 LabelNames: []string{"baz", "qux"}, 313 Body: &hclext.BodySchema{ 314 Attributes: []hclext.AttributeSchema{ 315 {Name: "qux", Required: true}, 316 }, 317 Blocks: []hclext.BlockSchema{ 318 { 319 Type: "baz", 320 LabelNames: []string{"foo", "bar"}, 321 Body: &hclext.BodySchema{ 322 Attributes: []hclext.AttributeSchema{}, 323 Blocks: []hclext.BlockSchema{}, 324 }, 325 }, 326 }, 327 }, 328 }, 329 }, 330 } 331 332 tests := []struct { 333 Name string 334 ServerImpl func() *hclext.BodySchema 335 Want *hclext.BodySchema 336 ErrCheck func(error) bool 337 }{ 338 { 339 Name: "nested schema", 340 ServerImpl: func() *hclext.BodySchema { 341 return schema 342 }, 343 Want: schema, 344 ErrCheck: neverHappend, 345 }, 346 { 347 Name: "nil schema", 348 ServerImpl: func() *hclext.BodySchema { 349 return nil 350 }, 351 Want: &hclext.BodySchema{ 352 Attributes: []hclext.AttributeSchema{}, 353 Blocks: []hclext.BlockSchema{}, 354 }, 355 ErrCheck: neverHappend, 356 }, 357 } 358 359 for _, test := range tests { 360 t.Run(test.Name, func(t *testing.T) { 361 client := startTestGRPCPluginServer(t, newMockRuleSet("test_ruleset", "0.1.0", mockRuleSetImpl{configSchema: test.ServerImpl})) 362 363 got, err := client.ConfigSchema() 364 if test.ErrCheck(err) { 365 t.Fatalf("failed to call ConfigSchema: %s", err) 366 } 367 368 if diff := cmp.Diff(got, test.Want); diff != "" { 369 t.Errorf("diff: %s", diff) 370 } 371 }) 372 } 373 } 374 375 func TestApplyGlobalConfig(t *testing.T) { 376 // default error check helper 377 neverHappend := func(err error) bool { return err != nil } 378 379 tests := []struct { 380 Name string 381 Arg *tflint.Config 382 ServerImpl func(*tflint.Config) error 383 ErrCheck func(error) bool 384 LegacyHost bool 385 }{ 386 { 387 Name: "nil config", 388 Arg: nil, 389 ServerImpl: func(config *tflint.Config) error { 390 if len(config.Rules) != 0 { 391 return fmt.Errorf("config rules should be empty, but %#v", config.Rules) 392 } 393 if config.DisabledByDefault != false { 394 return errors.New("disabled by default should be false") 395 } 396 return nil 397 }, 398 ErrCheck: neverHappend, 399 }, 400 { 401 Name: "full config", 402 Arg: &tflint.Config{ 403 Rules: map[string]*tflint.RuleConfig{ 404 "test1": {Name: "test1", Enabled: true}, 405 "test2": {Name: "test2", Enabled: false}, 406 }, 407 DisabledByDefault: true, 408 Only: []string{"test_rule1", "test_rule2"}, 409 Fix: true, 410 }, 411 ServerImpl: func(config *tflint.Config) error { 412 want := &tflint.Config{ 413 Rules: map[string]*tflint.RuleConfig{ 414 "test1": {Name: "test1", Enabled: true}, 415 "test2": {Name: "test2", Enabled: false}, 416 }, 417 DisabledByDefault: true, 418 Only: []string{"test_rule1", "test_rule2"}, 419 Fix: true, 420 } 421 422 if diff := cmp.Diff(config, want); diff != "" { 423 return fmt.Errorf("diff: %s", diff) 424 } 425 return nil 426 }, 427 ErrCheck: neverHappend, 428 }, 429 { 430 Name: "server returns an error", 431 Arg: nil, 432 ServerImpl: func(config *tflint.Config) error { 433 return errors.New("unexpected error") 434 }, 435 ErrCheck: func(err error) bool { 436 return err == nil || err.Error() != "unexpected error" 437 }, 438 }, 439 { 440 Name: "legacy host version (TFLint v0.41)", 441 Arg: nil, 442 ServerImpl: func(config *tflint.Config) error { 443 return nil 444 }, 445 LegacyHost: true, 446 ErrCheck: func(err error) bool { 447 return err == nil || err.Error() != "failed to satisfy version constraints; tflint-ruleset-test_ruleset requires >= 0.42, but TFLint version is 0.40 or 0.41" 448 }, 449 }, 450 } 451 452 for _, test := range tests { 453 t.Run(test.Name, func(t *testing.T) { 454 client := startTestGRPCPluginServer(t, newMockRuleSet("test_ruleset", "0.1.0", mockRuleSetImpl{applyGlobalConfig: test.ServerImpl})) 455 456 if !test.LegacyHost { 457 // call VersionConstraints to avoid SDK version incompatible error 458 if _, err := client.VersionConstraints(); err != nil { 459 t.Fatalf("failed to call VersionConstraints: %s", err) 460 } 461 } 462 err := client.ApplyGlobalConfig(test.Arg) 463 if test.ErrCheck(err) { 464 t.Fatalf("failed to call ApplyGlobalConfig: %s", err) 465 } 466 }) 467 } 468 } 469 470 func TestApplyConfig(t *testing.T) { 471 // default error check helper 472 neverHappend := func(err error) bool { return err != nil } 473 474 // test util functions 475 hclFile := func(filename string, code string) *hcl.File { 476 file, diags := hclsyntax.ParseConfig([]byte(code), filename, hcl.InitialPos) 477 if diags.HasErrors() { 478 panic(diags) 479 } 480 return file 481 } 482 schema := &hclext.BodySchema{ 483 Attributes: []hclext.AttributeSchema{{Name: "name"}}, 484 Blocks: []hclext.BlockSchema{ 485 { 486 Type: "block", 487 LabelNames: []string{"bar"}, 488 Body: &hclext.BodySchema{ 489 Attributes: []hclext.AttributeSchema{{Name: "nested"}}, 490 }, 491 }, 492 }, 493 } 494 495 tests := []struct { 496 Name string 497 Args func() (*hclext.BodyContent, map[string][]byte) 498 ServerImpl func(*hclext.BodyContent) error 499 ErrCheck func(error) bool 500 }{ 501 { 502 Name: "nil content", 503 Args: func() (*hclext.BodyContent, map[string][]byte) { 504 return nil, nil 505 }, 506 ServerImpl: func(content *hclext.BodyContent) error { 507 want := &hclext.BodyContent{ 508 Attributes: hclext.Attributes{}, 509 Blocks: hclext.Blocks{}, 510 } 511 512 if diff := cmp.Diff(content, want); diff != "" { 513 return fmt.Errorf("diff: %s", diff) 514 } 515 return nil 516 }, 517 ErrCheck: neverHappend, 518 }, 519 { 520 Name: "nested content", 521 Args: func() (*hclext.BodyContent, map[string][]byte) { 522 file := hclFile(".tflint.hcl", ` 523 name = "foo" 524 block "bar" { 525 nested = "baz" 526 }`) 527 content, diags := hclext.Content(file.Body, schema) 528 if diags.HasErrors() { 529 panic(diags) 530 } 531 532 return content, map[string][]byte{".tflint.hcl": file.Bytes} 533 }, 534 ServerImpl: func(content *hclext.BodyContent) error { 535 file := hclFile(".tflint.hcl", ` 536 name = "foo" 537 block "bar" { 538 nested = "baz" 539 }`) 540 want, diags := hclext.Content(file.Body, schema) 541 if diags.HasErrors() { 542 return diags 543 } 544 545 opts := cmp.Options{ 546 cmp.Comparer(func(x, y cty.Value) bool { 547 return x.GoString() == y.GoString() 548 }), 549 } 550 if diff := cmp.Diff(content, want, opts); diff != "" { 551 return fmt.Errorf("diff: %s", diff) 552 } 553 return nil 554 }, 555 ErrCheck: neverHappend, 556 }, 557 { 558 Name: "server returns an error", 559 Args: func() (*hclext.BodyContent, map[string][]byte) { 560 return nil, nil 561 }, 562 ServerImpl: func(content *hclext.BodyContent) error { 563 return errors.New("unexpected error") 564 }, 565 ErrCheck: func(err error) bool { 566 return err == nil || err.Error() != "unexpected error" 567 }, 568 }, 569 } 570 571 for _, test := range tests { 572 t.Run(test.Name, func(t *testing.T) { 573 client := startTestGRPCPluginServer(t, newMockRuleSet("test_ruleset", "0.1.0", mockRuleSetImpl{applyConfig: test.ServerImpl})) 574 575 err := client.ApplyConfig(test.Args()) 576 if test.ErrCheck(err) { 577 t.Fatalf("failed to call ApplyConfig: %s", err) 578 } 579 }) 580 } 581 } 582 583 var _ plugin2host.Server = &mockServer{} 584 585 type mockServer struct { 586 impl mockServerImpl 587 } 588 589 type mockServerImpl struct { 590 getFile func(string) (*hcl.File, error) 591 getFiles func(tflint.ModuleCtxType) map[string][]byte 592 applyChanges func(map[string][]byte) error 593 } 594 595 func (s *mockServer) GetOriginalwd() string { 596 return "/work" 597 } 598 599 func (s *mockServer) GetModulePath() []string { 600 return []string{} 601 } 602 603 func (s *mockServer) GetModuleContent(schema *hclext.BodySchema, opts tflint.GetModuleContentOption) (*hclext.BodyContent, hcl.Diagnostics) { 604 return &hclext.BodyContent{}, hcl.Diagnostics{} 605 } 606 607 func (s *mockServer) GetFile(filename string) (*hcl.File, error) { 608 if s.impl.getFile != nil { 609 return s.impl.getFile(filename) 610 } 611 return nil, nil 612 } 613 614 func (s *mockServer) GetRuleConfigContent(name string, schema *hclext.BodySchema) (*hclext.BodyContent, map[string][]byte, error) { 615 return &hclext.BodyContent{}, map[string][]byte{}, nil 616 } 617 618 func (s *mockServer) EvaluateExpr(expr hcl.Expression, opts tflint.EvaluateExprOption) (cty.Value, error) { 619 return cty.Value{}, nil 620 } 621 622 func (s *mockServer) EmitIssue(rule tflint.Rule, message string, location hcl.Range, fixable bool) (bool, error) { 623 return true, nil 624 } 625 626 func (s *mockServer) ApplyChanges(sources map[string][]byte) error { 627 if s.impl.applyChanges != nil { 628 return s.impl.applyChanges(sources) 629 } 630 return nil 631 } 632 633 func (s *mockServer) GetFiles(ctx tflint.ModuleCtxType) map[string][]byte { 634 if s.impl.getFiles != nil { 635 return s.impl.getFiles(ctx) 636 } 637 return map[string][]byte{} 638 } 639 640 type mockCustomRunner struct { 641 tflint.Runner 642 } 643 644 func (s *mockCustomRunner) Hello() string { 645 return "Hello from custom runner!" 646 } 647 648 func TestCheck(t *testing.T) { 649 // default error check helper 650 neverHappend := func(err error) bool { return err != nil } 651 652 // test util functions 653 hclFile := func(filename string, code string) (*hcl.File, error) { 654 file, diags := hclsyntax.ParseConfig([]byte(code), filename, hcl.InitialPos) 655 if diags.HasErrors() { 656 return file, diags 657 } 658 return file, nil 659 } 660 661 tests := []struct { 662 Name string 663 Arg func() plugin2host.Server 664 ServerImpl func(tflint.Runner) error 665 NewRunnerImpl func(tflint.Runner) (tflint.Runner, error) 666 ErrCheck func(error) bool 667 }{ 668 { 669 Name: "bidirectional", 670 Arg: func() plugin2host.Server { 671 return &mockServer{ 672 impl: mockServerImpl{ 673 getFile: func(filename string) (*hcl.File, error) { 674 return hclFile("test.tf", ` 675 resource "aws_instance" "foo" { 676 instance_type = "t2.micro" 677 }`) 678 }, 679 }, 680 } 681 }, 682 ServerImpl: func(runner tflint.Runner) error { 683 got, err := runner.GetFile("test.tf") 684 if err != nil { 685 return err 686 } 687 688 want, err := hclFile("test.tf", ` 689 resource "aws_instance" "foo" { 690 instance_type = "t2.micro" 691 }`) 692 if err != nil { 693 return err 694 } 695 696 opts := cmp.Options{ 697 cmp.Comparer(func(x, y cty.Value) bool { 698 return x.GoString() == y.GoString() 699 }), 700 cmp.AllowUnexported(hclsyntax.Body{}), 701 cmpopts.IgnoreFields(hcl.File{}, "Nav"), 702 } 703 if diff := cmp.Diff(got, want, opts); diff != "" { 704 return fmt.Errorf("diff: %s", diff) 705 } 706 return nil 707 }, 708 ErrCheck: neverHappend, 709 }, 710 { 711 Name: "host server returns an error", 712 Arg: func() plugin2host.Server { 713 return &mockServer{ 714 impl: mockServerImpl{ 715 getFile: func(filename string) (*hcl.File, error) { 716 return nil, errors.New("unexpected error") 717 }, 718 }, 719 } 720 }, 721 ServerImpl: func(runner tflint.Runner) error { 722 _, err := runner.GetFile("test.tf") 723 return err 724 }, 725 ErrCheck: func(err error) bool { 726 return err == nil || err.Error() != `failed to check "mock_rule" rule: unexpected error` 727 }, 728 }, 729 { 730 Name: "plugin server returns an error", 731 Arg: func() plugin2host.Server { 732 return &mockServer{} 733 }, 734 ServerImpl: func(runner tflint.Runner) error { 735 return errors.New("unexpected error") 736 }, 737 ErrCheck: func(err error) bool { 738 return err == nil || err.Error() != `failed to check "mock_rule" rule: unexpected error` 739 }, 740 }, 741 { 742 Name: "inject new runner", 743 Arg: func() plugin2host.Server { 744 return &mockServer{} 745 }, 746 NewRunnerImpl: func(runner tflint.Runner) (tflint.Runner, error) { 747 return &mockCustomRunner{runner}, nil 748 }, 749 ServerImpl: func(runner tflint.Runner) error { 750 return errors.New(runner.(*mockCustomRunner).Hello()) 751 }, 752 ErrCheck: func(err error) bool { 753 return err == nil || err.Error() != `failed to check "mock_rule" rule: Hello from custom runner!` 754 }, 755 }, 756 { 757 Name: "apply changes", 758 Arg: func() plugin2host.Server { 759 return &mockServer{ 760 impl: mockServerImpl{ 761 getFiles: func(tflint.ModuleCtxType) map[string][]byte { 762 return map[string][]byte{ 763 "main.tf": []byte(` 764 foo = 1 765 bar = 2 766 `), 767 } 768 }, 769 applyChanges: func(sources map[string][]byte) error { 770 want := map[string]string{ 771 "main.tf": ` 772 foo = 1 773 baz = 2 774 `, 775 } 776 got := map[string]string{} 777 for filename, source := range sources { 778 got[filename] = string(source) 779 } 780 if diff := cmp.Diff(got, want); diff != "" { 781 return fmt.Errorf("diff: %s", diff) 782 } 783 return nil 784 }, 785 }, 786 } 787 }, 788 ServerImpl: func(runner tflint.Runner) error { 789 return runner.EmitIssueWithFix( 790 &mockRule{}, 791 "test message", 792 hcl.Range{}, 793 func(f tflint.Fixer) error { 794 return f.ReplaceText( 795 hcl.Range{Filename: "main.tf", Start: hcl.Pos{Byte: 11}, End: hcl.Pos{Byte: 14}}, 796 "baz", 797 ) 798 }, 799 ) 800 }, 801 ErrCheck: neverHappend, 802 }, 803 { 804 Name: "apply changes with custom runner", 805 Arg: func() plugin2host.Server { 806 return &mockServer{ 807 impl: mockServerImpl{ 808 getFiles: func(tflint.ModuleCtxType) map[string][]byte { 809 return map[string][]byte{ 810 "main.tf": []byte(` 811 foo = 1 812 bar = 2 813 `), 814 } 815 }, 816 applyChanges: func(sources map[string][]byte) error { 817 want := map[string]string{ 818 "main.tf": ` 819 foo = 1 820 baz = 2 821 `, 822 } 823 got := map[string]string{} 824 for filename, source := range sources { 825 got[filename] = string(source) 826 } 827 if diff := cmp.Diff(got, want); diff != "" { 828 return fmt.Errorf("diff: %s", diff) 829 } 830 return nil 831 }, 832 }, 833 } 834 }, 835 NewRunnerImpl: func(runner tflint.Runner) (tflint.Runner, error) { 836 return &mockCustomRunner{runner}, nil 837 }, 838 ServerImpl: func(runner tflint.Runner) error { 839 return runner.EmitIssueWithFix( 840 &mockRule{}, 841 "test message", 842 hcl.Range{}, 843 func(f tflint.Fixer) error { 844 return f.ReplaceText( 845 hcl.Range{Filename: "main.tf", Start: hcl.Pos{Byte: 11}, End: hcl.Pos{Byte: 14}}, 846 "baz", 847 ) 848 }, 849 ) 850 }, 851 ErrCheck: neverHappend, 852 }, 853 { 854 Name: "apply errors", 855 Arg: func() plugin2host.Server { 856 return &mockServer{ 857 impl: mockServerImpl{ 858 getFiles: func(tflint.ModuleCtxType) map[string][]byte { 859 return map[string][]byte{ 860 "main.tf": []byte(` 861 foo = 1 862 bar = 2 863 `), 864 } 865 }, 866 applyChanges: func(sources map[string][]byte) error { 867 return errors.New("unexpected error") 868 }, 869 }, 870 } 871 }, 872 ServerImpl: func(runner tflint.Runner) error { 873 return runner.EmitIssueWithFix( 874 &mockRule{}, 875 "test message", 876 hcl.Range{}, 877 func(f tflint.Fixer) error { 878 return f.ReplaceText( 879 hcl.Range{Filename: "main.tf", Start: hcl.Pos{Byte: 11}, End: hcl.Pos{Byte: 14}}, 880 "baz", 881 ) 882 }, 883 ) 884 }, 885 ErrCheck: func(err error) bool { 886 return err == nil || err.Error() != `failed to apply fixes by "mock_rule" rule: unexpected error` 887 }, 888 }, 889 } 890 891 for _, test := range tests { 892 t.Run(test.Name, func(t *testing.T) { 893 client := startTestGRPCPluginServer(t, newMockRuleSet("test_ruleset", "0.1.0", mockRuleSetImpl{check: test.ServerImpl, newRunner: test.NewRunnerImpl})) 894 895 // call VersionConstraints to avoid SDK version incompatible error 896 if _, err := client.VersionConstraints(); err != nil { 897 t.Fatalf("failed to call VersionConstraints: %s", err) 898 } 899 if err := client.ApplyGlobalConfig(&tflint.Config{Fix: true}); err != nil { 900 t.Fatalf("failed to call ApplyGlobalConfig: %s", err) 901 } 902 err := client.Check(test.Arg()) 903 if test.ErrCheck(err) { 904 t.Fatalf("failed to call Check: %s", err) 905 } 906 }) 907 } 908 }