diff --git a/cmd/nerdctl/container/container_list_windows_test.go b/cmd/nerdctl/container/container_list_windows_test.go index 08bd3c3c5c8..1657ba83181 100644 --- a/cmd/nerdctl/container/container_list_windows_test.go +++ b/cmd/nerdctl/container/container_list_windows_test.go @@ -17,230 +17,274 @@ package container import ( + "encoding/json" "fmt" "strings" "testing" "gotest.tools/v3/assert" + "github.com/containerd/nerdctl/mod/tigron/require" + "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/mod/tigron/tig" + "github.com/containerd/nerdctl/v2/pkg/formatter" + "github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat" "github.com/containerd/nerdctl/v2/pkg/strutil" "github.com/containerd/nerdctl/v2/pkg/tabutil" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) -type psTestContainer struct { - name string - labels map[string]string - network string -} - -func preparePsTestContainer(t *testing.T, identity string, restart bool, hyperv bool) (*testutil.Base, psTestContainer) { - base := testutil.NewBase(t) +func setupPsTestContainer(identity string, restart bool, hyperv bool) func(data test.Data, helpers test.Helpers) { + return func(data test.Data, helpers test.Helpers) { + helpers.Ensure("pull", "--quiet", testutil.NginxAlpineImage) - base.Cmd("pull", "--quiet", testutil.NginxAlpineImage).AssertOK() + testContainerName := data.Identifier(identity) + data.Labels().Set("containerName", testContainerName) - testContainerName := testutil.Identifier(t) + identity - t.Cleanup(func() { - base.Cmd("rm", "-f", testContainerName).AssertOK() - }) - - // A container can have multiple labels. - // Therefore, this test container has multiple labels to check it. - testLabels := make(map[string]string) - keys := []string{ - testutil.Identifier(t) + identity, - testutil.Identifier(t) + identity, - } - // fill the value of testLabels - for _, k := range keys { - testLabels[k] = k - } + // A container can have multiple labels. + // Therefore, this test container has multiple labels to check it. + testLabels := make(map[string]string) + keys := []string{ + data.Identifier(identity), + data.Identifier(identity), + } + // fill the value of testLabels + for idx, k := range keys { + testLabels[k] = k + data.Labels().Set(fmt.Sprintf("label-key-%d", idx), k) + data.Labels().Set(fmt.Sprintf("label-value-%d", idx), k) + } - args := []string{ - "run", - "-d", - "--name", - testContainerName, - "--label", - formatter.FormatLabels(testLabels), - testutil.NginxAlpineImage, - } - if !restart { - args = append(args, "--restart=no") - } - if hyperv { - args = append(args[:3], args[1:]...) - args[1], args[2] = "--isolation", "hyperv" - } + args := []string{ + "run", + "-d", + "--name", + testContainerName, + "--label", + formatter.FormatLabels(testLabels), + testutil.NginxAlpineImage, + } + if !restart { + args = append(args, "--restart=no") + } + if hyperv { + args = append(args[:3], args[1:]...) + args[1], args[2] = "--isolation", "hyperv" + } - base.Cmd(args...).AssertOK() - if restart { - base.EnsureContainerStarted(testContainerName) + helpers.Ensure(args...) + if restart { + // Wait for container to start - using polling + helpers.Ensure("exec", testContainerName, "echo", "ready") + } } +} - return base, psTestContainer{ - name: testContainerName, - labels: testLabels, - network: testContainerName, +func cleanupPsTestContainer() func(data test.Data, helpers test.Helpers) { + return func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Labels().Get("containerName")) } } func TestListProcessContainer(t *testing.T) { - base, testContainer := preparePsTestContainer(t, "list", true, false) + testCase := nerdtest.Setup() - // hope there are no tests running parallel - base.Cmd("ps", "-n", "1", "-s").AssertOutWithFunc(func(stdout string) error { - // An example of nerdctl/docker ps -n 1 -s - // CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE - // be8d386c991e docker.io/library/busybox:latest "top" 1 second ago Up c1 16.0 KiB (virtual 1.3 MiB) + testCase.Setup = setupPsTestContainer("list", true, false) + testCase.Cleanup = cleanupPsTestContainer() - lines := strings.Split(strings.TrimSpace(stdout), "\n") - if len(lines) < 2 { - return fmt.Errorf("expected at least 2 lines, got %d", len(lines)) - } + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("ps", "-s", "--filter", fmt.Sprintf("name=%s", data.Labels().Get("containerName"))) + } - tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tSIZE") - err := tab.ParseHeader(lines[0]) - if err != nil { - return fmt.Errorf("failed to parse header: %v", err) - } + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + // An example of nerdctl/docker ps -n 1 -s + // CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE + // be8d386c991e docker.io/library/busybox:latest "top" 1 second ago Up c1 16.0 KiB (virtual 1.3 MiB) + + lines := strings.Split(strings.TrimSpace(stdout), "\n") + assert.Assert(t, len(lines) >= 2, fmt.Sprintf("expected at least 2 lines, got %d", len(lines))) - container, _ := tab.ReadRow(lines[1], "NAMES") - assert.Equal(t, container, testContainer.name) + tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tSIZE") + err := tab.ParseHeader(lines[0]) + assert.NilError(t, err, "failed to parse header") - image, _ := tab.ReadRow(lines[1], "IMAGE") - assert.Equal(t, image, testutil.NginxAlpineImage) + container, _ := tab.ReadRow(lines[1], "NAMES") + assert.Equal(t, container, data.Labels().Get("containerName")) - size, _ := tab.ReadRow(lines[1], "SIZE") + image, _ := tab.ReadRow(lines[1], "IMAGE") + assert.Equal(t, image, testutil.NginxAlpineImage) - // there is some difference between nerdctl and docker in calculating the size of the container - expectedSize := "36.0 MiB (virtual " + size, _ := tab.ReadRow(lines[1], "SIZE") - if !strings.Contains(size, expectedSize) { - return fmt.Errorf("expect container size %s, but got %s", expectedSize, size) + // there is some difference between nerdctl and docker in calculating the size of the container + // nerdctl: "36.0 MiB (virtual ...)" + // docker: "53.2kB (virtual 19.3MB)" or similar + assert.Assert(t, strings.Contains(size, "(virtual"), + fmt.Sprintf("expect container size to contain '(virtual', but got %s", size)) + }, } + } - return nil - }) + testCase.Run(t) } func TestListHyperVContainer(t *testing.T) { - if !testutil.HyperVSupported() { - t.Skip("HyperV is not enabled, skipping test") + testCase := nerdtest.Setup() + + testCase.Require = &test.Requirement{ + Check: func(_ test.Data, _ test.Helpers) (bool, string) { + if !testutil.HyperVSupported() { + return false, "HyperV is not enabled, skipping test" + } + return true, "" + }, } - base, testContainer := preparePsTestContainer(t, "list", true, true) - inspect := base.InspectContainer(testContainer.name) - //check with HCS if the container is ineed a VM - isHypervContainer, err := testutil.HyperVContainer(inspect) - if err != nil { - t.Fatalf("unable to list HCS containers: %s", err) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + setupPsTestContainer("list", true, true)(data, helpers) + + // Check with HCS if the container is indeed a VM + containerName := data.Labels().Get("containerName") + inspectOutput := helpers.Capture("inspect", containerName, "--format", "json") + + var inspect []dockercompat.Container + err := json.Unmarshal([]byte(inspectOutput), &inspect) + assert.NilError(helpers.T(), err, "failed to unmarshal inspect output") + assert.Assert(helpers.T(), len(inspect) > 0, "expected at least one container in inspect output") + + isHypervContainer, err := testutil.HyperVContainer(inspect[0]) + assert.NilError(helpers.T(), err, "unable to list HCS containers") + assert.Assert(helpers.T(), isHypervContainer, "expected HyperV container") } - assert.Assert(t, isHypervContainer, true) + testCase.Cleanup = cleanupPsTestContainer() - // hope there are no tests running parallel - base.Cmd("ps", "-n", "1", "-s").AssertOutWithFunc(func(stdout string) error { - // An example of nerdctl/docker ps -n 1 -s - // CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE - // be8d386c991e docker.io/library/busybox:latest "top" 1 second ago Up c1 16.0 KiB (virtual 1.3 MiB) + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("ps", "-n", "1", "-s") + } - lines := strings.Split(strings.TrimSpace(stdout), "\n") - if len(lines) < 2 { - return fmt.Errorf("expected at least 2 lines, got %d", len(lines)) - } + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + // An example of nerdctl/docker ps -n 1 -s + // CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE + // be8d386c991e docker.io/library/busybox:latest "top" 1 second ago Up c1 16.0 KiB (virtual 1.3 MiB) - tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tSIZE") - err := tab.ParseHeader(lines[0]) - if err != nil { - return fmt.Errorf("failed to parse header: %v", err) - } + lines := strings.Split(strings.TrimSpace(stdout), "\n") + assert.Assert(t, len(lines) >= 2, fmt.Sprintf("expected at least 2 lines, got %d", len(lines))) + + tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tSIZE") + err := tab.ParseHeader(lines[0]) + assert.NilError(t, err, "failed to parse header") - container, _ := tab.ReadRow(lines[1], "NAMES") - assert.Equal(t, container, testContainer.name) + container, _ := tab.ReadRow(lines[1], "NAMES") + assert.Equal(t, container, data.Labels().Get("containerName")) - image, _ := tab.ReadRow(lines[1], "IMAGE") - assert.Equal(t, image, testutil.NginxAlpineImage) + image, _ := tab.ReadRow(lines[1], "IMAGE") + assert.Equal(t, image, testutil.NginxAlpineImage) - size, _ := tab.ReadRow(lines[1], "SIZE") + size, _ := tab.ReadRow(lines[1], "SIZE") - // there is some difference between nerdctl and docker in calculating the size of the container - expectedSize := "72.0 MiB (virtual " + // there is some difference between nerdctl and docker in calculating the size of the container + expectedSize := "72.0 MiB (virtual " - if !strings.Contains(size, expectedSize) { - return fmt.Errorf("expect container size %s, but got %s", expectedSize, size) + assert.Assert(t, strings.Contains(size, expectedSize), + fmt.Sprintf("expect container size %s, but got %s", expectedSize, size)) + }, } + } - return nil - }) + testCase.Run(t) } func TestListProcessContainerWideMode(t *testing.T) { - testutil.DockerIncompatible(t) - base, testContainer := preparePsTestContainer(t, "listWithMode", true, false) + testCase := nerdtest.Setup() - // hope there are no tests running parallel - base.Cmd("ps", "-n", "1", "--format", "wide").AssertOutWithFunc(func(stdout string) error { + testCase.Require = require.Not(nerdtest.Docker) - // An example of nerdctl ps --format wide - // CONTAINER ID IMAGE PLATFORM COMMAND CREATED STATUS PORTS NAMES RUNTIME SIZE - // 17181f208b61 docker.io/library/busybox:latest linux/amd64 "top" About an hour ago Up busybox-17181 io.containerd.runc.v2 16.0 KiB (virtual 1.3 MiB) + testCase.Setup = setupPsTestContainer("listWithMode", true, false) + testCase.Cleanup = cleanupPsTestContainer() - lines := strings.Split(strings.TrimSpace(stdout), "\n") - if len(lines) < 2 { - return fmt.Errorf("expected at least 2 lines, got %d", len(lines)) - } + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("ps", "-n", "1", "--format", "wide") + } - tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tRUNTIME\tPLATFORM\tSIZE") - err := tab.ParseHeader(lines[0]) - if err != nil { - return fmt.Errorf("failed to parse header: %v", err) - } + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + // An example of nerdctl ps --format wide + // CONTAINER ID IMAGE PLATFORM COMMAND CREATED STATUS PORTS NAMES RUNTIME SIZE + // 17181f208b61 docker.io/library/busybox:latest linux/amd64 "top" About an hour ago Up busybox-17181 io.containerd.runc.v2 16.0 KiB (virtual 1.3 MiB) + + lines := strings.Split(strings.TrimSpace(stdout), "\n") + assert.Assert(t, len(lines) >= 2, fmt.Sprintf("expected at least 2 lines, got %d", len(lines))) + + tab := tabutil.NewReader("CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tRUNTIME\tPLATFORM\tSIZE") + err := tab.ParseHeader(lines[0]) + assert.NilError(t, err, "failed to parse header") - container, _ := tab.ReadRow(lines[1], "NAMES") - assert.Equal(t, container, testContainer.name) + container, _ := tab.ReadRow(lines[1], "NAMES") + assert.Equal(t, container, data.Labels().Get("containerName")) - image, _ := tab.ReadRow(lines[1], "IMAGE") - assert.Equal(t, image, testutil.NginxAlpineImage) + image, _ := tab.ReadRow(lines[1], "IMAGE") + assert.Equal(t, image, testutil.NginxAlpineImage) - runtime, _ := tab.ReadRow(lines[1], "RUNTIME") - assert.Equal(t, runtime, "io.containerd.runhcs.v1") + runtime, _ := tab.ReadRow(lines[1], "RUNTIME") + assert.Equal(t, runtime, "io.containerd.runhcs.v1") - size, _ := tab.ReadRow(lines[1], "SIZE") - expectedSize := "36.0 MiB (virtual " - if !strings.Contains(size, expectedSize) { - return fmt.Errorf("expect container size %s, but got %s", expectedSize, size) + size, _ := tab.ReadRow(lines[1], "SIZE") + expectedSize := "36.0 MiB (virtual " + assert.Assert(t, strings.Contains(size, expectedSize), + fmt.Sprintf("expect container size %s, but got %s", expectedSize, size)) + }, } - return nil - }) + } + + testCase.Run(t) } func TestListProcessContainerWithLabels(t *testing.T) { - base, testContainer := preparePsTestContainer(t, "listWithLabels", true, false) + testCase := nerdtest.Setup() - // hope there are no tests running parallel - base.Cmd("ps", "-n", "1", "--format", "{{.Labels}}").AssertOutWithFunc(func(stdout string) error { + testCase.Setup = setupPsTestContainer("listWithLabels", true, false) + testCase.Cleanup = cleanupPsTestContainer() - // An example of nerdctl ps --format "{{.Labels}}" - // key1=value1,key2=value2,key3=value3 - lines := strings.Split(strings.TrimSpace(stdout), "\n") - if len(lines) != 1 { - return fmt.Errorf("expected 1 line, got %d", len(lines)) - } + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("ps", "-n", "1", "--format", "{{.Labels}}") + } - // check labels using map - // 1. the results has no guarantee to show the same order. - // 2. the results has no guarantee to show only configured labels. - labelsMap, err := strutil.ParseCSVMap(lines[0]) - if err != nil { - return fmt.Errorf("failed to parse labels: %v", err) + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, t tig.T) { + // An example of nerdctl ps --format "{{.Labels}}" + // key1=value1,key2=value2,key3=value3 + lines := strings.Split(strings.TrimSpace(stdout), "\n") + assert.Assert(t, len(lines) == 1, fmt.Sprintf("expected 1 line, got %d", len(lines))) + + // check labels using map + // 1. the results has no guarantee to show the same order. + // 2. the results has no guarantee to show only configured labels. + labelsMap, err := strutil.ParseCSVMap(lines[0]) + assert.NilError(t, err, "failed to parse labels") + + // Verify the labels we set are present + for idx := 0; idx < 2; idx++ { + labelKey := data.Labels().Get(fmt.Sprintf("label-key-%d", idx)) + labelValue := data.Labels().Get(fmt.Sprintf("label-value-%d", idx)) + if value, ok := labelsMap[labelKey]; ok { + assert.Equal(t, value, labelValue) + } + } + }, } + } - for i := range testContainer.labels { - if value, ok := labelsMap[i]; ok { - assert.Equal(t, value, testContainer.labels[i]) - } - } - return nil - }) + testCase.Run(t) }