diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1501b0fcebf..ce0b4aa0dd4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,12 +99,7 @@ jobs: docker run --privileged --rm tonistiigi/binfmt --install linux/arm64 docker run --privileged --rm tonistiigi/binfmt --install linux/arm/v7 - name: "Run integration tests" - uses: nick-fields/retry@v3 - with: - timeout_minutes: 30 - max_attempts: 2 - retry_on: error - command: docker run -t --rm --privileged test-integration + run: docker run -t --rm --privileged test-integration test-integration-ipv6: runs-on: "ubuntu-${{ matrix.ubuntu }}" @@ -155,12 +150,7 @@ jobs: # On the other side, using the host network is easier at configuration. # Besides, each job is running on a different instance, which means using host network here # is safe and has no side effects on others. - uses: nick-fields/retry@v3 - with: - timeout_minutes: 30 - max_attempts: 2 - retry_on: error - command: docker run --network host -t --rm --privileged test-integration-ipv6 + run: docker run --network host -t --rm --privileged test-integration-ipv6 test-integration-rootless: runs-on: "ubuntu-${{ matrix.ubuntu }}" @@ -230,12 +220,7 @@ jobs: fi echo "WORKAROUND_ISSUE_622=${WORKAROUND_ISSUE_622}" >> "$GITHUB_ENV" - name: "Test (network driver=slirp4netns, port driver=builtin)" - uses: nick-fields/retry@v3 - with: - timeout_minutes: 30 - max_attempts: 2 - retry_on: error - command: docker run -t --rm --privileged -e WORKAROUND_ISSUE_622=${WORKAROUND_ISSUE_622} ${TEST_TARGET} + run: docker run -t --rm --privileged -e WORKAROUND_ISSUE_622=${WORKAROUND_ISSUE_622} ${TEST_TARGET} cross: runs-on: ubuntu-24.04 @@ -285,21 +270,11 @@ jobs: run: | sudo apt-get install -y expect - name: "Ensure that the integration test suite is compatible with Docker" - uses: nick-fields/retry@v3 - with: - timeout_minutes: 30 - max_attempts: 2 - retry_on: error - # See https://github.com/containerd/nerdctl/blob/main/docs/testing/README.md#about-parallelization - command: go test -p 1 -timeout 20m -v -exec sudo ./cmd/nerdctl/... -args -test.target=docker -test.allow-kill-daemon + # See https://github.com/containerd/nerdctl/blob/main/docs/testing.md#about-parallelization + run: go test -p 1 -timeout 20m -v -exec sudo ./cmd/nerdctl/... -args -test.target=docker -test.allow-kill-daemon - name: "Ensure that the IPv6 integration test suite is compatible with Docker" - uses: nick-fields/retry@v3 - with: - timeout_minutes: 30 - max_attempts: 2 - retry_on: error - # See https://github.com/containerd/nerdctl/blob/main/docs/testing/README.md#about-parallelization - command: go test -p 1 -timeout 20m -v -exec sudo ./cmd/nerdctl/... -args -test.target=docker -test.allow-kill-daemon -test.only-ipv6 + # See https://github.com/containerd/nerdctl/blob/main/docs/testing.md#about-parallelization + run: go test -p 1 -timeout 20m -v -exec sudo ./cmd/nerdctl/... -args -test.target=docker -test.allow-kill-daemon -test.only-ipv6 test-integration-windows: runs-on: windows-2022 diff --git a/Dockerfile b/Dockerfile index 36ff9c41381..e28f36ab602 100644 --- a/Dockerfile +++ b/Dockerfile @@ -280,6 +280,7 @@ FROM base AS test-integration ARG DEBIAN_FRONTEND=noninteractive # `expect` package contains `unbuffer(1)`, which is used for emulating TTY for testing RUN apt-get update -qq && apt-get install -qq --no-install-recommends \ + make \ expect \ git COPY --from=goversion /GOVERSION /GOVERSION @@ -290,6 +291,7 @@ ARG GOTESTSUM_VERSION RUN GOBIN=/usr/local/bin go install gotest.tools/gotestsum@${GOTESTSUM_VERSION} COPY . /go/src/github.com/containerd/nerdctl WORKDIR /go/src/github.com/containerd/nerdctl +RUN git config --global --add safe.directory /go/src/github.com/containerd/nerdctl VOLUME /tmp ENV CGO_ENABLED=0 # copy cosign binary for integration test @@ -318,8 +320,7 @@ RUN curl -o nydus-static.tgz -fsSL --proto '=https' --tlsv1.2 "https://github.co tar xzf nydus-static.tgz && \ mv nydus-static/nydus-image nydus-static/nydusd nydus-static/nydusify /usr/bin/ && \ rm nydus-static.tgz -CMD ["gotestsum", "--format=testname", "--rerun-fails=2", "--packages=./cmd/nerdctl/...", \ - "--", "-timeout=60m", "-p", "1", "-args", "-test.allow-kill-daemon"] +CMD ["make", "test"] FROM test-integration AS test-integration-rootless # Install SSH for creating systemd user session. @@ -342,9 +343,7 @@ RUN systemctl disable test-integration-ipfs-offline VOLUME /home/rootless/.local/share COPY ./Dockerfile.d/test-integration-rootless.sh / RUN chmod a+rx /test-integration-rootless.sh -CMD ["/test-integration-rootless.sh", \ - "gotestsum", "--format=testname", "--rerun-fails=2", "--packages=./cmd/nerdctl/...", \ - "--", "-timeout=60m", "-p", "1", "-args", "-test.allow-kill-daemon"] +CMD ["/test-integration-rootless.sh", "make", "test"] # test for CONTAINERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns FROM test-integration-rootless AS test-integration-rootless-port-slirp4netns @@ -352,7 +351,7 @@ COPY ./Dockerfile.d/home_rootless_.config_systemd_user_containerd.service.d_port RUN chown -R rootless:rootless /home/rootless/.config FROM test-integration AS test-integration-ipv6 -CMD ["gotestsum", "--format=testname", "--rerun-fails=2", "--packages=./cmd/nerdctl/...", \ +CMD ["gotestsum", "--format=testname", "--packages=./cmd/nerdctl/...", \ "--", "-timeout=60m", "-p", "1", "-args", "-test.allow-kill-daemon", "-test.only-ipv6"] FROM base AS demo diff --git a/Makefile b/Makefile index 0831c640047..0dfc2dfe805 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,14 @@ lint-yaml: lint-shell: $(call recursive_wildcard,$(MAKEFILE_DIR)/,*.sh) shellcheck -a -x $^ +test: test-no-retry test-flaky + +test-flaky: + gotestsum --rerun-fails=2 --format=testname --packages=./cmd/nerdctl/builder,./cmd/nerdctl/compose,./cmd/nerdctl/container,./cmd/nerdctl/ipfs,./cmd/nerdctl/image -- -timeout=60m -p 1 -args -test.allow-kill-daemon + +test-no-retry: + go test $(shell go list ./cmd/nerdctl/... | grep -v nerdctl/builder | grep -v nerdctl/compose | grep -v nerdctl/container | grep -v nerdctl/ipfs | grep -v nerdctl/image) -timeout=60m -p 1 -args -test.allow-kill-daemon + binaries: nerdctl install: diff --git a/cmd/nerdctl/completion/completion_linux_test.go b/cmd/nerdctl/completion/completion_linux_test.go index 8abb5483964..234c9a21ebc 100644 --- a/cmd/nerdctl/completion/completion_linux_test.go +++ b/cmd/nerdctl/completion/completion_linux_test.go @@ -20,53 +20,189 @@ import ( "testing" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) func TestCompletion(t *testing.T) { - testutil.DockerIncompatible(t) - base := testutil.NewBase(t) - const gsc = "__complete" - // cmd is executed with base.Args={"--namespace=nerdctl-test"} - base.Cmd(gsc, "--cgroup-manager", "").AssertOutContains("cgroupfs\n") - base.Cmd(gsc, "--snapshotter", "").AssertOutContains("native\n") - base.Cmd(gsc, "").AssertOutContains("run\t") - base.Cmd(gsc, "run", "-").AssertOutContains("--network\t") - base.Cmd(gsc, "run", "--n").AssertOutContains("--network\t") - base.Cmd(gsc, "run", "--ne").AssertOutContains("--network\t") - base.Cmd(gsc, "run", "--net", "").AssertOutContains("host\n") - base.Cmd(gsc, "run", "-it", "--net", "").AssertOutContains("host\n") - base.Cmd(gsc, "run", "-it", "--rm", "--net", "").AssertOutContains("host\n") - base.Cmd(gsc, "run", "--restart", "").AssertOutContains("always\n") - base.Cmd(gsc, "network", "rm", "").AssertOutNotContains("host\n") // host is unremovable - base.Cmd(gsc, "run", "--cap-add", "").AssertOutContains("sys_admin\n") - base.Cmd(gsc, "run", "--cap-add", "").AssertOutNotContains("CAP_SYS_ADMIN\n") // invalid form + nerdtest.Setup() - // Tests with an image - base.Cmd("pull", testutil.AlpineImage).AssertOK() - base.Cmd(gsc, "run", "-i", "").AssertOutContains(testutil.AlpineImage) - base.Cmd(gsc, "run", "-it", "").AssertOutContains(testutil.AlpineImage) - base.Cmd(gsc, "run", "-it", "--rm", "").AssertOutContains(testutil.AlpineImage) + testCase := &test.Case{ + Description: "Base completion", + Require: test.Not(nerdtest.Docker), + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("pull", testutil.AlpineImage) + helpers.Ensure("network", "create", data.Identifier()) + helpers.Ensure("volume", "create", data.Identifier()) + data.Set("identifier", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + helpers.Anyhow("volume", "rm", data.Identifier()) + }, + SubTests: []*test.Case{ + { + Description: "--cgroup-manager", + Command: test.RunCommand("__complete", "--cgroup-manager", ""), + Expected: test.Expects(0, nil, test.Contains("cgroupfs\n")), + }, + { + Description: "--snapshotter", + Command: test.RunCommand("__complete", "--snapshotter", ""), + Expected: test.Expects(0, nil, test.Contains("native\n")), + }, + { + Description: "empty", + Command: test.RunCommand("__complete", ""), + Expected: test.Expects(0, nil, test.Contains("run\t")), + }, + { + Description: "run -", + Command: test.RunCommand("__complete", "run", "-"), + Expected: test.Expects(0, nil, test.Contains("--network\t")), + }, + { + Description: "run --n", + Command: test.RunCommand("__complete", "run", "--n"), + Expected: test.Expects(0, nil, test.Contains("--network\t")), + }, + { + Description: "run --ne", + Command: test.RunCommand("__complete", "run", "--ne"), + Expected: test.Expects(0, nil, test.Contains("--network\t")), + }, + { + Description: "run --net", + Command: test.RunCommand("__complete", "run", "--net", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.All( + test.Contains("host\n"), + test.Contains(data.Get("identifier")+"\n"), + ), + } + }, + }, + { + Description: "run -it --net", + Command: test.RunCommand("__complete", "run", "-it", "--net", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.All( + test.Contains("host\n"), + test.Contains(data.Get("identifier")+"\n"), + ), + } + }, + }, + { + Description: "run -ti --rm --net", + Command: test.RunCommand("__complete", "run", "-it", "--rm", "--net", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.All( + test.Contains("host\n"), + test.Contains(data.Get("identifier")+"\n"), + ), + } + }, + }, + { + Description: "run --restart", + Command: test.RunCommand("__complete", "run", "--restart", ""), + Expected: test.Expects(0, nil, test.Contains("always\n")), + }, + { + Description: "network --rm", + Command: test.RunCommand("__complete", "network", "rm", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.All( + test.DoesNotContain("host\n"), + test.Contains(data.Get("identifier")+"\n"), + ), + } + }, + }, + { + Description: "run --cap-add", + Command: test.RunCommand("__complete", "run", "--cap-add", ""), + Expected: test.Expects(0, nil, test.All( + test.Contains("sys_admin\n"), + test.DoesNotContain("CAP_SYS_ADMIN\n"), + )), + }, + { + Description: "volume inspect", + Command: test.RunCommand("__complete", "volume", "inspect", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("identifier") + "\n"), + } + }, + }, + { + Description: "volume rm", + Command: test.RunCommand("__complete", "volume", "rm", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("identifier") + "\n"), + } + }, + }, + { + Description: "no namespace --cgroup-manager", + Command: func(data test.Data, helpers test.Helpers) test.Command { + cmd := helpers.Command() + cmd.Clear() + cmd.WithBinary("nerdctl") + cmd.WithArgs("__complete", "--cgroup-manager", "") + return cmd + }, + Expected: test.Expects(0, nil, test.Contains("cgroupfs\n")), + }, + { + Description: "no namespace empty", + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command().Clear().WithBinary("nerdctl").WithArgs("__complete", "") + }, + Expected: test.Expects(0, nil, test.Contains("run\t")), + }, + { + Description: "namespace space empty", + Command: func(data test.Data, helpers test.Helpers) test.Command { + // mind {"--namespace=nerdctl-test"} vs {"--namespace", "nerdctl-test"} + return helpers.Command().Clear().WithBinary("nerdctl"). + WithArgs("__complete", "--namespace", testutil.Namespace, "") + }, + Expected: test.Expects(0, nil, test.Contains("run\t")), + }, + { + Description: "run -i", + Command: test.RunCommand("__complete", "run", "-i", ""), + Expected: test.Expects(0, nil, test.Contains(testutil.AlpineImage)), + }, + { + Description: "run -it", + Command: test.RunCommand("__complete", "run", "-it", ""), + Expected: test.Expects(0, nil, test.Contains(testutil.AlpineImage)), + }, + { + Description: "run -it --rm", + Command: test.RunCommand("__complete", "run", "-it", "--rm", ""), + Expected: test.Expects(0, nil, test.Contains(testutil.AlpineImage)), + }, + { + Description: "namespace run -i", + Command: func(data test.Data, helpers test.Helpers) test.Command { + // mind {"--namespace=nerdctl-test"} vs {"--namespace", "nerdctl-test"} + return helpers.Command().Clear().WithBinary("nerdctl"). + WithArgs("__complete", "--namespace", testutil.Namespace, "run", "-i", "") + }, + Expected: test.Expects(0, nil, test.Contains(testutil.AlpineImage+"\n")), + }, + }, + } - // Tests with a network - testNetworkName := "nerdctl-test-completion" - defer base.Cmd("network", "rm", testNetworkName).Run() - base.Cmd("network", "create", testNetworkName).AssertOK() - base.Cmd(gsc, "network", "rm", "").AssertOutContains(testNetworkName) - base.Cmd(gsc, "run", "--net", "").AssertOutContains(testNetworkName) - - // Tests with a volume - testVolumekName := "nerdctl-test-completion" - defer base.Cmd("volume", "rm", testVolumekName).Run() - base.Cmd("volume", "create", testVolumekName).AssertOK() - base.Cmd(gsc, "volume", "inspect", "").AssertOutContains(testVolumekName) - base.Cmd(gsc, "volume", "rm", "").AssertOutContains(testVolumekName) - - // Tests with raw base (without Args={"--namespace=nerdctl-test"}) - rawBase := testutil.NewBase(t) - rawBase.Args = nil // unset "--namespace=nerdctl-test" - rawBase.Cmd(gsc, "--cgroup-manager", "").AssertOutContains("cgroupfs\n") - rawBase.Cmd(gsc, "").AssertOutContains("run\t") - // mind {"--namespace=nerdctl-test"} vs {"--namespace", "nerdctl-test"} - rawBase.Cmd(gsc, "--namespace", testutil.Namespace, "").AssertOutContains("run\t") - rawBase.Cmd(gsc, "--namespace", testutil.Namespace, "run", "-i", "").AssertOutContains(testutil.AlpineImage) + testCase.Run(t) } diff --git a/cmd/nerdctl/network/network_create_linux_test.go b/cmd/nerdctl/network/network_create_linux_test.go index adf61d67c11..ae590d94836 100644 --- a/cmd/nerdctl/network/network_create_linux_test.go +++ b/cmd/nerdctl/network/network_create_linux_test.go @@ -17,66 +17,93 @@ package network import ( - "fmt" "net" + "strings" "testing" "gotest.tools/v3/assert" - "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" + ipv6helper "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/test" ) -func TestNetworkCreateWithMTU(t *testing.T) { - testNetwork := testutil.Identifier(t) - base := testutil.NewBase(t) - - args := []string{ - "network", "create", testNetwork, - "--driver", "bridge", "--opt", "com.docker.network.driver.mtu=9216", - } - base.Cmd(args...).AssertOK() - defer base.Cmd("network", "rm", testNetwork).AssertOK() - - base.Cmd("run", "--rm", "--net", testNetwork, testutil.AlpineImage, "ifconfig", "eth0").AssertOutContains("MTU:9216") -} - func TestNetworkCreate(t *testing.T) { - base := testutil.NewBase(t) - testNetwork := testutil.Identifier(t) - - base.Cmd("network", "create", testNetwork).AssertOK() - defer base.Cmd("network", "rm", testNetwork).AssertOK() - - net := base.InspectNetwork(testNetwork) - assert.Equal(t, len(net.IPAM.Config), 1) - - base.Cmd("run", "--rm", "--net", testNetwork, testutil.CommonImage, "ip", "route").AssertOutContains(net.IPAM.Config[0].Subnet) - - base.Cmd("network", "create", testNetwork+"-1").AssertOK() - defer base.Cmd("network", "rm", testNetwork+"-1").AssertOK() - - base.Cmd("run", "--rm", "--net", testNetwork+"-1", testutil.CommonImage, "ip", "route").AssertOutNotContains(net.IPAM.Config[0].Subnet) -} - -func TestNetworkCreateIPv6(t *testing.T) { - base := testutil.NewBaseWithIPv6Compatible(t) - testNetwork := testutil.Identifier(t) - - subnetStr := "2001:db8:8::/64" - _, subnet, err := net.ParseCIDR(subnetStr) - assert.Assert(t, err == nil) - - base.Cmd("network", "create", "--ipv6", "--subnet", subnetStr, testNetwork).AssertOK() - t.Cleanup(func() { - base.Cmd("network", "rm", testNetwork).Run() - }) + nerdtest.Setup() + + testGroup := &test.Group{ + { + Description: "Network create", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier()) + netw := nerdtest.InspectNetwork(helpers, data.Identifier()) + assert.Equal(t, len(netw.IPAM.Config), 1) + data.Set("subnet", netw.IPAM.Config[0].Subnet) + + helpers.Ensure("network", "create", data.Identifier()+"-1") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + helpers.Anyhow("network", "rm", data.Identifier()+"-1") + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + data.Set("container2", helpers.Capture("run", "--rm", "--net", data.Identifier()+"-1", testutil.AlpineImage, "ip", "route")) + return helpers.Command("run", "--rm", "--net", data.Identifier(), testutil.AlpineImage, "ip", "route") + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Errors: nil, + Output: func(stdout string, info string, t *testing.T) { + assert.Assert(t, strings.Contains(stdout, data.Get("subnet")), info) + assert.Assert(t, !strings.Contains(data.Get("container2"), data.Get("subnet")), info) + }, + } + }, + }, + { + Description: "Network create with MTU", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier(), "--driver", "bridge", "--opt", "com.docker.network.driver.mtu=9216") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("run", "--rm", "--net", data.Identifier(), testutil.AlpineImage, "ifconfig", "eth0") + }, + Expected: test.Expects(0, nil, test.Contains("MTU:9216")), + }, + { + Description: "Network create with ipv6", + Require: nerdtest.OnlyIPv6, + Setup: func(data test.Data, helpers test.Helpers) { + subnetStr := "2001:db8:8::/64" + data.Set("subnetStr", subnetStr) + _, _, err := net.ParseCIDR(subnetStr) + assert.Assert(t, err == nil) + + helpers.Ensure("network", "create", data.Identifier(), "--ipv6", "--subnet", subnetStr) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("run", "--rm", "--net", data.Identifier(), testutil.CommonImage, "ip", "addr", "show", "dev", "eth0") + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + _, subnet, _ := net.ParseCIDR(data.Get("subnetStr")) + ip := ipv6helper.FindIPv6(stdout) + assert.Assert(t, subnet.Contains(ip), info) + }, + } + }, + }, + } - base.Cmd("run", "--rm", "--net", testNetwork, testutil.CommonImage, "ip", "addr", "show", "dev", "eth0").AssertOutWithFunc(func(stdout string) error { - ip := helpers.FindIPv6(stdout) - if subnet.Contains(ip) { - return nil - } - return fmt.Errorf("expected subnet %s include ip %s", subnet, ip) - }) + testGroup.Run(t) } diff --git a/cmd/nerdctl/network/network_inspect_test.go b/cmd/nerdctl/network/network_inspect_test.go index 1f91f09963e..4aa6147d063 100644 --- a/cmd/nerdctl/network/network_inspect_test.go +++ b/cmd/nerdctl/network/network_inspect_test.go @@ -17,78 +17,101 @@ package network import ( - "runtime" + "encoding/json" + "errors" "testing" "gotest.tools/v3/assert" "github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat" - "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) func TestNetworkInspect(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("IPAMConfig not implemented on Windows yet") - } + nerdtest.Setup() - testNetwork := testutil.Identifier(t) const ( testSubnet = "10.24.24.0/24" testGateway = "10.24.24.1" testIPRange = "10.24.24.0/25" ) - base := testutil.NewBase(t) - defer base.Cmd("network", "rm", testNetwork).Run() - - args := []string{ - "network", "create", "--label", "tag=testNetwork", "--subnet", testSubnet, - "--gateway", testGateway, "--ip-range", testIPRange, - testNetwork, - } - base.Cmd(args...).AssertOK() - got := base.InspectNetwork(testNetwork) - - assert.DeepEqual(base.T, testNetwork, got.Name) - - expectedLabels := map[string]string{ - "tag": "testNetwork", - } - assert.DeepEqual(base.T, expectedLabels, got.Labels) - - expectedIPAM := dockercompat.IPAM{ - Config: []dockercompat.IPAMConfig{ - { - Subnet: testSubnet, - Gateway: testGateway, - IPRange: testIPRange, + testGroup := &test.Group{ + { + Description: "Test network inspect", + // IPAMConfig is not implemented on Windows yet + Require: test.Not(test.Windows), + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", "--label", "tag=testNetwork", "--subnet", testSubnet, + "--gateway", testGateway, "--ip-range", testIPRange, data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("network", "inspect", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + var dc []dockercompat.Network + + err := json.Unmarshal([]byte(stdout), &dc) + assert.NilError(t, err, "Unable to unmarshal output\n"+info) + assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info) + got := dc[0] + + assert.Equal(t, got.Name, data.Identifier(), info) + assert.Equal(t, got.Labels["tag"], "testNetwork", info) + assert.Equal(t, len(got.IPAM.Config), 1, info) + assert.Equal(t, got.IPAM.Config[0].Subnet, testSubnet, info) + assert.Equal(t, got.IPAM.Config[0].Gateway, testGateway, info) + assert.Equal(t, got.IPAM.Config[0].IPRange, testIPRange, info) + }, + } }, }, - } - assert.DeepEqual(base.T, expectedIPAM, got.IPAM) -} - -func TestNetworkWithNamespace(t *testing.T) { - testutil.DockerIncompatible(t) - - t.Parallel() - - tID := testutil.Identifier(t) - base := testutil.NewBase(t) - baseOther := testutil.NewBaseWithNamespace(t, tID) + { + Description: "Test network with namespace", + Require: test.Not(nerdtest.Docker), + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + helpers.Anyhow("namespace", "remove", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("network", "create", data.Identifier()) + }, - tearDown := func() { - base.Cmd("network", "rm", tID).Run() - baseOther.Cmd("namespace", "remove", tID).Run() + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + cmd := helpers.Command().Clear().WithBinary("nerdctl").WithArgs("--namespace", data.Identifier()) + + cmd.Clone().WithArgs("network", "inspect", data.Identifier()).Run(&test.Expected{ + ExitCode: 1, + Errors: []error{errors.New("no such network")}, + }) + + cmd.Clone().WithArgs("network", "remove", data.Identifier()).Run(&test.Expected{ + ExitCode: 1, + Errors: []error{errors.New("no such network")}, + }) + + cmd.Clone().WithArgs("network", "ls").Run(&test.Expected{ + Output: test.DoesNotContain(data.Identifier()), + }) + + cmd.Clone().WithArgs("network", "prune", "-f").Run(&test.Expected{ + Output: test.DoesNotContain(data.Identifier()), + }) + }, + } + }, + }, } - tearDown() - t.Cleanup(tearDown) - - base.Cmd("network", "create", tID).AssertOK() - // Other namespace cannot inspect, prune, see, or remove this network - baseOther.Cmd("network", "inspect", tID).AssertFail() - baseOther.Cmd("network", "prune", "-f").AssertOutNotContains(tID) - baseOther.Cmd("network", "ls").AssertOutNotContains(tID) - baseOther.Cmd("network", "remove", tID).AssertFail() + testGroup.Run(t) } diff --git a/cmd/nerdctl/network/network_list_linux_test.go b/cmd/nerdctl/network/network_list_linux_test.go index 62927f04b3f..a9d2f0124eb 100644 --- a/cmd/nerdctl/network/network_list_linux_test.go +++ b/cmd/nerdctl/network/network_list_linux_test.go @@ -17,60 +17,77 @@ package network import ( - "errors" - "fmt" "strings" "testing" - "github.com/containerd/nerdctl/v2/pkg/testutil" + "gotest.tools/v3/assert" + + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) func TestNetworkLsFilter(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - tID := testutil.Identifier(t) - - var net1, net2 = tID + "net-1", tID + "net-2" - var label1 = tID + "=label-1" - netID1 := base.Cmd("network", "create", "--label="+label1, net1).Out() - defer base.Cmd("network", "rm", "-f", netID1).Run() - - netID2 := base.Cmd("network", "create", net2).Out() - defer base.Cmd("network", "rm", "-f", netID2).Run() + nerdtest.Setup() - base.Cmd("network", "ls", "--quiet", "--filter", "label="+tID).AssertOutWithFunc(func(stdout string) error { - var lines = strings.Split(strings.TrimSpace(stdout), "\n") - if len(lines) < 1 { - return errors.New("expected at least 1 lines") - } - netNames := map[string]struct{}{ - netID1[:12]: {}, - } + testCase := &test.Case{ + Description: "Test network list", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("identifier", data.Identifier()) + data.Set("label", data.Identifier()+"=label-1") + data.Set("netID1", helpers.Capture("network", "create", "--label="+data.Get("label"), data.Identifier()+"-1")) + data.Set("netID2", helpers.Capture("network", "create", data.Identifier()+"-2")) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()+"-1") + helpers.Anyhow("network", "rm", data.Identifier()+"-2") + }, + SubTests: []*test.Case{ + { + Description: "filter label", + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("network", "ls", "--quiet", "--filter", "label="+data.Get("label")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + var lines = strings.Split(strings.TrimSpace(stdout), "\n") + assert.Assert(t, len(lines) >= 1, info) + netNames := map[string]struct{}{ + data.Get("netID1")[:12]: {}, + } - for _, name := range lines { - _, ok := netNames[name] - if !ok { - return fmt.Errorf("unexpected netume %s found", name) - } - } - return nil - }) + for _, name := range lines { + _, ok := netNames[name] + assert.Assert(t, ok, info) + } + }, + } + }, + }, + { + Description: "filter name", + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("network", "ls", "--quiet", "--filter", "name="+data.Get("identifier")+"-2") + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + var lines = strings.Split(strings.TrimSpace(stdout), "\n") + assert.Assert(t, len(lines) >= 1, info) + netNames := map[string]struct{}{ + data.Get("netID2")[:12]: {}, + } - base.Cmd("network", "ls", "--quiet", "--filter", "name="+net2).AssertOutWithFunc(func(stdout string) error { - var lines = strings.Split(strings.TrimSpace(stdout), "\n") - if len(lines) < 1 { - return errors.New("expected at least 1 lines") - } - netNames := map[string]struct{}{ - netID2[:12]: {}, - } + for _, name := range lines { + _, ok := netNames[name] + assert.Assert(t, ok, info) + } + }, + } + }, + }, + }, + } - for _, name := range lines { - _, ok := netNames[name] - if !ok { - return fmt.Errorf("unexpected netume %s found", name) - } - } - return nil - }) + testCase.Run(t) } diff --git a/cmd/nerdctl/network/network_prune_linux_test.go b/cmd/nerdctl/network/network_prune_linux_test.go index cb137c4c68f..1692ac55053 100644 --- a/cmd/nerdctl/network/network_prune_linux_test.go +++ b/cmd/nerdctl/network/network_prune_linux_test.go @@ -20,19 +20,52 @@ import ( "testing" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) func TestNetworkPrune(t *testing.T) { - base := testutil.NewBase(t) - testNetwork := testutil.Identifier(t) - base.Cmd("network", "create", testNetwork).AssertOK() - defer base.Cmd("network", "prune", "-f").Run() - - tID := testutil.Identifier(t) - base.Cmd("run", "-d", "--net", testNetwork, "--name", tID, testutil.NginxAlpineImage).AssertOK() - defer base.Cmd("rm", "-f", tID).Run() - - base.Cmd("network", "prune", "-f").AssertOutNotContains(testNetwork) - base.Cmd("stop", tID).AssertOK() - base.Cmd("network", "prune", "-f").AssertOutContains(testNetwork) + nerdtest.Setup() + + testGroup := &test.Group{ + { + Description: "Prune does not collect started container network", + Require: nerdtest.Private, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier()) + helpers.Ensure("run", "-d", "--net", data.Identifier(), "--name", data.Identifier(), testutil.NginxAlpineImage) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Command: test.RunCommand("network", "prune", "-f"), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.DoesNotContain(data.Identifier()), + } + }, + }, + { + Description: "Prune does collect stopped container network", + Require: nerdtest.Private, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier()) + helpers.Ensure("run", "-d", "--net", data.Identifier(), "--name", data.Identifier(), testutil.NginxAlpineImage) + helpers.Ensure("stop", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Command: test.RunCommand("network", "prune", "-f"), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Identifier()), + } + }, + }, + } + + testGroup.Run(t) } diff --git a/cmd/nerdctl/network/network_remove_linux_test.go b/cmd/nerdctl/network/network_remove_linux_test.go index 7b8acb7b4b8..9e5bbaf200a 100644 --- a/cmd/nerdctl/network/network_remove_linux_test.go +++ b/cmd/nerdctl/network/network_remove_linux_test.go @@ -17,99 +17,124 @@ package network import ( + "errors" "testing" "github.com/vishvananda/netlink" "gotest.tools/v3/assert" - "github.com/containerd/nerdctl/v2/pkg/rootlessutil" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) func TestNetworkRemove(t *testing.T) { - if rootlessutil.IsRootless() { - t.Skip("test skipped for remove rootless network") + nerdtest.Setup() + + testCase := &test.Case{ + Description: "TestNetworkRemove", + Require: test.Not(nerdtest.Rootless), + SubTests: []*test.Case{ + { + Description: "Simple network remove", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier()) + data.Set("netID", nerdtest.InspectNetwork(helpers, data.Identifier()).ID) + helpers.Ensure("run", "--rm", "--net", data.Identifier(), "--name", data.Identifier(), testutil.CommonImage) + // Verity the network is here + _, err := netlink.LinkByName("br-" + data.Get("netID")[:12]) + assert.NilError(t, err, "failed to find network br-"+data.Get("netID")[:12], "%v") + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("network", "rm", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + _, err := netlink.LinkByName("br-" + data.Get("netID")[:12]) + assert.Error(t, err, "Link not found", info) + }, + } + }, + }, + { + Description: "Network remove when linked to container", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier()) + helpers.Ensure("run", "-d", "--net", data.Identifier(), "--name", data.Identifier(), testutil.CommonImage, "sleep", "infinity") + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("network", "rm", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 1, + Errors: []error{errors.New("is in use")}, + } + }, + }, + { + Description: "Network remove by id", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier()) + data.Set("netID", nerdtest.InspectNetwork(helpers, data.Identifier()).ID) + helpers.Ensure("run", "--rm", "--net", data.Identifier(), "--name", data.Identifier(), testutil.CommonImage) + // Verity the network is here + _, err := netlink.LinkByName("br-" + data.Get("netID")[:12]) + assert.NilError(t, err, "failed to find network br-"+data.Get("netID")[:12], "%v") + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("network", "rm", data.Get("netID")) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + _, err := netlink.LinkByName("br-" + data.Get("netID")[:12]) + assert.Error(t, err, "Link not found", info) + }, + } + }, + }, + { + Description: "Network remove by short id", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier()) + data.Set("netID", nerdtest.InspectNetwork(helpers, data.Identifier()).ID) + helpers.Ensure("run", "--rm", "--net", data.Identifier(), "--name", data.Identifier(), testutil.CommonImage) + // Verity the network is here + _, err := netlink.LinkByName("br-" + data.Get("netID")[:12]) + assert.NilError(t, err, "failed to find network br-"+data.Get("netID")[:12], "%v") + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("network", "rm", data.Get("netID")[:12]) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + _, err := netlink.LinkByName("br-" + data.Get("netID")[:12]) + assert.Error(t, err, "Link not found", info) + }, + } + }, + }, + }, } - base := testutil.NewBase(t) - networkName := testutil.Identifier(t) - base.Cmd("network", "create", networkName).AssertOK() - defer base.Cmd("network", "rm", networkName).Run() - - networkID := base.InspectNetwork(networkName).ID - - tID := testutil.Identifier(t) - base.Cmd("run", "--rm", "--net", networkName, "--name", tID, testutil.CommonImage).AssertOK() - - _, err := netlink.LinkByName("br-" + networkID[:12]) - assert.NilError(t, err) - - base.Cmd("network", "rm", networkName).AssertOK() - - _, err = netlink.LinkByName("br-" + networkID[:12]) - assert.Error(t, err, "Link not found") -} - -func TestNetworkRemoveWhenLinkWithContainer(t *testing.T) { - if rootlessutil.IsRootless() { - t.Skip("test skipped for remove rootless network") - } - base := testutil.NewBase(t) - networkName := testutil.Identifier(t) - - base.Cmd("network", "create", networkName).AssertOK() - defer base.Cmd("network", "rm", networkName).AssertOK() - - tID := testutil.Identifier(t) - base.Cmd("run", "-d", "--net", networkName, "--name", tID, testutil.AlpineImage, "sleep", "infinity").AssertOK() - defer base.Cmd("rm", "-f", tID).Run() - base.Cmd("network", "rm", networkName).AssertFail() -} - -func TestNetworkRemoveById(t *testing.T) { - if rootlessutil.IsRootless() { - t.Skip("test skipped for remove rootless network") - } - base := testutil.NewBase(t) - networkName := testutil.Identifier(t) - - base.Cmd("network", "create", networkName).AssertOK() - defer base.Cmd("network", "rm", networkName).Run() - - networkID := base.InspectNetwork(networkName).ID - - tID := testutil.Identifier(t) - base.Cmd("run", "--rm", "--net", networkName, "--name", tID, testutil.CommonImage).AssertOK() - - _, err := netlink.LinkByName("br-" + networkID[:12]) - assert.NilError(t, err) - - base.Cmd("network", "rm", networkID).AssertOK() - - _, err = netlink.LinkByName("br-" + networkID[:12]) - assert.Error(t, err, "Link not found") -} - -func TestNetworkRemoveByShortId(t *testing.T) { - if rootlessutil.IsRootless() { - t.Skip("test skipped for remove rootless network") - } - base := testutil.NewBase(t) - networkName := testutil.Identifier(t) - - base.Cmd("network", "create", networkName).AssertOK() - defer base.Cmd("network", "rm", networkName).Run() - - networkID := base.InspectNetwork(networkName).ID - - tID := testutil.Identifier(t) - base.Cmd("run", "--rm", "--net", networkName, "--name", tID, testutil.CommonImage).AssertOK() - - _, err := netlink.LinkByName("br-" + networkID[:12]) - assert.NilError(t, err) - - base.Cmd("network", "rm", networkID[:12]).AssertOK() - - _, err = netlink.LinkByName("br-" + networkID[:12]) - assert.Error(t, err, "Link not found") + testCase.Run(t) } diff --git a/cmd/nerdctl/system/system_events_linux_test.go b/cmd/nerdctl/system/system_events_linux_test.go index 5838e710866..c12d0161576 100644 --- a/cmd/nerdctl/system/system_events_linux_test.go +++ b/cmd/nerdctl/system/system_events_linux_test.go @@ -17,96 +17,84 @@ package system import ( - "fmt" - "strings" "testing" "time" - "gotest.tools/v3/assert" - "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) -func testEventFilter(t *testing.T, args ...string) string { - t.Parallel() - base := testutil.NewBase(t) - testContainerName := testutil.Identifier(t) - defer base.Cmd("rm", "-f", testContainerName).Run() - - fullArgs := []string{"events", "--filter"} - fullArgs = append(fullArgs, args...) - fullArgs = append(fullArgs, - "--format", - "json", - ) - - eventsCmd := base.Cmd(fullArgs...).Start() - base.Cmd("run", "--rm", testutil.CommonImage).Start() - time.Sleep(3 * time.Second) - return eventsCmd.Stdout() +func testEventFilterExecutor(data test.Data, helpers test.Helpers) test.Command { + cmd := helpers.Command("events", "--filter", data.Get("filter"), "--format", "json") + cmd.Background(1 * time.Second) + helpers.Ensure("run", "--rm", testutil.CommonImage) + return cmd } func TestEventFilters(t *testing.T) { + nerdtest.Setup() - type testCase struct { - name string - args []string - nerdctlOut string - dockerOut string - dockerSkip bool - } - testCases := []testCase{ + testGroup := &test.Group{ { - name: "CapitializedFilter", - args: []string{"event=START"}, - nerdctlOut: "\"Status\":\"start\"", - dockerOut: "\"status\":\"start\"", - dockerSkip: true, + Description: "CapitalizedFilter", + Require: test.Not(nerdtest.Docker), + Command: testEventFilterExecutor, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("output")), + } + }, + Data: test.WithData("filter", "event=START"). + Set("output", "\"Status\":\"start\""), }, { - name: "StartEventFilter", - args: []string{"event=start"}, - nerdctlOut: "\"Status\":\"start\"", - dockerOut: "\"status\":\"start\"", - dockerSkip: false, + Description: "StartEventFilter", + Command: testEventFilterExecutor, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("output")), + } + }, + Data: test.WithData("filter", "event=start"). + Set("output", "tatus\":\"start\""), }, { - name: "UnsupportedEventFilter", - args: []string{"event=unknown"}, - nerdctlOut: "\"Status\":\"unknown\"", - dockerSkip: true, + Description: "UnsupportedEventFilter", + Require: test.Not(nerdtest.Docker), + Command: testEventFilterExecutor, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("output")), + } + }, + Data: test.WithData("filter", "event=unknown"). + Set("output", "\"Status\":\"unknown\""), }, { - name: "StatusFilter", - args: []string{"status=start"}, - nerdctlOut: "\"Status\":\"start\"", - dockerOut: "\"status\":\"start\"", - dockerSkip: false, + Description: "StatusFilter", + Command: testEventFilterExecutor, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("output")), + } + }, + Data: test.WithData("filter", "status=start"). + Set("output", "tatus\":\"start\""), }, { - name: "UnsupportedStatusFilter", - args: []string{"status=unknown"}, - nerdctlOut: "\"Status\":\"unknown\"", - dockerSkip: true, + Description: "UnsupportedStatusFilter", + Require: test.Not(nerdtest.Docker), + Command: testEventFilterExecutor, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("output")), + } + }, + Data: test.WithData("filter", "status=unknown"). + Set("output", "\"Status\":\"unknown\""), }, } - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - actualOut := testEventFilter(t, tc.args...) - errorMsg := fmt.Sprintf("%s failed;\nActual Filter Result: '%s'", tc.name, actualOut) - - isDocker := testutil.GetTarget() == testutil.Docker - if isDocker && tc.dockerSkip { - t.Skip("test is incompatible with Docker") - } - - if isDocker { - assert.Equal(t, true, strings.Contains(actualOut, tc.dockerOut), errorMsg) - } else { - assert.Equal(t, true, strings.Contains(actualOut, tc.nerdctlOut), errorMsg) - } - }) - } + testGroup.Run(t) } diff --git a/cmd/nerdctl/system/system_info_test.go b/cmd/nerdctl/system/system_info_test.go index 3a10033930d..3c8f5c252da 100644 --- a/cmd/nerdctl/system/system_info_test.go +++ b/cmd/nerdctl/system/system_info_test.go @@ -21,41 +21,56 @@ import ( "fmt" "testing" + "gotest.tools/v3/assert" + "github.com/containerd/nerdctl/v2/pkg/infoutil" "github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat" - "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) -func testInfoJSON(stdout string) error { - var info dockercompat.Info - if err := json.Unmarshal([]byte(stdout), &info); err != nil { - return err - } +func testInfoComparator(stdout string, info string, t *testing.T) { + var dinf dockercompat.Info + err := json.Unmarshal([]byte(stdout), &dinf) + assert.NilError(t, err, "failed to unmarshal stdout"+info) unameM := infoutil.UnameM() - if info.Architecture != unameM { - return fmt.Errorf("expected info.Architecture to be %q, got %q", unameM, info.Architecture) - } - return nil + assert.Assert(t, dinf.Architecture == unameM, fmt.Sprintf("expected info.Architecture to be %q, got %q", unameM, dinf.Architecture)+info) } func TestInfo(t *testing.T) { - base := testutil.NewBase(t) - base.Cmd("info", "--format", "{{json .}}").AssertOutWithFunc(testInfoJSON) -} + nerdtest.Setup() -func TestInfoConvenienceForm(t *testing.T) { - testutil.DockerIncompatible(t) // until https://github.com/docker/cli/pull/3355 gets merged - base := testutil.NewBase(t) - base.Cmd("info", "--format", "json").AssertOutWithFunc(testInfoJSON) -} - -func TestInfoWithNamespace(t *testing.T) { - testutil.DockerIncompatible(t) - base := testutil.NewBase(t) - base.Args = nil // unset "--namespace=nerdctl-test" - - base.Cmd("info").AssertOutContains("Namespace: default") + testGroup := &test.Group{ + { + Description: "info", + Command: test.RunCommand("info", "--format", "{{json .}}"), + Expected: test.Expects(0, nil, testInfoComparator), + }, + { + Description: "info convenience form", + Command: test.RunCommand("info", "--format", "json"), + Expected: test.Expects(0, nil, testInfoComparator), + }, + { + Description: "info with namespace", + Require: test.Not(nerdtest.Docker), + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command().Clear().WithBinary("nerdctl").WithArgs("info") + }, + Expected: test.Expects(0, nil, test.Contains("Namespace: default")), + }, + { + Description: "info with namespace env var", + Env: map[string]string{ + "CONTAINERD_NAMESPACE": "test", + }, + Require: test.Not(nerdtest.Docker), + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command().Clear().WithBinary("nerdctl").WithArgs("info") + }, + Expected: test.Expects(0, nil, test.Contains("Namespace: test")), + }, + } - base.Env = append(base.Env, "CONTAINERD_NAMESPACE=test") - base.Cmd("info").AssertOutContains("Namespace: test") + testGroup.Run(t) } diff --git a/cmd/nerdctl/system/system_prune_linux_test.go b/cmd/nerdctl/system/system_prune_linux_test.go index d792282398f..40d08f1beea 100644 --- a/cmd/nerdctl/system/system_prune_linux_test.go +++ b/cmd/nerdctl/system/system_prune_linux_test.go @@ -17,86 +17,90 @@ package system import ( - "bytes" "fmt" - "io" - "os" - "os/exec" "strings" "testing" - "github.com/containerd/log" + "gotest.tools/v3/assert" "github.com/containerd/nerdctl/v2/pkg/buildkitutil" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) func TestSystemPrune(t *testing.T) { - testutil.RequiresBuild(t) - // FIXME: using a dedicated namespace does not work with rootful (because of buildkit running) - // t.Parallel() - // namespaceID := testutil.Identifier(t) - // base := testutil.NewBaseWithNamespace(t, namespaceID) - base := testutil.NewBase(t) - base.Cmd("container", "prune", "-f").AssertOK() - base.Cmd("network", "prune", "-f").AssertOK() - base.Cmd("volume", "prune", "-f").AssertOK() - base.Cmd("image", "prune", "-f", "--all").AssertOK() - - nID := testutil.Identifier(t) - base.Cmd("network", "create", nID).AssertOK() - defer base.Cmd("network", "rm", nID).Run() - - vID := testutil.Identifier(t) - base.Cmd("volume", "create", vID).AssertOK() - defer base.Cmd("volume", "rm", vID).Run() - - vID2 := base.Cmd("volume", "create").Out() - defer base.Cmd("volume", "rm", vID2).Run() - - tID := testutil.Identifier(t) - base.Cmd("run", "-v", fmt.Sprintf("%s:/volume", vID), "--net", nID, - "--name", tID, testutil.CommonImage).AssertOK() - defer base.Cmd("rm", "-f", tID).Run() - - base.Cmd("ps", "-a").AssertOutContains(tID) - base.Cmd("images").AssertOutContains(testutil.ImageRepo(testutil.CommonImage)) - - base.Cmd("system", "prune", "-f", "--volumes", "--all").AssertOK() - base.Cmd("volume", "ls").AssertOutContains(vID) // docker system prune --all --volume does not prune named volume - base.Cmd("volume", "ls").AssertOutNotContains(vID2) // docker system prune --all --volume prune anonymous volume - base.Cmd("ps", "-a").AssertOutNotContains(tID) - base.Cmd("network", "ls").AssertOutNotContains(nID) - base.Cmd("images").AssertOutNotContains(testutil.ImageRepo(testutil.CommonImage)) - - if testutil.GetTarget() != testutil.Nerdctl { - t.Skip("test skipped for buildkitd is not available with docker-compatible tests") + nerdtest.Setup() + + testGroup := &test.Group{ + { + Description: "volume prune all success", + // Private because of prune evidently + Require: nerdtest.Private, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier()) + helpers.Ensure("volume", "create", data.Identifier()) + anonIdentifier := helpers.Capture("volume", "create") + helpers.Ensure("run", "-v", fmt.Sprintf("%s:/volume", data.Identifier()), + "--net", data.Identifier(), "--name", data.Identifier(), testutil.CommonImage) + + data.Set("anonIdentifier", anonIdentifier) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + helpers.Anyhow("volume", "rm", data.Identifier()) + helpers.Anyhow("volume", "rm", data.Get("anonIdentifier")) + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: test.RunCommand("system", "prune", "-f", "--volumes", "--all"), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout string, info string, t *testing.T) { + volumes := helpers.Capture("volume", "ls") + networks := helpers.Capture("network", "ls") + images := helpers.Capture("images") + containers := helpers.Capture("ps", "-a") + assert.Assert(t, strings.Contains(volumes, data.Identifier()), volumes) + assert.Assert(t, !strings.Contains(volumes, data.Get("anonIdentifier")), volumes) + assert.Assert(t, !strings.Contains(containers, data.Identifier()), containers) + assert.Assert(t, !strings.Contains(networks, data.Identifier()), networks) + assert.Assert(t, !strings.Contains(images, testutil.CommonImage), images) + }, + } + }, + }, + { + Description: "buildkit", + // FIXME: using a dedicated namespace does not work with rootful (because of buildkitd) + NoParallel: true, + // buildkitd is not available with docker + Require: test.Require(nerdtest.Build, test.Not(nerdtest.Docker)), + // FIXME: this test will happily say "green" even if the command actually fails to do its duty + // if there is nothing in the build cache. + // Ensure with setup here that we DO build something first + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("system", "prune", "-f", "--volumes", "--all") + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + buildctlBinary, err := buildkitutil.BuildctlBinary() + if err != nil { + t.Fatal(err) + } + + host, err := buildkitutil.GetBuildkitHost(testutil.Namespace) + if err != nil { + t.Fatal(err) + } + + buildctlArgs := buildkitutil.BuildctlBaseArgs(host) + buildctlArgs = append(buildctlArgs, "du") + + return helpers.CustomCommand(buildctlBinary, buildctlArgs...) + }, + Expected: test.Expects(0, nil, test.Contains("Total:\t\t0B")), + }, } - buildctlBinary, err := buildkitutil.BuildctlBinary() - if err != nil { - t.Fatal(err) - } - host, err := buildkitutil.GetBuildkitHost(testutil.Namespace) - if err != nil { - t.Fatal(err) - } - - buildctlArgs := buildkitutil.BuildctlBaseArgs(host) - buildctlArgs = append(buildctlArgs, "du") - log.L.Debugf("running %s %v", buildctlBinary, buildctlArgs) - buildctlCmd := exec.Command(buildctlBinary, buildctlArgs...) - buildctlCmd.Env = os.Environ() - stdout := bytes.NewBuffer(nil) - buildctlCmd.Stdout = stdout - if err := buildctlCmd.Run(); err != nil { - t.Fatal(err) - } - readAll, err := io.ReadAll(stdout) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(readAll), "Total:\t\t0B") { - t.Errorf("buildkit cache is not pruned: %s", string(readAll)) - } + testGroup.Run(t) } diff --git a/pkg/cmd/container/commit.go b/pkg/cmd/container/commit.go index 32f78d871be..45a16ac0227 100644 --- a/pkg/cmd/container/commit.go +++ b/pkg/cmd/container/commit.go @@ -57,7 +57,7 @@ func Commit(ctx context.Context, client *containerd.Client, rawRef string, req s if found.MatchCount > 1 { return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) } - imageID, err := commit.Commit(ctx, client, found.Container, opts) + imageID, err := commit.Commit(ctx, client, found.Container, opts, options.GOptions) if err != nil { return err } diff --git a/pkg/cmd/container/remove.go b/pkg/cmd/container/remove.go index 5acc7465c5a..bc055945824 100644 --- a/pkg/cmd/container/remove.go +++ b/pkg/cmd/container/remove.go @@ -107,8 +107,23 @@ func RemoveContainer(ctx context.Context, c containerd.Container, globalOptions return err } + // Get datastore + dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address) + if err != nil { + return err + } + + // Ensure we do have a stateDir label + stateDir := containerLabels[labels.StateDir] + if stateDir == "" { + stateDir, err = containerutil.ContainerStateDirPath(globalOptions.Namespace, dataStore, c.ID()) + if err != nil { + return err + } + } + // Lock the container state - lf, err := containerutil.Lock(ctx, c) + lf, err := containerutil.Lock(stateDir) if err != nil { return err } @@ -132,11 +147,7 @@ func RemoveContainer(ctx context.Context, c containerd.Container, globalOptions if err != nil { return err } - // Get datastore - dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address) - if err != nil { - return err - } + // Get namestore nameStore, err := namestore.New(dataStore, containerNamespace) if err != nil { diff --git a/pkg/containerutil/lock.go b/pkg/containerutil/lock.go index 896f6f1f618..228b8790575 100644 --- a/pkg/containerutil/lock.go +++ b/pkg/containerutil/lock.go @@ -17,27 +17,12 @@ package containerutil import ( - "context" - "errors" "path/filepath" - "github.com/containerd/containerd/v2/client" - - "github.com/containerd/nerdctl/v2/pkg/labels" "github.com/containerd/nerdctl/v2/pkg/store" ) -func Lock(ctx context.Context, c client.Container) (store.Store, error) { - containerLabels, err := c.Labels(ctx) - if err != nil { - return nil, err - } - - stateDir := containerLabels[labels.StateDir] - if stateDir == "" { - return nil, errors.New("container is missing statedir label") - } - +func Lock(stateDir string) (store.Store, error) { stor, err := store.New(filepath.Join(stateDir, "oplock"), 0, 0) if err != nil { return nil, err diff --git a/pkg/imgutil/commit/commit.go b/pkg/imgutil/commit/commit.go index b504035dd22..70a48f81586 100644 --- a/pkg/imgutil/commit/commit.go +++ b/pkg/imgutil/commit/commit.go @@ -44,6 +44,8 @@ import ( "github.com/containerd/log" "github.com/containerd/platforms" + "github.com/containerd/nerdctl/v2/pkg/api/types" + "github.com/containerd/nerdctl/v2/pkg/clientutil" "github.com/containerd/nerdctl/v2/pkg/containerutil" imgutil "github.com/containerd/nerdctl/v2/pkg/imgutil" "github.com/containerd/nerdctl/v2/pkg/labels" @@ -66,8 +68,29 @@ var ( emptyDigest = digest.Digest("") ) -func Commit(ctx context.Context, client *containerd.Client, container containerd.Container, opts *Opts) (digest.Digest, error) { - lf, err := containerutil.Lock(ctx, container) +func Commit(ctx context.Context, client *containerd.Client, container containerd.Container, opts *Opts, globalOptions types.GlobalCommandOptions) (digest.Digest, error) { + // Get labels + containerLabels, err := container.Labels(ctx) + if err != nil { + return emptyDigest, err + } + + // Get datastore + dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address) + if err != nil { + return emptyDigest, err + } + + // Ensure we do have a stateDir label + stateDir := containerLabels[labels.StateDir] + if stateDir == "" { + stateDir, err = containerutil.ContainerStateDirPath(globalOptions.Namespace, dataStore, container.ID()) + if err != nil { + return emptyDigest, err + } + } + + lf, err := containerutil.Lock(stateDir) if err != nil { return emptyDigest, err } diff --git a/pkg/testutil/nerdtest/test.go b/pkg/testutil/nerdtest/test.go index a2f7a5bd3c2..b9fdcdef8a1 100644 --- a/pkg/testutil/nerdtest/test.go +++ b/pkg/testutil/nerdtest/test.go @@ -194,7 +194,8 @@ func nerdctlSetup(testCase *test.Case, t *testing.T) test.Command { testCase.Env["DOCKER_CONFIG"] = testCase.Data.TempDir() testCase.Env["NERDCTL_TOML"] = filepath.Join(testCase.Data.TempDir(), "nerdctl.toml") dt.WithConfig(HostsDir, test.ConfigValue(testCase.Data.TempDir())) - dt.WithConfig(DataRoot, test.ConfigValue(testCase.Data.TempDir())) + // Setting data root is more trouble than anything and does not significantly increase isolation + // dt.WithConfig(DataRoot, test.ConfigValue(testCase.Data.TempDir())) } testUtilBase = testutil.NewBaseWithNamespace(t, pvNamespace) if testUtilBase.Target == testutil.Docker { @@ -237,12 +238,28 @@ func nerdctlSetup(testCase *test.Case, t *testing.T) test.Command { } // If we were in a custom namespace, not inherited - make sure we clean up the namespace - // FIXME: this is broken, and custom namespaces are not cleaned properly if testUtilBase.Target == testutil.Nerdctl && pvNamespace != "" && !inherited { cleanup := func() { - cl := baseCommand.Clone() - cl.WithArgs("namespace", "remove", pvNamespace) - cl.Run(nil) + // Stop all containers, then prune everything + containerList := baseCommand.Clone() + containerList.WithArgs("ps", "-q") + containerList.Run(&test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + if stdout != "" { + containerRm := baseCommand.Clone() + containerRm.WithArgs("rm", "-f", stdout) + containerRm.Run(&test.Expected{}) + } + }, + }) + + systemPrune := baseCommand.Clone() + systemPrune.WithArgs("system", "prune", "-f", "--all", "--volumes") + systemPrune.Run(&test.Expected{}) + + cleanNamespace := baseCommand.Clone() + cleanNamespace.WithArgs("namespace", "remove", pvNamespace) + cleanNamespace.Run(nil) } cleanup() t.Cleanup(cleanup) diff --git a/pkg/testutil/testutil_linux.go b/pkg/testutil/testutil_linux.go index 0629abbdac2..dce9431e021 100644 --- a/pkg/testutil/testutil_linux.go +++ b/pkg/testutil/testutil_linux.go @@ -126,7 +126,7 @@ func NewDelayOnceReader(wrapped io.Reader) io.Reader { func (r *delayOnceReader) Read(p []byte) (int, error) { // FIXME: this is obviously not exact science. At 1 second, it will fail regularly on the CI under load. - r.once.Do(func() { time.Sleep(2 * time.Second) }) + r.once.Do(func() { time.Sleep(5 * time.Second) }) n, err := r.wrapped.Read(p) if errors.Is(err, io.EOF) { time.Sleep(time.Second)