mirror of
https://github.com/docker/compose.git
synced 2026-08-30 13:31:54 +00:00
test(e2e): run and up scenarios
- compose run splits into seven scenarios (one-off basics, ports, deps, optional deps, quiet pull, --pull always, chained build contexts). The orphan-warning cases fold into the basic scenario: an exited one-off IS the orphan the next run warns about, no second compose file needed. Piped-stdin and stop-signal tests stay legacy. - up/compose_up: unhealthy service, dependency exit (both now also locking that dependents stay in created state), build dependencies (project-scoped image name instead of the fixture's global one), optional dependency, --all-resources, profile targeting (dropping the fixture's global container_names), image-by-ID, exit-code-from (42 and 143), port ranges, stdout/stderr separation (StderrContains joins the vocabulary) and logging-driver reconfiguration (now locking the Recreated expectation). The Ctrl-C dependency test stays legacy. - Fixtures start-fail/, resources/, stop/, port-range/, stdout-stderr/, logging-driver/, profiles/ and most of dependencies/ and run-test/ are removed. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
9409c461f7
commit
ee7f323fed
35 changed files with 347 additions and 490 deletions
|
|
@ -88,6 +88,20 @@ func StdoutContains(sub string) Check {
|
|||
}
|
||||
}
|
||||
|
||||
// StderrContains expects the command's stderr to contain a string, e.g. a
|
||||
// container's stderr stream relayed by up.
|
||||
func StderrContains(sub string) Check {
|
||||
return Check{
|
||||
name: fmt.Sprintf("stderr contains %q", sub),
|
||||
fn: func(ctx *CheckContext) error {
|
||||
if !strings.Contains(ctx.result.Stderr(), sub) {
|
||||
return fmt.Errorf("not found in stderr")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// OutputNotContains expects the command output not to contain a string.
|
||||
func OutputNotContains(sub string) Check {
|
||||
return Check{
|
||||
|
|
|
|||
|
|
@ -17,227 +17,157 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/icmd"
|
||||
"gotest.tools/v3/poll"
|
||||
)
|
||||
|
||||
func TestLocalComposeRun(t *testing.T) {
|
||||
func TestComposeRun(t *testing.T) {
|
||||
s := NewScenario(t, "run must execute a one-off with the service command or an override, starting its dependencies")
|
||||
s.Step("run executes the service's own command and starts its dependency",
|
||||
ComposeCmd("run", "back"),
|
||||
StdoutContains("Hello there!!"),
|
||||
OutputNotContains("orphan"),
|
||||
ServiceState("db", "running"),
|
||||
OneOffState("back", "exited"),
|
||||
ServiceNotCreated("front")).
|
||||
Step("run with an override command warns about the previous one-off, now an orphan",
|
||||
ComposeCmd("run", "back", "echo", "Hello one more time"),
|
||||
StdoutContains("Hello one more time"),
|
||||
OutputContains("orphan")).
|
||||
Step("COMPOSE_IGNORE_ORPHANS silences the warning",
|
||||
ComposeCmd("run", "back", "echo", "Hello again").WithEnv("COMPOSE_IGNORE_ORPHANS=True"),
|
||||
StdoutContains("Hello again"),
|
||||
OutputNotContains("orphan")).
|
||||
Step("run --rm leaves the earlier one-offs alone and removes its own container",
|
||||
ComposeCmd("run", "--rm", "back", "echo", "Hello and gone").WithEnv("COMPOSE_IGNORE_ORPHANS=True"),
|
||||
StdoutContains("Hello and gone"),
|
||||
OneOffsUntouched("back")).
|
||||
Step("run --volumes bind-mounts the requested host path",
|
||||
ComposeCmd("run", "--volumes", s.Dir()+":/foo", "back", "/bin/sh", "-c", "ls /foo").WithEnv("COMPOSE_IGNORE_ORPHANS=True"),
|
||||
StdoutContains("compose.yaml")).
|
||||
Step("run --env-from-file injects the file's variables",
|
||||
ComposeCmd("run", "--env-from-file", s.Dir()+"/run.env", "front", "env").WithEnv("COMPOSE_IGNORE_ORPHANS=True"),
|
||||
StdoutContains("FOO=BAR")).
|
||||
Step("run --env injects the variable",
|
||||
ComposeCmd("run", "--env", "FOO=BAR", "front", "env").WithEnv("COMPOSE_IGNORE_ORPHANS=True"),
|
||||
StdoutContains("FOO=BAR"))
|
||||
}
|
||||
|
||||
func TestComposeRunPorts(t *testing.T) {
|
||||
s := NewScenario(t, "run must only publish ports when asked: --publish for ad-hoc, --service-ports for the model's")
|
||||
s.Step("run --publish maps the requested port, not the model's",
|
||||
ComposeCmd("run", "--publish", "8081:80", "-d", "back", "/bin/sh", "-c", "sleep 30")).
|
||||
Step("the ad-hoc mapping is live",
|
||||
DockerCmd("ps", "--filter", "label=com.docker.compose.project="+s.Project()),
|
||||
OutputContains("8081->80/tcp"),
|
||||
OutputNotContains("8082->80/tcp")).
|
||||
Step("run --service-ports maps the model's ports",
|
||||
ComposeCmd("run", "--service-ports", "-d", "back", "/bin/sh", "-c", "sleep 30")).
|
||||
Step("the model's mapping is live",
|
||||
DockerCmd("ps", "--filter", "label=com.docker.compose.project="+s.Project()),
|
||||
OutputContains("8082->80/tcp"))
|
||||
}
|
||||
|
||||
func TestComposeRunDeps(t *testing.T) {
|
||||
// Regression test for https://github.com/docker/compose/issues/9459
|
||||
// run used to start other services of the project beyond the target's
|
||||
// dependency chain.
|
||||
NewScenario(t, "run must start the target's dependencies and nothing else, unless --no-deps").
|
||||
Step("run starts the shared dependency but not the sibling service",
|
||||
ComposeCmd("run", "service_a"),
|
||||
OutputContains("shared_dep"),
|
||||
OutputNotContains("service_b"),
|
||||
ServiceNotCreated("service_b")).
|
||||
Step("run --no-deps starts nothing but the one-off",
|
||||
ComposeCmd("run", "--no-deps", "service_a").WithEnv("COMPOSE_IGNORE_ORPHANS=True"),
|
||||
OutputNotContains("service_b"),
|
||||
OutputNotContains("shared_dep"),
|
||||
NotRecreated("shared_dep"),
|
||||
ServiceNotCreated("service_b"))
|
||||
}
|
||||
|
||||
func TestComposeRunNotRequiredDeps(t *testing.T) {
|
||||
NewScenario(t, "run must skip a dependency marked required: false when its profile is inactive").
|
||||
Step("run executes the service without materializing the optional dependency",
|
||||
ComposeCmd("run", "foo"),
|
||||
OutputContains("foo"),
|
||||
ServiceNotCreated("bar"))
|
||||
}
|
||||
|
||||
func TestComposeRunQuietPull(t *testing.T) {
|
||||
NewScenario(t, "run --quiet-pull and COMPOSE_PROGRESS=quiet must silence pull progress at two levels").
|
||||
Step("start without the image locally",
|
||||
ComposeCmd("down", "--rmi", "all")).
|
||||
Step("--quiet-pull keeps the decision but drops the layer progress",
|
||||
ComposeCmd("run", "--quiet-pull", "backend"),
|
||||
OutputNotContains("Pull complete"),
|
||||
OutputContains("Pulled")).
|
||||
Step("remove the image again",
|
||||
ComposeCmd("down", "--rmi", "all")).
|
||||
Step("COMPOSE_PROGRESS=quiet silences the pull entirely",
|
||||
ComposeCmd("run", "backend").WithEnv("COMPOSE_PROGRESS=quiet"),
|
||||
OutputNotContains("Pull complete"),
|
||||
OutputNotContains("Pulled"))
|
||||
}
|
||||
|
||||
func TestComposeRunPullAlways(t *testing.T) {
|
||||
NewScenario(t, "run --pull always must pull the image even when present locally").
|
||||
Step("run reports the pull it was asked to always perform",
|
||||
ComposeCmd("run", "--pull", "always", "backend"),
|
||||
OutputContains("Image nginx Pulling"),
|
||||
OutputContains("Image nginx Pulled"))
|
||||
}
|
||||
|
||||
func TestComposeRunBuild(t *testing.T) {
|
||||
s := NewScenario(t, "run must build a service whose image comes from another service's build context")
|
||||
s.Defer(
|
||||
DockerCmd("image", "rm", "-f", s.Project()+"-build").MayFail(),
|
||||
DockerCmd("image", "rm", "-f", s.Project()+"-build_base").MayFail()).
|
||||
Step("run builds the chained images and executes the command",
|
||||
ComposeCmd("run", "build", "echo", "hello world"),
|
||||
StdoutContains("hello world"))
|
||||
}
|
||||
|
||||
func TestComposeRunRmStopSignal(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
defer c.cleanupWithDown(t, "run-test")
|
||||
|
||||
t.Run("compose run", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "back")
|
||||
lines := Lines(res.Stdout())
|
||||
assert.Equal(t, lines[len(lines)-1], "Hello there!!", res.Stdout())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "orphan"))
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo",
|
||||
"Hello one more time")
|
||||
lines = Lines(res.Stdout())
|
||||
assert.Equal(t, lines[len(lines)-1], "Hello one more time", res.Stdout())
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "orphan"))
|
||||
projectName := "run-test"
|
||||
t.Cleanup(func() {
|
||||
c.cleanupWithDown(t, projectName)
|
||||
})
|
||||
|
||||
t.Run("check run container exited", func(t *testing.T) {
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
lines := Lines(res.Stdout())
|
||||
var runContainerID string
|
||||
var truncatedSlug string
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
containerID := fields[len(fields)-1]
|
||||
assert.Assert(t, !strings.HasPrefix(containerID, "run-test-front"))
|
||||
if strings.HasPrefix(containerID, "run-test-back") {
|
||||
// only the one-off container for back service
|
||||
assert.Assert(t, strings.HasPrefix(containerID, "run-test-back-run-"), containerID)
|
||||
truncatedSlug = strings.Replace(containerID, "run-test-back-run-", "", 1)
|
||||
runContainerID = containerID
|
||||
}
|
||||
if strings.HasPrefix(containerID, "run-test-db-1") {
|
||||
assert.Assert(t, strings.Contains(line, "Up"), line)
|
||||
}
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "-f", "./fixtures/ps-test/compose.yaml", "run", "--rm", "-d", "nginx")
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerCmd(t, "ps", "--quiet", "--filter", "name=run-test-nginx")
|
||||
containerID := strings.TrimSpace(res.Stdout())
|
||||
|
||||
res = c.RunDockerCmd(t, "stop", containerID)
|
||||
res.Assert(t, icmd.Success)
|
||||
// --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())
|
||||
}
|
||||
assert.Assert(t, runContainerID != "")
|
||||
res = c.RunDockerCmd(t, "inspect", runContainerID)
|
||||
res.Assert(t, icmd.Expected{Out: ` "Status": "exited"`})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": "run-test"`})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.oneoff": "True",`})
|
||||
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.slug": "` + truncatedSlug})
|
||||
})
|
||||
return poll.Success()
|
||||
}, poll.WithTimeout(10*time.Second), poll.WithDelay(500*time.Millisecond))
|
||||
}
|
||||
|
||||
t.Run("compose run --rm", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--rm", "back", "echo",
|
||||
"Hello again")
|
||||
lines := Lines(res.Stdout())
|
||||
assert.Equal(t, lines[len(lines)-1], "Hello again", res.Stdout())
|
||||
|
||||
res = c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "run-test-back"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down", "--remove-orphans")
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("compose run --volumes", func(t *testing.T) {
|
||||
wd, err := os.Getwd()
|
||||
assert.NilError(t, err)
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--volumes", wd+":/foo",
|
||||
"back", "/bin/sh", "-c", "ls /foo")
|
||||
res.Assert(t, icmd.Expected{Out: "compose_run_test.go"})
|
||||
|
||||
res = c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "run-test-back"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("compose run --publish", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/ports.yaml", "run", "--publish", "8081:80", "-d", "back",
|
||||
"/bin/sh", "-c", "sleep 1")
|
||||
res := c.RunDockerCmd(t, "ps")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "8081->80/tcp"), res.Stdout())
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "8082->80/tcp"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("compose run --service-ports", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/ports.yaml", "run", "--service-ports", "-d", "back",
|
||||
"/bin/sh", "-c", "sleep 1")
|
||||
res := c.RunDockerCmd(t, "ps")
|
||||
assert.Assert(t, strings.Contains(res.Stdout(), "8082->80/tcp"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("compose run orphan", func(t *testing.T) {
|
||||
// Use different compose files to get an orphan container
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/orphan.yaml", "run", "simple")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "orphan"))
|
||||
|
||||
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "back", "echo", "Hello")
|
||||
res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) {
|
||||
cmd.Env = append(cmd.Env, "COMPOSE_IGNORE_ORPHANS=True")
|
||||
})
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "orphan"))
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down")
|
||||
icmd.RunCmd(cmd, func(c *icmd.Cmd) {
|
||||
c.Env = append(c.Env, "COMPOSE_REMOVE_ORPHANS=True")
|
||||
})
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
|
||||
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("run starts only container and dependencies", func(t *testing.T) {
|
||||
// ensure that even if another service is up run does not start it: https://github.com/docker/compose/issues/9459
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "up", "service_b", "--menu=false")
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "run", "service_a")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "shared_dep"), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "service_b"), res.Combined())
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "down", "--remove-orphans")
|
||||
})
|
||||
|
||||
t.Run("run without dependencies", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "run", "--no-deps", "service_a")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "shared_dep"), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "service_b"), res.Combined())
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/deps.yaml", "down", "--remove-orphans")
|
||||
})
|
||||
|
||||
t.Run("run with not required dependency", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/deps-not-required.yaml", "run", "foo")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "foo"), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "bar"), res.Combined())
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/deps-not-required.yaml", "down", "--remove-orphans")
|
||||
})
|
||||
|
||||
t.Run("--quiet-pull", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/quiet-pull.yaml", "down", "--remove-orphans", "--rmi", "all")
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/quiet-pull.yaml", "run", "--quiet-pull", "backend")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "Pull complete"), res.Combined())
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "Pulled"), res.Combined())
|
||||
})
|
||||
|
||||
t.Run("COMPOSE_PROGRESS quiet", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/quiet-pull.yaml", "down", "--remove-orphans", "--rmi", "all")
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/run-test/quiet-pull.yaml", "run", "backend")
|
||||
res = icmd.RunCmd(cmd, func(c *icmd.Cmd) {
|
||||
c.Env = append(c.Env, "COMPOSE_PROGRESS=quiet")
|
||||
})
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "Pull complete"), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), "Pulled"), res.Combined())
|
||||
})
|
||||
|
||||
t.Run("--pull", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/pull.yaml", "down", "--remove-orphans", "--rmi", "all")
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/pull.yaml", "run", "--pull", "always", "backend")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "Image nginx Pulling"), res.Combined())
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "Image nginx Pulled"), res.Combined())
|
||||
})
|
||||
|
||||
t.Run("compose run --env-from-file", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--env-from-file", "./fixtures/run-test/run.env",
|
||||
"front", "env")
|
||||
res.Assert(t, icmd.Expected{Out: "FOO=BAR"})
|
||||
})
|
||||
|
||||
t.Run("compose run -rm with stop signal", func(t *testing.T) {
|
||||
projectName := "run-test"
|
||||
res := c.RunDockerComposeCmd(t, "--project-name", projectName, "-f", "./fixtures/ps-test/compose.yaml", "run", "--rm", "-d", "nginx")
|
||||
res.Assert(t, icmd.Success)
|
||||
|
||||
res = c.RunDockerCmd(t, "ps", "--quiet", "--filter", "name=run-test-nginx")
|
||||
containerID := strings.TrimSpace(res.Stdout())
|
||||
|
||||
res = c.RunDockerCmd(t, "stop", containerID)
|
||||
res.Assert(t, icmd.Success)
|
||||
// --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) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--env", "FOO=BAR",
|
||||
"front", "env")
|
||||
res.Assert(t, icmd.Expected{Out: "FOO=BAR"})
|
||||
})
|
||||
|
||||
t.Run("compose run --build", func(t *testing.T) {
|
||||
c.cleanupWithDown(t, "run-test", "--rmi=local")
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "build", "echo", "hello world")
|
||||
res.Assert(t, icmd.Expected{Out: "hello world"})
|
||||
})
|
||||
func TestComposeRunPipedInput(t *testing.T) {
|
||||
if composeStandaloneMode {
|
||||
t.Skip("Skipping test compose with piped input detection in standalone mode")
|
||||
}
|
||||
c := NewParallelCLI(t)
|
||||
defer c.cleanupWithDown(t, "run-piped-test")
|
||||
|
||||
t.Run("compose run with piped input detection", func(t *testing.T) {
|
||||
if composeStandaloneMode {
|
||||
t.Skip("Skipping test compose with piped input detection in standalone mode")
|
||||
}
|
||||
// Test that piped input is properly detected and TTY is automatically disabled
|
||||
// This tests the logic added in run.go that checks dockerCli.In().IsTerminal()
|
||||
cmd := c.NewCmd("sh", "-c", "echo 'piped-content' | docker compose -f ./fixtures/run-test/piped-test.yaml run --rm piped-test")
|
||||
cmd := c.NewCmd("sh", "-c", "echo 'piped-content' | docker compose -p run-piped-test -f ./fixtures/run-test/piped-test.yaml run --rm piped-test")
|
||||
res := icmd.RunCmd(cmd)
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: "piped-content"})
|
||||
|
|
@ -245,12 +175,9 @@ func TestLocalComposeRun(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("compose run piped input should not allocate TTY", func(t *testing.T) {
|
||||
if composeStandaloneMode {
|
||||
t.Skip("Skipping test compose with piped input detection in standalone mode")
|
||||
}
|
||||
// Test that when stdin is piped, the container correctly detects no TTY
|
||||
// This verifies that the automatic noTty=true setting works correctly
|
||||
cmd := c.NewCmd("sh", "-c", "echo '' | docker compose -f ./fixtures/run-test/piped-test.yaml run --rm tty-test")
|
||||
cmd := c.NewCmd("sh", "-c", "echo '' | docker compose -p run-piped-test -f ./fixtures/run-test/piped-test.yaml run --rm tty-test")
|
||||
res := icmd.RunCmd(cmd)
|
||||
|
||||
res.Assert(t, icmd.Expected{Out: "No TTY detected"})
|
||||
|
|
@ -258,12 +185,9 @@ func TestLocalComposeRun(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("compose run piped input with explicit --tty should fail", func(t *testing.T) {
|
||||
if composeStandaloneMode {
|
||||
t.Skip("Skipping test compose with piped input detection in standalone mode")
|
||||
}
|
||||
// Test that explicitly requesting TTY with piped input fails with proper error message
|
||||
// This should trigger the "input device is not a TTY" error
|
||||
cmd := c.NewCmd("sh", "-c", "echo 'test' | docker compose -f ./fixtures/run-test/piped-test.yaml run --rm --tty piped-test")
|
||||
cmd := c.NewCmd("sh", "-c", "echo 'test' | docker compose -p run-piped-test -f ./fixtures/run-test/piped-test.yaml run --rm --tty piped-test")
|
||||
res := icmd.RunCmd(cmd)
|
||||
|
||||
res.Assert(t, icmd.Expected{
|
||||
|
|
@ -273,12 +197,9 @@ func TestLocalComposeRun(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("compose run piped input with --no-tty=false should fail", func(t *testing.T) {
|
||||
if composeStandaloneMode {
|
||||
t.Skip("Skipping test compose with piped input detection in standalone mode")
|
||||
}
|
||||
// Test that explicitly disabling --no-tty (i.e., requesting TTY) with piped input fails
|
||||
// This should also trigger the "input device is not a TTY" error
|
||||
cmd := c.NewCmd("sh", "-c", "echo 'test' | docker compose -f ./fixtures/run-test/piped-test.yaml run --rm --no-tty=false piped-test")
|
||||
cmd := c.NewCmd("sh", "-c", "echo 'test' | docker compose -p run-piped-test -f ./fixtures/run-test/piped-test.yaml run --rm --no-tty=false piped-test")
|
||||
res := icmd.RunCmd(cmd)
|
||||
|
||||
res.Assert(t, icmd.Expected{
|
||||
|
|
|
|||
|
|
@ -20,95 +20,61 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
func TestUpWait(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
const projectName = "e2e-deps-wait"
|
||||
|
||||
timeout := time.After(30 * time.Second)
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
//nolint:nolintlint,testifylint // helper asserts inside goroutine; acceptable in this e2e test
|
||||
res := c.RunDockerComposeCmd(t, "-f", "fixtures/dependencies/deps-completed-successfully.yaml", "--project-name", projectName, "up", "--wait", "-d")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "e2e-deps-wait-oneshot-1"), res.Combined())
|
||||
done <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatal("test did not finish in time")
|
||||
case <-done:
|
||||
break
|
||||
}
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
s := NewScenario(t, "up --wait must return once dependencies completed and services run")
|
||||
s.Step("up --wait returns with the long-running service up and the oneshot completed",
|
||||
ComposeCmd("up", "--wait", "-d").Within(30*time.Second),
|
||||
OutputContains(s.Project()+"-oneshot-1"),
|
||||
ServiceState("longrunning", "running"),
|
||||
ServiceState("oneshot", "exited"))
|
||||
}
|
||||
|
||||
func TestUpExitCodeFrom(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
const projectName = "e2e-exit-code-from"
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/start-fail/start-depends_on-long-lived.yaml", "--project-name", projectName, "up", "--menu=false", "--exit-code-from=failure", "failure")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 42})
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--remove-orphans")
|
||||
NewScenario(t, "up --exit-code-from must return the selected service's exit code").
|
||||
Step("up returns the failing service's code once it exits",
|
||||
ComposeCmd("up", "--menu=false", "--exit-code-from=failure", "failure").MayFail().Within(60*time.Second),
|
||||
ExitCode(42))
|
||||
}
|
||||
|
||||
func TestUpExitCodeFromContainerKilled(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
const projectName = "e2e-exit-code-from-kill"
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/start-fail/start-depends_on-long-lived.yaml", "--project-name", projectName, "up", "--menu=false", "--exit-code-from=test")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 143})
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--remove-orphans")
|
||||
NewScenario(t, "up --exit-code-from must report 143 for a service stopped by the abort").
|
||||
Step("the watched long-lived service is stopped when another exits",
|
||||
ComposeCmd("up", "--menu=false", "--exit-code-from=test").MayFail().Within(60*time.Second),
|
||||
ExitCode(143))
|
||||
}
|
||||
|
||||
func TestPortRange(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
const projectName = "e2e-port-range"
|
||||
|
||||
reset := func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--remove-orphans", "--timeout=0")
|
||||
}
|
||||
reset()
|
||||
t.Cleanup(reset)
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/port-range/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
res.Assert(t, icmd.Success)
|
||||
NewScenario(t, "a published port range must accommodate scaled replicas and single ports alike").
|
||||
Step("up binds every replica within the range",
|
||||
ComposeCmd("up", "-d"),
|
||||
ServiceScale("a", 5),
|
||||
ServiceState("b", "running"),
|
||||
ServiceState("c", "running"))
|
||||
}
|
||||
|
||||
func TestStdoutStderr(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
const projectName = "e2e-stdout-stderr"
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/stdout-stderr/compose.yaml", "--project-name", projectName, "up", "--menu=false")
|
||||
res.Assert(t, icmd.Expected{Out: "log to stdout", Err: "log to stderr"})
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--remove-orphans")
|
||||
NewScenario(t, "up must relay each container stream to its own: stdout to stdout, stderr to stderr").
|
||||
Step("the two streams arrive separated",
|
||||
ComposeCmd("up", "--menu=false"),
|
||||
StdoutContains("log to stdout"),
|
||||
StderrContains("log to stderr"))
|
||||
}
|
||||
|
||||
func TestLoggingDriver(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "e2e-logging-driver"
|
||||
defer c.cleanupWithDown(t, projectName)
|
||||
|
||||
host := "HOST=127.0.0.1"
|
||||
res := c.RunDockerCmd(t, "info", "-f", "{{.OperatingSystem}}")
|
||||
os := res.Stdout()
|
||||
if strings.TrimSpace(os) == "Docker Desktop" {
|
||||
host = "HOST=host.docker.internal"
|
||||
s := NewScenario(t, "a logging-driver address change must reconfigure the service on the next up")
|
||||
host := "127.0.0.1"
|
||||
if strings.Contains(s.CLI().RunDockerCmd(t, "info", "-f", "{{.OperatingSystem}}").Stdout(), "Docker Desktop") {
|
||||
host = "host.docker.internal"
|
||||
}
|
||||
|
||||
cmd := c.NewDockerComposeCmd(t, "-f", "fixtures/logging-driver/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
cmd.Env = append(cmd.Env, host, "BAR=foo")
|
||||
icmd.RunCmd(cmd).Assert(t, icmd.Success)
|
||||
|
||||
cmd = c.NewDockerComposeCmd(t, "-f", "fixtures/logging-driver/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
cmd.Env = append(cmd.Env, host, "BAR=zot")
|
||||
icmd.RunCmd(cmd).Assert(t, icmd.Success)
|
||||
s.Env("HOST="+host).
|
||||
Step("up starts the log collector and the app",
|
||||
ComposeCmd("up", "-d").WithEnv("BAR=foo"),
|
||||
ServiceState("fluentbit", "running"),
|
||||
ServiceState("app", "running")).
|
||||
Step("a collector config change is applied by recreating it",
|
||||
ComposeCmd("up", "-d").WithEnv("BAR=zot"),
|
||||
Recreated("fluentbit"),
|
||||
ServiceState("app", "running"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
# Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM busybox:1.35.0
|
||||
RUN echo "hello"
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
services:
|
||||
a:
|
||||
image: nginx:alpine
|
||||
scale: 5
|
||||
ports:
|
||||
- "6005-6015:80"
|
||||
|
||||
b:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- 80
|
||||
|
||||
c:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- 80
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
services:
|
||||
back:
|
||||
image: alpine
|
||||
command: echo "Hello there!!"
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- backnet
|
||||
db:
|
||||
image: nginx:alpine
|
||||
networks:
|
||||
- backnet
|
||||
volumes:
|
||||
- data:/test
|
||||
front:
|
||||
image: nginx:alpine
|
||||
networks:
|
||||
- frontnet
|
||||
build:
|
||||
build:
|
||||
dockerfile_inline: "FROM base"
|
||||
additional_contexts:
|
||||
base: "service:build_base"
|
||||
build_base:
|
||||
build:
|
||||
dockerfile_inline: "FROM alpine"
|
||||
networks:
|
||||
frontnet:
|
||||
backnet:
|
||||
volumes:
|
||||
data:
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
services:
|
||||
simple:
|
||||
image: alpine
|
||||
command: echo "Hi there!!"
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
services:
|
||||
backend:
|
||||
image: hello-world
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
>&2 echo "log to stderr"
|
||||
echo "log to stdout"
|
||||
14
pkg/e2e/testdata/TestComposeRun/compose.yaml
vendored
Normal file
14
pkg/e2e/testdata/TestComposeRun/compose.yaml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
services:
|
||||
back:
|
||||
image: alpine
|
||||
command: echo "Hello there!!"
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
front:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
|
|
@ -1 +1,2 @@
|
|||
FOO=BAR
|
||||
|
||||
9
pkg/e2e/testdata/TestComposeRunBuild/compose.yaml
vendored
Normal file
9
pkg/e2e/testdata/TestComposeRunBuild/compose.yaml
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
services:
|
||||
build:
|
||||
build:
|
||||
dockerfile_inline: "FROM base"
|
||||
additional_contexts:
|
||||
base: "service:build_base"
|
||||
build_base:
|
||||
build:
|
||||
dockerfile_inline: "FROM alpine"
|
||||
|
|
@ -10,4 +10,4 @@ services:
|
|||
depends_on:
|
||||
- shared_dep
|
||||
shared_dep:
|
||||
image: bash
|
||||
image: bash
|
||||
|
|
@ -7,5 +7,7 @@ services:
|
|||
required: false
|
||||
condition: service_healthy
|
||||
bar:
|
||||
image: nginx:alpine
|
||||
profiles: [not-required]
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
profiles: [not-required]
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
services:
|
||||
backend:
|
||||
image: nginx
|
||||
command: nginx -t
|
||||
command: nginx -t
|
||||
3
pkg/e2e/testdata/TestComposeRunQuietPull/compose.yaml
vendored
Normal file
3
pkg/e2e/testdata/TestComposeRunQuietPull/compose.yaml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
services:
|
||||
backend:
|
||||
image: hello-world
|
||||
|
|
@ -8,7 +8,9 @@ services:
|
|||
FOO: ${BAR}
|
||||
|
||||
app:
|
||||
image: nginx
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
depends_on:
|
||||
fluentbit:
|
||||
condition: service_started
|
||||
22
pkg/e2e/testdata/TestPortRange/compose.yaml
vendored
Normal file
22
pkg/e2e/testdata/TestPortRange/compose.yaml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
services:
|
||||
a:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
scale: 5
|
||||
ports:
|
||||
- "6005-6015:80"
|
||||
|
||||
b:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
ports:
|
||||
- 80
|
||||
|
||||
c:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
ports:
|
||||
- 80
|
||||
5
pkg/e2e/testdata/TestScaleDoesntRecreate/compose.yaml
vendored
Normal file
5
pkg/e2e/testdata/TestScaleDoesntRecreate/compose.yaml
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
services:
|
||||
simple:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
3
pkg/e2e/testdata/TestStdoutStderr/log_to_stderr.sh
vendored
Normal file
3
pkg/e2e/testdata/TestStdoutStderr/log_to_stderr.sh
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
>&2 echo "log to stderr"
|
||||
echo "log to stdout"
|
||||
|
||||
14
pkg/e2e/testdata/TestUpExitCodeFromContainerKilled/compose.yaml
vendored
Normal file
14
pkg/e2e/testdata/TestUpExitCodeFromContainerKilled/compose.yaml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
services:
|
||||
safe:
|
||||
image: 'alpine'
|
||||
init: true
|
||||
command: ['/bin/sh', '-c', 'sleep infinity'] # never exiting
|
||||
failure:
|
||||
image: 'alpine'
|
||||
init: true
|
||||
command: ['/bin/sh', '-c', 'sleep 1 ; echo "exiting with error" ; exit 42']
|
||||
test:
|
||||
image: 'alpine'
|
||||
init: true
|
||||
command: ['/bin/sh', '-c', 'sleep 99999 ; echo "tests are OK"'] # very long job
|
||||
depends_on: [safe]
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
services:
|
||||
foo:
|
||||
container_name: foo_c
|
||||
profiles: [ test ]
|
||||
image: alpine
|
||||
depends_on: [ db ]
|
||||
|
||||
bar:
|
||||
container_name: bar_c
|
||||
profiles: [ test ]
|
||||
image: alpine
|
||||
|
||||
|
||||
db:
|
||||
container_name: db_c
|
||||
image: alpine
|
||||
image: alpine
|
||||
|
|
@ -2,4 +2,4 @@ volumes:
|
|||
my_vol: {}
|
||||
|
||||
networks:
|
||||
my_net: {}
|
||||
my_net: {}
|
||||
3
pkg/e2e/testdata/TestUpWithBuildDependencies/Dockerfile
vendored
Normal file
3
pkg/e2e/testdata/TestUpWithBuildDependencies/Dockerfile
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
FROM busybox:1.35.0
|
||||
RUN echo "hello"
|
||||
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
services:
|
||||
foo:
|
||||
image: built-image-dependency
|
||||
image: ${BUILT_IMAGE}
|
||||
build:
|
||||
context: .
|
||||
bar:
|
||||
image: built-image-dependency
|
||||
image: ${BUILT_IMAGE}
|
||||
depends_on:
|
||||
- foo
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
services:
|
||||
web:
|
||||
image: nginx:alpine
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
db:
|
||||
image: alpine
|
||||
command: sh -c "exit 1"
|
||||
|
||||
13
pkg/e2e/testdata/TestUpWithDependencyNotRequired/compose.yaml
vendored
Normal file
13
pkg/e2e/testdata/TestUpWithDependencyNotRequired/compose.yaml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
services:
|
||||
foo:
|
||||
image: bash
|
||||
command: echo "foo"
|
||||
depends_on:
|
||||
bar:
|
||||
required: false
|
||||
condition: service_healthy
|
||||
bar:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
profiles: [not-required]
|
||||
|
|
@ -21,7 +21,6 @@ package e2e
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
|
@ -29,19 +28,17 @@ import (
|
|||
"time"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/icmd"
|
||||
|
||||
"github.com/docker/compose/v5/pkg/utils"
|
||||
)
|
||||
|
||||
func TestUpServiceUnhealthy(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
const projectName = "e2e-start-fail"
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/start-fail/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `container e2e-start-fail-fail-1 is unhealthy`})
|
||||
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
s := NewScenario(t, "up must fail when a service never turns healthy")
|
||||
s.Step("up reports the unhealthy container and fails",
|
||||
ComposeCmd("up", "-d").MayFail().Within(60*time.Second),
|
||||
ExitCode(1),
|
||||
OutputContains("container "+s.Project()+"-fail-1 is unhealthy"),
|
||||
ServiceState("depends", "created"))
|
||||
}
|
||||
|
||||
func TestUpDependenciesNotStopped(t *testing.T) {
|
||||
|
|
@ -107,118 +104,74 @@ func TestUpDependenciesNotStopped(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpWithBuildDependencies(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("up with service using image build by an another service", func(t *testing.T) {
|
||||
// ensure local test run does not reuse previously build image
|
||||
c.RunDockerOrExitError(t, "rmi", "built-image-dependency")
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/dependencies",
|
||||
"-f", "fixtures/dependencies/service-image-depends-on.yaml", "up", "-d")
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/dependencies",
|
||||
"-f", "fixtures/dependencies/service-image-depends-on.yaml", "down", "--rmi", "all")
|
||||
})
|
||||
|
||||
res.Assert(t, icmd.Success)
|
||||
})
|
||||
s := NewScenario(t, "up must build a service's image before starting another service that reuses it")
|
||||
image := s.Project() + "-built"
|
||||
s.Env("BUILT_IMAGE="+image).
|
||||
Defer(DockerCmd("image", "rm", "-f", image).MayFail()).
|
||||
Step("up builds once and starts both services from the built image",
|
||||
ComposeCmd("up", "-d"),
|
||||
ImageExists(image))
|
||||
}
|
||||
|
||||
func TestUpWithDependencyExit(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
t.Run("up with dependency to exit before being healthy", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/dependencies",
|
||||
"-f", "fixtures/dependencies/dependency-exit.yaml", "up", "-d")
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", "dependencies", "down")
|
||||
})
|
||||
|
||||
res.Assert(t, icmd.Expected{ExitCode: 1, Err: "dependency failed to start: container dependencies-db-1 exited (1)"})
|
||||
})
|
||||
s := NewScenario(t, "up must fail when a dependency exits before turning healthy")
|
||||
s.Step("up reports the exited dependency and fails",
|
||||
ComposeCmd("up", "-d").MayFail(),
|
||||
ExitCode(1),
|
||||
OutputContains("dependency failed to start: container "+s.Project()+"-db-1 exited (1)"),
|
||||
ServiceState("web", "created"))
|
||||
}
|
||||
|
||||
func TestScaleDoesntRecreate(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-scale"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "fixtures/simple-composefile/compose.yaml", "--project-name", projectName, "up", "-d")
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "fixtures/simple-composefile/compose.yaml", "--project-name", projectName, "up", "--scale", "simple=2", "-d")
|
||||
assert.Check(t, !strings.Contains(res.Combined(), "Recreated"))
|
||||
NewScenario(t, "scaling up must add a replica without recreating the existing one").
|
||||
Step("up starts the first replica",
|
||||
ComposeCmd("up", "-d"),
|
||||
ReplicaNumbers("simple", 1)).
|
||||
Step("up --scale adds the second replica, keeping the first",
|
||||
ComposeCmd("up", "--scale", "simple=2", "-d"),
|
||||
ReplicaNumbers("simple", 1, 2),
|
||||
OutputNotContains("Recreated"))
|
||||
}
|
||||
|
||||
func TestUpWithDependencyNotRequired(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-dependency-not-required"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/deps-not-required.yaml", "--project-name", projectName,
|
||||
"--profile", "not-required", "up", "-d")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), "foo"), res.Combined())
|
||||
assert.Assert(t, strings.Contains(res.Combined(), " optional dependency \"bar\" failed to start"), res.Combined())
|
||||
NewScenario(t, "up must start the service even when an optional dependency cannot").
|
||||
Step("up succeeds, reporting the optional dependency failure",
|
||||
ComposeCmd("--profile", "not-required", "up", "-d"),
|
||||
OutputContains("foo"),
|
||||
OutputContains(`optional dependency "bar" failed to start`))
|
||||
}
|
||||
|
||||
func TestUpWithAllResources(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-all-resources"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "-v")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/resources/compose.yaml", "--all-resources", "--project-name", projectName, "up")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), fmt.Sprintf(`Volume %s_my_vol Created`, projectName)), res.Combined())
|
||||
assert.Assert(t, strings.Contains(res.Combined(), fmt.Sprintf(`Network %s_my_net Created`, projectName)), res.Combined())
|
||||
s := NewScenario(t, "up --all-resources must create volumes and networks no service uses")
|
||||
s.Step("up creates the unused volume and network",
|
||||
ComposeCmd("--all-resources", "up"),
|
||||
OutputContains("Volume "+s.Project()+"_my_vol Created"),
|
||||
OutputContains("Network "+s.Project()+"_my_net Created"))
|
||||
}
|
||||
|
||||
func TestUpProfile(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-up-profile"
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "--profile", "test", "down", "-v")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/profiles/docker-compose.yaml", "--project-name", projectName, "up", "foo")
|
||||
assert.Assert(t, strings.Contains(res.Combined(), `Container db_c Created`), res.Combined())
|
||||
assert.Assert(t, strings.Contains(res.Combined(), `Container foo_c Created`), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), `Container bar_c Created`), res.Combined())
|
||||
NewScenario(t, "up on a profiled service must start it and its dependencies, not its profile siblings").
|
||||
Step("up starts the target and its dependency only",
|
||||
ComposeCmd("up", "foo"),
|
||||
ServiceState("foo", "exited"),
|
||||
ServiceState("db", "exited"),
|
||||
ServiceNotCreated("bar"))
|
||||
}
|
||||
|
||||
func TestUpImageID(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-up-image-id"
|
||||
|
||||
digest := strings.TrimSpace(c.RunDockerCmd(t, "image", "inspect", "alpine", "-f", "{{ .ID }}").Stdout())
|
||||
s := NewScenario(t, "a service image referenced by its bare ID must be usable")
|
||||
digest := strings.TrimSpace(s.CLI().RunDockerCmd(t, "image", "inspect", "alpine", "-f", "{{ .ID }}").Stdout())
|
||||
_, id, _ := strings.Cut(digest, ":")
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "-v")
|
||||
})
|
||||
|
||||
c = NewCLI(t, WithEnv(fmt.Sprintf("ID=%s", id)))
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-composefile/id.yaml", "--project-name", projectName, "up")
|
||||
s.Env("ID="+id).
|
||||
Step("up runs the container from the image ID",
|
||||
ComposeCmd("up"))
|
||||
}
|
||||
|
||||
func TestUpStopWithLogsMixed(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-stop-logs"
|
||||
|
||||
t.Cleanup(func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "-v")
|
||||
})
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/stop/compose.yaml", "--project-name", projectName, "up", "--abort-on-container-exit")
|
||||
// assert we still get service2 logs after service 1 Stopped event
|
||||
res.Assert(t, icmd.Expected{
|
||||
Err: "Container compose-e2e-stop-logs-service1-1 Stopped",
|
||||
})
|
||||
// assert we get stop hook logs
|
||||
res.Assert(t, icmd.Expected{Out: "service2-1 -> | stop hook running...\nservice2-1 | 64 bytes"})
|
||||
s := NewScenario(t, "on abort, logs of surviving services must keep flowing while others stop, hooks included")
|
||||
s.Step("up aborts on the first exit but still relays service2's logs and stop hook",
|
||||
ComposeCmd("up", "--abort-on-container-exit").Within(60*time.Second),
|
||||
StderrContains("Container "+s.Project()+"-service1-1 Stopped"),
|
||||
StdoutContains("stop hook running..."),
|
||||
StdoutContains("64 bytes"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue