mirror of
https://github.com/docker/compose.git
synced 2026-08-28 12:23:49 +00:00
fix: make e2e tests pass reliably locally with Docker Desktop
- Fix stale image/container reuse across test runs - Add registry readiness check and async removal polling - Skip multi-arch test when docker driver supports it - Use t.Cleanup for reliable teardown, fix project name mismatches - Re-enable 4 previously skipped tests that now pass Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
This commit is contained in:
parent
66c21c3b6c
commit
8e0d5e17a7
12 changed files with 80 additions and 33 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -36,8 +36,11 @@ 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()
|
||||
|
||||
|
|
@ -45,14 +48,15 @@ func TestRunBuildOnce(t *testing.T) {
|
|||
|
||||
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()
|
||||
|
||||
|
|
@ -64,14 +68,15 @@ 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()
|
||||
|
||||
|
|
@ -79,8 +84,6 @@ func TestRunBuildOnce(t *testing.T) {
|
|||
|
||||
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")
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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))
|
||||
|
||||
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})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue