From 9e648d1eabdf3ce665de61867343111e65fd4516 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Tue, 3 Feb 2026 09:32:01 -0500 Subject: [PATCH] did you know there's still a race condition? THERE IS --- .../terraform/context_apply_action_test.go | 126 ++++-------------- 1 file changed, 27 insertions(+), 99 deletions(-) diff --git a/internal/terraform/context_apply_action_test.go b/internal/terraform/context_apply_action_test.go index 833982d344..3dea91c3a9 100644 --- a/internal/terraform/context_apply_action_test.go +++ b/internal/terraform/context_apply_action_test.go @@ -5,13 +5,11 @@ package terraform import ( "fmt" - "path/filepath" "sync" "testing" "time" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/hcl/v2" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" @@ -39,7 +37,7 @@ func TestContextApply_actions(t *testing.T) { expectInvokeActionCalled bool expectInvokeActionCalls []providers.InvokeActionRequest expectInvokeActionCallsAreUnordered bool - expectDiagnostics func(m *configs.Config) tfdiags.Diagnostics + expectDiagnostics tfdiags.Diagnostics ignoreWarnings bool assertHooks func(*testing.T, actionHookCapture) @@ -100,10 +98,8 @@ resource "test_object" "a" { if len(capture.completeActionHooks) != 2 { t.Error("expected 2 complete action hooks") } - evaluateHook := func(got HookActionIdentity, wantAddr string, wantEvent configs.ActionTriggerEvent) { trigger := got.ActionTrigger.(*plans.LifecycleActionTrigger) - if trigger.ActionTriggerEvent != wantEvent { t.Errorf("wrong event, got %s, want %s", trigger.ActionTriggerEvent, wantEvent) } @@ -111,7 +107,6 @@ resource "test_object" "a" { t.Errorf("wrong address: %s", diff) } } - // the before should have happened first, and the order should // be correct. evaluateHook(capture.startActionHooks[0], "action.action_example.hello", configs.BeforeCreate) @@ -193,23 +188,11 @@ resource "test_object" "a" { events: func(req providers.InvokeActionRequest) []providers.InvokeActionEvent { return []providers.InvokeActionEvent{ providers.InvokeActionEvent_Completed{ - Diagnostics: tfdiags.Diagnostics{ - tfdiags.Sourceless( - tfdiags.Error, - "test case for failing", - "this simulates a provider failing", - ), - }, + Diagnostics: testActionError(), }, } }, - expectDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { - return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "test case for failing", - Detail: "this simulates a provider failing", - }) - }, + expectDiagnostics: testActionError(), }, "before_create failing with successfully completed actions": { @@ -237,13 +220,7 @@ resource "test_object" "a" { if !req.PlannedActionData.IsNull() && req.PlannedActionData.GetAttr("attr").AsString() == "failure" { return []providers.InvokeActionEvent{ providers.InvokeActionEvent_Completed{ - Diagnostics: tfdiags.Diagnostics{ - tfdiags.Sourceless( - tfdiags.Error, - "test case for failing", - "this simulates a provider failing", - ), - }, + Diagnostics: testActionError(), }, } } else { @@ -252,13 +229,7 @@ resource "test_object" "a" { } } }, - expectDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { - return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "test case for failing", - Detail: "this simulates a provider failing", - }) - }, + expectDiagnostics: testActionError(), }, "before_create failing when calling invoke": { @@ -277,28 +248,9 @@ resource "test_object" "a" { }, expectInvokeActionCalled: true, callingInvokeReturnsDiagnostics: func(providers.InvokeActionRequest) tfdiags.Diagnostics { - return tfdiags.Diagnostics{ - tfdiags.Sourceless( - tfdiags.Error, - "test case for failing", - "this simulates a provider failing before the action is invoked", - ), - } - }, - expectDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { - return tfdiags.Diagnostics{}.Append( - &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Error when invoking action", - Detail: "test case for failing: this simulates a provider failing before the action is invoked", - Subject: &hcl.Range{ - Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - Start: hcl.Pos{Line: 7, Column: 18, Byte: 148}, - End: hcl.Pos{Line: 7, Column: 47, Byte: 175}, - }, - }, - ) + return testActionError() }, + expectDiagnostics: testActionError(), }, "failing an action by action event stops next actions in list": { @@ -326,7 +278,7 @@ resource "test_object" "a" { if !r.PlannedActionData.IsNull() && r.PlannedActionData.GetAttr("attr").AsString() == "failure" { return []providers.InvokeActionEvent{ providers.InvokeActionEvent_Completed{ - Diagnostics: tfdiags.Diagnostics{}.Append(tfdiags.Sourceless(tfdiags.Error, "test case for failing", "this simulates a provider failing")), + Diagnostics: testActionError(), }, } } @@ -336,20 +288,7 @@ resource "test_object" "a" { } }, - expectDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { - return tfdiags.Diagnostics{}.Append( - &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Error when invoking action", - Detail: "test case for failing: this simulates a provider failing", - Subject: &hcl.Range{ - Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - Start: hcl.Pos{Line: 13, Column: 47, Byte: 288}, - End: hcl.Pos{Line: 13, Column: 76, Byte: 317}, - }, - }, - ) - }, + expectDiagnostics: testActionError(), // We expect two calls but not the third one, because the second action fails expectInvokeActionCalls: []providers.InvokeActionRequest{{ ActionType: "action_example", @@ -388,30 +327,11 @@ resource "test_object" "a" { callingInvokeReturnsDiagnostics: func(r providers.InvokeActionRequest) tfdiags.Diagnostics { if !r.PlannedActionData.IsNull() && r.PlannedActionData.GetAttr("attr").AsString() == "failure" { // Simulate a failure for the second action - return tfdiags.Diagnostics{ - tfdiags.Sourceless( - tfdiags.Error, - "test case for failing", - "this simulates a provider failing", - ), - } + return testActionError() } return tfdiags.Diagnostics{} }, - expectDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { - return tfdiags.Diagnostics{}.Append( - &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Error when invoking action", - Detail: "test case for failing: this simulates a provider failing", - Subject: &hcl.Range{ - Filename: filepath.Join(m.Module.SourceDir, "main.tf"), - Start: hcl.Pos{Line: 13, Column: 47, Byte: 288}, - End: hcl.Pos{Line: 13, Column: 76, Byte: 317}, - }, - }, - ) - }, + expectDiagnostics: testActionError(), // We expect two calls but not the third one, because the second action fails expectInvokeActionCalls: []providers.InvokeActionRequest{{ ActionType: "action_example", @@ -484,13 +404,11 @@ resource "test_object" "b" { }, }), expectInvokeActionCalled: false, - expectDiagnostics: func(m *configs.Config) tfdiags.Diagnostics { - return tfdiags.Diagnostics{}.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Action configuration unknown during apply", - Detail: "The action action.action_example.hello was not fully known during apply.\n\nThis is a bug in Terraform, please report it.", - }) - }, + expectDiagnostics: tfdiags.Diagnostics{tfdiags.Sourceless( + tfdiags.Error, + "Action configuration unknown during apply", + "The action action.action_example.hello was not fully known during apply.\n\nThis is a bug in Terraform, please report it.", + )}, }, "action with secrets in configuration": { @@ -1691,7 +1609,7 @@ resource "test_object" "a" { _, diags = ctx.Apply(plan, m, tc.applyOpts) if tc.expectDiagnostics != nil { - tfdiags.AssertDiagnosticsMatch(t, diags, tc.expectDiagnostics(m)) + tfdiags.AssertDiagnosticsMatch(t, diags, tc.expectDiagnostics) } else { if tc.ignoreWarnings { tfdiags.AssertNoErrors(t, diags) @@ -2965,3 +2883,13 @@ func testContextActionProvider(invokeActionFn func(req providers.InvokeActionReq }, } } + +func testActionError() tfdiags.Diagnostics { + return tfdiags.Diagnostics{ + tfdiags.Sourceless( + tfdiags.Error, + "test case for failing", + "this simulates a provider failing", + ), + } +}