diff --git a/cmd/nerdctl/compose/compose_run_linux_test.go b/cmd/nerdctl/compose/compose_run_linux_test.go index 2f05e732fd8..e68edb7d44c 100644 --- a/cmd/nerdctl/compose/compose_run_linux_test.go +++ b/cmd/nerdctl/compose/compose_run_linux_test.go @@ -19,6 +19,7 @@ package compose import ( "fmt" "io" + "os" "path/filepath" "strconv" "strings" @@ -38,6 +39,27 @@ import ( "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" ) +func composeRunCleanup() test.Butler { + return func(data test.Data, helpers test.Helpers) { + composePath := data.Temp().Path("compose.yaml") + // Tigron runs cleanup before setup too. A fresh temp project has no + // manifest or resources yet, so avoid waiting for the global compose lock. + if _, err := os.Stat(composePath); os.IsNotExist(err) { + return + } + // A background compose run holds the global compose lock. Stop its exact + // test container first so the process exits before compose rm acquires it. + helpers.Anyhow("stop", data.Identifier()) + helpers.Anyhow("compose", "-f", composePath, "rm", "-f", "-s", "-v") + // Docker Compose can leave explicitly named one-off containers behind. + // Keep the compatibility run isolated from concurrently running tests. + if nerdtest.IsDocker() { + helpers.Anyhow("rm", "-f", "-v", data.Identifier()) + } + helpers.Anyhow("compose", "-f", composePath, "down", "-v") + } +} + func TestComposeRun(t *testing.T) { const expectedOutput = "speed 38400 baud" @@ -71,10 +93,7 @@ services: return cmd }, Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(expectedOutput)), - Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - }, + Cleanup: composeRunCleanup(), }, { Description: "pty run with --rm", @@ -104,10 +123,7 @@ services: Output: expect.Contains(expectedOutput), } }, - Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - }, + Cleanup: composeRunCleanup(), }, } @@ -116,6 +132,9 @@ services: func TestComposeRunWithServicePorts(t *testing.T) { testCase := nerdtest.Setup() + // A background compose run holds the global compose lock until cleanup. + testCase.NoParallel = true + cleanup := composeRunCleanup() testCase.Setup = func(data test.Data, helpers test.Helpers) { hostPort, err := portlock.Acquire(0) @@ -139,8 +158,6 @@ services: data.Labels().Set("composeYAML", composePath) data.Labels().Set("hostPort", strconv.Itoa(hostPort)) - // specify the name of container in order to remove - // TODO: when `compose rm` is implemented, replace it. cmd := helpers.Command("compose", "-f", composePath, "run", "--service-ports", "--name", data.Identifier(), "web") cmd.WithPseudoTTY() cmd.Background() @@ -148,10 +165,7 @@ services: } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - if composeYAML := data.Labels().Get("composeYAML"); composeYAML != "" { - helpers.Anyhow("compose", "-f", composeYAML, "down", "-v") - } + cleanup(data, helpers) if portStr := data.Labels().Get("hostPort"); portStr != "" { if port, err := strconv.Atoi(portStr); err == nil { _ = portlock.Release(port) @@ -183,6 +197,9 @@ services: func TestComposeRunWithPublish(t *testing.T) { testCase := nerdtest.Setup() + // A background compose run holds the global compose lock until cleanup. + testCase.NoParallel = true + cleanup := composeRunCleanup() testCase.Setup = func(data test.Data, helpers test.Helpers) { hostPort, err := portlock.Acquire(0) @@ -204,8 +221,6 @@ services: data.Labels().Set("composeYAML", composePath) data.Labels().Set("hostPort", strconv.Itoa(hostPort)) - // specify the name of container in order to remove - // TODO: when `compose rm` is implemented, replace it. cmd := helpers.Command("compose", "-f", composePath, "run", "--publish", fmt.Sprintf("%d:80", hostPort), "--name", data.Identifier(), "web") cmd.WithPseudoTTY() cmd.Background() @@ -213,10 +228,7 @@ services: } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - if composeYAML := data.Labels().Get("composeYAML"); composeYAML != "" { - helpers.Anyhow("compose", "-f", composeYAML, "down", "-v") - } + cleanup(data, helpers) if portStr := data.Labels().Get("hostPort"); portStr != "" { if port, err := strconv.Atoi(portStr); err == nil { _ = portlock.Release(port) @@ -285,10 +297,7 @@ services: testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(partialOutput)) - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - } + testCase.Cleanup = composeRunCleanup() testCase.Run(t) } @@ -331,10 +340,7 @@ services: testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(partialOutput)) - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - } + testCase.Cleanup = composeRunCleanup() testCase.Run(t) } @@ -376,10 +382,7 @@ services: testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(expectedOutput)) - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - } + testCase.Cleanup = composeRunCleanup() testCase.Run(t) } @@ -434,10 +437,7 @@ services: } } - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - } + testCase.Cleanup = composeRunCleanup() testCase.Run(t) } @@ -478,10 +478,7 @@ services: testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(partialOutput)) - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - } + testCase.Cleanup = composeRunCleanup() testCase.Run(t) } @@ -524,10 +521,7 @@ services: testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(partialOutput)) - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - } + testCase.Cleanup = composeRunCleanup() testCase.Run(t) } @@ -582,10 +576,7 @@ services: } } - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", "-v", data.Identifier()) - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - } + testCase.Cleanup = composeRunCleanup() testCase.Run(t) }