test framework: expand variables available to test assertions (#33611)

This commit is contained in:
Liam Cervante 2023-08-01 09:59:29 +02:00 committed by GitHub
parent bf6f32c19a
commit 3bea1171af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 110 additions and 7 deletions

View File

@ -9,6 +9,8 @@ import (
"strings"
"time"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/command/arguments"
@ -507,7 +509,9 @@ func (runner *TestRunner) ExecuteTestRun(mgr *TestStateManager, run *moduletest.
run.Diagnostics = run.Diagnostics.Append(diags)
}
variables, diags := buildInputVariablesForAssertions(run, file, globals)
variables, reset, diags := prepareInputVariablesForAssertions(config, run, file, globals)
defer reset()
run.Diagnostics = run.Diagnostics.Append(diags)
if diags.HasErrors() {
run.Status = moduletest.Error
@ -943,7 +947,7 @@ func (manager *TestStateManager) cleanupStates(file *moduletest.File, globals ma
// buildInputVariablesForTest creates a terraform.InputValues mapping for
// variable values that are relevant to the config being tested.
//
// Crucially, it differs from buildInputVariablesForAssertions in that it only
// Crucially, it differs from prepareInputVariablesForAssertions in that it only
// includes variables that are reference by the config and not everything that
// is defined within the test run block and test file.
func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, config *configs.Config, globals map[string]backend.UnparsedVariableValue) (terraform.InputValues, tfdiags.Diagnostics) {
@ -986,15 +990,19 @@ func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, conf
return backend.ParseVariableValues(variables, config.Module.Variables)
}
// buildInputVariablesForAssertions creates a terraform.InputValues mapping that
// contains all the variables defined for a given run and file, alongside any
// unset variables that have defaults within the provided config.
// prepareInputVariablesForAssertions creates a terraform.InputValues mapping
// that contains all the variables defined for a given run and file, alongside
// any unset variables that have defaults within the provided config.
//
// Crucially, it differs from buildInputVariablesForTest in that the returned
// input values include all variables available even if they are not defined
// within the config. This allows the assertions to refer to variables defined
// solely within the test file, and not only those within the configuration.
func buildInputVariablesForAssertions(run *moduletest.Run, file *moduletest.File, globals map[string]backend.UnparsedVariableValue) (terraform.InputValues, tfdiags.Diagnostics) {
//
// In addition, it modifies the provided config so that any variables that are
// available are also defined in the config. It returns a function that resets
// the config which must be called so the config can be reused going forward.
func prepareInputVariablesForAssertions(config *configs.Config, run *moduletest.Run, file *moduletest.File, globals map[string]backend.UnparsedVariableValue) (terraform.InputValues, func(), tfdiags.Diagnostics) {
variables := make(map[string]backend.UnparsedVariableValue)
if run != nil {
@ -1030,6 +1038,9 @@ func buildInputVariablesForAssertions(run *moduletest.Run, file *moduletest.File
variables[name] = variable
}
// We've gathered all the values we have, let's convert them into
// terraform.InputValues so they can be passed into the Terraform graph.
inputs := make(terraform.InputValues, len(variables))
var diags tfdiags.Diagnostics
for name, variable := range variables {
@ -1037,5 +1048,59 @@ func buildInputVariablesForAssertions(run *moduletest.Run, file *moduletest.File
diags = diags.Append(valueDiags)
inputs[name] = value
}
return inputs, diags
// Next, we're going to apply any default values from the configuration.
// We do this after the conversion into terraform.InputValues, as the
// defaults have already been converted into cty.Value objects.
for name, variable := range config.Module.Variables {
if _, exists := variables[name]; exists {
// Then we don't want to apply the default for this variable as we
// already have a value.
continue
}
if variable.Default != cty.NilVal {
inputs[name] = &terraform.InputValue{
Value: variable.Default,
SourceType: terraform.ValueFromConfig,
SourceRange: tfdiags.SourceRangeFromHCL(variable.DeclRange),
}
}
}
// Finally, we're going to do a some modifications to the config.
// If we have got variable values from the test file we need to make sure
// they have an equivalent entry in the configuration. We're going to do
// that dynamically here.
// First, take a backup of the existing configuration so we can easily
// restore it later.
currentVars := make(map[string]*configs.Variable)
for name, variable := range config.Module.Variables {
currentVars[name] = variable
}
// Next, let's go through our entire inputs and add any that aren't already
// defined into the config.
for name, value := range inputs {
if _, exists := config.Module.Variables[name]; exists {
continue
}
config.Module.Variables[name] = &configs.Variable{
Name: name,
Type: value.Value.Type(),
ConstraintType: value.Value.Type(),
DeclRange: value.SourceRange.ToHCL(),
}
}
// We return our input values, a function that will reset the variables
// within the config so it can be used again, and any diagnostics reporting
// variables that we couldn't parse.
return inputs, func() {
config.Module.Variables = currentVars
}, diags
}

View File

@ -124,6 +124,14 @@ func TestTest(t *testing.T) {
expected: "1 passed, 0 failed",
code: 0,
},
"default_variables": {
expected: "1 passed, 0 failed.",
code: 0,
},
"undefined_variables": {
expected: "1 passed, 0 failed.",
code: 0,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {

View File

@ -0,0 +1,5 @@
variable "input" {
type = string
default = "Hello, world!"
}

View File

@ -0,0 +1,7 @@
run "applies_defaults" {
assert {
condition = var.input == "Hello, world!"
error_message = "should have applied default value"
}
}

View File

@ -0,0 +1,5 @@
variable "input" {
type = string
default = "Hello, world!"
}

View File

@ -0,0 +1,13 @@
variables {
# config_free isn't defined in the config, but we'll
# still let users refer to it within the assertions.
config_free = "Hello, world!"
}
run "applies_defaults" {
assert {
condition = var.input == var.config_free
error_message = "should have applied default value"
}
}