Merge branch 'main' into stacks-variable-validation-blocks

pull/38240/head
sahar-azizighannad 1 month ago committed by GitHub
commit cf7feaec5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -321,6 +321,57 @@ func (m *Meta) setupTestExecution(mode moduletest.CommandMode, command string, r
return
}
loader, err := m.initConfigLoader()
if err != nil {
diags = diags.Append(err)
view.Diagnostics(nil, nil, diags)
return
}
registerFileSource := func(filename string, src []byte) {
loader.Parser().ForceFileSource(filename, src)
}
// Collect variables for "terraform test"
preparation.TestVariables, moreDiags = arguments.CollectValuesForTests(preparation.Args.TestDirectory, registerFileSource)
diags = diags.Append(moreDiags)
// Collect variable values and add them to the operation request.
// We must collect these before loading config, because
// loadConfigWithTests needs const variable values available in
// m.VariableValues to resolve dynamic module sources.
var varDiags tfdiags.Diagnostics
preparation.Variables, varDiags = preparation.Args.Vars.CollectValues(registerFileSource)
diags = diags.Append(varDiags)
if diags.HasErrors() {
view.Diagnostics(nil, nil, diags)
return
}
// Only populate m.VariableValues with variables that are declared
// as const in the root module. loadConfigWithTests uses
// m.VariableValues to resolve dynamic module sources via
// ParseConstVariableValues, which would otherwise error on
// undeclared variables passed via -var that are intended for
// test runs rather than the root module.
//
// We do an early load of just the root module to discover which
// variables are const. We discard non-error diagnostics from this
// early load since loadConfigWithTests will re-parse and report them.
earlyMod, earlyDiags := m.loadSingleModuleWithTests(".", preparation.Args.TestDirectory)
if earlyDiags.HasErrors() {
diags = diags.Append(earlyDiags)
view.Diagnostics(nil, nil, diags)
return
}
constVars := make(map[string]arguments.UnparsedVariableValue)
for name, val := range preparation.Variables {
if decl, exists := earlyMod.Variables[name]; exists && decl.Const {
constVars[name] = val
}
}
m.VariableValues = constVars
preparation.Config, moreDiags = m.loadConfigWithTests(".", preparation.Args.TestDirectory)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
@ -377,30 +428,6 @@ func (m *Meta) setupTestExecution(mode moduletest.CommandMode, command string, r
return
}
loader, err := m.initConfigLoader()
if err != nil {
diags = diags.Append(err)
view.Diagnostics(nil, nil, diags)
return
}
registerFileSource := func(filename string, src []byte) {
loader.Parser().ForceFileSource(filename, src)
}
// Collect variables for "terraform test"
preparation.TestVariables, moreDiags = arguments.CollectValuesForTests(preparation.Args.TestDirectory, registerFileSource)
diags = diags.Append(moreDiags)
// Collect variable value and add them to the operation request
var varDiags tfdiags.Diagnostics
preparation.Variables, varDiags = preparation.Args.Vars.CollectValues(registerFileSource)
diags = diags.Append(varDiags)
if diags.HasErrors() {
view.Diagnostics(nil, nil, diags)
return
}
opts, err := m.contextOpts()
if err != nil {
diags = diags.Append(err)

@ -433,6 +433,25 @@ func TestTest_Runs(t *testing.T) {
expectedOut: []string{"2 passed, 0 failed."},
code: 0,
},
"dynamic_source_with_default": {
expectedOut: []string{"1 passed, 0 failed."},
code: 0,
},
"dynamic_source_missing_var": {
initCode: 1,
expectedErr: []string{"No value for required variable"},
code: 1,
},
"dynamic_source_nonexistent_module": {
initCode: 1,
expectedErr: []string{"Unreadable module directory"},
code: 1,
},
"dynamic_source_non_const_var": {
initCode: 1,
expectedErr: []string{"Invalid module source"},
code: 1,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
@ -2439,6 +2458,267 @@ func TestTest_ModuleDependencies(t *testing.T) {
}
}
func TestTest_DynamicSourceWithVarFlag(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "dynamic_source_with_var_flag")), td)
t.Chdir(td)
store := &testing_command.ResourceStore{
Data: make(map[string]cty.Value),
}
providerSource, close := newMockProviderSource(t, map[string][]string{
"test": {"1.0.0"},
})
defer close()
streams, done := terminal.StreamsForTesting(t)
view := views.NewView(streams)
ui := new(cli.MockUi)
meta := Meta{
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): func() (providers.Interface, error) {
return testing_command.NewProvider(store).Provider, nil
},
},
},
Ui: ui,
View: view,
Streams: streams,
ProviderSource: providerSource,
}
init := &InitCommand{Meta: meta}
if code := init.Run([]string{"-var", "module_name=example"}); code != 0 {
output := done(t)
t.Fatalf("expected status code 0 but got %d: %s", code, output.All())
}
// Reset the streams for the next command.
streams, done = terminal.StreamsForTesting(t)
meta.Streams = streams
meta.View = views.NewView(streams)
c := &TestCommand{Meta: meta}
code := c.Run([]string{"-var", "module_name=example", "-no-color"})
output := done(t)
if code != 0 {
t.Errorf("expected status code 0 but got %d:\n\n%s", code, output.All())
}
if !strings.Contains(output.Stdout(), "1 passed, 0 failed.") {
t.Errorf("output didn't contain expected string:\n\n%s", output.Stdout())
}
if len(store.Data) != 0 {
t.Errorf("should have deleted all resources on completion but left %d", len(store.Data))
}
}
func TestTest_DynamicSourceWithLocalValue(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "dynamic_source_with_local_value")), td)
t.Chdir(td)
store := &testing_command.ResourceStore{
Data: make(map[string]cty.Value),
}
providerSource, close := newMockProviderSource(t, map[string][]string{
"test": {"1.0.0"},
})
defer close()
streams, done := terminal.StreamsForTesting(t)
view := views.NewView(streams)
ui := new(cli.MockUi)
meta := Meta{
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): func() (providers.Interface, error) {
return testing_command.NewProvider(store).Provider, nil
},
},
},
Ui: ui,
View: view,
Streams: streams,
ProviderSource: providerSource,
}
init := &InitCommand{Meta: meta}
if code := init.Run([]string{"-var", "module_name=example"}); code != 0 {
output := done(t)
t.Fatalf("expected status code 0 but got %d: %s", code, output.All())
}
// Reset the streams for the next command.
streams, done = terminal.StreamsForTesting(t)
meta.Streams = streams
meta.View = views.NewView(streams)
c := &TestCommand{Meta: meta}
code := c.Run([]string{"-var", "module_name=example", "-no-color"})
output := done(t)
if code != 0 {
t.Errorf("expected status code 0 but got %d:\n\n%s", code, output.All())
}
if !strings.Contains(output.Stdout(), "1 passed, 0 failed.") {
t.Errorf("output didn't contain expected string:\n\n%s", output.Stdout())
}
if len(store.Data) != 0 {
t.Errorf("should have deleted all resources on completion but left %d", len(store.Data))
}
}
func TestTest_DynamicSourceNested(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "dynamic_source_nested")), td)
t.Chdir(td)
store := &testing_command.ResourceStore{
Data: make(map[string]cty.Value),
}
providerSource, close := newMockProviderSource(t, map[string][]string{
"test": {"1.0.0"},
})
defer close()
streams, done := terminal.StreamsForTesting(t)
view := views.NewView(streams)
ui := new(cli.MockUi)
meta := Meta{
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): func() (providers.Interface, error) {
return testing_command.NewProvider(store).Provider, nil
},
},
},
Ui: ui,
View: view,
Streams: streams,
ProviderSource: providerSource,
}
init := &InitCommand{Meta: meta}
if code := init.Run([]string{"-var", "child_name=child"}); code != 0 {
output := done(t)
t.Fatalf("expected status code 0 but got %d: %s", code, output.All())
}
// Reset the streams for the next command.
streams, done = terminal.StreamsForTesting(t)
meta.Streams = streams
meta.View = views.NewView(streams)
c := &TestCommand{Meta: meta}
code := c.Run([]string{"-var", "child_name=child", "-no-color"})
output := done(t)
if code != 0 {
t.Errorf("expected status code 0 but got %d:\n\n%s", code, output.All())
}
if !strings.Contains(output.Stdout(), "1 passed, 0 failed.") {
t.Errorf("output didn't contain expected string:\n\n%s", output.Stdout())
}
if len(store.Data) != 0 {
t.Errorf("should have deleted all resources on completion but left %d", len(store.Data))
}
}
func TestTest_DynamicSourceWithSetupModule(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "dynamic_source_with_setup_module")), td)
t.Chdir(td)
// Our two providers will share a common set of values to make things
// easier.
store := &testing_command.ResourceStore{
Data: make(map[string]cty.Value),
}
// We set it up so the setup provider will write into the data sources
// available to the test provider.
test := testing_command.NewProvider(store)
setup := testing_command.NewProvider(store)
test.SetDataPrefix("data")
test.SetResourcePrefix("resource")
// Let's make the setup provider write into the data for test provider.
setup.SetResourcePrefix("data")
providerSource, close := newMockProviderSource(t, map[string][]string{
"test": {"1.0.0"},
"setup": {"1.0.0"},
})
defer close()
streams, done := terminal.StreamsForTesting(t)
view := views.NewView(streams)
ui := new(cli.MockUi)
meta := Meta{
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): providers.FactoryFixed(test.Provider),
addrs.NewDefaultProvider("setup"): providers.FactoryFixed(setup.Provider),
},
},
Ui: ui,
View: view,
Streams: streams,
ProviderSource: providerSource,
}
init := &InitCommand{Meta: meta}
if code := init.Run(nil); code != 0 {
output := done(t)
t.Fatalf("expected status code 0 but got %d: %s", code, output.All())
}
// Reset the streams for the next command.
streams, done = terminal.StreamsForTesting(t)
meta.Streams = streams
meta.View = views.NewView(streams)
c := &TestCommand{Meta: meta}
code := c.Run([]string{"-no-color"})
output := done(t)
printedOutput := false
if code != 0 {
printedOutput = true
t.Errorf("expected status code 0 but got %d:\n\n%s", code, output.All())
}
if !strings.Contains(output.Stdout(), "2 passed, 0 failed.") {
if !printedOutput {
t.Errorf("output didn't contain expected string:\n\n%s", output.All())
} else {
t.Errorf("output didn't contain expected string: %q", output.Stdout())
}
}
if test.ResourceCount() > 0 {
t.Errorf("should have deleted all resources on completion but left %s", test.ResourceString())
}
if setup.ResourceCount() > 0 {
t.Errorf("should have deleted all resources on completion but left %s", setup.ResourceString())
}
}
func TestTest_CatchesErrorsBeforeDestroy(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "invalid_default_state")), td)

