From 83f48b8f87af0e3c88fa5c97992d14f248fa55e0 Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Sat, 18 Apr 2026 09:55:04 +0200 Subject: [PATCH 1/4] refactored compose run linux tests to nerdtest.Setup Migrated 9 out of 10 tests in compose_run_linux_test.go from legacy testutil.NewBase to the nerdtest/tigron framework. Replaced unbuffer wrapper with WithPseudoTTY, goroutine polling with cmd.Background and EnsureContainerStarted, and hardcoded ports with portlock.Acquire. Signed-off-by: sathiraumesh --- cmd/nerdctl/compose/compose_run_linux_test.go | 496 ++++++++++-------- 1 file changed, 284 insertions(+), 212 deletions(-) diff --git a/cmd/nerdctl/compose/compose_run_linux_test.go b/cmd/nerdctl/compose/compose_run_linux_test.go index c68d9fa258d..268919fef2c 100644 --- a/cmd/nerdctl/compose/compose_run_linux_test.go +++ b/cmd/nerdctl/compose/compose_run_linux_test.go @@ -19,20 +19,21 @@ package compose import ( "fmt" "io" + "strconv" "strings" "testing" - "time" "gotest.tools/v3/assert" - "github.com/containerd/log" "github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/mod/tigron/tig" "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/portlock" "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" ) @@ -113,132 +114,121 @@ services: } func TestComposeRunWithServicePorts(t *testing.T) { - base := testutil.NewBase(t) - // specify the name of container in order to remove - // TODO: when `compose rm` is implemented, replace it. - containerName := testutil.Identifier(t) + testCase := nerdtest.Setup() - dockerComposeYAML := fmt.Sprintf(` + testCase.Setup = func(data test.Data, helpers test.Helpers) { + hostPort, err := portlock.Acquire(0) + assert.NilError(helpers.T(), err) + data.Labels().Set("hostPort", strconv.Itoa(hostPort)) + + dockerComposeYAML := fmt.Sprintf(` services: web: image: %s ports: - - 8080:80 -`, testutil.NginxAlpineImage) - - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() + - %d:80 +`, testutil.NginxAlpineImage, hostPort) + + data.Temp().Save(dockerComposeYAML, "compose.yaml") + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "--service-ports", + "--name", + data.Identifier(), + "web", + ) + cmd.WithPseudoTTY() + cmd.Background() + nerdtest.EnsureContainerStarted(helpers, data.Identifier()) + } - defer base.Cmd("rm", "-f", "-v", containerName).Run() - go func() { - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "--service-ports", "--name", containerName, "web").Run() - }() - - checkNginx := func() error { - resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 5, false) - if err != nil { - return err - } - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return err + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if p := data.Labels().Get("hostPort"); p != "" { + if port, err := strconv.Atoi(p); err == nil { + _ = portlock.Release(port) + } } - if !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) { - t.Logf("respBody=%q", respBody) - return fmt.Errorf("respBody does not contain %q", testutil.NginxAlpineIndexHTMLSnippet) - } - return nil } - var nginxWorking bool - for i := 0; i < 30; i++ { - t.Logf("(retry %d)", i) - err := checkNginx() - if err == nil { - nginxWorking = true - break + + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, t tig.T) { + host := fmt.Sprintf("http://127.0.0.1:%s", data.Labels().Get("hostPort")) + resp, err := nettestutil.HTTPGet(host, 30, false) + assert.NilError(t, err) + respBody, err := io.ReadAll(resp.Body) + assert.NilError(t, err) + assert.Assert(t, strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet)) + }, } - t.Log(err) - time.Sleep(3 * time.Second) - } - if !nginxWorking { - t.Fatal("nginx is not working") } - t.Log("nginx seems functional") + + testCase.Run(t) } func TestComposeRunWithPublish(t *testing.T) { - base := testutil.NewBase(t) - // specify the name of container in order to remove - // TODO: when `compose rm` is implemented, replace it. - containerName := testutil.Identifier(t) + testCase := nerdtest.Setup() - dockerComposeYAML := fmt.Sprintf(` + testCase.Setup = func(data test.Data, helpers test.Helpers) { + hostPort, err := portlock.Acquire(0) + assert.NilError(helpers.T(), err) + data.Labels().Set("hostPort", strconv.Itoa(hostPort)) + + dockerComposeYAML := fmt.Sprintf(` services: web: image: %s `, testutil.NginxAlpineImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() + data.Temp().Save(dockerComposeYAML, "compose.yaml") + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "--publish", fmt.Sprintf("%d:80", hostPort), + "--name", + data.Identifier(), + "web", + ) + cmd.WithPseudoTTY() + cmd.Background() + nerdtest.EnsureContainerStarted(helpers, data.Identifier()) + } - defer base.Cmd("rm", "-f", "-v", containerName).Run() - go func() { - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "--publish", "8080:80", "--name", containerName, "web").Run() - }() - - checkNginx := func() error { - resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 5, false) - if err != nil { - return err - } - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return err + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if p := data.Labels().Get("hostPort"); p != "" { + if port, err := strconv.Atoi(p); err == nil { + _ = portlock.Release(port) + } } - if !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) { - t.Logf("respBody=%q", respBody) - return fmt.Errorf("respBody does not contain %q", testutil.NginxAlpineIndexHTMLSnippet) - } - return nil } - var nginxWorking bool - for i := 0; i < 30; i++ { - t.Logf("(retry %d)", i) - err := checkNginx() - if err == nil { - nginxWorking = true - break + + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, t tig.T) { + host := fmt.Sprintf("http://127.0.0.1:%s", data.Labels().Get("hostPort")) + resp, err := nettestutil.HTTPGet(host, 30, false) + assert.NilError(t, err) + respBody, err := io.ReadAll(resp.Body) + assert.NilError(t, err) + assert.Assert(t, strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet)) + }, } - t.Log(err) - time.Sleep(3 * time.Second) } - if !nginxWorking { - t.Fatal("nginx is not working") - } - t.Log("nginx seems functional") + + testCase.Run(t) } func TestComposeRunWithEnv(t *testing.T) { - base := testutil.NewBase(t) - // specify the name of container in order to remove - // TODO: when `compose rm` is implemented, replace it. - containerName := testutil.Identifier(t) - - dockerComposeYAML := fmt.Sprintf(` + var dockerComposeYAML = fmt.Sprintf(` services: alpine: image: %s @@ -248,28 +238,39 @@ services: - "echo $$FOO" `, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() + testCase := nerdtest.Setup() - defer base.Cmd("rm", "-f", "-v", containerName).Run() - const partialOutput = "bar" - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "-e", "FOO=bar", "--name", containerName, "alpine").AssertOutContains(partialOutput) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "-e", "FOO=bar", + "--name", + data.Identifier(), + "alpine", + ) + cmd.WithPseudoTTY() + return cmd + } + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("bar")) + + testCase.Run(t) } func TestComposeRunWithUser(t *testing.T) { - base := testutil.NewBase(t) - // specify the name of container in order to remove - // TODO: when `compose rm` is implemented, replace it. - containerName := testutil.Identifier(t) - - dockerComposeYAML := fmt.Sprintf(` + var dockerComposeYAML = fmt.Sprintf(` services: alpine: image: %s @@ -278,26 +279,39 @@ services: - -u `, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() + testCase := nerdtest.Setup() - defer base.Cmd("rm", "-f", "-v", containerName).Run() - const partialOutput = "5000" - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "--user", "5000", "--name", containerName, "alpine").AssertOutContains(partialOutput) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "--user", "5000", + "--name", + data.Identifier(), + "alpine", + ) + cmd.WithPseudoTTY() + return cmd + } + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("5000")) + + testCase.Run(t) } func TestComposeRunWithLabel(t *testing.T) { - base := testutil.NewBase(t) - containerName := testutil.Identifier(t) - - dockerComposeYAML := fmt.Sprintf(` + var dockerComposeYAML = fmt.Sprintf(` services: alpine: image: %s @@ -308,33 +322,50 @@ services: - "foo=bar" `, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() + testCase := nerdtest.Setup() - defer base.Cmd("rm", "-f", "-v", containerName).Run() - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "--label", "foo=rab", "--label", "x=y", "--name", containerName, "alpine").AssertOK() + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "--label", "foo=rab", + "--label", "x=y", + "--name", + data.Identifier(), + "alpine", + ) + cmd.WithPseudoTTY() + return cmd + } - container := base.InspectContainer(containerName) - if container.Config == nil { - log.L.Errorf("test failed, cannot fetch container config") - t.Fail() + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: expect.ExitCodeSuccess, + Output: func(stdout string, t tig.T) { + fooLabel := helpers.Capture("inspect", "--format", "{{index .Config.Labels \"foo\"}}", data.Identifier()) + xLabel := helpers.Capture("inspect", "--format", "{{index .Config.Labels \"x\"}}", data.Identifier()) + assert.Equal(t, strings.TrimSpace(fooLabel), "rab") + assert.Equal(t, strings.TrimSpace(xLabel), "y") + }, + } } - assert.Equal(t, container.Config.Labels["foo"], "rab") - assert.Equal(t, container.Config.Labels["x"], "y") + + testCase.Run(t) } func TestComposeRunWithArgs(t *testing.T) { - base := testutil.NewBase(t) - containerName := testutil.Identifier(t) - - dockerComposeYAML := fmt.Sprintf(` + var dockerComposeYAML = fmt.Sprintf(` services: alpine: image: %s @@ -342,28 +373,39 @@ services: - echo `, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() + testCase := nerdtest.Setup() - defer base.Cmd("rm", "-f", "-v", containerName).Run() - const partialOutput = "hello world" - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "--name", containerName, "alpine", partialOutput).AssertOutContains(partialOutput) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "--name", + data.Identifier(), + "alpine", + "hello world", + ) + cmd.WithPseudoTTY() + return cmd + } + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("hello world")) + + testCase.Run(t) } func TestComposeRunWithEntrypoint(t *testing.T) { - base := testutil.NewBase(t) - // specify the name of container in order to remove - // TODO: when `compose rm` is implemented, replace it. - containerName := testutil.Identifier(t) - - dockerComposeYAML := fmt.Sprintf(` + var dockerComposeYAML = fmt.Sprintf(` services: alpine: image: %s @@ -371,26 +413,40 @@ services: - stty # should be changed `, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() + testCase := nerdtest.Setup() - defer base.Cmd("rm", "-f", "-v", containerName).Run() - const partialOutput = "hello world" - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "--entrypoint", "echo", "--name", containerName, "alpine", partialOutput).AssertOutContains(partialOutput) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "--entrypoint", "echo", + "--name", + data.Identifier(), + "alpine", + "hello world", + ) + cmd.WithPseudoTTY() + return cmd + } + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("hello world")) + + testCase.Run(t) } func TestComposeRunWithVolume(t *testing.T) { - base := testutil.NewBase(t) - containerName := testutil.Identifier(t) - - dockerComposeYAML := fmt.Sprintf(` + var dockerComposeYAML = fmt.Sprintf(` services: alpine: image: %s @@ -398,30 +454,46 @@ services: - stty # no meaning, just put any command `, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() + testCase := nerdtest.Setup() - // The directory is automatically removed by Cleanup - tmpDir := t.TempDir() - destinationDir := "/data" - volumeFlagStr := fmt.Sprintf("%s:%s", tmpDir, destinationDir) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + } - defer base.Cmd("rm", "-f", "-v", containerName).Run() - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "--volume", volumeFlagStr, "--name", containerName, "alpine").AssertOK() - - container := base.InspectContainer(containerName) - errMsg := fmt.Sprintf("test failed, cannot find volume: %v", container.Mounts) - assert.Assert(t, container.Mounts != nil, errMsg) - assert.Assert(t, len(container.Mounts) == 1, errMsg) - assert.Assert(t, container.Mounts[0].Source == tmpDir, errMsg) - assert.Assert(t, container.Mounts[0].Destination == destinationDir, errMsg) + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + volumeFlag := fmt.Sprintf("%s:/data", data.Temp().Dir()) + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "--volume", volumeFlag, + "--name", + data.Identifier(), + "alpine", + ) + cmd.WithPseudoTTY() + return cmd + } + + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: expect.ExitCodeSuccess, + Output: func(stdout string, t tig.T) { + source := helpers.Capture("inspect", "--format", "{{(index .Mounts 0).Source}}", data.Identifier()) + dest := helpers.Capture("inspect", "--format", "{{(index .Mounts 0).Destination}}", data.Identifier()) + assert.Equal(t, strings.TrimSpace(source), data.Temp().Dir()) + assert.Equal(t, strings.TrimSpace(dest), "/data") + }, + } + } + + testCase.Run(t) } func TestComposePushAndPullWithCosignVerify(t *testing.T) { From 4b72b6027b60e94a0bc3cefebf18ffe18b5f5aeb Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Sat, 16 May 2026 13:27:03 +0200 Subject: [PATCH 2/4] tring to fix test faliure issue Signed-off-by: sathiraumesh --- cmd/nerdctl/compose/compose_run_linux_test.go | 32 +++---------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/cmd/nerdctl/compose/compose_run_linux_test.go b/cmd/nerdctl/compose/compose_run_linux_test.go index 268919fef2c..9a7248ee540 100644 --- a/cmd/nerdctl/compose/compose_run_linux_test.go +++ b/cmd/nerdctl/compose/compose_run_linux_test.go @@ -19,7 +19,6 @@ package compose import ( "fmt" "io" - "strconv" "strings" "testing" @@ -33,7 +32,6 @@ import ( "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil" - "github.com/containerd/nerdctl/v2/pkg/testutil/portlock" "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" ) @@ -117,17 +115,13 @@ func TestComposeRunWithServicePorts(t *testing.T) { testCase := nerdtest.Setup() testCase.Setup = func(data test.Data, helpers test.Helpers) { - hostPort, err := portlock.Acquire(0) - assert.NilError(helpers.T(), err) - data.Labels().Set("hostPort", strconv.Itoa(hostPort)) - dockerComposeYAML := fmt.Sprintf(` services: web: image: %s ports: - - %d:80 -`, testutil.NginxAlpineImage, hostPort) + - 8080:80 +`, testutil.NginxAlpineImage) data.Temp().Save(dockerComposeYAML, "compose.yaml") cmd := helpers.Command( @@ -148,18 +142,12 @@ services: testCase.Cleanup = func(data test.Data, helpers test.Helpers) { helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - if p := data.Labels().Get("hostPort"); p != "" { - if port, err := strconv.Atoi(p); err == nil { - _ = portlock.Release(port) - } - } } testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ Output: func(stdout string, t tig.T) { - host := fmt.Sprintf("http://127.0.0.1:%s", data.Labels().Get("hostPort")) - resp, err := nettestutil.HTTPGet(host, 30, false) + resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 30, false) assert.NilError(t, err) respBody, err := io.ReadAll(resp.Body) assert.NilError(t, err) @@ -175,10 +163,6 @@ func TestComposeRunWithPublish(t *testing.T) { testCase := nerdtest.Setup() testCase.Setup = func(data test.Data, helpers test.Helpers) { - hostPort, err := portlock.Acquire(0) - assert.NilError(helpers.T(), err) - data.Labels().Set("hostPort", strconv.Itoa(hostPort)) - dockerComposeYAML := fmt.Sprintf(` services: web: @@ -191,7 +175,7 @@ services: "-f", data.Temp().Path("compose.yaml"), "run", - "--publish", fmt.Sprintf("%d:80", hostPort), + "--publish", "8080:80", "--name", data.Identifier(), "web", @@ -204,18 +188,12 @@ services: testCase.Cleanup = func(data test.Data, helpers test.Helpers) { helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") - if p := data.Labels().Get("hostPort"); p != "" { - if port, err := strconv.Atoi(p); err == nil { - _ = portlock.Release(port) - } - } } testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ Output: func(stdout string, t tig.T) { - host := fmt.Sprintf("http://127.0.0.1:%s", data.Labels().Get("hostPort")) - resp, err := nettestutil.HTTPGet(host, 30, false) + resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 30, false) assert.NilError(t, err) respBody, err := io.ReadAll(resp.Body) assert.NilError(t, err) From 32c8d8d1f9e845d50e660784894669fc1ad05283 Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Sat, 16 May 2026 20:29:17 +0200 Subject: [PATCH 3/4] trying to fix the test case faliure for runninig cleanup initially Signed-off-by: sathiraumesh --- cmd/nerdctl/compose/compose_run_linux_test.go | 58 +++++++++++++------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/cmd/nerdctl/compose/compose_run_linux_test.go b/cmd/nerdctl/compose/compose_run_linux_test.go index 9a7248ee540..f712275fd47 100644 --- a/cmd/nerdctl/compose/compose_run_linux_test.go +++ b/cmd/nerdctl/compose/compose_run_linux_test.go @@ -53,6 +53,7 @@ services: Description: "pty run", Setup: func(data test.Data, helpers test.Helpers) { data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { cmd := helpers.Command( @@ -70,13 +71,16 @@ services: Expected: test.Expects(0, 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") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } }, }, { Description: "pty run with --rm", Setup: func(data test.Data, helpers test.Helpers) { data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { cmd := helpers.Command( @@ -103,7 +107,9 @@ services: }, 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") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } }, }, } @@ -124,6 +130,7 @@ services: `, testutil.NginxAlpineImage) data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) cmd := helpers.Command( "compose", "-f", @@ -140,8 +147,9 @@ services: } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } } testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { @@ -170,6 +178,7 @@ services: `, testutil.NginxAlpineImage) data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) cmd := helpers.Command( "compose", "-f", @@ -186,8 +195,9 @@ services: } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } } testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { @@ -220,11 +230,13 @@ services: testCase.Setup = func(data test.Data, helpers test.Helpers) { data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } } testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -261,11 +273,13 @@ services: testCase.Setup = func(data test.Data, helpers test.Helpers) { data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } } testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -304,11 +318,13 @@ services: testCase.Setup = func(data test.Data, helpers test.Helpers) { data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } } testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -355,11 +371,13 @@ services: testCase.Setup = func(data test.Data, helpers test.Helpers) { data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } } testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -395,11 +413,13 @@ services: testCase.Setup = func(data test.Data, helpers test.Helpers) { data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } } testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -436,11 +456,13 @@ services: testCase.Setup = func(data test.Data, helpers test.Helpers) { data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) } testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "rm", "-f", "-v") - helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + if yaml := data.Labels().Get("composeYAML"); yaml != "" { + helpers.Anyhow("compose", "-f", yaml, "down", "-v") + } } testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { From 02560e3ad6a43bb4a8284bff70a6d573157fd515 Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Sat, 16 May 2026 21:05:59 +0200 Subject: [PATCH 4/4] test: revert TestComposeRunWithServicePorts and TestComposeRunWithPublish to legacy framework These two tests run a long-lived nginx container that the test needs to poll over HTTP. The nerdtest cmd.Background() + EnsureContainerStarted pattern was failing in rootless mode: the backgrounded compose run's stderr is captured into a pty buffer that nothing reads, so when the inner `nerdctl run -d` fails (likely due to host port 8080 conflict), the error is invisible and EnsureContainerStarted just times out with a misleading "Command should succeed" assertion. Restoring the pre-refactor testutil.NewBase + goroutine + HTTP probe pattern for these two tests only. The other tests in the file run quick, exiting commands and keep using nerdtest. Signed-off-by: sathiraumesh --- cmd/nerdctl/compose/compose_run_linux_test.go | 159 ++++++++++-------- 1 file changed, 91 insertions(+), 68 deletions(-) diff --git a/cmd/nerdctl/compose/compose_run_linux_test.go b/cmd/nerdctl/compose/compose_run_linux_test.go index f712275fd47..6eaae416bbc 100644 --- a/cmd/nerdctl/compose/compose_run_linux_test.go +++ b/cmd/nerdctl/compose/compose_run_linux_test.go @@ -21,6 +21,7 @@ import ( "io" "strings" "testing" + "time" "gotest.tools/v3/assert" @@ -118,10 +119,12 @@ services: } func TestComposeRunWithServicePorts(t *testing.T) { - testCase := nerdtest.Setup() + base := testutil.NewBase(t) + // specify the name of container in order to remove + // TODO: when `compose rm` is implemented, replace it. + containerName := testutil.Identifier(t) - testCase.Setup = func(data test.Data, helpers test.Helpers) { - dockerComposeYAML := fmt.Sprintf(` + dockerComposeYAML := fmt.Sprintf(` services: web: image: %s @@ -129,90 +132,110 @@ services: - 8080:80 `, testutil.NginxAlpineImage) - data.Temp().Save(dockerComposeYAML, "compose.yaml") - data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) - cmd := helpers.Command( - "compose", - "-f", - data.Temp().Path("compose.yaml"), - "run", - "--service-ports", - "--name", - data.Identifier(), - "web", - ) - cmd.WithPseudoTTY() - cmd.Background() - nerdtest.EnsureContainerStarted(helpers, data.Identifier()) - } + comp := testutil.NewComposeDir(t, dockerComposeYAML) + defer comp.CleanUp() + projectName := comp.ProjectName() + t.Logf("projectName=%q", projectName) + defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - if yaml := data.Labels().Get("composeYAML"); yaml != "" { - helpers.Anyhow("compose", "-f", yaml, "down", "-v") + defer base.Cmd("rm", "-f", "-v", containerName).Run() + go func() { + // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. + // unbuffer(1) can be installed with `apt-get install expect`. + unbuffer := []string{"unbuffer"} + base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), + "run", "--service-ports", "--name", containerName, "web").Run() + }() + + checkNginx := func() error { + resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 5, false) + if err != nil { + return err + } + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + if !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) { + t.Logf("respBody=%q", respBody) + return fmt.Errorf("respBody does not contain %q", testutil.NginxAlpineIndexHTMLSnippet) } + return nil } - - testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - Output: func(stdout string, t tig.T) { - resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 30, false) - assert.NilError(t, err) - respBody, err := io.ReadAll(resp.Body) - assert.NilError(t, err) - assert.Assert(t, strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet)) - }, + var nginxWorking bool + for i := 0; i < 30; i++ { + t.Logf("(retry %d)", i) + err := checkNginx() + if err == nil { + nginxWorking = true + break } + t.Log(err) + time.Sleep(3 * time.Second) } - - testCase.Run(t) + if !nginxWorking { + t.Fatal("nginx is not working") + } + t.Log("nginx seems functional") } func TestComposeRunWithPublish(t *testing.T) { - testCase := nerdtest.Setup() + base := testutil.NewBase(t) + // specify the name of container in order to remove + // TODO: when `compose rm` is implemented, replace it. + containerName := testutil.Identifier(t) - testCase.Setup = func(data test.Data, helpers test.Helpers) { - dockerComposeYAML := fmt.Sprintf(` + dockerComposeYAML := fmt.Sprintf(` services: web: image: %s `, testutil.NginxAlpineImage) - data.Temp().Save(dockerComposeYAML, "compose.yaml") - data.Labels().Set("composeYAML", data.Temp().Path("compose.yaml")) - cmd := helpers.Command( - "compose", - "-f", - data.Temp().Path("compose.yaml"), - "run", - "--publish", "8080:80", - "--name", - data.Identifier(), - "web", - ) - cmd.WithPseudoTTY() - cmd.Background() - nerdtest.EnsureContainerStarted(helpers, data.Identifier()) - } + comp := testutil.NewComposeDir(t, dockerComposeYAML) + defer comp.CleanUp() + projectName := comp.ProjectName() + t.Logf("projectName=%q", projectName) + defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() - testCase.Cleanup = func(data test.Data, helpers test.Helpers) { - if yaml := data.Labels().Get("composeYAML"); yaml != "" { - helpers.Anyhow("compose", "-f", yaml, "down", "-v") + defer base.Cmd("rm", "-f", "-v", containerName).Run() + go func() { + // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. + // unbuffer(1) can be installed with `apt-get install expect`. + unbuffer := []string{"unbuffer"} + base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), + "run", "--publish", "8080:80", "--name", containerName, "web").Run() + }() + + checkNginx := func() error { + resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 5, false) + if err != nil { + return err + } + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + if !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) { + t.Logf("respBody=%q", respBody) + return fmt.Errorf("respBody does not contain %q", testutil.NginxAlpineIndexHTMLSnippet) } + return nil } - - testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - Output: func(stdout string, t tig.T) { - resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 30, false) - assert.NilError(t, err) - respBody, err := io.ReadAll(resp.Body) - assert.NilError(t, err) - assert.Assert(t, strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet)) - }, + var nginxWorking bool + for i := 0; i < 30; i++ { + t.Logf("(retry %d)", i) + err := checkNginx() + if err == nil { + nginxWorking = true + break } + t.Log(err) + time.Sleep(3 * time.Second) } - - testCase.Run(t) + if !nginxWorking { + t.Fatal("nginx is not working") + } + t.Log("nginx seems functional") } func TestComposeRunWithEnv(t *testing.T) {