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 }