From f49530f9b7522afc3bc1c579ea53f748a96ee36c Mon Sep 17 00:00:00 2001 From: Samuel K Date: Sun, 3 May 2026 01:42:04 -0500 Subject: [PATCH] feat(config): support custom workspaceMount from devcontainer.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add unit tests for getWorkspace() covering custom mount, default mount, default mount with workspaceFolder, and empty/unset workspaceMount. Add E2E test validating custom workspace mount with testdata. The workspaceMount field was already wired through config parsing and container creation — this commit adds test coverage for the feature. --- e2e/tests/up/provider_docker.go | 25 +++++++ .../docker-workspace-mount/.devcontainer.json | 6 ++ pkg/devcontainer/run_test.go | 68 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 e2e/tests/up/testdata/docker-workspace-mount/.devcontainer.json diff --git a/e2e/tests/up/provider_docker.go b/e2e/tests/up/provider_docker.go index 57d950a7d..361be75b1 100644 --- a/e2e/tests/up/provider_docker.go +++ b/e2e/tests/up/provider_docker.go @@ -528,6 +528,31 @@ var _ = ginkgo.Describe( To(gomega.ContainElement("apparmor=unconfined")) }, ginkgo.SpecTimeout(framework.TimeoutShort())) + ginkgo.It("custom workspace mount", func(ctx context.Context) { + tempDir, err := dtc.setupAndUp(ctx, "tests/up/testdata/docker-workspace-mount") + framework.ExpectNoError(err) + + workspace, err := dtc.f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dtc.findWorkspaceContainer(ctx, workspace) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1)) + + var details []container.InspectResponse + err = dtc.dockerHelper.Inspect(ctx, ids, "container", &details) + framework.ExpectNoError(err) + + hasCustomMount := false + for _, m := range details[0].Mounts { + if m.Destination == "/custom-workspace" { + hasCustomMount = true + break + } + } + gomega.Expect(hasCustomMount).To(gomega.BeTrue()) + }, ginkgo.SpecTimeout(framework.TimeoutShort())) + ginkgo.It("multi devcontainer selection", func(ctx context.Context) { tempDir, err := setupWorkspace( "tests/up/testdata/docker-multi-devcontainer", diff --git a/e2e/tests/up/testdata/docker-workspace-mount/.devcontainer.json b/e2e/tests/up/testdata/docker-workspace-mount/.devcontainer.json new file mode 100644 index 000000000..e24babbdb --- /dev/null +++ b/e2e/tests/up/testdata/docker-workspace-mount/.devcontainer.json @@ -0,0 +1,6 @@ +{ + "name": "Go", + "image": "ghcr.io/devsy-org/test-images/go:1", + "workspaceMount": "type=bind,source=${localWorkspaceFolder},target=/custom-workspace", + "workspaceFolder": "/custom-workspace" +} diff --git a/pkg/devcontainer/run_test.go b/pkg/devcontainer/run_test.go index 1f9746a16..ffe987e8c 100644 --- a/pkg/devcontainer/run_test.go +++ b/pkg/devcontainer/run_test.go @@ -130,3 +130,71 @@ func searchString(s, substr string) bool { } return false } + +func TestGetWorkspace_CustomWorkspaceMount(t *testing.T) { + customMount := "type=bind,source=/host/src,target=/custom-ws" + conf := &config.DevContainerConfig{ + NonComposeBase: config.NonComposeBase{ + WorkspaceMount: customMount, + }, + } + + mount, folder := getWorkspace("/ignored", "ws-id", conf) + + if mount != customMount { + t.Fatalf("expected raw workspaceMount string, got %q", mount) + } + if folder != "/custom-ws" { + t.Fatalf("expected target /custom-ws, got %q", folder) + } +} + +func TestGetWorkspace_DefaultMount(t *testing.T) { + conf := &config.DevContainerConfig{} + + mount, folder := getWorkspace("/home/user/project", "abc123", conf) + + if !contains(mount, "type=bind") || !contains(mount, "source=/home/user/project") { + t.Fatalf("expected bind mount with source, got %q", mount) + } + if !contains(mount, "target=/workspaces/abc123") { + t.Fatalf("expected target /workspaces/abc123, got %q", mount) + } + if folder != "/workspaces/abc123" { + t.Fatalf("expected /workspaces/abc123, got %q", folder) + } +} + +func TestGetWorkspace_DefaultMountWithWorkspaceFolder(t *testing.T) { + conf := &config.DevContainerConfig{ + DevContainerConfigBase: config.DevContainerConfigBase{ + WorkspaceFolder: "/app", + }, + } + + mount, folder := getWorkspace("/home/user/project", "ws-id", conf) + + if !contains(mount, "type=bind") || !contains(mount, "target=/app") { + t.Fatalf("expected bind mount with target /app, got %q", mount) + } + if folder != "/app" { + t.Fatalf("expected /app, got %q", folder) + } +} + +func TestGetWorkspace_EmptyWorkspaceMount(t *testing.T) { + conf := &config.DevContainerConfig{ + NonComposeBase: config.NonComposeBase{ + WorkspaceMount: "", + }, + } + + mount, folder := getWorkspace("/home/user/project", "ws-id", conf) + + if folder != "/workspaces/ws-id" { + t.Fatalf("expected default folder, got %q", folder) + } + if !contains(mount, "type=bind") { + t.Fatalf("expected default bind mount, got %q", mount) + } +}