From 4d9404a59ea2bb4351098e0b2f437a1005d25fcc Mon Sep 17 00:00:00 2001 From: Elbaz Date: Wed, 6 Sep 2023 13:40:12 +0300 Subject: [PATCH] Fix `opentf test` crash on nil output (#267) Co-authored-by: Elbaz --- internal/command/test_test.go | 9 +++++++ .../testdata/test/not_exists_output/main.tf | 2 ++ .../test/not_exists_output/main.tftest.hcl | 6 +++++ .../command/testdata/test/null_output/main.tf | 3 +++ .../testdata/test/null_output/main.tftest.hcl | 6 +++++ internal/opentf/evaluate.go | 25 ++++++++++++------- 6 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 internal/command/testdata/test/not_exists_output/main.tf create mode 100644 internal/command/testdata/test/not_exists_output/main.tftest.hcl create mode 100644 internal/command/testdata/test/null_output/main.tf create mode 100644 internal/command/testdata/test/null_output/main.tftest.hcl diff --git a/internal/command/test_test.go b/internal/command/test_test.go index 56754fed49..55ffb7ab0e 100644 --- a/internal/command/test_test.go +++ b/internal/command/test_test.go @@ -152,6 +152,10 @@ func TestTest(t *testing.T) { expected: "3 passed, 0 failed.", code: 0, }, + "null_output": { + expected: "1 passed, 0 failed.", + code: 0, + }, } for name, tc := range tcs { t.Run(name, func(t *testing.T) { @@ -227,6 +231,11 @@ func TestTest_Full_Output(t *testing.T) { expected: "Blocks of type \"check\" are not expected here.", code: 1, }, + "not_exists_output": { + expected: "Error: Reference to undeclared output value", + args: []string{"-no-color"}, + code: 1, + }, "refresh_conflicting_config": { expected: "Incompatible plan options", code: 1, diff --git a/internal/command/testdata/test/not_exists_output/main.tf b/internal/command/testdata/test/not_exists_output/main.tf new file mode 100644 index 0000000000..8e8f142df0 --- /dev/null +++ b/internal/command/testdata/test/not_exists_output/main.tf @@ -0,0 +1,2 @@ +resource "test_resource" "resource" { +} \ No newline at end of file diff --git a/internal/command/testdata/test/not_exists_output/main.tftest.hcl b/internal/command/testdata/test/not_exists_output/main.tftest.hcl new file mode 100644 index 0000000000..53c4ca064e --- /dev/null +++ b/internal/command/testdata/test/not_exists_output/main.tftest.hcl @@ -0,0 +1,6 @@ +run "not_exists" { + assert { + condition = output.something_that_does_not_exist == null + error_message = "Should fail for Reference to undeclared output value" + } +} diff --git a/internal/command/testdata/test/null_output/main.tf b/internal/command/testdata/test/null_output/main.tf new file mode 100644 index 0000000000..012bae927f --- /dev/null +++ b/internal/command/testdata/test/null_output/main.tf @@ -0,0 +1,3 @@ +output "my_null_output" { + value = null +} \ No newline at end of file diff --git a/internal/command/testdata/test/null_output/main.tftest.hcl b/internal/command/testdata/test/null_output/main.tftest.hcl new file mode 100644 index 0000000000..aacc8d7c94 --- /dev/null +++ b/internal/command/testdata/test/null_output/main.tftest.hcl @@ -0,0 +1,6 @@ +run "null" { + assert { + condition = output.my_null_output == null + error_message = "Should work" + } +} \ No newline at end of file diff --git a/internal/opentf/evaluate.go b/internal/opentf/evaluate.go index 3d6a0ad8c9..20c114b1fe 100644 --- a/internal/opentf/evaluate.go +++ b/internal/opentf/evaluate.go @@ -975,17 +975,24 @@ func (d *evaluationStateData) GetOutput(addr addrs.OutputValue, rng tfdiags.Sour output := d.Evaluator.State.OutputValue(addr.Absolute(d.ModulePath)) - val := output.Value - if val == cty.NilVal { - // Not evaluated yet? - val = cty.DynamicVal - } + // https://github.com/opentffoundation/opentf/issues/257 + // If the output is null - it does not serialize as part of the node_output state https://github.com/opentffoundation/opentf/blob/4b623c56ffe9e6c1dc345e54470b71b0f261297a/internal/opentf/node_output.go#L592-L596 + // In such a case, we should simply return a nil value because OpenTF test crash to evaluate for invalid memory address or nil pointer dereference + if output == nil { + return cty.NilVal, diags + } else { + val := output.Value + if val == cty.NilVal { + // Not evaluated yet? + val = cty.DynamicVal + } - if output.Sensitive { - val = val.Mark(marks.Sensitive) - } + if output.Sensitive { + val = val.Mark(marks.Sensitive) + } - return val, diags + return val, diags + } } func (d *evaluationStateData) GetCheckBlock(addr addrs.Check, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {