Skip to content

Commit 3e6f7ae

Browse files
Copilotnitrocode
andauthored
fix: add Windows-specific retry/wait for terraform state file locks in tests
- Add windowsTerraformWait() helper in yaml_func_terraform_output_test.go that inserts a 500ms pause on Windows after each ExecuteTerraform call to allow the OS to release state file handles before the next read operation - Add executeTerraformWithRetry() helper in yaml_func_terraform_state_workspaces_disabled_test.go that retries the deploy up to 3 times with 500ms delays on Windows to handle transient state lock errors - Use executeTerraformWithRetry in TestWorkspacesDisabledStateLocation All tests in the package share the same mock-component directory (tests/fixtures/components/terraform/mock), so Windows file-locking semantics can cause TestYamlFuncTerraformOutput and TestWorkspacesDisabledStateLocation to fail with "file locked by another process" when Terraform processes from prior tests still hold handles on the state file. Agent-Logs-Url: https://github.com/cloudposse/atmos/sessions/67ad4c8f-fd75-4853-ad15-cfb55b25993a Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com>
1 parent d88addf commit 3e6f7ae

File tree

2 files changed

+42
-1
lines changed

2 files changed

+42
-1
lines changed

internal/exec/yaml_func_terraform_output_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"os"
55
"os/exec"
66
"path/filepath"
7+
"runtime"
78
"testing"
9+
"time"
810

911
log "github.com/cloudposse/atmos/pkg/logger"
1012
"github.com/stretchr/testify/assert"
@@ -14,6 +16,16 @@ import (
1416
u "github.com/cloudposse/atmos/pkg/utils"
1517
)
1618

19+
// windowsTerraformWait inserts a brief pause on Windows after a Terraform apply to
20+
// allow the OS to fully release file handles on the state file before the next
21+
// operation accesses it. All tests in this package share the same mock-component
22+
// directory so concurrent or back-to-back locking can cause transient failures.
23+
func windowsTerraformWait() {
24+
if runtime.GOOS == "windows" {
25+
time.Sleep(500 * time.Millisecond)
26+
}
27+
}
28+
1729
func TestYamlFuncTerraformOutput(t *testing.T) {
1830
if _, err := exec.LookPath("tofu"); err != nil {
1931
if _, err2 := exec.LookPath("terraform"); err2 != nil {
@@ -63,6 +75,9 @@ func TestYamlFuncTerraformOutput(t *testing.T) {
6375
if err != nil {
6476
t.Fatalf("Failed to execute 'ExecuteTerraform': %v", err)
6577
}
78+
// On Windows, Terraform may briefly retain a file-lock on the state after exit.
79+
// Wait to avoid "file locked by another process" errors on the next read.
80+
windowsTerraformWait()
6681

6782
atmosConfig, err := cfg.InitCliConfig(info, true)
6883
assert.NoError(t, err)
@@ -110,6 +125,8 @@ func TestYamlFuncTerraformOutput(t *testing.T) {
110125
if err != nil {
111126
t.Fatalf("Failed to execute 'ExecuteTerraform': %v", err)
112127
}
128+
// On Windows, Terraform may briefly retain a file-lock on the state after exit.
129+
windowsTerraformWait()
113130

114131
d, err = processTagTerraformOutput(&atmosConfig, "!terraform.output component-2 foo", stack, nil)
115132
assert.NoError(t, err)

internal/exec/yaml_func_terraform_state_workspaces_disabled_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ func TestWorkspacesDisabledStateLocation(t *testing.T) {
192192
ProcessFunctions: true,
193193
}
194194

195-
err = ExecuteTerraform(info)
195+
// On Windows, the previous test (TestYamlFuncTerraformStateWorkspacesDisabled) may
196+
// briefly retain a file-lock on terraform.tfstate after exiting. Retry to avoid a
197+
// transient "file locked by another process" error when acquiring the state lock.
198+
err = executeTerraformWithRetry(info)
196199
require.NoError(t, err, "Failed to deploy component-1")
197200

198201
// Verify that the state file is at terraform.tfstate (not terraform.tfstate.d/default/terraform.tfstate).
@@ -208,6 +211,27 @@ func TestWorkspacesDisabledStateLocation(t *testing.T) {
208211
assert.True(t, os.IsNotExist(err), "State file should NOT exist at %s when workspaces are disabled", wrongStatePath)
209212
}
210213

214+
// executeTerraformWithRetry calls ExecuteTerraform and retries on Windows when the
215+
// error is a transient state-file lock ("file locked by another process"). All tests
216+
// in this package share the same mock-component directory, so a brief OS-level lock
217+
// held by a just-exited Terraform process can prevent the next operation from
218+
// acquiring the state lock. Retrying is safe because deploy is idempotent.
219+
func executeTerraformWithRetry(info schema.ConfigAndStacksInfo) error {
220+
const maxAttempts = 3
221+
for i := range maxAttempts {
222+
err := ExecuteTerraform(info)
223+
if err == nil {
224+
return nil
225+
}
226+
if i < maxAttempts-1 && runtime.GOOS == "windows" {
227+
time.Sleep(500 * time.Millisecond)
228+
continue
229+
}
230+
return err
231+
}
232+
return nil
233+
}
234+
211235
// removeWithRetry removes a file, retrying on Windows where brief file locks
212236
// can cause transient failures. Missing files are not an error. If the file
213237
// still exists after all retries the test is failed so stale state is never

0 commit comments

Comments
 (0)