From 6065bc593f37e00cb7dc65e068e64393819c6cf8 Mon Sep 17 00:00:00 2001 From: Siddhartha Sonker <158144589+siddharthasonker95@users.noreply.github.com> Date: Mon, 6 May 2024 18:19:42 +0530 Subject: [PATCH] Fixed tofu test when module has no resource (#1409) Signed-off-by: siddharthasonker95 <158144589+siddharthasonker95@users.noreply.github.com> --- CHANGELOG.md | 1 + internal/command/test_test.go | 4 ++++ .../first/main.tf | 7 +++++++ .../test/pass_module_with_no_resource/main.tf | 5 +++++ .../main.tftest.hcl | 8 +++++++ .../second/main.tf | 5 +++++ internal/tofu/test_context.go | 21 +++++++++++++++++++ 7 files changed, 51 insertions(+) create mode 100644 internal/command/testdata/test/pass_module_with_no_resource/first/main.tf create mode 100644 internal/command/testdata/test/pass_module_with_no_resource/main.tf create mode 100644 internal/command/testdata/test/pass_module_with_no_resource/main.tftest.hcl create mode 100644 internal/command/testdata/test/pass_module_with_no_resource/second/main.tf diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f04206764..1ea9bc47a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ENHANCEMENTS: BUG FIXES: * Added a check in the `tofu test` to validate that the names of test run blocks do not contain spaces. ([#1489](https://github.com/opentofu/opentofu/pull/1489)) +* `tofu test` now supports accessing module outputs when the module has no resources. ([#1409](https://github.com/opentofu/opentofu/pull/1409)) ## Previous Releases diff --git a/internal/command/test_test.go b/internal/command/test_test.go index 04a77d9edc..9365ac398c 100644 --- a/internal/command/test_test.go +++ b/internal/command/test_test.go @@ -799,6 +799,10 @@ func TestTest_Modules(t *testing.T) { code int skip bool }{ + "pass_module_with_no_resource": { + expected: "main.tftest.hcl... pass\n run \"run\"... pass\n\nSuccess! 1 passed, 0 failed.\n", + code: 0, + }, "with_nested_setup_modules": { expected: "main.tftest.hcl... pass\n run \"load_module\"... pass\n\nSuccess! 1 passed, 0 failed.\n", code: 0, diff --git a/internal/command/testdata/test/pass_module_with_no_resource/first/main.tf b/internal/command/testdata/test/pass_module_with_no_resource/first/main.tf new file mode 100644 index 0000000000..25408ebd8d --- /dev/null +++ b/internal/command/testdata/test/pass_module_with_no_resource/first/main.tf @@ -0,0 +1,7 @@ +module "second" { + source = "../second" +} + +output "id" { + value = module.second.id +} \ No newline at end of file diff --git a/internal/command/testdata/test/pass_module_with_no_resource/main.tf b/internal/command/testdata/test/pass_module_with_no_resource/main.tf new file mode 100644 index 0000000000..1d945425af --- /dev/null +++ b/internal/command/testdata/test/pass_module_with_no_resource/main.tf @@ -0,0 +1,5 @@ +module "first" { + source = "./first" +} + +resource "test_resource" "resource" {} \ No newline at end of file diff --git a/internal/command/testdata/test/pass_module_with_no_resource/main.tftest.hcl b/internal/command/testdata/test/pass_module_with_no_resource/main.tftest.hcl new file mode 100644 index 0000000000..43d4f75f3b --- /dev/null +++ b/internal/command/testdata/test/pass_module_with_no_resource/main.tftest.hcl @@ -0,0 +1,8 @@ +run "run" { + command = apply + + assert { + condition = module.first.id != 0 + error_message = "Fail" + } +} \ No newline at end of file diff --git a/internal/command/testdata/test/pass_module_with_no_resource/second/main.tf b/internal/command/testdata/test/pass_module_with_no_resource/second/main.tf new file mode 100644 index 0000000000..0b3cd15c2d --- /dev/null +++ b/internal/command/testdata/test/pass_module_with_no_resource/second/main.tf @@ -0,0 +1,5 @@ +resource "test_resource" "resource" {} + +output "id" { + value = test_resource.resource.id +} \ No newline at end of file diff --git a/internal/tofu/test_context.go b/internal/tofu/test_context.go index 49b1fd8de5..758b9932b9 100644 --- a/internal/tofu/test_context.go +++ b/internal/tofu/test_context.go @@ -67,6 +67,13 @@ func (ctx *TestContext) EvaluateAgainstPlan(run *moduletest.Run) { } func (ctx *TestContext) evaluate(state *states.SyncState, changes *plans.ChangesSync, run *moduletest.Run, operation walkOperation) { + // The state does not include the module that has no resources, making its outputs unusable. + // synchronizeStates function synchronizes the state with the planned state, ensuring inclusion of all modules. + if ctx.Plan != nil && ctx.Plan.PlannedState != nil && + len(ctx.State.Modules) != len(ctx.Plan.PlannedState.Modules) { + state = synchronizeStates(ctx.State, ctx.Plan.PlannedState) + } + data := &evaluationStateData{ Evaluator: &Evaluator{ Operation: operation, @@ -186,3 +193,17 @@ func (ctx *TestContext) evaluate(state *states.SyncState, changes *plans.Changes } } } + +// synchronizeStates compares the planned state to the current state and incorporates any missing modules +// from the planned state into the current state. +// +// If a module has no resources, it is included in the current state to ensure that its output variables are usable. +func synchronizeStates(state, plannedState *states.State) *states.SyncState { + newState := state.DeepCopy() + for key, value := range plannedState.Modules { + if _, exists := newState.Modules[key]; !exists { + newState.Modules[key] = value + } + } + return newState.SyncWrapper() +}