From 5deab9f721d6ddd3825a5cb01bb0279ba2e005e4 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Fri, 22 May 2026 13:55:21 +0100 Subject: [PATCH] test: wait for devcontainer readiness (#25567) --- cli/open_test.go | 67 ++++++++++++++++++++++++++++++++- coderd/coderdtest/coderdtest.go | 16 ++++++++ coderd/workspaceapps/db_test.go | 13 +------ 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/cli/open_test.go b/cli/open_test.go index 595bb2f1ce..564fbe657a 100644 --- a/cli/open_test.go +++ b/cli/open_test.go @@ -433,7 +433,72 @@ func TestOpenVSCodeDevContainer(t *testing.T) { agentcontainers.WithContainerLabelIncludeFilter("coder.test", t.Name()), ) }) - coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).AgentNames([]string{parentAgentName, devcontainerName}).Wait() + resources := coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).AgentNames([]string{parentAgentName}).Wait() + parentAgent := coderdtest.RequireWorkspaceAgentByName(t, resources, parentAgentName) + parentAgentID := parentAgent.ID + + // Agent connection does not guarantee the parent agent's container API + // has completed its first devcontainer update. Wait for that endpoint so + // parallel open commands do not race the initial cache population. + ctx := testutil.Context(t, testutil.WaitSuperLong) + testutil.Eventually(ctx, t, func(ctx context.Context) bool { + resp, err := client.WorkspaceAgentListContainers(ctx, parentAgentID, nil) + if err != nil { + t.Logf("list containers: %v", err) + return false + } + var devcontainerAgentID uuid.UUID + for _, dc := range resp.Devcontainers { + if dc.ID != devcontainerID { + continue + } + if dc.Status != codersdk.WorkspaceAgentDevcontainerStatusRunning { + t.Logf("devcontainer %s status %q", devcontainerName, dc.Status) + return false + } + if dc.Container == nil { + t.Logf("devcontainer %s missing container", devcontainerName) + return false + } + if dc.Container.ID != containerID { + t.Logf("devcontainer %s has container %s, want %s", devcontainerName, dc.Container.ID, containerID) + return false + } + if dc.Agent == nil { + t.Logf("devcontainer %s missing subagent", devcontainerName) + return false + } + if dc.Agent.Name != devcontainerName { + t.Logf("devcontainer %s has subagent %s, want %s", devcontainerName, dc.Agent.Name, devcontainerName) + return false + } + devcontainerAgentID = dc.Agent.ID + } + if devcontainerAgentID == uuid.Nil { + t.Logf("devcontainer %s not found", devcontainerName) + return false + } + + workspace, err := client.Workspace(ctx, workspace.ID) + if err != nil { + t.Logf("get workspace: %v", err) + return false + } + for _, resource := range workspace.LatestBuild.Resources { + for _, workspaceAgent := range resource.Agents { + if workspaceAgent.ID != devcontainerAgentID { + continue + } + if workspaceAgent.Status != codersdk.WorkspaceAgentConnected { + t.Logf("devcontainer subagent %s status %q", devcontainerAgentID, workspaceAgent.Status) + return false + } + return true + } + } + t.Logf("devcontainer subagent %s not found in workspace", devcontainerAgentID) + return false + }, testutil.IntervalMedium, "devcontainer did not become ready") insideWorkspaceEnv := map[string]string{ "CODER": "true", diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index e62b201252..ab8d2271c3 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -1278,6 +1278,22 @@ func NewWorkspaceAgentWaiter(t testing.TB, client *codersdk.Client, workspaceID } } +// RequireWorkspaceAgentByName avoids weak nil UUID assertions when a fixture requires a specific agent. +func RequireWorkspaceAgentByName(t testing.TB, resources []codersdk.WorkspaceResource, name string) codersdk.WorkspaceAgent { + t.Helper() + + for _, resource := range resources { + for _, agent := range resource.Agents { + if agent.Name == name { + return agent + } + } + } + + require.FailNowf(t, "workspace agent not found", "workspace agent %q not found in resources", name) + return codersdk.WorkspaceAgent{} +} + // AgentNames instructs the waiter to wait for the given, named agents to be connected and will // return even if other agents are not connected. func (w WorkspaceAgentWaiter) AgentNames(names []string) WorkspaceAgentWaiter { diff --git a/coderd/workspaceapps/db_test.go b/coderd/workspaceapps/db_test.go index d59160f1b5..1631c7d403 100644 --- a/coderd/workspaceapps/db_test.go +++ b/coderd/workspaceapps/db_test.go @@ -218,17 +218,8 @@ func Test_ResolveRequest(t *testing.T) { _ = agenttest.New(t, client.URL, agentAuthToken) resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID, agentName) - - agentID := uuid.Nil - for _, resource := range resources { - for _, agnt := range resource.Agents { - if agnt.Name == agentName { - agentID = agnt.ID - break - } - } - } - require.NotEqual(t, uuid.Nil, agentID) + agent := coderdtest.RequireWorkspaceAgentByName(t, resources, agentName) + agentID := agent.ID // Reset audit logs so cleanup check can pass. connLogger.Reset()