Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions pkg/e2e/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,25 @@ func TestBuildPlatformsStandardErrors(t *testing.T) {
})

t.Run("builder does not support multi-arch", func(t *testing.T) {
// Docker Desktop with containerd image store uses the docker driver
// but supports multi-platform builds, so this error won't occur.
inspect := c.RunDockerCmd(t, "buildx", "inspect", "--bootstrap")
output := inspect.Stdout()
isDockerDriver := false
platforms := ""
for _, line := range strings.Split(output, "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "Driver:") {
isDockerDriver = strings.TrimSpace(strings.TrimPrefix(trimmed, "Driver:")) == "docker"
}
if strings.HasPrefix(trimmed, "Platforms:") {
platforms = trimmed
}
}
if isDockerDriver && strings.Contains(platforms, "linux/amd64") && strings.Contains(platforms, "linux/arm64") {
t.Skip("docker driver supports multi-platform (containerd image store enabled)")
}

res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test/platforms", "build")
res.Assert(t, icmd.Expected{
ExitCode: 1,
Expand Down
27 changes: 15 additions & 12 deletions pkg/e2e/compose_run_build_once_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,27 @@ func TestRunBuildOnce(t *testing.T) {

t.Run("dependency with pull_policy build is built only once", func(t *testing.T) {
projectName := randomProjectName("build-once")
_ = c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once.yaml", "down", "--rmi", "local", "--remove-orphans", "-v")
res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once.yaml", "--verbose", "run", "--build", "--rm", "curl")
composeFile := "./fixtures/run-test/build-once.yaml"
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "down", "--rmi", "local", "--remove-orphans", "-v")
})
res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "--verbose", "run", "--build", "--rm", "curl")

output := res.Stdout()

nginxBuilds := countServiceBuilds(output, projectName, "nginx")

assert.Equal(t, nginxBuilds, 1, "nginx should build once, built %d times\nOutput:\n%s", nginxBuilds, output)
assert.Assert(t, strings.Contains(res.Stdout(), "curl service"))

c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once.yaml", "down", "--remove-orphans")
})

t.Run("nested dependencies build only once each", func(t *testing.T) {
projectName := randomProjectName("build-nested")
_ = c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-nested.yaml", "down", "--rmi", "local", "--remove-orphans", "-v")
res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-nested.yaml", "--verbose", "run", "--build", "--rm", "app")
composeFile := "./fixtures/run-test/build-once-nested.yaml"
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "down", "--rmi", "local", "--remove-orphans", "-v")
})
res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "--verbose", "run", "--build", "--rm", "app")

output := res.Stdout()

Expand All @@ -64,23 +68,22 @@ func TestRunBuildOnce(t *testing.T) {
assert.Equal(t, apiBuilds, 1, "api should build once, built %d times\nOutput:\n%s", apiBuilds, output)
assert.Equal(t, appBuilds, 1, "app should build once, built %d times\nOutput:\n%s", appBuilds, output)
assert.Assert(t, strings.Contains(output, "App running"))

c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-nested.yaml", "down", "--rmi", "local", "--remove-orphans", "-v")
})

t.Run("service with no dependencies builds once", func(t *testing.T) {
projectName := randomProjectName("build-simple")
_ = c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-no-deps.yaml", "down", "--rmi", "local", "--remove-orphans")
res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-no-deps.yaml", "run", "--build", "--rm", "simple")
composeFile := "./fixtures/run-test/build-once-no-deps.yaml"
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "down", "--rmi", "local", "--remove-orphans", "-v")
})
res := c.RunDockerComposeCmd(t, "-p", projectName, "-f", composeFile, "run", "--build", "--rm", "simple")

output := res.Stdout()

simpleBuilds := countServiceBuilds(output, projectName, "simple")

assert.Equal(t, simpleBuilds, 1, "simple should build once, built %d times\nOutput:\n%s", simpleBuilds, output)
assert.Assert(t, strings.Contains(res.Stdout(), "Simple service"))

c.RunDockerComposeCmd(t, "-p", projectName, "-f", "./fixtures/run-test/build-once-no-deps.yaml", "down", "--remove-orphans")
})
}

Expand Down
12 changes: 10 additions & 2 deletions pkg/e2e/compose_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ import (
"os"
"strings"
"testing"
"time"

"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
"gotest.tools/v3/poll"
)

