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  }