@ -0,0 +1,8 @@
variable "module_name" {
type = string
const = true
}
module "mod" {
source = "./modules/${var.module_name}"
}

@ -0,0 +1,6 @@
run "should_not_reach" {
assert {
condition = true
error_message = "should not reach this point"
}
}

@ -0,0 +1,3 @@
resource "test_resource" "foo" {
value = "bar"
}

@ -0,0 +1,9 @@
variable "child_name" {
type = string
const = true
}
module "parent" {
source = "./modules/parent"
child_name = var.child_name
}

@ -0,0 +1,6 @@
run "validate_nested_dynamic_module" {
assert {
condition = module.parent.value == "from_child"
error_message = "expected from_child from nested dynamically sourced module"
}
}

@ -0,0 +1,7 @@
resource "test_resource" "foo" {
value = "from_child"
}
output "value" {
value = test_resource.foo.value
}

@ -0,0 +1,12 @@
variable "child_name" {
type = string
const = true
}
module "child" {
source = "../${var.child_name}"
}
output "value" {
value = module.child.value
}

@ -0,0 +1,7 @@
variable "module_name" {
type = string
}
module "mod" {
source = "./modules/${var.module_name}"
}

@ -0,0 +1,6 @@
run "should_not_reach" {
assert {
condition = true
error_message = "should not reach this point"
}
}