func TestLocalComposeRun(t *testing.T) {
Expand Down Expand Up @@ -207,8 +209,14 @@ func TestLocalComposeRun(t *testing.T) {

res = c.RunDockerCmd(t, "stop", containerID)
res.Assert(t, icmd.Success)
res = c.RunDockerCmd(t, "ps", "--all", "--filter", "name=run-test-nginx", "--format", "'{{.Names}}'")
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test-nginx"), res.Stdout())
// --rm auto-removal is async, wait for the container to be removed
poll.WaitOn(t, func(l poll.LogT) poll.Result {
res = c.RunDockerCmd(t, "ps", "--all", "--filter", "name=run-test-nginx", "--format", "'{{.Names}}'")
if strings.Contains(res.Stdout(), "run-test-nginx") {
return poll.Continue("container still present: %s", res.Stdout())
}
return poll.Success()
}, poll.WithTimeout(10*time.Second), poll.WithDelay(500*time.Millisecond))
})

t.Run("compose run --env", func(t *testing.T) {
Expand Down
9 changes: 3 additions & 6 deletions pkg/e2e/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,13 @@ func TestDownComposefileInParentFolder(t *testing.T) {
}

func TestAttachRestart(t *testing.T) {
t.Skip("Skipping test until we can fix it")

if _, ok := os.LookupEnv("CI"); ok {
t.Skip("Skipping test on CI... flaky")
}
c := NewParallelCLI(t)

cmd := c.NewDockerComposeCmd(t, "--ansi=never", "--project-directory", "./fixtures/attach-restart", "up")
res := icmd.StartCmd(cmd)
defer c.RunDockerComposeCmd(t, "-p", "attach-restart", "down")
t.Cleanup(func() {
c.RunDockerComposeCmd(t, "-p", "attach-restart", "down")
})

c.WaitForCondition(t, func() (bool, string) {
debug := res.Combined()
Expand Down
5 changes: 3 additions & 2 deletions pkg/e2e/env_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ func TestUnusedMissingEnvFile(t *testing.T) {

func TestRunEnvFile(t *testing.T) {
c := NewParallelCLI(t)
defer c.cleanupWithDown(t, "run_dotenv")
const projectName = "run-dotenv"
defer c.cleanupWithDown(t, projectName)

res := c.RunDockerComposeCmd(t, "--project-directory", "./fixtures/env_file", "run", "serviceC", "env")
res := c.RunDockerComposeCmd(t, "-p", projectName, "--project-directory", "./fixtures/env_file", "run", "--rm", "serviceC", "env")
res.Assert(t, icmd.Expected{Out: "FOO=BAR"})
}
6 changes: 4 additions & 2 deletions pkg/e2e/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
)

func TestComposeModel(t *testing.T) {
t.Skip("waiting for docker-model release")
if _, err := findPluginExecutable(DockerModelExecutableName); err != nil {
t.Skip("docker-model plugin not available")
}
c := NewParallelCLI(t)
defer c.cleanupWithDown(t, "model-test")

c.RunDockerComposeCmd(t, "-f", "./fixtures/model/compose.yaml", "run", "test", "sh", "-c", "curl ${FOO_URL}")
c.RunDockerComposeCmd(t, "-p", "model-test", "-f", "./fixtures/model/compose.yaml", "run", "--rm", "test", "sh", "-c", "curl ${FOO_URL}")
}
1 change: 0 additions & 1 deletion pkg/e2e/networks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ func TestNetworkModes(t *testing.T) {
}

func TestNetworkConfigChanged(t *testing.T) {
t.Skip("unstable")
// fixture is shared with TestNetworks and is not safe to run concurrently
c := NewCLI(t)
const projectName = "network_config_change"
Expand Down
4 changes: 0 additions & 4 deletions pkg/e2e/pause_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"fmt"
"net"
"net/http"
"os"
"testing"
"time"

Expand All @@ -31,9 +30,6 @@ import (
)

func TestPause(t *testing.T) {
if _, ok := os.LookupEnv("CI"); ok {
t.Skip("Skipping test on CI... flaky")
}
cli := NewParallelCLI(t, WithEnv(
"COMPOSE_PROJECT_NAME=e2e-pause",
"COMPOSE_FILE=./fixtures/pause/compose.yaml"))
Expand Down
3 changes: 3 additions & 0 deletions pkg/e2e/ps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func TestPs(t *testing.T) {
c := NewParallelCLI(t)
const projectName = "e2e-ps"

// ensure clean state from any previous failed run
c.RunDockerComposeCmdNoCheck(t, "--project-name", projectName, "down", "--remove-orphans")

res := c.RunDockerComposeCmd(t, "-f", "./fixtures/ps-test/compose.yaml", "--project-name", projectName, "up", "-d")
assert.NilError(t, res.Error)
t.Cleanup(func() {
Expand Down
17 changes: 17 additions & 0 deletions pkg/e2e/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ package e2e

import (
"fmt"
"net/http"
"strings"
"testing"
"time"

"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
"gotest.tools/v3/poll"
)

func TestPublishChecks(t *testing.T) {
Expand Down Expand Up @@ -136,6 +139,20 @@ func TestPublish(t *testing.T) {
c.RunDockerCmd(t, "rm", "--force", registryName)
})

// Wait for registry to be ready
registryURL := "http://" + registry + "/v2/"
poll.WaitOn(t, func(l poll.LogT) poll.Result {
resp, err := http.Get(registryURL) //nolint:gosec,noctx
if err != nil {
return poll.Continue("registry not ready: %v", err)
}
_ = resp.Body.Close()
if resp.StatusCode < 500 {
return poll.Success()
}
return poll.Continue("registry not ready, status %d", resp.StatusCode)
}, poll.WithTimeout(10*time.Second), poll.WithDelay(100*time.Millisecond))
Comment on lines +142 to +154
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The registry readiness poll uses http.Get without any request timeout. If the TCP connection succeeds but the server never responds (or a proxy/network issue stalls reads), this can hang indefinitely and bypass the poll timeout. Use a http.Client with a short Timeout or use the existing HTTPGetWithRetry helper (pkg/e2e/framework.go) so each attempt is bounded by a per-request timeout and the overall poll timeout remains effective.

Copilot uses AI. Check for mistakes.

res := c.RunDockerComposeCmd(t, "-f", "./fixtures/publish/oci/compose.yaml", "-f", "./fixtures/publish/oci/compose-override.yaml",
"-p", projectName, "publish", "--with-env", "--yes", "--insecure-registry", registry+"/test:test")
res.Assert(t, icmd.Expected{ExitCode: 0})
Expand Down
6 changes: 3 additions & 3 deletions pkg/e2e/watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ func TestWatchMultiServices(t *testing.T) {
testFile := filepath.Join(tmpdir, "test")
assert.NilError(t, os.WriteFile(testFile, []byte("test"), 0o600))

cmd := c.NewDockerComposeCmd(t, "-p", projectName, "-f", composeFilePath, "up", "--watch")
cmd := c.NewDockerComposeCmd(t, "-p", projectName, "-f", composeFilePath, "up", "--build", "--watch")
buffer := bytes.NewBuffer(nil)
cmd.Stdout = buffer
watch := icmd.StartCmd(cmd)
Expand All @@ -345,7 +345,7 @@ func TestWatchMultiServices(t *testing.T) {
return poll.Success()
}
return poll.Continue("%v", watch.Stdout())
})
}, poll.WithTimeout(90*time.Second))

waitRebuild := func(service string, expected string) {
poll.WaitOn(t, func(l poll.LogT) poll.Result {
Expand All @@ -354,7 +354,7 @@ func TestWatchMultiServices(t *testing.T) {
return poll.Success()
}
return poll.Continue("%v", cat.Combined())
})
}, poll.WithTimeout(90*time.Second))
}
waitRebuild("a", "test")
waitRebuild("b", "test")
Expand Down
4 changes: 3 additions & 1 deletion pkg/utils/safebuffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ func (b *SafeBuffer) RequireEventuallyContains(t testing.TB, v string) {
}
return poll.Success()
},
poll.WithTimeout(2*time.Second),
// 10s: container startup on Docker Desktop (macOS) with VM overhead
// can take 3-8s vs <1s on native Linux CI
poll.WithTimeout(10*time.Second),
poll.WithDelay(20*time.Millisecond),
)
}
Loading