Skip to content

Commit

Permalink
test: allow using global variables in suite-level variable definitions
Browse files Browse the repository at this point in the history
Closes #34534
  • Loading branch information
DanielMSchmidt committed Feb 19, 2024
1 parent 0d1ce55 commit b76481b
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 8 deletions.
75 changes: 68 additions & 7 deletions internal/backend/local/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,10 @@ type TestFileRunner struct {
// variables within run blocks.
PriorOutputs map[addrs.Run]cty.Value

// globalVariables are globally defined variables, e.g. through tfvars or CLI flags
globalVariables map[string]backend.UnparsedVariableValue
// fileVariables are defined in the variables section of a test file
fileVariables map[string]hcl.Expression
}

// TestFileState is a helper struct that just maps a run block to the state that
Expand Down Expand Up @@ -1008,11 +1011,16 @@ func (runner *TestFileRunner) GetVariables(config *configs.Config, run *modulete
}

// Finally, we'll check to see which variables are actually defined within
// the configuration.
for name := range config.Module.Variables {
relevantVariables[name] = true
}

// We also include all global variables since they might be used in the
// suites variables section
for name := range runner.globalVariables {
relevantVariables[name] = true
}

// Now we know which variables are actually needed by this run block.

// We're going to run over all the sets of variables we have access to:
Expand All @@ -1033,6 +1041,7 @@ func (runner *TestFileRunner) GetVariables(config *configs.Config, run *modulete
parsingMode := configs.VariableParseHCL

cfg, exists := config.Module.Variables[name]

if exists {
// Unless we have some configuration that can actually tell us
// what parsing mode to use.
Expand All @@ -1057,29 +1066,80 @@ func (runner *TestFileRunner) GetVariables(config *configs.Config, run *modulete
values[name] = value
}

// Second, we'll check the run level variables.
// Second, we'll check the file level variables
var exprs []hcl.Expression
for _, expr := range runner.fileVariables {
exprs = append(exprs, expr)
}

// Preformat the variables we've processed already - these will be made
// available to the eval context.
variables := make(map[string]cty.Value)
for name, value := range values {
variables[name] = value.Value
}

ctx, ctxDiags := hcltest.EvalContext(hcltest.TargetRunBlock, exprs, variables, runner.PriorOutputs)
diags = diags.Append(ctxDiags)

var failedContext bool
if ctxDiags.HasErrors() {
// If we couldn't build the context, we won't actually process these
// variables. Instead, we'll fill them with an empty value but still
// make a note that the user did provide them.
failedContext = true
}

for name, expr := range runner.fileVariables {
if !relevantVariables[name] {
// We'll add a warning for this. Since we're right in the run block
// users shouldn't be defining variables that are not relevant.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Value for undeclared variable",
Detail: fmt.Sprintf("The module under test does not declare a variable named %q, but it is declared in run block %q.", name, run.Name),
Subject: expr.Range().Ptr(),
})
continue
}

value := cty.NilVal
if !failedContext {
var valueDiags hcl.Diagnostics
value, valueDiags = expr.Value(ctx)
diags = diags.Append(valueDiags)
}

values[name] = &terraform.InputValue{
Value: value,
SourceType: terraform.ValueFromConfig,
SourceRange: tfdiags.SourceRangeFromHCL(expr.Range()),
}
}

// Third, we'll check the run level variables.

// This is a bit more complicated, as the run level variables can reference
// previously defined variables.

// Preload the available expressions, we're going to validate them when we
// build the context.
var exprs []hcl.Expression
exprs = []hcl.Expression{}
for _, expr := range run.Config.Variables {
exprs = append(exprs, expr)
}

// Preformat the variables we've processed already - these will be made
// available to the eval context.
variables := make(map[string]cty.Value)
variables = make(map[string]cty.Value)
for name, value := range values {
variables[name] = value.Value
}

ctx, ctxDiags := hcltest.EvalContext(hcltest.TargetRunBlock, exprs, variables, runner.PriorOutputs)
ctx, ctxDiags = hcltest.EvalContext(hcltest.TargetRunBlock, exprs, variables, runner.PriorOutputs)
diags = diags.Append(ctxDiags)

var failedContext bool
failedContext = false
if ctxDiags.HasErrors() {
// If we couldn't build the context, we won't actually process these
// variables. Instead, we'll fill them with an empty value but still
Expand Down Expand Up @@ -1260,8 +1320,9 @@ func (runner *TestFileRunner) initVariables(file *moduletest.File) {
runner.globalVariables[name] = value
}
}
runner.fileVariables = make(map[string]hcl.Expression)
for name, expr := range file.Config.Variables {
runner.globalVariables[name] = unparsedTestVariableValue{expr}
runner.fileVariables[name] = expr
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/moduletest/hcl/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func EvalContext(target EvalContextTarget, expressions []hcl.Expression, availab
if _, exists := availableVariables[addr.Name]; !exists {
// This variable reference doesn't exist.

detail := fmt.Sprintf("The input variable %q is not available to the current run block. You can only reference variables defined at the file or global levels when populating the variables block within a run block.", addr.Name)
detail := fmt.Sprintf("The input variable %q is not available to the current context. Within the variables block of a run block you can only reference variables defined at the file or global levels; within the variables block of a suite you can only reference variables defined at the global levels.", addr.Name)
if availableRunOutputs == nil {
detail = fmt.Sprintf("The input variable %q is not available to the current provider configuration. You can only reference variables defined at the file or global levels within provider configurations.", addr.Name)
}
Expand Down

0 comments on commit b76481b

Please sign in to comment.