Skip to content

Commit 9381e28

Browse files
test: add integration-style test
1 parent d744bd3 commit 9381e28

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed

agent/agent_test.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2458,6 +2458,199 @@ func TestAgent_DevcontainersDisabledForSubAgent(t *testing.T) {
24582458
require.Contains(t, err.Error(), "Dev Container integration inside other Dev Containers is explicitly not supported.")
24592459
}
24602460

2461+
// TestAgent_DevcontainerPrebuildClaim tests that we correctly handle
2462+
// the claiming process for running devcontainers.
2463+
//
2464+
// You can run it manually as follows:
2465+
//
2466+
// CODER_TEST_USE_DOCKER=1 go test -count=1 ./agent -run TestAgent_DevcontainerPrebuildClaim
2467+
func TestAgent_DevcontainerPrebuildClaim(t *testing.T) {
2468+
if os.Getenv("CODER_TEST_USE_DOCKER") != "1" {
2469+
t.Skip("Set CODER_TEST_USE_DOCKER=1 to run this test")
2470+
}
2471+
if _, err := exec.LookPath("devcontainer"); err != nil {
2472+
t.Skip("This test requires the devcontainer CLI: npm install -g @devcontainers/cli")
2473+
}
2474+
2475+
pool, err := dockertest.NewPool("")
2476+
require.NoError(t, err, "Could not connect to docker")
2477+
2478+
var (
2479+
ctx = testutil.Context(t, testutil.WaitShort)
2480+
2481+
devcontainerID = uuid.New()
2482+
devcontainerLogSourceID = uuid.New()
2483+
2484+
workspaceFolder = filepath.Join(t.TempDir(), "project")
2485+
devcontainerPath = filepath.Join(workspaceFolder, ".devcontainer")
2486+
devcontainerConfig = filepath.Join(devcontainerPath, "devcontainer.json")
2487+
)
2488+
2489+
// Given: A devcontainer project.
2490+
t.Logf("Workspace folder: %s", workspaceFolder)
2491+
2492+
err = os.MkdirAll(devcontainerPath, 0o755)
2493+
require.NoError(t, err, "create dev container directory")
2494+
2495+
err = os.WriteFile(devcontainerConfig, []byte(`{
2496+
"name": "project",
2497+
"image": "busybox:latest",
2498+
"cmd": ["sleep", "infinity"],
2499+
"runArgs": ["--label=`+agentcontainers.DevcontainerIsTestRunLabel+`=true"],
2500+
"customizations": {
2501+
"coder": {
2502+
"apps": [{
2503+
"slug": "zed",
2504+
"url": "zed://ssh/${localEnv:CODER_WORKSPACE_AGENT_NAME}.${localEnv:CODER_WORKSPACE_NAME}.${localEnv:CODER_WORKSPACE_OWNER_NAME}.coder${containerWorkspaceFolder}"
2505+
}]
2506+
}
2507+
}
2508+
}`), 0o600)
2509+
require.NoError(t, err, "write devcontainer config")
2510+
2511+
// Given: A manifest with a devcontainer to be started.
2512+
manifest := agentsdk.Manifest{
2513+
OwnerName: "prebuilds",
2514+
WorkspaceName: "prebuilds-xyz-123",
2515+
2516+
Devcontainers: []codersdk.WorkspaceAgentDevcontainer{
2517+
{ID: devcontainerID, Name: "test", WorkspaceFolder: workspaceFolder},
2518+
},
2519+
Scripts: []codersdk.WorkspaceAgentScript{
2520+
{ID: devcontainerID, LogSourceID: devcontainerLogSourceID},
2521+
},
2522+
}
2523+
2524+
conn, client, _, _, _ := setupAgent(t, manifest, 0, func(_ *agenttest.Client, o *agent.Options) {
2525+
o.Devcontainers = true
2526+
o.DevcontainerAPIOptions = append(o.DevcontainerAPIOptions,
2527+
agentcontainers.WithContainerLabelIncludeFilter(agentcontainers.DevcontainerLocalFolderLabel, workspaceFolder),
2528+
agentcontainers.WithContainerLabelIncludeFilter(agentcontainers.DevcontainerIsTestRunLabel, "true"),
2529+
)
2530+
})
2531+
2532+
testutil.Eventually(ctx, t, func(ctx context.Context) bool {
2533+
return slices.Contains(client.GetLifecycleStates(), codersdk.WorkspaceAgentLifecycleReady)
2534+
}, testutil.IntervalMedium, "agent not ready")
2535+
2536+
var dcPrebuild codersdk.WorkspaceAgentDevcontainer
2537+
testutil.Eventually(ctx, t, func(ctx context.Context) bool {
2538+
resp, err := conn.ListContainers(ctx)
2539+
require.NoError(t, err)
2540+
2541+
for _, dc := range resp.Devcontainers {
2542+
if dc.Container == nil {
2543+
continue
2544+
}
2545+
2546+
v, ok := dc.Container.Labels[agentcontainers.DevcontainerLocalFolderLabel]
2547+
if ok && v == workspaceFolder {
2548+
dcPrebuild = dc
2549+
return true
2550+
}
2551+
}
2552+
2553+
return false
2554+
}, testutil.IntervalMedium, "devcontainer not found")
2555+
defer func() {
2556+
pool.Client.RemoveContainer(docker.RemoveContainerOptions{
2557+
ID: dcPrebuild.Container.ID,
2558+
RemoveVolumes: true,
2559+
Force: true,
2560+
})
2561+
}()
2562+
2563+
subAgents := client.GetSubAgents()
2564+
require.Len(t, subAgents, 1)
2565+
2566+
subAgent := subAgents[0]
2567+
subAgentID, err := uuid.FromBytes(subAgent.GetId())
2568+
require.NoError(t, err)
2569+
2570+
subAgentApps, err := client.GetSubAgentApps(subAgentID)
2571+
require.NoError(t, err)
2572+
require.Len(t, subAgentApps, 1)
2573+
2574+
subAgentApp := subAgentApps[0]
2575+
require.Equal(t, "zed://ssh/project.prebuilds-xyz-123.prebuilds.coder/workspaces/project", subAgentApp.GetUrl())
2576+
2577+
// Close the client and connection
2578+
client.Close()
2579+
conn.Close()
2580+
2581+
// Given: A manifest with a devcontainer to be started.
2582+
manifest = agentsdk.Manifest{
2583+
OwnerName: "user",
2584+
WorkspaceName: "user-workspace",
2585+
2586+
Devcontainers: []codersdk.WorkspaceAgentDevcontainer{
2587+
{ID: devcontainerID, Name: "test", WorkspaceFolder: workspaceFolder},
2588+
},
2589+
Scripts: []codersdk.WorkspaceAgentScript{
2590+
{ID: devcontainerID, LogSourceID: devcontainerLogSourceID},
2591+
},
2592+
}
2593+
2594+
conn, client, _, _, _ = setupAgent(t, manifest, 0, func(_ *agenttest.Client, o *agent.Options) {
2595+
o.Devcontainers = true
2596+
o.DevcontainerAPIOptions = append(o.DevcontainerAPIOptions,
2597+
agentcontainers.WithContainerLabelIncludeFilter(agentcontainers.DevcontainerLocalFolderLabel, workspaceFolder),
2598+
agentcontainers.WithContainerLabelIncludeFilter(agentcontainers.DevcontainerIsTestRunLabel, "true"),
2599+
)
2600+
})
2601+
2602+
testutil.Eventually(ctx, t, func(ctx context.Context) bool {
2603+
return slices.Contains(client.GetLifecycleStates(), codersdk.WorkspaceAgentLifecycleReady)
2604+
}, testutil.IntervalMedium, "agent not ready")
2605+
2606+
var dcClaimed codersdk.WorkspaceAgentDevcontainer
2607+
testutil.Eventually(ctx, t, func(ctx context.Context) bool {
2608+
resp, err := conn.ListContainers(ctx)
2609+
require.NoError(t, err)
2610+
2611+
for _, dc := range resp.Devcontainers {
2612+
if dc.Container == nil {
2613+
continue
2614+
}
2615+
2616+
v, ok := dc.Container.Labels[agentcontainers.DevcontainerLocalFolderLabel]
2617+
if ok && v == workspaceFolder {
2618+
dcClaimed = dc
2619+
return true
2620+
}
2621+
}
2622+
2623+
return false
2624+
}, testutil.IntervalMedium, "devcontainer not found")
2625+
defer func() {
2626+
if dcClaimed.Container.ID != dcPrebuild.Container.ID {
2627+
pool.Client.RemoveContainer(docker.RemoveContainerOptions{
2628+
ID: dcClaimed.Container.ID,
2629+
RemoveVolumes: true,
2630+
Force: true,
2631+
})
2632+
}
2633+
}()
2634+
2635+
// Then: We expect the claimed devcontainer and prebuild devcontainer
2636+
// to be using the same underlying container.
2637+
require.Equal(t, dcPrebuild.Container.ID, dcClaimed.Container.ID)
2638+
2639+
subAgents = client.GetSubAgents()
2640+
require.Len(t, subAgents, 1)
2641+
2642+
subAgent = subAgents[0]
2643+
subAgentID, err = uuid.FromBytes(subAgent.GetId())
2644+
require.NoError(t, err)
2645+
2646+
subAgentApps, err = client.GetSubAgentApps(subAgentID)
2647+
require.NoError(t, err)
2648+
require.Len(t, subAgentApps, 1)
2649+
2650+
subAgentApp = subAgentApps[0]
2651+
require.Equal(t, "zed://ssh/project.user-workspace.user.coder/workspaces/project", subAgentApp.GetUrl())
2652+
}
2653+
24612654
func TestAgent_Dial(t *testing.T) {
24622655
t.Parallel()
24632656

0 commit comments

Comments
 (0)