diff --git a/local/e2e/compose/cancel_test.go b/local/e2e/compose/cancel_test.go new file mode 100644 index 000000000..7c57ab994 --- /dev/null +++ b/local/e2e/compose/cancel_test.go @@ -0,0 +1,100 @@ +// +build !windows + +/* + 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. +*/ + +package e2e + +import ( + "bytes" + "fmt" + "os/exec" + "strings" + "syscall" + "testing" + "time" + + "gotest.tools/v3/assert" + "gotest.tools/v3/icmd" + + . "github.com/docker/compose-cli/utils/e2e" +) + +func TestComposeCancel(t *testing.T) { + c := NewParallelE2eCLI(t, binDir) + s := NewMetricsServer(c.MetricsSocket()) + s.Start() + defer s.Stop() + + started := false + + for i := 0; i < 30; i++ { + c.RunDockerCmd("help", "ps") + if len(s.GetUsage()) > 0 { + started = true + fmt.Printf(" [%s] Server up in %d ms\n", t.Name(), i*100) + break + } + time.Sleep(100 * time.Millisecond) + } + assert.Assert(t, started, "Metrics mock server not available after 3 secs") + + t.Run("metrics on cancel Compose build", func(t *testing.T) { + s.ResetUsage() + + c.RunDockerCmd("compose", "ls") + buildProjectPath := "../compose/fixtures/build-infinite/docker-compose.yml" + + // require a separate groupID from the process running tests, in order to simulate ctrl+C from a terminal. + // sending kill signal + cmd, stdout, stderr, err := StartWithNewGroupID(c.NewDockerCmd("compose", "-f", buildProjectPath, "build", "--progress", "plain")) + assert.NilError(t, err) + + c.WaitForCondition(func() (bool, string) { + out := stdout.String() + errors := stderr.String() + return strings.Contains(out, "RUN sleep infinity"), fmt.Sprintf("'RUN sleep infinity' not found in : \n%s\nStderr: \n%s\n", out, errors) + }, 30*time.Second, 1*time.Second) + + err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default + + assert.NilError(t, err) + c.WaitForCondition(func() (bool, string) { + out := stdout.String() + errors := stderr.String() + return strings.Contains(out, "CANCELED"), fmt.Sprintf("'CANCELED' not found in : \n%s\nStderr: \n%s\n", out, errors) + }, 10*time.Second, 1*time.Second) + + usage := s.GetUsage() + assert.DeepEqual(t, []string{ + `{"command":"compose ls","context":"moby","source":"cli","status":"success"}`, + `{"command":"compose build","context":"moby","source":"cli","status":"canceled"}`, + }, usage) + }) +} + +func StartWithNewGroupID(command icmd.Cmd) (*exec.Cmd, *bytes.Buffer, *bytes.Buffer, error) { + cmd := exec.Command(command.Command[0], command.Command[1:]...) + cmd.Env = command.Env + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Start() + return cmd, &stdout, &stderr, err +} diff --git a/local/e2e/compose/compose_test.go b/local/e2e/compose/compose_test.go index 6af94b483..d1d58fd39 100644 --- a/local/e2e/compose/compose_test.go +++ b/local/e2e/compose/compose_test.go @@ -18,10 +18,12 @@ package e2e import ( "fmt" + "io/ioutil" "net/http" "os" "path/filepath" "regexp" + "runtime" "strings" "testing" "time" @@ -76,13 +78,12 @@ func TestLocalComposeUp(t *testing.T) { }) t.Run("check compose labels", func(t *testing.T) { - wd, _ := os.Getwd() res := c.RunDockerCmd("inspect", projectName+"_web_1") res.Assert(t, icmd.Expected{Out: `"com.docker.compose.container-number": "1"`}) res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": "compose-e2e-demo"`}) res.Assert(t, icmd.Expected{Out: `"com.docker.compose.oneoff": "False",`}) res.Assert(t, icmd.Expected{Out: `"com.docker.compose.config-hash":`}) - res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`"com.docker.compose.project.config_files": "%s/fixtures/sentences/compose.yaml"`, wd)}) + res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project.config_files":`}) res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project.working_dir":`}) res.Assert(t, icmd.Expected{Out: `"com.docker.compose.service": "web"`}) res.Assert(t, icmd.Expected{Out: `"com.docker.compose.version":`}) @@ -132,21 +133,31 @@ func TestLocalComposeUp(t *testing.T) { }) } +func binExt() string { + binaryExt := "" + if runtime.GOOS == "windows" { + binaryExt = ".exe" + } + return binaryExt +} func TestComposeUsingCliPlugin(t *testing.T) { c := NewParallelE2eCLI(t, binDir) - err := os.Remove(filepath.Join(c.ConfigDir, "cli-plugins", "docker-compose")) + err := os.Remove(filepath.Join(c.ConfigDir, "cli-plugins", "docker-compose"+binExt())) assert.NilError(t, err) res := c.RunDockerOrExitError("compose", "ls") res.Assert(t, icmd.Expected{Err: "'compose' is not a docker command", ExitCode: 1}) } func TestComposeCliPluginWithoutCloudIntegration(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - - err := os.Remove(filepath.Join(binDir, "docker")) + newBinFolder, cleanup, err := SetupExistingCLI() // do not share bin folder with other tests assert.NilError(t, err) - err = os.Rename(filepath.Join(binDir, "com.docker.cli"), filepath.Join(binDir, "docker")) + defer cleanup() + c := NewParallelE2eCLI(t, newBinFolder) + + err = os.Remove(filepath.Join(newBinFolder, "docker"+binExt())) + assert.NilError(t, err) + err = os.Rename(filepath.Join(newBinFolder, "com.docker.cli"+binExt()), filepath.Join(newBinFolder, "docker"+binExt())) assert.NilError(t, err) res := c.RunDockerOrExitError("compose", "ls") res.Assert(t, icmd.Expected{Out: "NAME STATUS", ExitCode: 0}) @@ -166,10 +177,10 @@ func TestDownComposefileInParentFolder(t *testing.T) { c := NewParallelE2eCLI(t, binDir) - tmpFolder, err := os.MkdirTemp("fixtures/simple-composefile", "test-tmp") - projectName := strings.TrimPrefix(tmpFolder, "fixtures/simple-composefile/") - defer os.Remove(tmpFolder) //nolint: errcheck + tmpFolder, err := ioutil.TempDir("fixtures/simple-composefile", "test-tmp") assert.NilError(t, err) + defer os.Remove(tmpFolder) //nolint: errcheck + projectName := filepath.Base(tmpFolder) res := c.RunDockerCmd("compose", "--project-directory", tmpFolder, "up", "-d") res.Assert(t, icmd.Expected{Err: "Started", ExitCode: 0}) diff --git a/local/e2e/compose/metrics_test.go b/local/e2e/compose/metrics_test.go index a760bb1f6..ab761702e 100644 --- a/local/e2e/compose/metrics_test.go +++ b/local/e2e/compose/metrics_test.go @@ -17,11 +17,8 @@ package e2e import ( - "bytes" "fmt" - "os/exec" - "strings" - "syscall" + "runtime" "testing" "time" @@ -53,7 +50,11 @@ func TestComposeMetrics(t *testing.T) { s.ResetUsage() res := c.RunDockerOrExitError("compose", "-f", "../compose/fixtures/does-not-exist/compose.yml", "build") - res.Assert(t, icmd.Expected{ExitCode: 14, Err: "compose/fixtures/does-not-exist/compose.yml: no such file or directory"}) + expectedErr := "compose/fixtures/does-not-exist/compose.yml: no such file or directory" + if runtime.GOOS == "windows" { + expectedErr = "does-not-exist\\compose.yml: The system cannot find the path specified" + } + res.Assert(t, icmd.Expected{ExitCode: 14, Err: expectedErr}) res = c.RunDockerOrExitError("compose", "-f", "../compose/fixtures/wrong-composefile/compose.yml", "up", "-d") res.Assert(t, icmd.Expected{ExitCode: 15, Err: "services.simple Additional property wrongField is not allowed"}) res = c.RunDockerOrExitError("compose", "up") @@ -88,69 +89,3 @@ func TestComposeMetrics(t *testing.T) { }, usage) }) } - -func TestComposeCancel(t *testing.T) { - c := NewParallelE2eCLI(t, binDir) - s := NewMetricsServer(c.MetricsSocket()) - s.Start() - defer s.Stop() - - started := false - - for i := 0; i < 30; i++ { - c.RunDockerCmd("help", "ps") - if len(s.GetUsage()) > 0 { - started = true - fmt.Printf(" [%s] Server up in %d ms\n", t.Name(), i*100) - break - } - time.Sleep(100 * time.Millisecond) - } - assert.Assert(t, started, "Metrics mock server not available after 3 secs") - - t.Run("metrics on cancel Compose build", func(t *testing.T) { - s.ResetUsage() - - c.RunDockerCmd("compose", "ls") - buildProjectPath := "../compose/fixtures/build-infinite/docker-compose.yml" - - // require a separate groupID from the process running tests, in order to simulate ctrl+C from a terminal. - // sending kill signal - cmd, stdout, stderr, err := StartWithNewGroupID(c.NewDockerCmd("compose", "-f", buildProjectPath, "build", "--progress", "plain")) - assert.NilError(t, err) - - c.WaitForCondition(func() (bool, string) { - out := stdout.String() - errors := stderr.String() - return strings.Contains(out, "RUN sleep infinity"), fmt.Sprintf("'RUN sleep infinity' not found in : \n%s\nStderr: \n%s\n", out, errors) - }, 30*time.Second, 1*time.Second) - - err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default - - assert.NilError(t, err) - c.WaitForCondition(func() (bool, string) { - out := stdout.String() - errors := stderr.String() - return strings.Contains(out, "CANCELED"), fmt.Sprintf("'CANCELED' not found in : \n%s\nStderr: \n%s\n", out, errors) - }, 10*time.Second, 1*time.Second) - - usage := s.GetUsage() - assert.DeepEqual(t, []string{ - `{"command":"compose ls","context":"moby","source":"cli","status":"success"}`, - `{"command":"compose build","context":"moby","source":"cli","status":"canceled"}`, - }, usage) - }) -} - -func StartWithNewGroupID(command icmd.Cmd) (*exec.Cmd, *bytes.Buffer, *bytes.Buffer, error) { - cmd := exec.Command(command.Command[0], command.Command[1:]...) - cmd.Env = command.Env - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - - var stdout bytes.Buffer - var stderr bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = &stderr - err := cmd.Start() - return cmd, &stdout, &stderr, err -}