fix: deflake TestWorkspaceTagsTerraform with context-aware build waits (#26315)

`TestWorkspaceTagsTerraform` runs a real terraform provisioner but
waited on builds with `coderdtest` helpers whose deadlines are sized for
the echo provisioner used by most tests, which replays canned responses
and completes in well under a second. On Windows runners, where
terraform providers are not cached and every `terraform init` downloads
from the registry, template imports exceeded the 25s budget in
`AwaitTemplateVersionJobCompleted` and workspace builds exceeded the 10s
context in `AwaitWorkspaceBuildJobCompleted`, even though the test
intends a 120s budget.

Add `AwaitTemplateVersionJobCompletedWithTimeout` and
`AwaitWorkspaceBuildJobCompletedWithTimeout`, which take a
caller-provided wait bound, and use them in the test with
`2*testutil.WaitSuperLong` (120s). Also fix
`AwaitWorkspaceBuildJobCompleted` creating a `WaitShort` (10s) context
while polling for `WaitMedium` (15s), which guaranteed `context deadline
exceeded` errors for the final five seconds of polling.

`TestWorkspaceTemplateParamsChange` has the same shape (real terraform
provisioner, 120s test context, plain await helpers) and the same latent
bug, so it gets the same fix.

Closes https://github.com/coder/internal/issues/1470 (Linear: PLAT-176)

<details>
<summary>Root cause analysis</summary>

Two CI failures, same mechanism:

- 2026-04-16 (run 24493089585, windows-2022):
`overrides_with_dynamic_option_from_var/dynamic` failed at
`coderdtest.AwaitTemplateVersionJobCompleted` with `Condition never
satisfied ... make sure you set IncludeProvisionerDaemon!`. The template
import job (real terraform init/plan, with network provider download)
did not complete within `WaitLong` (25s).
- 2026-05-27 (run 26492817796, windows-2022): `tag_param/dynamic` failed
at `coderdtest.AwaitWorkspaceBuildJobCompleted` with `failed to get
workspace build ...: context deadline exceeded`. The helper's internal
context was `WaitShort` (10s) while its polling window was `WaitMedium`
(15s), so after 10s every poll could only fail. The logged `terraform
apply: exit status 1` and the `TempDir RemoveAll ... Access is denied`
cleanup error are consequences of test teardown canceling the in-flight
job while the provider exe was still file-locked.

The test declares a 120s budget (`2*testutil.WaitSuperLong`, commented
"This can take a while"), but the await helpers ignored it and applied
their own 10-25s budgets. `testutil.CacheTFProviders` is a no-op on
Windows, so real builds are much slower there.

This change raises the ceiling for the tests rather than making
terraform faster; both observed failure signatures are eliminated. The
default helper budgets are unchanged for the ~880 existing call sites.
One small behavior change: `AwaitTemplateVersionJobCompleted` previously
marked the test failed on any transient poll error via `assert.NoError`;
it now logs and keeps polling, matching the workspace build helper, and
still fails on timeout.

`TestWorkspaceTemplateParamsChange` is the sibling real-terraform test
in the same file (also covered by the original provider-caching work in
#20603). It runs three sequential real builds with the plain await
helpers under a 120s context, so it is exposed to the same Windows
slowness even though it has not produced its own issue yet. Its context
is raised to `6*testutil.WaitSuperLong` to outlast three sequential
await budgets.

API note: a context-taking variant was considered first, but a
`time.Duration` parameter avoids an implicit "context must have a
deadline" contract and matches how the existing helpers manage their own
wait budgets.

</details>

---

🤖 This PR was generated by Coder Agents on behalf of @jscottmiller.
This commit is contained in:
J. Scott Miller
2026-06-29 09:56:42 -05:00
committed by GitHub
parent 5f42bbcdbf
commit 1dea00dd04
2 changed files with 56 additions and 20 deletions
+18 -9
View File
@@ -3233,8 +3233,9 @@ func TestWorkspaceTemplateParamsChange(t *testing.T) {
_ = coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, nil)
// This can take a while, so set a relatively long timeout.
ctx := testutil.Context(t, 2*testutil.WaitSuperLong)
// This can take a while, so set a long timeout that outlasts the three
// build awaits below.
ctx := testutil.Context(t, 6*testutil.WaitSuperLong)
// Creating a template as a template admin must succeed
templateFiles := map[string]string{"main.tf": mainTfTemplate}
@@ -3250,7 +3251,9 @@ func TestWorkspaceTemplateParamsChange(t *testing.T) {
UserVariableValues: []codersdk.VariableValue{},
})
require.NoError(t, err, "failed to create template version")
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID)
// Uncached Windows runners make real terraform builds much slower than
// the default await timeout.
coderdtest.AwaitTemplateVersionJobCompletedWithTimeout(t, templateAdmin, tv.ID, 2*testutil.WaitSuperLong)
tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, tv.ID)
// Set to dynamic params
@@ -3281,7 +3284,8 @@ func TestWorkspaceTemplateParamsChange(t *testing.T) {
// Then: the build should succeed. The updated value of param_min should be
// used to validate param instead of the value defined in the temp
require.NoError(t, err, "failed to create workspace")
createBuild := coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID)
// Same timeout reason as above.
createBuild := coderdtest.AwaitWorkspaceBuildJobCompletedWithTimeout(t, member, ws.LatestBuild.ID, 2*testutil.WaitSuperLong)
require.Equal(t, createBuild.Status, codersdk.WorkspaceStatusRunning)
// File should exist
@@ -3293,7 +3297,8 @@ func TestWorkspaceTemplateParamsChange(t *testing.T) {
Transition: codersdk.WorkspaceTransitionDelete,
})
require.NoError(t, err)
build = coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, build.ID)
// Same timeout reason as above.
build = coderdtest.AwaitWorkspaceBuildJobCompletedWithTimeout(t, member, build.ID, 2*testutil.WaitSuperLong)
require.Equal(t, codersdk.WorkspaceStatusDeleted, build.Status)
logsCh, closeLogs, err := member.WorkspaceBuildLogsAfter(ctx, build.ID, 0)
@@ -3527,8 +3532,9 @@ func workspaceTagsTerraform(t *testing.T, tc testWorkspaceTagsTerraformCase, dyn
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
// This can take a while, so set a relatively long timeout.
ctx := testutil.Context(t, 2*testutil.WaitSuperLong)
// This can take a while, so set a long timeout that outlasts both build
// awaits below.
ctx := testutil.Context(t, 4*testutil.WaitSuperLong)
emptyTar := testutil.CreateTar(t, map[string]string{"main.tf": ""})
emptyFi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(emptyTar))
@@ -3566,7 +3572,9 @@ func workspaceTagsTerraform(t *testing.T, tc testWorkspaceTagsTerraformCase, dyn
TemplateID: tpl.ID,
})
require.NoError(t, err, "failed to create template version")
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID)
// Uncached Windows runners make real terraform builds much slower than
// the default await timeout.
coderdtest.AwaitTemplateVersionJobCompletedWithTimeout(t, templateAdmin, tv.ID, 2*testutil.WaitSuperLong)
err = templateAdmin.UpdateActiveTemplateVersion(ctx, tpl.ID, codersdk.UpdateActiveTemplateVersion{
ID: tv.ID,
@@ -3583,7 +3591,8 @@ func workspaceTagsTerraform(t *testing.T, tc testWorkspaceTagsTerraformCase, dyn
require.NoError(t, err, "failed to create workspace")
tagJSON, _ := json.Marshal(ws.LatestBuild.Job.Tags)
t.Logf("Created workspace build [%s] with tags: %s", ws.LatestBuild.Job.Type, tagJSON)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID)
// Same timeout reason as above.
coderdtest.AwaitWorkspaceBuildJobCompletedWithTimeout(t, member, ws.LatestBuild.ID, 2*testutil.WaitSuperLong)
}
}