Skip to content

Commit 41fddac

Browse files
committed
add integration test
1 parent 234fc25 commit 41fddac

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

agent/agent_test.go

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy