Skip to content

Commit 2d93ee3

Browse files
committed
add integration test
1 parent 234fc25 commit 2d93ee3

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed

agent/agent_test.go

+128
Original file line numberDiff line numberDiff line change
@@ -1937,6 +1937,134 @@ func TestAgent_ReconnectingPTYContainer(t *testing.T) {
19371937
require.ErrorIs(t, tr.ReadUntil(ctx, nil), io.EOF)
19381938
}
19391939

1940+
// This tests end-to-end functionality of auto-starting a devcontainer.
1941+
// It runs "devcontainer up" which creates a real Docker container. As
1942+
// such, it does not run by default in CI.
1943+
//
1944+
// You can run it manually as follows:
1945+
//
1946+
// CODER_TEST_USE_DOCKER=1 go test -count=1 ./agent -run TestAgent_DevcontainerAutostart
1947+
func TestAgent_DevcontainerAutostart(t *testing.T) {
1948+
t.Parallel()
1949+
if os.Getenv("CODER_TEST_USE_DOCKER") != "1" {
1950+
t.Skip("Set CODER_TEST_USE_DOCKER=1 to run this test")
1951+
}
1952+
1953+
ctx := testutil.Context(t, testutil.WaitLong)
1954+
1955+
// Connect to Docker
1956+
pool, err := dockertest.NewPool("")
1957+
require.NoError(t, err, "Could not connect to docker")
1958+
1959+
// Prepare temporary devcontainer for test (mywork).
1960+
devcontainerID := uuid.New()
1961+
tempWorkspaceFolder := t.TempDir()
1962+
tempWorkspaceFolder = filepath.Join(tempWorkspaceFolder, "mywork")
1963+
t.Logf("Workspace folder: %s", tempWorkspaceFolder)
1964+
devcontainerPath := filepath.Join(tempWorkspaceFolder, ".devcontainer")
1965+
err = os.MkdirAll(devcontainerPath, 0o755)
1966+
require.NoError(t, err, "create devcontainer directory")
1967+
devcontainerFile := filepath.Join(devcontainerPath, "devcontainer.json")
1968+
err = os.WriteFile(devcontainerFile, []byte(`{
1969+
"name": "mywork",
1970+
"image": "busybox:latest",
1971+
"cmd": ["sleep", "infinity"]
1972+
}`), 0o600)
1973+
require.NoError(t, err, "write devcontainer.json")
1974+
1975+
manifest := agentsdk.Manifest{
1976+
// Set up pre-conditions for auto-starting a devcontainer, the script
1977+
// is expected to be prepared by the provisioner normally.
1978+
Devcontainers: []codersdk.WorkspaceAgentDevcontainer{
1979+
{
1980+
ID: devcontainerID,
1981+
Name: "test",
1982+
WorkspaceFolder: tempWorkspaceFolder,
1983+
},
1984+
},
1985+
Scripts: []codersdk.WorkspaceAgentScript{
1986+
{
1987+
ID: devcontainerID,
1988+
LogSourceID: agentsdk.ExternalLogSourceID,
1989+
RunOnStart: true,
1990+
Script: "echo this-will-be-replaced",
1991+
DisplayName: "Dev Container (test)",
1992+
},
1993+
},
1994+
}
1995+
// nolint: dogsled
1996+
conn, _, _, _, _ := setupAgent(t, manifest, 0, func(_ *agenttest.Client, o *agent.Options) {
1997+
o.ExperimentalDevcontainersEnabled = true
1998+
})
1999+
2000+
t.Logf("Waiting for container with label: devcontainer.local_folder=%s", tempWorkspaceFolder)
2001+
2002+
var container docker.APIContainers
2003+
require.Eventually(t, func() bool {
2004+
containers, err := pool.Client.ListContainers(docker.ListContainersOptions{All: true})
2005+
if err != nil {
2006+
t.Logf("Error listing containers: %v", err)
2007+
return false
2008+
}
2009+
2010+
for _, c := range containers {
2011+
t.Logf("Found container: %s with labels: %v", c.ID[:12], c.Labels)
2012+
if labelValue, ok := c.Labels["devcontainer.local_folder"]; ok {
2013+
if labelValue == tempWorkspaceFolder {
2014+
t.Logf("Found matching container: %s", c.ID[:12])
2015+
container = c
2016+
return true
2017+
}
2018+
}
2019+
}
2020+
2021+
return false
2022+
}, testutil.WaitSuperLong, testutil.IntervalMedium, "no container with workspace folder label found")
2023+
2024+
t.Cleanup(func() {
2025+
// We can't rely on pool here because the container is not
2026+
// managed by it (it is managed by @devcontainer/cli).
2027+
err := pool.Client.RemoveContainer(docker.RemoveContainerOptions{
2028+
ID: container.ID,
2029+
RemoveVolumes: true,
2030+
Force: true,
2031+
})
2032+
assert.NoError(t, err, "remove container")
2033+
})
2034+
2035+
containerInfo, err := pool.Client.InspectContainer(container.ID)
2036+
require.NoError(t, err, "inspect container")
2037+
t.Logf("Container state: status: %v", containerInfo.State.Status)
2038+
require.True(t, containerInfo.State.Running, "container should be running")
2039+
2040+
ac, err := conn.ReconnectingPTY(ctx, uuid.New(), 80, 80, "", func(opts *workspacesdk.AgentReconnectingPTYInit) {
2041+
opts.Container = container.ID
2042+
})
2043+
require.NoError(t, err, "failed to create ReconnectingPTY")
2044+
defer ac.Close()
2045+
2046+
// Use terminal reader so we can see output in case somethin goes wrong.
2047+
tr := testutil.NewTerminalReader(t, ac)
2048+
2049+
require.NoError(t, tr.ReadUntil(ctx, func(line string) bool {
2050+
return strings.Contains(line, "#") || strings.Contains(line, "$")
2051+
}), "find prompt")
2052+
2053+
wantFileName := "file-from-devcontainer"
2054+
wantFile := filepath.Join(tempWorkspaceFolder, wantFileName)
2055+
2056+
require.NoError(t, json.NewEncoder(ac).Encode(workspacesdk.ReconnectingPTYRequest{
2057+
// NOTE(mafredri): We must use absolute path here for some reason.
2058+
Data: fmt.Sprintf("touch /workspaces/mywork/%s; exit\r", wantFileName),
2059+
}), "create file inside devcontainer")
2060+
2061+
// Wait for the connection to close to ensure the touch was executed.
2062+
require.ErrorIs(t, tr.ReadUntil(ctx, nil), io.EOF)
2063+
2064+
_, err = os.Stat(wantFile)
2065+
require.NoError(t, err, "file should exist outside devcontainer")
2066+
}
2067+
19402068
func TestAgent_Dial(t *testing.T) {
19412069
t.Parallel()
19422070

0 commit comments

Comments
 (0)