diff --git a/internal/command/meta_vars.go b/internal/command/meta_vars.go index 909c599a72..0d7bc70f41 100644 --- a/internal/command/meta_vars.go +++ b/internal/command/meta_vars.go @@ -223,7 +223,7 @@ func (m *Meta) addVarsFromFile(filename string, sourceType tofu.ValueSourceType, return diags } -// unparsedVariableValueLiteral is a backend.UnparsedVariableValue +// unparsedVariableValueExpression is a backend.UnparsedVariableValue // implementation that was actually already parsed (!). This is // intended to deal with expressions inside "tfvars" files. type unparsedVariableValueExpression struct { diff --git a/internal/command/test.go b/internal/command/test.go index 0d20d2bfde..c242fdb08d 100644 --- a/internal/command/test.go +++ b/internal/command/test.go @@ -501,7 +501,7 @@ func (runner *TestFileRunner) ExecuteTestRun(run *moduletest.Run, file *modulete return state, false } - variables, resetVariables, variableDiags := prepareInputVariablesForAssertions(config, run, file, runner.Suite.GlobalVariables) + variables, resetVariables, variableDiags := runner.prepareInputVariablesForAssertions(config, run, file, runner.Suite.GlobalVariables) defer resetVariables() run.Diagnostics = run.Diagnostics.Append(variableDiags) @@ -574,7 +574,7 @@ func (runner *TestFileRunner) ExecuteTestRun(run *moduletest.Run, file *modulete return updated, true } - variables, resetVariables, variableDiags := prepareInputVariablesForAssertions(config, run, file, runner.Suite.GlobalVariables) + variables, resetVariables, variableDiags := runner.prepareInputVariablesForAssertions(config, run, file, runner.Suite.GlobalVariables) defer resetVariables() run.Diagnostics = run.Diagnostics.Append(variableDiags) @@ -1023,6 +1023,26 @@ func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, conf return backend.ParseVariableValues(variables, config.Module.Variables) } +type testVariableValueExpression struct { + expr hcl.Expression + sourceType tofu.ValueSourceType + ctx *hcl.EvalContext +} + +func (v testVariableValueExpression) ParseVariableValue(mode configs.VariableParsingMode) (*tofu.InputValue, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + val, hclDiags := v.expr.Value(v.ctx) + diags = diags.Append(hclDiags) + + rng := tfdiags.SourceRangeFromHCL(v.expr.Range()) + + return &tofu.InputValue{ + Value: val, + SourceType: v.sourceType, + SourceRange: rng, + }, diags +} + // prepareInputVariablesForAssertions creates a tofu.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. @@ -1032,17 +1052,36 @@ func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, conf // within the config. This allows the assertions to refer to variables defined // solely within the test file, and not only those within the configuration. // +// It also allows references to previously run test module's outputs as variable +// expressions. This relies upon the evaluation order and will not sort the test cases +// to run in the dependent order. +// // 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) (tofu.InputValues, func(), tfdiags.Diagnostics) { +func (runner *TestFileRunner) prepareInputVariablesForAssertions(config *configs.Config, run *moduletest.Run, file *moduletest.File, globals map[string]backend.UnparsedVariableValue) (tofu.InputValues, func(), tfdiags.Diagnostics) { + runCtx := make(map[string]cty.Value) + for _, state := range runner.States { + if state.Run == nil { + continue + } + outputs := make(map[string]cty.Value) + mod := state.State.Modules[""] // Empty string is what is used by the module in the test runner + for outName, out := range mod.OutputValues { + outputs[outName] = out.Value + } + runCtx[state.Run.Name] = cty.ObjectVal(outputs) + } + ctx := &hcl.EvalContext{Variables: map[string]cty.Value{"run": cty.ObjectVal(runCtx)}} + variables := make(map[string]backend.UnparsedVariableValue) if run != nil { for name, expr := range run.Config.Variables { - variables[name] = unparsedVariableValueExpression{ + variables[name] = testVariableValueExpression{ expr: expr, sourceType: tofu.ValueFromConfig, + ctx: ctx, } } } @@ -1054,9 +1093,10 @@ func prepareInputVariablesForAssertions(config *configs.Config, run *moduletest. // that value to take precedence. continue } - variables[name] = unparsedVariableValueExpression{ + variables[name] = testVariableValueExpression{ expr: expr, sourceType: tofu.ValueFromConfig, + ctx: ctx, } } } diff --git a/internal/command/test_test.go b/internal/command/test_test.go index 152065cfe2..decfed2b14 100644 --- a/internal/command/test_test.go +++ b/internal/command/test_test.go @@ -798,6 +798,10 @@ func TestTest_Modules(t *testing.T) { expected: "main.tftest.hcl... pass\n run \"first\"... pass\n run \"second\"... pass\n\nSuccess! 2 passed, 0 failed.\n", code: 0, }, + "variables_reference": { + expected: "main.tftest.hcl... pass\n run \"setup\"... pass\n run \"test\"... pass\n\nSuccess! 2 passed, 0 failed.\n", + code: 0, + }, } for name, tc := range tcs { diff --git a/internal/command/testdata/test/variables_reference/main.tf b/internal/command/testdata/test/variables_reference/main.tf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/command/testdata/test/variables_reference/main.tftest.hcl b/internal/command/testdata/test/variables_reference/main.tftest.hcl new file mode 100644 index 0000000000..c72e27259e --- /dev/null +++ b/internal/command/testdata/test/variables_reference/main.tftest.hcl @@ -0,0 +1,15 @@ +variables { + content = "some value" +} + +run "setup" { + module { + source = "./setup" + } +} + +run "test" { + variables { + file_name = run.setup.file_name + } +} diff --git a/internal/command/testdata/test/variables_reference/setup/main.tf b/internal/command/testdata/test/variables_reference/setup/main.tf new file mode 100644 index 0000000000..0bf33f4f1f --- /dev/null +++ b/internal/command/testdata/test/variables_reference/setup/main.tf @@ -0,0 +1,7 @@ +variable "content" { + type = string +} + +output "file_name" { + value = "output_value" +}