github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/parser_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"testing"
     7  
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/google/go-cmp/cmp/cmpopts"
    10  	"github.com/hashicorp/hcl/v2"
    11  	"github.com/hashicorp/hcl/v2/hcltest"
    12  	"github.com/spf13/afero"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  func TestLoadConfigDir(t *testing.T) {
    17  	tests := []struct {
    18  		name    string
    19  		files   map[string]string
    20  		baseDir string
    21  		dir     string
    22  		want    *Module
    23  	}{
    24  		{
    25  			name: "HCL native files",
    26  			files: map[string]string{
    27  				"main.tf":          "",
    28  				"main_override.tf": "",
    29  				"override.tf":      "",
    30  			},
    31  			baseDir: ".",
    32  			dir:     ".",
    33  			want: &Module{
    34  				SourceDir: ".",
    35  				primaries: map[string]*hcl.File{
    36  					"main.tf": {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: "main.tf"}})},
    37  				},
    38  				overrides: map[string]*hcl.File{
    39  					"main_override.tf": {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: "main_override.tf"}})},
    40  					"override.tf":      {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: "override.tf"}})},
    41  				},
    42  				Sources: map[string][]byte{
    43  					"main.tf":          {},
    44  					"main_override.tf": {},
    45  					"override.tf":      {},
    46  				},
    47  				Files: map[string]*hcl.File{
    48  					"main.tf":          {Body: hcl.EmptyBody()},
    49  					"main_override.tf": {Body: hcl.EmptyBody()},
    50  					"override.tf":      {Body: hcl.EmptyBody()},
    51  				},
    52  			},
    53  		},
    54  		{
    55  			name: "HCL JSON files",
    56  			files: map[string]string{
    57  				"main.tf.json":          "{}",
    58  				"main_override.tf.json": "{}",
    59  				"override.tf.json":      "{}",
    60  			},
    61  			baseDir: ".",
    62  			dir:     ".",
    63  			want: &Module{
    64  				SourceDir: ".",
    65  				primaries: map[string]*hcl.File{
    66  					"main.tf.json": {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: "main.tf.json"}})},
    67  				},
    68  				overrides: map[string]*hcl.File{
    69  					"main_override.tf.json": {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: "main_override.tf.json"}})},
    70  					"override.tf.json":      {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: "override.tf.json"}})},
    71  				},
    72  				Sources: map[string][]byte{
    73  					"main.tf.json":          []byte("{}"),
    74  					"main_override.tf.json": []byte("{}"),
    75  					"override.tf.json":      []byte("{}"),
    76  				},
    77  				Files: map[string]*hcl.File{
    78  					"main.tf.json":          {Body: hcl.EmptyBody()},
    79  					"main_override.tf.json": {Body: hcl.EmptyBody()},
    80  					"override.tf.json":      {Body: hcl.EmptyBody()},
    81  				},
    82  			},
    83  		},
    84  		{
    85  			name: "with base dir",
    86  			files: map[string]string{
    87  				"main.tf":          "",
    88  				"main_override.tf": "",
    89  				"override.tf":      "",
    90  			},
    91  			baseDir: "foo",
    92  			dir:     ".",
    93  			want: &Module{
    94  				SourceDir: ".",
    95  				primaries: map[string]*hcl.File{
    96  					filepath.Join("foo", "main.tf"): {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: filepath.Join("foo", "main.tf")}})},
    97  				},
    98  				overrides: map[string]*hcl.File{
    99  					filepath.Join("foo", "main_override.tf"): {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: filepath.Join("foo", "main_override.tf")}})},
   100  					filepath.Join("foo", "override.tf"):      {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: filepath.Join("foo", "override.tf")}})},
   101  				},
   102  				Sources: map[string][]byte{
   103  					filepath.Join("foo", "main.tf"):          {},
   104  					filepath.Join("foo", "main_override.tf"): {},
   105  					filepath.Join("foo", "override.tf"):      {},
   106  				},
   107  				Files: map[string]*hcl.File{
   108  					filepath.Join("foo", "main.tf"):          {Body: hcl.EmptyBody()},
   109  					filepath.Join("foo", "main_override.tf"): {Body: hcl.EmptyBody()},
   110  					filepath.Join("foo", "override.tf"):      {Body: hcl.EmptyBody()},
   111  				},
   112  			},
   113  		},
   114  		{
   115  			name: "with dir",
   116  			files: map[string]string{
   117  				filepath.Join("bar", "main.tf"):          "",
   118  				filepath.Join("bar", "main_override.tf"): "",
   119  				filepath.Join("bar", "override.tf"):      "",
   120  			},
   121  			baseDir: ".",
   122  			dir:     "bar",
   123  			want: &Module{
   124  				SourceDir: "bar",
   125  				primaries: map[string]*hcl.File{
   126  					filepath.Join("bar", "main.tf"): {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: filepath.Join("bar", "main.tf")}})},
   127  				},
   128  				overrides: map[string]*hcl.File{
   129  					filepath.Join("bar", "main_override.tf"): {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: filepath.Join("bar", "main_override.tf")}})},
   130  					filepath.Join("bar", "override.tf"):      {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: filepath.Join("bar", "override.tf")}})},
   131  				},
   132  				Sources: map[string][]byte{
   133  					filepath.Join("bar", "main.tf"):          {},
   134  					filepath.Join("bar", "main_override.tf"): {},
   135  					filepath.Join("bar", "override.tf"):      {},
   136  				},
   137  				Files: map[string]*hcl.File{
   138  					filepath.Join("bar", "main.tf"):          {Body: hcl.EmptyBody()},
   139  					filepath.Join("bar", "main_override.tf"): {Body: hcl.EmptyBody()},
   140  					filepath.Join("bar", "override.tf"):      {Body: hcl.EmptyBody()},
   141  				},
   142  			},
   143  		},
   144  		{
   145  			name: "with basedir + dir",
   146  			files: map[string]string{
   147  				filepath.Join("bar", "main.tf"):          "",
   148  				filepath.Join("bar", "main_override.tf"): "",
   149  				filepath.Join("bar", "override.tf"):      "",
   150  			},
   151  			baseDir: "foo",
   152  			dir:     "bar",
   153  			want: &Module{
   154  				SourceDir: "bar",
   155  				primaries: map[string]*hcl.File{
   156  					filepath.Join("foo", "bar", "main.tf"): {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: filepath.Join("foo", "bar", "main.tf")}})},
   157  				},
   158  				overrides: map[string]*hcl.File{
   159  					filepath.Join("foo", "bar", "main_override.tf"): {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: filepath.Join("foo", "bar", "main_override.tf")}})},
   160  					filepath.Join("foo", "bar", "override.tf"):      {Body: hcltest.MockBody(&hcl.BodyContent{MissingItemRange: hcl.Range{Filename: filepath.Join("foo", "bar", "override.tf")}})},
   161  				},
   162  				Sources: map[string][]byte{
   163  					filepath.Join("foo", "bar", "main.tf"):          {},
   164  					filepath.Join("foo", "bar", "main_override.tf"): {},
   165  					filepath.Join("foo", "bar", "override.tf"):      {},
   166  				},
   167  				Files: map[string]*hcl.File{
   168  					filepath.Join("foo", "bar", "main.tf"):          {Body: hcl.EmptyBody()},
   169  					filepath.Join("foo", "bar", "main_override.tf"): {Body: hcl.EmptyBody()},
   170  					filepath.Join("foo", "bar", "override.tf"):      {Body: hcl.EmptyBody()},
   171  				},
   172  			},
   173  		},
   174  	}
   175  
   176  	for _, test := range tests {
   177  		t.Run(test.name, func(t *testing.T) {
   178  			fs := afero.Afero{Fs: afero.NewMemMapFs()}
   179  			for name, content := range test.files {
   180  				if err := fs.WriteFile(name, []byte(content), os.ModePerm); err != nil {
   181  					t.Fatal(err)
   182  				}
   183  			}
   184  			parser := NewParser(fs)
   185  
   186  			mod, diags := parser.LoadConfigDir(test.baseDir, test.dir)
   187  			if diags.HasErrors() {
   188  				t.Fatal(diags)
   189  			}
   190  
   191  			if mod.SourceDir != test.want.SourceDir {
   192  				t.Errorf("SourceDir: want=%s, got=%s", test.want.SourceDir, mod.SourceDir)
   193  			}
   194  
   195  			primaries := map[string]string{}
   196  			for i, f := range mod.primaries {
   197  				primaries[i] = f.Body.MissingItemRange().Filename
   198  			}
   199  			primariesWant := map[string]string{}
   200  			for i, f := range test.want.primaries {
   201  				primariesWant[i] = f.Body.MissingItemRange().Filename
   202  			}
   203  			if diff := cmp.Diff(primaries, primariesWant); diff != "" {
   204  				t.Errorf(diff)
   205  			}
   206  
   207  			overrides := map[string]string{}
   208  			for i, f := range mod.overrides {
   209  				overrides[i] = f.Body.MissingItemRange().Filename
   210  			}
   211  			overridesWant := map[string]string{}
   212  			for i, f := range test.want.overrides {
   213  				overridesWant[i] = f.Body.MissingItemRange().Filename
   214  			}
   215  			if diff := cmp.Diff(overrides, overridesWant); diff != "" {
   216  				t.Errorf(diff)
   217  			}
   218  
   219  			if diff := cmp.Diff(mod.Sources, test.want.Sources); diff != "" {
   220  				t.Errorf(diff)
   221  			}
   222  
   223  			files := []string{}
   224  			for name := range mod.Files {
   225  				files = append(files, name)
   226  			}
   227  			filesWant := []string{}
   228  			for name := range test.want.Files {
   229  				filesWant = append(filesWant, name)
   230  			}
   231  			opt := cmpopts.SortSlices(func(x, y string) bool { return x > y })
   232  			if diff := cmp.Diff(files, filesWant, opt); diff != "" {
   233  				t.Errorf(diff)
   234  			}
   235  		})
   236  	}
   237  }
   238  
   239  func TestLoadConfigDirFiles(t *testing.T) {
   240  	tests := []struct {
   241  		name    string
   242  		files   map[string]string
   243  		baseDir string
   244  		dir     string
   245  		want    []string
   246  	}{
   247  		{
   248  			name: "HCL native files",
   249  			files: map[string]string{
   250  				"main.tf":          "",
   251  				"main_override.tf": "",
   252  				"override.tf":      "",
   253  			},
   254  			baseDir: ".",
   255  			dir:     ".",
   256  			want: []string{
   257  				"main.tf",
   258  				"main_override.tf",
   259  				"override.tf",
   260  			},
   261  		},
   262  		{
   263  			name: "HCL JSON files",
   264  			files: map[string]string{
   265  				"main.tf.json":          "{}",
   266  				"main_override.tf.json": "{}",
   267  				"override.tf.json":      "{}",
   268  			},
   269  			baseDir: ".",
   270  			dir:     ".",
   271  			want: []string{
   272  				"main.tf.json",
   273  				"main_override.tf.json",
   274  				"override.tf.json",
   275  			},
   276  		},
   277  		{
   278  			name: "with base dir",
   279  			files: map[string]string{
   280  				"main.tf":          "",
   281  				"main_override.tf": "",
   282  				"override.tf":      "",
   283  			},
   284  			baseDir: "foo",
   285  			dir:     ".",
   286  			want: []string{
   287  				filepath.Join("foo", "main.tf"),
   288  				filepath.Join("foo", "main_override.tf"),
   289  				filepath.Join("foo", "override.tf"),
   290  			},
   291  		},
   292  		{
   293  			name: "with dir",
   294  			files: map[string]string{
   295  				filepath.Join("bar", "main.tf"):          "",
   296  				filepath.Join("bar", "main_override.tf"): "",
   297  				filepath.Join("bar", "override.tf"):      "",
   298  			},
   299  			baseDir: ".",
   300  			dir:     "bar",
   301  			want: []string{
   302  				filepath.Join("bar", "main.tf"),
   303  				filepath.Join("bar", "main_override.tf"),
   304  				filepath.Join("bar", "override.tf"),
   305  			},
   306  		},
   307  		{
   308  			name: "with basedir + dir",
   309  			files: map[string]string{
   310  				filepath.Join("bar", "main.tf"):          "",
   311  				filepath.Join("bar", "main_override.tf"): "",
   312  				filepath.Join("bar", "override.tf"):      "",
   313  			},
   314  			baseDir: "foo",
   315  			dir:     "bar",
   316  			want: []string{
   317  				filepath.Join("foo", "bar", "main.tf"),
   318  				filepath.Join("foo", "bar", "main_override.tf"),
   319  				filepath.Join("foo", "bar", "override.tf"),
   320  			},
   321  		},
   322  	}
   323  
   324  	for _, test := range tests {
   325  		t.Run(test.name, func(t *testing.T) {
   326  			fs := afero.Afero{Fs: afero.NewMemMapFs()}
   327  			for name, content := range test.files {
   328  				if err := fs.WriteFile(name, []byte(content), os.ModePerm); err != nil {
   329  					t.Fatal(err)
   330  				}
   331  			}
   332  			parser := NewParser(fs)
   333  
   334  			files, diags := parser.LoadConfigDirFiles(test.baseDir, test.dir)
   335  			if diags.HasErrors() {
   336  				t.Fatal(diags)
   337  			}
   338  
   339  			opt := cmpopts.SortSlices(func(x, y string) bool { return x > y })
   340  
   341  			got := []string{}
   342  			for name := range files {
   343  				got = append(got, name)
   344  			}
   345  			if diff := cmp.Diff(got, test.want, opt); diff != "" {
   346  				t.Errorf(diff)
   347  			}
   348  		})
   349  	}
   350  }
   351  
   352  func TestLoadValuesFile(t *testing.T) {
   353  	tests := []struct {
   354  		name    string
   355  		files   map[string]string
   356  		baseDir string
   357  		path    string
   358  		want    map[string]cty.Value
   359  		sources map[string][]byte
   360  	}{
   361  		{
   362  			name: "default",
   363  			files: map[string]string{
   364  				"terraform.tfvars": `foo="bar"`,
   365  			},
   366  			baseDir: ".",
   367  			path:    "terraform.tfvars",
   368  			want: map[string]cty.Value{
   369  				"foo": cty.StringVal("bar"),
   370  			},
   371  			sources: map[string][]byte{
   372  				"terraform.tfvars": []byte(`foo="bar"`),
   373  			},
   374  		},
   375  		{
   376  			name: "with base dir",
   377  			files: map[string]string{
   378  				"terraform.tfvars": `foo="bar"`,
   379  			},
   380  			baseDir: "baz",
   381  			path:    "terraform.tfvars",
   382  			want: map[string]cty.Value{
   383  				"foo": cty.StringVal("bar"),
   384  			},
   385  			sources: map[string][]byte{
   386  				filepath.Join("baz", "terraform.tfvars"): []byte(`foo="bar"`),
   387  			},
   388  		},
   389  	}
   390  
   391  	for _, test := range tests {
   392  		t.Run(test.name, func(t *testing.T) {
   393  			fs := afero.Afero{Fs: afero.NewMemMapFs()}
   394  			for name, content := range test.files {
   395  				if err := fs.WriteFile(name, []byte(content), os.ModePerm); err != nil {
   396  					t.Fatal(err)
   397  				}
   398  			}
   399  			parser := NewParser(fs)
   400  
   401  			got, diags := parser.LoadValuesFile(test.baseDir, test.path)
   402  			if diags.HasErrors() {
   403  				t.Fatal(diags)
   404  			}
   405  
   406  			opt := cmpopts.IgnoreUnexported(cty.Value{})
   407  			if diff := cmp.Diff(got, test.want, opt); diff != "" {
   408  				t.Error(diff)
   409  			}
   410  			if diff := cmp.Diff(parser.Sources(), test.sources); diff != "" {
   411  				t.Error(diff)
   412  			}
   413  		})
   414  	}
   415  }
   416  
   417  func TestIsConfigDir(t *testing.T) {
   418  	tests := []struct {
   419  		name    string
   420  		files   map[string]string
   421  		baseDir string
   422  		dir     string
   423  		want    bool
   424  	}{
   425  		{
   426  			name: "HCL native files (primary)",
   427  			files: map[string]string{
   428  				"main.tf": "",
   429  			},
   430  			baseDir: ".",
   431  			dir:     ".",
   432  			want:    true,
   433  		},
   434  		{
   435  			name: "HCL native files (override)",
   436  			files: map[string]string{
   437  				"override.tf": "",
   438  			},
   439  			baseDir: ".",
   440  			dir:     ".",
   441  			want:    true,
   442  		},
   443  		{
   444  			name: "HCL JSON files (primary)",
   445  			files: map[string]string{
   446  				"main.tf.json": "{}",
   447  			},
   448  			baseDir: ".",
   449  			dir:     ".",
   450  			want:    true,
   451  		},
   452  		{
   453  			name: "HCL JSON files (override)",
   454  			files: map[string]string{
   455  				"override.tf.json": "{}",
   456  			},
   457  			baseDir: ".",
   458  			dir:     ".",
   459  			want:    true,
   460  		},
   461  		{
   462  			name: "non-HCL files",
   463  			files: map[string]string{
   464  				"README.md": "",
   465  			},
   466  			baseDir: ".",
   467  			dir:     ".",
   468  			want:    false,
   469  		},
   470  	}
   471  
   472  	for _, test := range tests {
   473  		t.Run(test.name, func(t *testing.T) {
   474  			fs := afero.Afero{Fs: afero.NewMemMapFs()}
   475  			for name, content := range test.files {
   476  				if err := fs.WriteFile(name, []byte(content), os.ModePerm); err != nil {
   477  					t.Fatal(err)
   478  				}
   479  			}
   480  			parser := NewParser(fs)
   481  
   482  			got := parser.IsConfigDir(test.baseDir, test.dir)
   483  
   484  			if got != test.want {
   485  				t.Errorf("want=%t, got=%t", test.want, got)
   486  			}
   487  		})
   488  	}
   489  }
   490  
   491  func TestExists(t *testing.T) {
   492  	tests := []struct {
   493  		name  string
   494  		files map[string]string
   495  		path  string
   496  		want  bool
   497  	}{
   498  		{
   499  			name: "exists",
   500  			files: map[string]string{
   501  				"foo": "",
   502  			},
   503  			path: "foo",
   504  			want: true,
   505  		},
   506  		{
   507  			name: "not exists",
   508  			files: map[string]string{
   509  				"foo": "",
   510  			},
   511  			path: "bar",
   512  			want: false,
   513  		},
   514  	}
   515  
   516  	for _, test := range tests {
   517  		t.Run(test.name, func(t *testing.T) {
   518  			fs := afero.Afero{Fs: afero.NewMemMapFs()}
   519  			for name, content := range test.files {
   520  				if err := fs.WriteFile(name, []byte(content), os.ModePerm); err != nil {
   521  					t.Fatal(err)
   522  				}
   523  			}
   524  			parser := NewParser(fs)
   525  
   526  			got := parser.Exists(test.path)
   527  
   528  			if got != test.want {
   529  				t.Errorf("want=%t, got=%t", test.want, got)
   530  			}
   531  		})
   532  	}
   533  }