Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/v1.14/BUG FIXES-20250924-110416.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'console and test: return explicit diagnostics when referencing resources that were not included in the most recent operation.'
time: 2025-09-24T11:04:16.860364+02:00
custom:
Issue: "37663"
129 changes: 124 additions & 5 deletions internal/command/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,11 +412,6 @@ func TestTest_Runs(t *testing.T) {
"no-tests": {
code: 0,
},
"expect-failures-assertions": {
expectedOut: []string{"0 passed, 1 failed."},
expectedErr: []string{"Test assertion failed"},
code: 1,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -5454,6 +5449,130 @@ func TestTest_JUnitOutput(t *testing.T) {
}
}

func TestTest_ReferencesIntoIncompletePlan(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "expect-failures-assertions")), td)
t.Chdir(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 {
output := done(t)
t.Fatalf("expected status code %d but got %d: %s", 0, code, output.All())
}

// Reset the streams for the next command.
streams, done = terminal.StreamsForTesting(t)
meta.Streams = streams
meta.View = views.NewView(streams)

c := &TestCommand{
Meta: meta,
}

code := c.Run([]string{"-no-color"})
if code != 1 {
t.Errorf("expected status code %d but got %d", 0, code)
}
output := done(t)

out, err := output.Stdout(), output.Stderr()

expectedOut := `main.tftest.hcl... in progress
run "fail"... fail
main.tftest.hcl... tearing down
main.tftest.hcl... fail

Failure! 0 passed, 1 failed.
`

if diff := cmp.Diff(out, expectedOut); len(diff) > 0 {
t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expectedOut, out, diff)
}

if !strings.Contains(err, "Reference to uninitialized resource") {
t.Errorf("missing reference to uninitialized resource error: \n%s", err)
}

if !strings.Contains(err, "Reference to uninitialized local") {
t.Errorf("missing reference to uninitialized local error: \n%s", err)
}
}

func TestTest_ReferencesIntoTargetedPlan(t *testing.T) {
td := t.TempDir()
testCopyDir(t, testFixturePath(path.Join("test", "invalid-reference-with-target")), td)
t.Chdir(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 {
output := done(t)
t.Fatalf("expected status code %d but got %d: %s", 0, code, output.All())
}

// Reset the streams for the next command.
streams, done = terminal.StreamsForTesting(t)
meta.Streams = streams
meta.View = views.NewView(streams)

c := &TestCommand{
Meta: meta,
}

code := c.Run([]string{"-no-color"})
if code != 1 {
t.Errorf("expected status code %d but got %d", 0, code)
}
output := done(t)

err := output.Stderr()

if !strings.Contains(err, "Reference to uninitialized variable") {
t.Errorf("missing reference to uninitialized variable error: \n%s", err)
}
}

// https://github.com/hashicorp/terraform/issues/37546
func TestTest_TeardownOrder(t *testing.T) {
td := t.TempDir()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

variable "input" {
type = string
}

resource "test_resource" "one" {
value = var.input
}

resource "test_resource" "two" {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

run "test" {
command = plan

plan_options {
target = [test_resource.two]
}

variables {
input = "hello"
}

assert {
condition = var.input == "hello"
error_message = "wrong input"
}
}
1 change: 1 addition & 0 deletions internal/terraform/context_plan_actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ list "test_resource" "test1" {
},
},
"query run, action references resource": {
toBeImplemented: true, // TODO: Fix the graph built by query operations.
module: map[string]string{
"main.tf": `
action "test_action" "hello" {
Expand Down
32 changes: 27 additions & 5 deletions internal/terraform/evaluate.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,18 @@ func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfd
return ret, diags
}

val := d.Evaluator.NamedValues.GetInputVariableValue(d.ModulePath.InputVariable(addr.Name))
var val cty.Value
if target := d.ModulePath.InputVariable(addr.Name); !d.Evaluator.NamedValues.HasInputVariableValue(target) {
val = cty.DynamicVal
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to uninitialized variable",
Detail: fmt.Sprintf("The variable %s was not processed by the most recent operation, this likely means the previous operation either failed or was incomplete due to targeting.", addr),
Subject: rng.ToHCL().Ptr(),
})
} else {
val = d.Evaluator.NamedValues.GetInputVariableValue(target)
}

// Mark if sensitive and/or ephemeral
if config.Sensitive {
Expand Down Expand Up @@ -342,11 +353,17 @@ func (d *evaluationStateData) GetLocalValue(addr addrs.LocalValue, rng tfdiags.S
return cty.DynamicVal, diags
}

if target := addr.Absolute(d.ModulePath); d.Evaluator.NamedValues.HasLocalValue(target) {
return d.Evaluator.NamedValues.GetLocalValue(addr.Absolute(d.ModulePath)), diags
target := addr.Absolute(d.ModulePath)
if !d.Evaluator.NamedValues.HasLocalValue(target) {
return cty.DynamicVal, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to uninitialized local value",
Detail: fmt.Sprintf("The local value %s was not processed by the most recent operation, this likely means the previous operation either failed or was incomplete due to targeting.", addr),
Subject: rng.ToHCL().Ptr(),
})
}

return cty.DynamicVal, diags
return d.Evaluator.NamedValues.GetLocalValue(target), diags
}

func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
Expand Down Expand Up @@ -556,7 +573,12 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
if addr.Mode == addrs.EphemeralResourceMode {
unknownVal = unknownVal.Mark(marks.Ephemeral)
}
return unknownVal, diags
return unknownVal, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to uninitialized resource",
Detail: fmt.Sprintf("The resource %s was not processed by the most recent operation, this likely means the previous operation either failed or was incomplete due to targeting.", addr),
Subject: rng.ToHCL().Ptr(),
})
}

if _, _, hasUnknownKeys := d.Evaluator.Instances.ResourceInstanceKeys(addr.Absolute(moduleAddr)); hasUnknownKeys {
Expand Down