@ -0,0 +1,3 @@
resource "test_resource" "foo" {
value = "bar"
}

@ -0,0 +1,9 @@
variable "module_name" {
type = string
const = true
default = "nonexistent"
}
module "mod" {
source = "./modules/${var.module_name}"
}

@ -0,0 +1,6 @@
run "should_not_reach" {
assert {
condition = true
error_message = "should not reach this point"
}
}

@ -0,0 +1,3 @@
resource "test_resource" "foo" {
value = "bar"
}

@ -0,0 +1,9 @@
variable "module_name" {
type = string
const = true
default = "example"
}
module "mod" {
source = "./modules/${var.module_name}"
}

@ -0,0 +1,6 @@
run "validate_dynamic_module" {
assert {
condition = module.mod.value == "bar"
error_message = "expected bar from dynamically sourced module"
}
}

@ -0,0 +1,7 @@
resource "test_resource" "foo" {
value = "bar"
}
output "value" {
value = test_resource.foo.value
}

@ -0,0 +1,12 @@
variable "module_name" {
type = string
const = true
}
locals {
module_source = "./modules/${var.module_name}"
}
module "mod" {
source = local.module_source
}

@ -0,0 +1,6 @@
run "validate_dynamic_module" {
assert {
condition = module.mod.value == "bar"
error_message = "expected bar from dynamically sourced module"
}
}

@ -0,0 +1,7 @@
resource "test_resource" "foo" {
value = "bar"
}
output "value" {
value = test_resource.foo.value
}

@ -0,0 +1,14 @@
variable "module_name" {
type = string
const = true
default = "example"
}
variable "managed_id" {
type = string
}
module "mod" {
source = "./modules/${var.module_name}"
id = var.managed_id
}

@ -0,0 +1,21 @@
variables {
managed_id = "B853C121"
}
run "setup" {
module {
source = "./setup"
}
variables {
value = "Hello, world!"
id = "B853C121"
}
}
run "test" {
assert {
condition = module.mod.value == "Hello, world!"
error_message = "expected value from setup module via dynamic source"
}
}

@ -0,0 +1,15 @@
variable "id" {
type = string
}
data "test_data_source" "managed_data" {
id = var.id
}
resource "test_resource" "foo" {
value = data.test_data_source.managed_data.value
}
output "value" {
value = test_resource.foo.value
}

@ -0,0 +1,13 @@
variable "value" {
type = string
}
variable "id" {
type = string
}
resource "test_resource" "managed" {
provider = setup
id = var.id
value = var.value
}

@ -0,0 +1,8 @@
variable "module_name" {
type = string
const = true
}
module "mod" {
source = "./modules/${var.module_name}"
}

@ -0,0 +1,6 @@
run "validate_dynamic_module" {
assert {
condition = module.mod.value == "bar"
error_message = "expected bar from dynamically sourced module"
}
}

@ -0,0 +1,7 @@
resource "test_resource" "foo" {
value = "bar"
}
output "value" {
value = test_resource.foo.value
}
Loading…
Cancel
Save