mirror of https://github.com/hashicorp/terraform
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
352 lines
13 KiB
352 lines
13 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package configs
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
)
|
|
|
|
// TestParseLoadConfigDirSuccess is a simple test that just verifies that
|
|
// a number of test configuration directories (in testdata/valid-modules)
|
|
// can be parsed without raising any diagnostics.
|
|
//
|
|
// It also re-tests the individual files in testdata/valid-files as if
|
|
// they were single-file modules, to ensure that they can be bundled into
|
|
// modules correctly.
|
|
//
|
|
// This test does not verify that reading these modules produces the correct
|
|
// module element contents. More detailed assertions may be made on some subset
|
|
// of these configuration files in other tests.
|
|
func TestParserLoadConfigDirSuccess(t *testing.T) {
|
|
dirs, err := ioutil.ReadDir("testdata/valid-modules")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, info := range dirs {
|
|
name := info.Name()
|
|
t.Run(name, func(t *testing.T) {
|
|
parser := NewParser(nil)
|
|
path := filepath.Join("testdata/valid-modules", name)
|
|
|
|
mod, diags := parser.LoadConfigDir(path)
|
|
if len(diags) != 0 && len(mod.ActiveExperiments) != 0 {
|
|
// As a special case to reduce churn while we're working
|
|
// through experimental features, we'll ignore the warning
|
|
// that an experimental feature is active if the module
|
|
// intentionally opted in to that feature.
|
|
// If you want to explicitly test for the feature warning
|
|
// to be generated, consider using testdata/warning-files
|
|
// instead.
|
|
filterDiags := make(hcl.Diagnostics, 0, len(diags))
|
|
for _, diag := range diags {
|
|
if diag.Severity != hcl.DiagWarning {
|
|
continue
|
|
}
|
|
match := false
|
|
for exp := range mod.ActiveExperiments {
|
|
allowedSummary := fmt.Sprintf("Experimental feature %q is active", exp.Keyword())
|
|
if diag.Summary == allowedSummary {
|
|
match = true
|
|
break
|
|
}
|
|
}
|
|
if !match {
|
|
filterDiags = append(filterDiags, diag)
|
|
}
|
|
}
|
|
diags = filterDiags
|
|
}
|
|
if len(diags) != 0 {
|
|
t.Errorf("unexpected diagnostics")
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag)
|
|
}
|
|
}
|
|
|
|
if mod.SourceDir != path {
|
|
t.Errorf("wrong SourceDir value %q; want %s", mod.SourceDir, path)
|
|
}
|
|
|
|
if len(mod.Tests) > 0 {
|
|
// We only load tests when requested, and we didn't request this
|
|
// time.
|
|
t.Errorf("should not have loaded tests, but found %d", len(mod.Tests))
|
|
}
|
|
})
|
|
}
|
|
|
|
// The individual files in testdata/valid-files should also work
|
|
// when loaded as modules.
|
|
files, err := ioutil.ReadDir("testdata/valid-files")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, info := range files {
|
|
name := info.Name()
|
|
t.Run(fmt.Sprintf("%s as module", name), func(t *testing.T) {
|
|
src, err := ioutil.ReadFile(filepath.Join("testdata/valid-files", name))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
parser := testParser(map[string]string{
|
|
"mod/" + name: string(src),
|
|
})
|
|
|
|
_, diags := parser.LoadConfigDir("mod")
|
|
if diags.HasErrors() {
|
|
t.Errorf("unexpected error diagnostics")
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestParserLoadConfigDirWithTests(t *testing.T) {
|
|
directories := []string{
|
|
"testdata/valid-modules/with-tests",
|
|
"testdata/valid-modules/with-tests-expect-failures",
|
|
"testdata/valid-modules/with-tests-nested",
|
|
"testdata/valid-modules/with-tests-very-nested",
|
|
"testdata/valid-modules/with-tests-json",
|
|
"testdata/valid-modules/with-mocks",
|
|
}
|
|
|
|
for _, directory := range directories {
|
|
t.Run(directory, func(t *testing.T) {
|
|
|
|
testDirectory := DefaultTestDirectory
|
|
if directory == "testdata/valid-modules/with-tests-very-nested" {
|
|
testDirectory = "very/nested"
|
|
}
|
|
|
|
parser := NewParser(nil)
|
|
mod, diags := parser.LoadConfigDirWithTests(directory, testDirectory)
|
|
if len(diags) > 0 { // We don't want any warnings or errors.
|
|
t.Errorf("unexpected diagnostics")
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag)
|
|
}
|
|
}
|
|
|
|
if len(mod.Tests) != 2 {
|
|
t.Errorf("incorrect number of test files found: %d", len(mod.Tests))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParserLoadTestFiles_Invalid(t *testing.T) {
|
|
|
|
tcs := map[string][]string{
|
|
"duplicate_data_overrides": {
|
|
"duplicate_data_overrides.tftest.hcl:7,3-16: Duplicate override_data block; An override_data block targeting data.aws_instance.test has already been defined at duplicate_data_overrides.tftest.hcl:2,3-16.",
|
|
"duplicate_data_overrides.tftest.hcl:18,1-14: Duplicate override_data block; An override_data block targeting data.aws_instance.test has already been defined at duplicate_data_overrides.tftest.hcl:13,1-14.",
|
|
"duplicate_data_overrides.tftest.hcl:29,3-16: Duplicate override_data block; An override_data block targeting data.aws_instance.test has already been defined at duplicate_data_overrides.tftest.hcl:24,3-16.",
|
|
},
|
|
"duplicate_mixed_providers": {
|
|
"duplicate_mixed_providers.tftest.hcl:3,1-20: Duplicate provider block; A provider for aws is already defined at duplicate_mixed_providers.tftest.hcl:1,10-15.",
|
|
"duplicate_mixed_providers.tftest.hcl:9,1-20: Duplicate provider block; A provider for aws.test is already defined at duplicate_mixed_providers.tftest.hcl:5,10-15.",
|
|
},
|
|
"duplicate_mock_data_sources": {
|
|
"duplicate_mock_data_sources.tftest.hcl:7,13-27: Duplicate mock_data block; A mock_data block for aws_instance has already been defined at duplicate_mock_data_sources.tftest.hcl:3,3-27.",
|
|
},
|
|
"duplicate_mock_providers": {
|
|
"duplicate_mock_providers.tftest.hcl:3,1-20: Duplicate provider block; A provider for aws is already defined at duplicate_mock_providers.tftest.hcl:1,15-20.",
|
|
"duplicate_mock_providers.tftest.hcl:9,1-20: Duplicate provider block; A provider for aws.test is already defined at duplicate_mock_providers.tftest.hcl:5,15-20.",
|
|
},
|
|
"duplicate_mock_resources": {
|
|
"duplicate_mock_resources.tftest.hcl:7,17-31: Duplicate mock_resource block; A mock_resource block for aws_instance has already been defined at duplicate_mock_resources.tftest.hcl:3,3-31.",
|
|
},
|
|
"duplicate_module_overrides": {
|
|
"duplicate_module_overrides.tftest.hcl:7,1-16: Duplicate override_module block; An override_module block targeting module.child has already been defined at duplicate_module_overrides.tftest.hcl:2,1-16.",
|
|
"duplicate_module_overrides.tftest.hcl:18,3-18: Duplicate override_module block; An override_module block targeting module.child has already been defined at duplicate_module_overrides.tftest.hcl:13,3-18.",
|
|
},
|
|
"duplicate_providers": {
|
|
"duplicate_providers.tftest.hcl:3,1-15: Duplicate provider block; A provider for aws is already defined at duplicate_providers.tftest.hcl:1,10-15.",
|
|
"duplicate_providers.tftest.hcl:9,1-15: Duplicate provider block; A provider for aws.test is already defined at duplicate_providers.tftest.hcl:5,10-15.",
|
|
},
|
|
"duplicate_resource_overrides": {
|
|
"duplicate_resource_overrides.tftest.hcl:7,3-20: Duplicate override_resource block; An override_resource block targeting aws_instance.test has already been defined at duplicate_resource_overrides.tftest.hcl:2,3-20.",
|
|
"duplicate_resource_overrides.tftest.hcl:18,1-18: Duplicate override_resource block; An override_resource block targeting aws_instance.test has already been defined at duplicate_resource_overrides.tftest.hcl:13,1-18.",
|
|
"duplicate_resource_overrides.tftest.hcl:29,3-20: Duplicate override_resource block; An override_resource block targeting aws_instance.test has already been defined at duplicate_resource_overrides.tftest.hcl:24,3-20.",
|
|
},
|
|
"invalid_data_override": {
|
|
"invalid_data_override.tftest.hcl:6,1-14: Missing target attribute; override_data blocks must specify a target address.",
|
|
},
|
|
"invalid_data_override_target": {
|
|
"invalid_data_override_target.tftest.hcl:8,3-24: Invalid override target; You can only target data sources from override_data blocks, not module.child.",
|
|
"invalid_data_override_target.tftest.hcl:3,3-31: Invalid override target; You can only target data sources from override_data blocks, not aws_instance.target.",
|
|
},
|
|
"invalid_mock_data_sources": {
|
|
"invalid_mock_data_sources.tftest.hcl:7,13-16: Variables not allowed; Variables may not be used here.",
|
|
},
|
|
"invalid_mock_resources": {
|
|
"invalid_mock_resources.tftest.hcl:7,13-16: Variables not allowed; Variables may not be used here.",
|
|
},
|
|
"invalid_module_override": {
|
|
"invalid_module_override.tftest.hcl:5,1-16: Missing target attribute; override_module blocks must specify a target address.",
|
|
"invalid_module_override.tftest.hcl:11,3-9: Unsupported argument; An argument named \"values\" is not expected here.",
|
|
},
|
|
"invalid_module_override_target": {
|
|
"invalid_module_override_target.tftest.hcl:3,3-31: Invalid override target; You can only target modules from override_module blocks, not aws_instance.target.",
|
|
"invalid_module_override_target.tftest.hcl:8,3-36: Invalid override target; You can only target modules from override_module blocks, not data.aws_instance.target.",
|
|
},
|
|
"invalid_resource_override": {
|
|
"invalid_resource_override.tftest.hcl:6,1-18: Missing target attribute; override_resource blocks must specify a target address.",
|
|
},
|
|
"invalid_resource_override_target": {
|
|
"invalid_resource_override_target.tftest.hcl:3,3-36: Invalid override target; You can only target resources from override_resource blocks, not data.aws_instance.target.",
|
|
"invalid_resource_override_target.tftest.hcl:8,3-24: Invalid override target; You can only target resources from override_resource blocks, not module.child.",
|
|
},
|
|
}
|
|
|
|
for name, expected := range tcs {
|
|
t.Run(name, func(t *testing.T) {
|
|
src, err := os.ReadFile(fmt.Sprintf("testdata/invalid-test-files/%s.tftest.hcl", name))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
parser := testParser(map[string]string{
|
|
fmt.Sprintf("%s.tftest.hcl", name): string(src),
|
|
})
|
|
|
|
_, actual := parser.LoadTestFile(fmt.Sprintf("%s.tftest.hcl", name))
|
|
assertExactDiagnostics(t, actual, expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParserLoadConfigDirWithTests_ReturnsWarnings(t *testing.T) {
|
|
parser := NewParser(nil)
|
|
mod, diags := parser.LoadConfigDirWithTests("testdata/valid-modules/with-tests", "not_real")
|
|
if len(diags) != 1 {
|
|
t.Errorf("expected exactly 1 diagnostic, but found %d", len(diags))
|
|
} else {
|
|
if diags[0].Severity != hcl.DiagWarning {
|
|
t.Errorf("expected warning severity but found %d", diags[0].Severity)
|
|
}
|
|
|
|
if diags[0].Summary != "Test directory does not exist" {
|
|
t.Errorf("expected summary to be \"Test directory does not exist\" but was \"%s\"", diags[0].Summary)
|
|
}
|
|
|
|
if diags[0].Detail != "Requested test directory testdata/valid-modules/with-tests/not_real does not exist." {
|
|
t.Errorf("expected detail to be \"Requested test directory testdata/valid-modules/with-tests/not_real does not exist.\" but was \"%s\"", diags[0].Detail)
|
|
}
|
|
}
|
|
|
|
// Despite the warning, should still have loaded the tests in the
|
|
// configuration directory.
|
|
if len(mod.Tests) != 2 {
|
|
t.Errorf("incorrect number of test files found: %d", len(mod.Tests))
|
|
}
|
|
}
|
|
|
|
// TestParseLoadConfigDirFailure is a simple test that just verifies that
|
|
// a number of test configuration directories (in testdata/invalid-modules)
|
|
// produce diagnostics when parsed.
|
|
//
|
|
// It also re-tests the individual files in testdata/invalid-files as if
|
|
// they were single-file modules, to ensure that their errors are still
|
|
// detected when loading as part of a module.
|
|
//
|
|
// This test does not verify that reading these modules produces any
|
|
// diagnostics in particular. More detailed assertions may be made on some subset
|
|
// of these configuration files in other tests.
|
|
func TestParserLoadConfigDirFailure(t *testing.T) {
|
|
dirs, err := ioutil.ReadDir("testdata/invalid-modules")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, info := range dirs {
|
|
name := info.Name()
|
|
t.Run(name, func(t *testing.T) {
|
|
parser := NewParser(nil)
|
|
path := filepath.Join("testdata/invalid-modules", name)
|
|
|
|
_, diags := parser.LoadConfigDirWithTests(path, "tests")
|
|
if !diags.HasErrors() {
|
|
t.Errorf("no errors; want at least one")
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// The individual files in testdata/valid-files should also work
|
|
// when loaded as modules.
|
|
files, err := ioutil.ReadDir("testdata/invalid-files")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, info := range files {
|
|
name := info.Name()
|
|
t.Run(fmt.Sprintf("%s as module", name), func(t *testing.T) {
|
|
src, err := ioutil.ReadFile(filepath.Join("testdata/invalid-files", name))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
parser := testParser(map[string]string{
|
|
"mod/" + name: string(src),
|
|
})
|
|
|
|
_, diags := parser.LoadConfigDir("mod")
|
|
if !diags.HasErrors() {
|
|
t.Errorf("no errors; want at least one")
|
|
for _, diag := range diags {
|
|
t.Logf("- %s", diag)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestIsEmptyDir(t *testing.T) {
|
|
val, err := IsEmptyDir(filepath.Join("testdata", "valid-files"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if val {
|
|
t.Fatal("should not be empty")
|
|
}
|
|
}
|
|
|
|
func TestIsEmptyDir_noExist(t *testing.T) {
|
|
val, err := IsEmptyDir(filepath.Join("testdata", "nopenopenope"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if !val {
|
|
t.Fatal("should be empty")
|
|
}
|
|
}
|
|
|
|
func TestIsEmptyDir_noConfigs(t *testing.T) {
|
|
val, err := IsEmptyDir(filepath.Join("testdata", "dir-empty"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if !val {
|
|
t.Fatal("should be empty")
|
|
}
|
|
}
|