From 2d6860ad0a27d78c5037cc51d9721446db998e1b Mon Sep 17 00:00:00 2001 From: Ogulcan Aydogan Date: Thu, 14 May 2026 15:07:10 +0100 Subject: [PATCH 1/4] refactor: migrate container_create_linux_test.go to nerdtest.Setup Migrate TestCreateWithLabel, TestCreateWithMACAddress, TestCreateWithTty, and TestCreateFromOCIArchive from testutil.NewBase to the Tigron-based nerdtest.Setup pattern. Also fix TestUsernsMappingCreateCmd which called nerdtest.Setup() without using its return value; now uses the returned *test.Case directly. Helper function removeUsernsConfig updated to accept tig.T instead of *testing.T to align with the rest of the Tigron-based test infrastructure. Part of #4613. Signed-off-by: Ogulcan Aydogan --- .../container/container_create_linux_test.go | 592 +++++++++++------- 1 file changed, 365 insertions(+), 227 deletions(-) diff --git a/cmd/nerdctl/container/container_create_linux_test.go b/cmd/nerdctl/container/container_create_linux_test.go index 1551cdf3555..6e8290902b4 100644 --- a/cmd/nerdctl/container/container_create_linux_test.go +++ b/cmd/nerdctl/container/container_create_linux_test.go @@ -36,148 +36,277 @@ import ( "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" ) func TestCreateWithLabel(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - tID := testutil.Identifier(t) - - base.Cmd("create", "--name", tID, "--label", "foo=bar", testutil.NginxAlpineImage, "echo", "foo").AssertOK() - defer base.Cmd("rm", "-f", tID).Run() - inspect := base.InspectContainer(tID) - assert.Equal(base.T, "bar", inspect.Config.Labels["foo"]) - // the label `maintainer`` is defined by image - assert.Equal(base.T, "NGINX Docker Maintainers ", inspect.Config.Labels["maintainer"]) + testCase := nerdtest.Setup() + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + } + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("create", "--name", data.Identifier(), "--label", "foo=bar", testutil.NginxAlpineImage, "echo", "foo") + } + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + fooLabel := strings.TrimSpace(helpers.Capture("inspect", "--format", `{{index .Config.Labels "foo"}}`, data.Identifier())) + assert.Equal(t, "bar", fooLabel) + maintainerLabel := strings.TrimSpace(helpers.Capture("inspect", "--format", `{{index .Config.Labels "maintainer"}}`, data.Identifier())) + assert.Equal(t, "NGINX Docker Maintainers ", maintainerLabel) + }, + } + } + testCase.Run(t) } func TestCreateWithMACAddress(t *testing.T) { - base := testutil.NewBase(t) - tID := testutil.Identifier(t) - networkBridge := "testNetworkBridge" + tID - networkMACvlan := "testNetworkMACvlan" + tID - networkIPvlan := "testNetworkIPvlan" + tID - - tearDown := func() { - base.Cmd("network", "rm", networkBridge).Run() - base.Cmd("network", "rm", networkMACvlan).Run() - base.Cmd("network", "rm", networkIPvlan).Run() + testCase := nerdtest.Setup() + + const ( + networkBridgeKey = "networkBridge" + networkMACvlanKey = "networkMACvlan" + networkIPvlanKey = "networkIPvlan" + defaultMacKey = "defaultMac" + macAddressKey = "macAddress" + ) + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Labels().Set(networkBridgeKey, "testNetworkBridge"+data.Identifier()) + data.Labels().Set(networkMACvlanKey, "testNetworkMACvlan"+data.Identifier()) + data.Labels().Set(networkIPvlanKey, "testNetworkIPvlan"+data.Identifier()) + helpers.Ensure("network", "create", data.Labels().Get(networkBridgeKey), "--driver", "bridge") + helpers.Ensure("network", "create", data.Labels().Get(networkMACvlanKey), "--driver", "macvlan") + helpers.Ensure("network", "create", data.Labels().Get(networkIPvlanKey), "--driver", "ipvlan") + defaultMac := strings.TrimSpace(helpers.Capture("run", "--rm", "--network", "host", + testutil.CommonImage, "sh", "-c", "ip addr show eth0 | grep ether | awk '{printf $2}'")) + data.Labels().Set(defaultMacKey, defaultMac) + } + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Labels().Get(networkBridgeKey)) + helpers.Anyhow("network", "rm", data.Labels().Get(networkMACvlanKey)) + helpers.Anyhow("network", "rm", data.Labels().Get(networkIPvlanKey)) } - tearDown() - t.Cleanup(tearDown) - - base.Cmd("network", "create", networkBridge, "--driver", "bridge").AssertOK() - base.Cmd("network", "create", networkMACvlan, "--driver", "macvlan").AssertOK() - base.Cmd("network", "create", networkIPvlan, "--driver", "ipvlan").AssertOK() - - defaultMac := base.Cmd("run", "--rm", "-i", "--network", "host", testutil.CommonImage). - CmdOption(testutil.WithStdin(strings.NewReader("ip addr show eth0 | grep ether | awk '{printf $2}'"))). - Run().Stdout() - - passedMac := "we expect the generated mac on the output" - tests := []struct { - Network string - WantErr bool - Expect string - }{ - {"host", false, defaultMac}, // anything but the actual address being passed - {"none", false, ""}, - {"container:whatever" + tID, true, "container"}, // "No such container" vs. "could not find container" - {"bridge", false, passedMac}, - {networkBridge, false, passedMac}, - {networkMACvlan, false, passedMac}, - {networkIPvlan, true, "not support"}, + setupMAC := func(data test.Data, helpers test.Helpers) { + macAddress, err := nettestutil.GenerateMACAddress() + assert.NilError(helpers.T(), err, "failed to generate MAC address") + data.Labels().Set(macAddressKey, macAddress) } - for i, test := range tests { - containerName := fmt.Sprintf("%s_%d", tID, i) - testName := fmt.Sprintf("%s_container:%s_network:%s_expect:%s", tID, containerName, test.Network, test.Expect) - expect := test.Expect - network := test.Network - wantErr := test.WantErr - t.Run(testName, func(tt *testing.T) { - tt.Parallel() - - macAddress, err := nettestutil.GenerateMACAddress() - if err != nil { - tt.Errorf("failed to generate MAC address: %s", err) - } - if expect == passedMac { - expect = macAddress - } - tearDown := func() { - base.Cmd("rm", "-f", containerName).Run() - } - tearDown() - tt.Cleanup(tearDown) - // This is currently blocked by https://github.com/containerd/nerdctl/pull/3104 - // res := base.Cmd("create", "-i", "--network", network, "--mac-address", macAddress, testutil.CommonImage).Run() - res := base.Cmd("create", "--network", network, "--name", containerName, - "--mac-address", macAddress, testutil.CommonImage, - "sh", "-c", "--", "ip addr show").Run() - - if !wantErr { - assert.Assert(t, res.ExitCode == 0, "Command should have succeeded", res) - // This is currently blocked by: https://github.com/containerd/nerdctl/pull/3104 - // res = base.Cmd("start", "-i", containerName). - // CmdOption(testutil.WithStdin(strings.NewReader("ip addr show eth0 | grep ether | awk '{printf $2}'"))).Run() - res = base.Cmd("start", "-a", containerName).Run() - // FIXME: flaky - this has failed on the CI once, with the output NOT containing anything - // https://github.com/containerd/nerdctl/actions/runs/11392051487/job/31697214002?pr=3535#step:7:271 - assert.Assert(t, strings.Contains(res.Stdout(), expect), fmt.Sprintf("expected output to contain %q: %q", expect, res.Stdout())) - assert.Assert(t, res.ExitCode == 0, "Command should have succeeded") - } else { - if nerdtest.IsDocker() && - (network == networkIPvlan || network == "container:whatever"+tID) { - // unlike nerdctl - // when using network ipvlan or container in Docker - // it delays fail on executing start command - assert.Assert(t, res.ExitCode == 0, "Command should have succeeded", res) - res = base.Cmd("start", "-i", "-a", containerName). - CmdOption(testutil.WithStdin(strings.NewReader("ip addr show eth0 | grep ether | awk '{printf $2}'"))).Run() - } - // See https://github.com/containerd/nerdctl/issues/3101 - if nerdtest.IsDocker() && - (network == networkBridge) { - expect = "" + makeCreateCommand := func(network string) test.Executor { + return func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("create", + "--network", network, + "--name", data.Identifier(), + "--mac-address", data.Labels().Get(macAddressKey), + testutil.CommonImage, "sh", "-c", "--", "ip addr show") + } + } + makeDynamicCreateCommand := func(networkKey string) test.Executor { + return func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("create", + "--network", data.Labels().Get(networkKey), + "--name", data.Identifier(), + "--mac-address", data.Labels().Get(macAddressKey), + testutil.CommonImage, "sh", "-c", "--", "ip addr show") + } + } + + testCase.SubTests = []*test.Case{ + { + Description: "host network - container inherits host MAC", + Setup: setupMAC, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: makeCreateCommand("host"), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + startOut := helpers.Capture("start", "-a", data.Identifier()) + assert.Assert(t, strings.Contains(startOut, data.Labels().Get(defaultMacKey)), + fmt.Sprintf("expected start output to contain %q: %q", data.Labels().Get(defaultMacKey), startOut)) + }, + } + }, + }, + { + Description: "none network - MAC address flag is accepted", + Setup: setupMAC, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: makeCreateCommand("none"), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + helpers.Ensure("start", "-a", data.Identifier()) + }, + } + }, + }, + { + Description: "container network - nonexistent container fails", + Setup: setupMAC, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: makeCreateCommand("container:nonexistent-container-for-test"), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + if nerdtest.IsDocker() { + // Docker delays the failure to start time + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + helpers.Command("start", "-i", "-a", data.Identifier()). + Run(&test.Expected{ + ExitCode: 1, + Errors: []error{errors.New("container")}, + }) + }, + } } - if expect != "" { - assert.Assert(t, strings.Contains(res.Combined(), expect), fmt.Sprintf("expected output to contain %q: %q", expect, res.Combined())) - } else { - assert.Assert(t, res.Combined() == "", fmt.Sprintf("expected output to be empty: %q", res.Combined())) + return &test.Expected{ + ExitCode: 1, + Errors: []error{errors.New("container")}, + } + }, + }, + { + Description: "bridge network - MAC address is applied", + Setup: setupMAC, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: makeCreateCommand("bridge"), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + startOut := helpers.Capture("start", "-a", data.Identifier()) + assert.Assert(t, strings.Contains(startOut, data.Labels().Get(macAddressKey)), + fmt.Sprintf("expected start output to contain %q: %q", data.Labels().Get(macAddressKey), startOut)) + }, + } + }, + }, + { + Description: "custom bridge network - MAC address is applied", + Setup: setupMAC, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: makeDynamicCreateCommand(networkBridgeKey), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + startOut := helpers.Capture("start", "-a", data.Identifier()) + assert.Assert(t, strings.Contains(startOut, data.Labels().Get(macAddressKey)), + fmt.Sprintf("expected start output to contain %q: %q", data.Labels().Get(macAddressKey), startOut)) + }, + } + }, + }, + { + Description: "macvlan network - MAC address is applied", + Setup: setupMAC, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: makeDynamicCreateCommand(networkMACvlanKey), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + startOut := helpers.Capture("start", "-a", data.Identifier()) + assert.Assert(t, strings.Contains(startOut, data.Labels().Get(macAddressKey)), + fmt.Sprintf("expected start output to contain %q: %q", data.Labels().Get(macAddressKey), startOut)) + }, + } + }, + }, + { + Description: "ipvlan network - MAC address setting not supported", + Setup: setupMAC, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: makeDynamicCreateCommand(networkIPvlanKey), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + if nerdtest.IsDocker() { + // Docker delays the failure to start time + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + helpers.Command("start", "-i", "-a", data.Identifier()). + Run(&test.Expected{ + ExitCode: 1, + Errors: []error{errors.New("not support")}, + }) + }, + } + } + return &test.Expected{ + ExitCode: 1, + Errors: []error{errors.New("not support")}, } - assert.Assert(t, res.ExitCode != 0, "Command should have failed", res) - } - }) + }, + }, } + testCase.Run(t) } func TestCreateWithTty(t *testing.T) { - base := testutil.NewBase(t) - imageName := testutil.CommonImage - withoutTtyContainerName := "without-terminal-" + testutil.Identifier(t) - withTtyContainerName := "with-terminal-" + testutil.Identifier(t) - - // without -t, fail - base.Cmd("create", "--name", withoutTtyContainerName, imageName, "stty").AssertOK() - base.Cmd("start", withoutTtyContainerName).AssertOK() - defer base.Cmd("container", "rm", "-f", withoutTtyContainerName).AssertOK() - base.Cmd("logs", withoutTtyContainerName).AssertCombinedOutContains("stty: standard input: Not a tty") - withoutTtyContainer := base.InspectContainer(withoutTtyContainerName) - assert.Equal(base.T, 1, withoutTtyContainer.State.ExitCode) - - // with -t, success - base.Cmd("create", "-t", "--name", withTtyContainerName, imageName, "stty").AssertOK() - base.Cmd("start", withTtyContainerName).AssertOK() - defer base.Cmd("container", "rm", "-f", withTtyContainerName).AssertOK() - base.Cmd("logs", withTtyContainerName).AssertCombinedOutContains("speed 38400 baud; line = 0;") - withTtyContainer := base.InspectContainer(withTtyContainerName) - assert.Equal(base.T, 0, withTtyContainer.State.ExitCode) + testCase := nerdtest.Setup() + testCase.SubTests = []*test.Case{ + { + Description: "create without tty - stty exits with error", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("create", "--name", data.Identifier(), testutil.CommonImage, "stty") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("container", "rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("start", "-a", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 1, + Errors: []error{errors.New("stty: standard input: Not a tty")}, + } + }, + }, + { + Description: "create with tty - stty succeeds", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("create", "-t", "--name", data.Identifier(), testutil.CommonImage, "stty") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("container", "rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("start", "-a", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + assert.Assert(t, strings.Contains(stdout, "speed 38400 baud; line = 0;"), + fmt.Sprintf("expected stdout to contain speed info: %q", stdout)) + }, + } + }, + }, + } + testCase.Run(t) } // TestIssue2993 tests https://github.com/containerd/nerdctl/issues/2993 @@ -301,110 +430,121 @@ func TestIssue2993(t *testing.T) { } func TestCreateFromOCIArchive(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - - // Docker does not support creating containers from OCI archive. - testutil.DockerIncompatible(t) - - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - containerName := testutil.Identifier(t) - - teardown := func() { - base.Cmd("rm", "-f", containerName).Run() - base.Cmd("rmi", "-f", imageName).Run() - } - defer teardown() - teardown() + testCase := nerdtest.Setup() + testCase.Require = require.All( + nerdtest.Build, + require.Not(nerdtest.Docker), + ) const sentinel = "test-nerdctl-create-from-oci-archive" - dockerfile := fmt.Sprintf(`FROM %s - CMD ["echo", "%s"]`, testutil.CommonImage, sentinel) - buildCtx := helpers.CreateBuildContext(t, dockerfile) - tag := fmt.Sprintf("%s:latest", imageName) - tarPath := fmt.Sprintf("%s/%s.tar", buildCtx, imageName) - - base.Cmd("build", "--tag", tag, fmt.Sprintf("--output=type=oci,dest=%s", tarPath), buildCtx).AssertOK() - base.Cmd("create", "--rm", "--name", containerName, fmt.Sprintf("oci-archive://%s", tarPath)).AssertOK() - base.Cmd("start", "--attach", containerName).AssertOutContains("test-nerdctl-create-from-oci-archive") + testCase.Setup = func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf("FROM %s\nCMD [\"echo\", \"%s\"]", testutil.CommonImage, sentinel) + err := os.WriteFile(filepath.Join(data.Temp().Path(), "Dockerfile"), []byte(dockerfile), 0644) + assert.NilError(helpers.T(), err) + + imageName := data.Identifier("image") + ":latest" + tarPath := data.Temp().Path("image.tar") + data.Labels().Set("imageName", imageName) + data.Labels().Set("tarPath", tarPath) + + helpers.Ensure("build", "--tag", imageName, + fmt.Sprintf("--output=type=oci,dest=%s", tarPath), + data.Temp().Path()) + helpers.Ensure("create", "--rm", "--name", data.Identifier(), + fmt.Sprintf("oci-archive://%s", tarPath)) + } + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Labels().Get("imageName")) + helpers.Anyhow("builder", "prune", "--all", "--force") + } + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("start", "--attach", data.Identifier()) + } + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + assert.Assert(t, strings.Contains(stdout, sentinel), + fmt.Sprintf("expected stdout to contain %q: %q", sentinel, stdout)) + }, + } + } + testCase.Run(t) } func TestUsernsMappingCreateCmd(t *testing.T) { - nerdtest.Setup() - - testCase := &test.Case{ - Require: require.All( - nerdtest.AllowModifyUserns, - nerdtest.RemapIDs, - require.Not(nerdtest.Docker)), - NoParallel: true, - Setup: func(data test.Data, helpers test.Helpers) { - data.Labels().Set("validUserns", "nerdctltestuser") - data.Labels().Set("expectedHostUID", "123456789") - data.Labels().Set("invalidUserns", "invaliduser") + testCase := nerdtest.Setup() + testCase.Require = require.All( + nerdtest.AllowModifyUserns, + nerdtest.RemapIDs, + require.Not(nerdtest.Docker)) + testCase.NoParallel = true + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Labels().Set("validUserns", "nerdctltestuser") + data.Labels().Set("expectedHostUID", "123456789") + data.Labels().Set("invalidUserns", "invaliduser") + } + testCase.SubTests = []*test.Case{ + { + Description: "Test container create with valid Userns", + NoParallel: true, // Changes system config so running in non parallel mode + Setup: func(data test.Data, helpers test.Helpers) { + err := appendUsernsConfig(data.Labels().Get("validUserns"), data.Labels().Get("expectedHostUID"), helpers) + assert.NilError(helpers.T(), err, "Failed to append Userns config") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + removeUsernsConfig(helpers.T(), data.Labels().Get("validUserns"), helpers) + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + helpers.Ensure("create", "--tty", "--userns-remap", data.Labels().Get("validUserns"), "--name", data.Identifier(), testutil.NginxAlpineImage) + return helpers.Command("start", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + actualHostUID, err := getContainerHostUID(helpers, data.Identifier()) + assert.NilError(t, err, "Failed to get container host UID") + assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID")) + }, + } + }, }, - SubTests: []*test.Case{ - { - Description: "Test container create with valid Userns", - NoParallel: true, // Changes system config so running in non parallel mode - Setup: func(data test.Data, helpers test.Helpers) { - err := appendUsernsConfig(data.Labels().Get("validUserns"), data.Labels().Get("expectedHostUID"), helpers) - assert.NilError(t, err, "Failed to append Userns config") - }, - Cleanup: func(data test.Data, helpers test.Helpers) { - removeUsernsConfig(t, data.Labels().Get("validUserns"), helpers) - helpers.Anyhow("rm", "-f", data.Identifier()) - }, - Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - helpers.Ensure("create", "--tty", "--userns-remap", data.Labels().Get("validUserns"), "--name", data.Identifier(), testutil.NginxAlpineImage) - return helpers.Command("start", data.Identifier()) - }, - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - ExitCode: 0, - Output: func(stdout string, t tig.T) { - actualHostUID, err := getContainerHostUID(helpers, data.Identifier()) - assert.NilError(t, err, "Failed to get container host UID") - assert.Assert(t, actualHostUID == data.Labels().Get("expectedHostUID")) - }, - } - }, - }, - { - Description: "Test container create failure with valid Userns and privileged flag", - NoParallel: true, // Changes system config so running in non parallel mode - Setup: func(data test.Data, helpers test.Helpers) { - err := appendUsernsConfig(data.Labels().Get("validUserns"), data.Labels().Get("expectedHostUID"), helpers) - assert.NilError(t, err, "Failed to append Userns config") - }, - Cleanup: func(data test.Data, helpers test.Helpers) { - removeUsernsConfig(t, data.Labels().Get("validUserns"), helpers) - }, - Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - return helpers.Command("create", "--tty", "--privileged", "--userns-remap", data.Labels().Get("validUserns"), "--name", data.Identifier(), testutil.NginxAlpineImage) - }, - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - ExitCode: 1, - } - }, - }, - { - Description: "Test container create with invalid Userns", - NoParallel: true, // Changes system config so running in non parallel mode - Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", data.Identifier()) - }, - Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - return helpers.Command("create", "--tty", "--userns-remap", data.Labels().Get("invalidUserns"), "--name", data.Identifier(), testutil.NginxAlpineImage) - }, - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - ExitCode: 1, - } - }, + { + Description: "Test container create failure with valid Userns and privileged flag", + NoParallel: true, // Changes system config so running in non parallel mode + Setup: func(data test.Data, helpers test.Helpers) { + err := appendUsernsConfig(data.Labels().Get("validUserns"), data.Labels().Get("expectedHostUID"), helpers) + assert.NilError(helpers.T(), err, "Failed to append Userns config") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + removeUsernsConfig(helpers.T(), data.Labels().Get("validUserns"), helpers) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("create", "--tty", "--privileged", "--userns-remap", data.Labels().Get("validUserns"), "--name", data.Identifier(), testutil.NginxAlpineImage) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 1, + } + }, + }, + { + Description: "Test container create with invalid Userns", + NoParallel: true, // Changes system config so running in non parallel mode + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("create", "--tty", "--userns-remap", data.Labels().Get("invalidUserns"), "--name", data.Identifier(), testutil.NginxAlpineImage) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 1, + } }, }, } @@ -473,34 +613,32 @@ func addUser(username string, hostID string, helpers test.Helpers) { ExitCode: 0}) } -func removeUsernsConfig(t *testing.T, userns string, helpers test.Helpers) { +func removeUsernsConfig(t tig.T, userns string, helpers test.Helpers) { delUser(userns, helpers) delGroup(userns, helpers) - tempDir := helpers.T().TempDir() + tempDir := t.TempDir() files := []string{"subuid", "subgid"} for _, file := range files { fileBak := filepath.Join(tempDir, file) s, err := os.Open(fileBak) if err != nil { - t.Logf("failed to open %s, Error: %s", fileBak, err) + t.Log(fmt.Sprintf("failed to open %s, Error: %s", fileBak, err)) continue } defer s.Close() d, err := os.Open(filepath.Join("/etc/%s", file)) if err != nil { - t.Logf("failed to open %s, Error: %s", file, err) + t.Log(fmt.Sprintf("failed to open %s, Error: %s", file, err)) continue - } defer d.Close() _, err = io.Copy(d, s) if err != nil { - t.Logf("failed to restore. Copy %s to %s failed, Error %s", fileBak, file, err) + t.Log(fmt.Sprintf("failed to restore. Copy %s to %s failed, Error %s", fileBak, file, err)) continue } - } } From d0200058a21e08037e1c8dda57ead2ebdf5e5eac Mon Sep 17 00:00:00 2001 From: Ogulcan Aydogan Date: Thu, 14 May 2026 23:42:59 +0100 Subject: [PATCH 2/4] fix: use logs instead of start -a for tty container in TestCreateWithTty With -t, the container's output goes through a pseudoTTY. Attaching via "start -a" does not reliably forward PTY output through Tigron's subprocess pipe, causing the stty check to fail on certain containerd versions (e.g. v1.7.30). Match the original test's approach: start the container without -a, then read its output via "nerdctl logs" which goes through the log driver and is always available after the container exits. Signed-off-by: Ogulcan Aydogan --- cmd/nerdctl/container/container_create_linux_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/nerdctl/container/container_create_linux_test.go b/cmd/nerdctl/container/container_create_linux_test.go index 6e8290902b4..06a712053fc 100644 --- a/cmd/nerdctl/container/container_create_linux_test.go +++ b/cmd/nerdctl/container/container_create_linux_test.go @@ -288,20 +288,20 @@ func TestCreateWithTty(t *testing.T) { Description: "create with tty - stty succeeds", Setup: func(data test.Data, helpers test.Helpers) { helpers.Ensure("create", "-t", "--name", data.Identifier(), testutil.CommonImage, "stty") + // start without -a: tty output is not forwarded over a pipe, so + // capturing it via "start -a" is unreliable. Use "logs" instead, + // which reads from the containerd log driver regardless of tty. + helpers.Ensure("start", data.Identifier()) }, Cleanup: func(data test.Data, helpers test.Helpers) { helpers.Anyhow("container", "rm", "-f", data.Identifier()) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - return helpers.Command("start", "-a", data.Identifier()) + return helpers.Command("logs", data.Identifier()) }, Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, - Output: func(stdout string, t tig.T) { - assert.Assert(t, strings.Contains(stdout, "speed 38400 baud; line = 0;"), - fmt.Sprintf("expected stdout to contain speed info: %q", stdout)) - }, + Output: expect.Contains("speed 38400 baud; line = 0;"), } }, }, From 047d081e51a4006ace71173e01b5f792a4ca004e Mon Sep 17 00:00:00 2001 From: Ogulcan Aydogan Date: Fri, 15 May 2026 11:45:06 +0100 Subject: [PATCH 3/4] refactor: use tigron expect constants for exit codes Replace hardcoded exit code literals (0, 1) in Tigron test.Expected structs with the named constants from the expect package: ExitCodeSuccess, ExitCodeGenericFail. Existing ExitCodeNoCheck usages were already correct. Signed-off-by: Ogulcan Aydogan --- .../container/container_create_linux_test.go | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/nerdctl/container/container_create_linux_test.go b/cmd/nerdctl/container/container_create_linux_test.go index 06a712053fc..72431064fcb 100644 --- a/cmd/nerdctl/container/container_create_linux_test.go +++ b/cmd/nerdctl/container/container_create_linux_test.go @@ -51,7 +51,7 @@ func TestCreateWithLabel(t *testing.T) { } testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { fooLabel := strings.TrimSpace(helpers.Capture("inspect", "--format", `{{index .Config.Labels "foo"}}`, data.Identifier())) assert.Equal(t, "bar", fooLabel) @@ -126,7 +126,7 @@ func TestCreateWithMACAddress(t *testing.T) { Command: makeCreateCommand("host"), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { startOut := helpers.Capture("start", "-a", data.Identifier()) assert.Assert(t, strings.Contains(startOut, data.Labels().Get(defaultMacKey)), @@ -144,7 +144,7 @@ func TestCreateWithMACAddress(t *testing.T) { Command: makeCreateCommand("none"), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { helpers.Ensure("start", "-a", data.Identifier()) }, @@ -162,18 +162,18 @@ func TestCreateWithMACAddress(t *testing.T) { if nerdtest.IsDocker() { // Docker delays the failure to start time return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { helpers.Command("start", "-i", "-a", data.Identifier()). Run(&test.Expected{ - ExitCode: 1, + ExitCode: expect.ExitCodeGenericFail, Errors: []error{errors.New("container")}, }) }, } } return &test.Expected{ - ExitCode: 1, + ExitCode: expect.ExitCodeGenericFail, Errors: []error{errors.New("container")}, } }, @@ -187,7 +187,7 @@ func TestCreateWithMACAddress(t *testing.T) { Command: makeCreateCommand("bridge"), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { startOut := helpers.Capture("start", "-a", data.Identifier()) assert.Assert(t, strings.Contains(startOut, data.Labels().Get(macAddressKey)), @@ -205,7 +205,7 @@ func TestCreateWithMACAddress(t *testing.T) { Command: makeDynamicCreateCommand(networkBridgeKey), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { startOut := helpers.Capture("start", "-a", data.Identifier()) assert.Assert(t, strings.Contains(startOut, data.Labels().Get(macAddressKey)), @@ -223,7 +223,7 @@ func TestCreateWithMACAddress(t *testing.T) { Command: makeDynamicCreateCommand(networkMACvlanKey), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { startOut := helpers.Capture("start", "-a", data.Identifier()) assert.Assert(t, strings.Contains(startOut, data.Labels().Get(macAddressKey)), @@ -243,18 +243,18 @@ func TestCreateWithMACAddress(t *testing.T) { if nerdtest.IsDocker() { // Docker delays the failure to start time return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { helpers.Command("start", "-i", "-a", data.Identifier()). Run(&test.Expected{ - ExitCode: 1, + ExitCode: expect.ExitCodeGenericFail, Errors: []error{errors.New("not support")}, }) }, } } return &test.Expected{ - ExitCode: 1, + ExitCode: expect.ExitCodeGenericFail, Errors: []error{errors.New("not support")}, } }, @@ -279,7 +279,7 @@ func TestCreateWithTty(t *testing.T) { }, Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 1, + ExitCode: expect.ExitCodeGenericFail, Errors: []error{errors.New("stty: standard input: Not a tty")}, } }, @@ -363,7 +363,7 @@ func TestIssue2993(t *testing.T) { }, Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 1, + ExitCode: expect.ExitCodeGenericFail, Errors: []error{errors.New("is already used by ID")}, Output: func(stdout string, t tig.T) { containersDirs, err := os.ReadDir(data.Labels().Get(containersPathKey)) @@ -410,7 +410,7 @@ func TestIssue2993(t *testing.T) { }, Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Errors: []error{}, Output: func(stdout string, t tig.T) { containersDirs, err := os.ReadDir(data.Labels().Get(containersPathKey)) @@ -464,7 +464,7 @@ func TestCreateFromOCIArchive(t *testing.T) { } testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { assert.Assert(t, strings.Contains(stdout, sentinel), fmt.Sprintf("expected stdout to contain %q: %q", sentinel, stdout)) @@ -504,7 +504,7 @@ func TestUsernsMappingCreateCmd(t *testing.T) { }, Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 0, + ExitCode: expect.ExitCodeSuccess, Output: func(stdout string, t tig.T) { actualHostUID, err := getContainerHostUID(helpers, data.Identifier()) assert.NilError(t, err, "Failed to get container host UID") @@ -528,7 +528,7 @@ func TestUsernsMappingCreateCmd(t *testing.T) { }, Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 1, + ExitCode: expect.ExitCodeGenericFail, } }, }, @@ -543,7 +543,7 @@ func TestUsernsMappingCreateCmd(t *testing.T) { }, Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 1, + ExitCode: expect.ExitCodeGenericFail, } }, }, @@ -608,9 +608,9 @@ func appendUsernsConfig(userns string, hostUID string, helpers test.Helpers) err func addUser(username string, hostID string, helpers test.Helpers) { helpers.Custom("groupadd", "-g", hostID, username).Run(&test.Expected{ - ExitCode: 0}) + ExitCode: expect.ExitCodeSuccess}) helpers.Custom("useradd", "-u", hostID, "-g", hostID, "-s", "/bin/false", username).Run(&test.Expected{ - ExitCode: 0}) + ExitCode: expect.ExitCodeSuccess}) } func removeUsernsConfig(t tig.T, userns string, helpers test.Helpers) { From 9acaed1dc006b5cbc44ec54e6680f91959426182 Mon Sep 17 00:00:00 2001 From: Ogulcan Aydogan Date: Sat, 16 May 2026 23:09:29 +0100 Subject: [PATCH 4/4] refactor: use explicit ExitCode constant in test.Expected struct Signed-off-by: Ogulcan Aydogan --- cmd/nerdctl/container/container_create_linux_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/nerdctl/container/container_create_linux_test.go b/cmd/nerdctl/container/container_create_linux_test.go index 72431064fcb..e0921d1937e 100644 --- a/cmd/nerdctl/container/container_create_linux_test.go +++ b/cmd/nerdctl/container/container_create_linux_test.go @@ -301,7 +301,8 @@ func TestCreateWithTty(t *testing.T) { }, Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - Output: expect.Contains("speed 38400 baud; line = 0;"), + ExitCode: expect.ExitCodeSuccess, + Output: expect.Contains("speed 38400 baud; line = 0;"), } }, },