mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Allow referencing output from test run in local variables block (tofu test) (#1254)
Signed-off-by: siddharthasonker95 <158144589+siddharthasonker95@users.noreply.github.com>
This commit is contained in:
parent
e21a14fb0c
commit
accfe1c412
@ -18,6 +18,7 @@ ENHANCEMENTS:
|
||||
* Added "cidrcontains" function. ([$366](https://github.com/opentofu/opentofu/issues/366))
|
||||
* Allow test run blocks to reference previous run block's module outputs ([#1129](https://github.com/opentofu/opentofu/pull/1129))
|
||||
* Support the XDG Base Directory Specification ([#1200](https://github.com/opentofu/opentofu/pull/1200))
|
||||
* Allow referencing the output from a test run in the local variables block of another run (tofu test). ([#1254](https://github.com/opentofu/opentofu/pull/1254))
|
||||
|
||||
BUG FIXES:
|
||||
* `tofu test` resources cleanup at the end of tests changed to use simple reverse run block order. ([#1043](https://github.com/opentofu/opentofu/pull/1043))
|
||||
|
@ -662,7 +662,7 @@ func (runner *TestFileRunner) destroy(config *configs.Config, state *states.Stat
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config, runner.Suite.GlobalVariables)
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config, runner.Suite.GlobalVariables, runner.States)
|
||||
diags = diags.Append(variableDiags)
|
||||
|
||||
if diags.HasErrors() {
|
||||
@ -724,7 +724,7 @@ func (runner *TestFileRunner) plan(config *configs.Config, state *states.State,
|
||||
references, referenceDiags := run.GetReferences()
|
||||
diags = diags.Append(referenceDiags)
|
||||
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config, runner.Suite.GlobalVariables)
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config, runner.Suite.GlobalVariables, runner.States)
|
||||
diags = diags.Append(variableDiags)
|
||||
|
||||
if diags.HasErrors() {
|
||||
@ -988,15 +988,17 @@ func (runner *TestFileRunner) Cleanup(file *moduletest.File) {
|
||||
// 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) (tofu.InputValues, tfdiags.Diagnostics) {
|
||||
func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, config *configs.Config, globals map[string]backend.UnparsedVariableValue, states map[string]*TestFileState) (tofu.InputValues, tfdiags.Diagnostics) {
|
||||
variables := make(map[string]backend.UnparsedVariableValue)
|
||||
evalCtx := getEvalContextFromStates(states)
|
||||
for name := range config.Module.Variables {
|
||||
if run != nil {
|
||||
if expr, exists := run.Config.Variables[name]; exists {
|
||||
// Local variables take precedence.
|
||||
variables[name] = unparsedVariableValueExpression{
|
||||
variables[name] = testVariableValueExpression{
|
||||
expr: expr,
|
||||
sourceType: tofu.ValueFromConfig,
|
||||
ctx: evalCtx,
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -1028,6 +1030,34 @@ func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, conf
|
||||
return backend.ParseVariableValues(variables, config.Module.Variables)
|
||||
}
|
||||
|
||||
// getEvalContextFromStates constructs an hcl.EvalContext based on the provided map
|
||||
// of TestFileState instances. It extracts the relevant information from the
|
||||
// states to create a context suitable for HCL evaluation, including the output
|
||||
// values of modules.
|
||||
//
|
||||
// Parameters:
|
||||
// - states: A map of TestFileState instances containing the state information.
|
||||
//
|
||||
// Returns:
|
||||
// - *hcl.EvalContext: The constructed HCL evaluation context.
|
||||
func getEvalContextFromStates(states map[string]*TestFileState) *hcl.EvalContext {
|
||||
runCtx := make(map[string]cty.Value)
|
||||
for _, state := range 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)}}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
type testVariableValueExpression struct {
|
||||
expr hcl.Expression
|
||||
sourceType tofu.ValueSourceType
|
||||
@ -1065,19 +1095,7 @@ func (v testVariableValueExpression) ParseVariableValue(mode configs.VariablePar
|
||||
// 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 (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)}}
|
||||
ctx := getEvalContextFromStates(runner.States)
|
||||
|
||||
variables := make(map[string]backend.UnparsedVariableValue)
|
||||
|
||||
|
@ -1118,3 +1118,73 @@ Condition expression could not be evaluated at this time.
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTest_LocalVariables(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(path.Join("test", "pass_with_local_variable")), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
provider := testing_command.NewProvider(nil)
|
||||
|
||||
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: metaOverridesForProvider(provider.Provider),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
Streams: streams,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
init := &InitCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
if code := init.Run(nil); code != 0 {
|
||||
t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
|
||||
}
|
||||
|
||||
c := &TestCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
code := c.Run([]string{"-verbose", "-no-color"})
|
||||
output := done(t)
|
||||
|
||||
if code != 0 {
|
||||
t.Errorf("expected status code 0 but got %d", code)
|
||||
}
|
||||
|
||||
expected := `tests/test.tftest.hcl... pass
|
||||
run "first"... pass
|
||||
|
||||
|
||||
Outputs:
|
||||
|
||||
foo = "bar"
|
||||
run "second"... pass
|
||||
|
||||
No changes. Your infrastructure matches the configuration.
|
||||
|
||||
OpenTofu has compared your real infrastructure against your configuration and
|
||||
found no differences, so no changes are needed.
|
||||
|
||||
Success! 2 passed, 0 failed.
|
||||
`
|
||||
|
||||
actual := output.All()
|
||||
|
||||
if diff := cmp.Diff(actual, expected); len(diff) > 0 {
|
||||
t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
|
||||
}
|
||||
|
||||
if provider.ResourceCount() > 0 {
|
||||
t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
|
||||
}
|
||||
}
|
||||
|
4
internal/command/testdata/test/pass_with_local_variable/main.tf
vendored
Normal file
4
internal/command/testdata/test/pass_with_local_variable/main.tf
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
output "foo" {
|
||||
description = "Output"
|
||||
value = "bar"
|
||||
}
|
15
internal/command/testdata/test/pass_with_local_variable/tests/test.tftest.hcl
vendored
Normal file
15
internal/command/testdata/test/pass_with_local_variable/tests/test.tftest.hcl
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
run "first" {
|
||||
command = apply
|
||||
}
|
||||
|
||||
run "second" {
|
||||
command = plan
|
||||
|
||||
module {
|
||||
source = "./tests/testmodule"
|
||||
}
|
||||
|
||||
variables {
|
||||
foo = run.first.foo
|
||||
}
|
||||
}
|
3
internal/command/testdata/test/pass_with_local_variable/tests/testmodule/main.tf
vendored
Normal file
3
internal/command/testdata/test/pass_with_local_variable/tests/testmodule/main.tf
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
variable "foo" {
|
||||
type = string
|
||||
}
|
Loading…
Reference in New Issue
Block a user