From 1fd7aae5a4ad8b402cc49b9c1f34943f59a40c76 Mon Sep 17 00:00:00 2001 From: apostasie Date: Fri, 11 Oct 2024 14:35:05 -0700 Subject: [PATCH] Migrate container tests Signed-off-by: apostasie --- .github/workflows/project.yml | 2 +- .github/workflows/test.yml | 2 +- .../builder/builder_build_linux_test.go | 88 +-- cmd/nerdctl/builder/builder_build_test.go | 369 ++++++++---- cmd/nerdctl/builder/builder_linux_test.go | 219 +++---- .../container/container_attach_linux_test.go | 158 ++--- .../container/container_commit_linux_test.go | 1 + .../container/container_commit_test.go | 81 ++- .../container/container_cp_linux_test.go | 539 +++++++++++------- .../container/container_create_linux_test.go | 20 +- .../container/container_create_test.go | 110 ++++ .../container_create_windows_test.go | 55 -- cmd/nerdctl/container/container_diff_test.go | 4 +- cmd/nerdctl/container/container_logs_test.go | 2 + .../container/container_prune_linux_test.go | 46 +- .../container/container_remove_test.go | 29 +- .../container/container_run_linux_test.go | 135 +++-- .../container_run_verify_linux_test.go | 78 ++- .../container/container_start_linux_test.go | 71 ++- .../container/container_stats_linux_test.go | 66 ++- cmd/nerdctl/container/container_top_test.go | 71 +++ .../container/container_top_unix_test.go | 43 -- .../container/container_top_windows_test.go | 34 +- cmd/nerdctl/container/container_wait_test.go | 35 +- cmd/nerdctl/image/image_convert_linux_test.go | 12 +- cmd/nerdctl/image/image_list_test.go | 9 +- cmd/nerdctl/image/image_load_test.go | 2 +- cmd/nerdctl/image/image_prune_test.go | 8 +- cmd/nerdctl/image/image_pull_linux_test.go | 4 +- cmd/nerdctl/image/image_push_linux_test.go | 16 +- cmd/nerdctl/ipfs/ipfs_compose_linux_test.go | 13 +- cmd/nerdctl/ipfs/ipfs_kubo_linux_test.go | 4 +- cmd/nerdctl/ipfs/ipfs_registry_linux_test.go | 8 +- cmd/nerdctl/ipfs/ipfs_simple_linux_test.go | 20 +- cmd/nerdctl/login/login_linux_test.go | 9 +- .../network/network_create_linux_test.go | 8 +- .../network/network_list_linux_test.go | 3 - pkg/testutil/test/helpers.go | 2 +- 38 files changed, 1423 insertions(+), 953 deletions(-) create mode 100644 cmd/nerdctl/container/container_create_test.go delete mode 100644 cmd/nerdctl/container/container_create_windows_test.go create mode 100644 cmd/nerdctl/container/container_top_test.go delete mode 100644 cmd/nerdctl/container/container_top_unix_test.go diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml index a03da712e02..6961f0fd565 100644 --- a/.github/workflows/project.yml +++ b/.github/workflows/project.yml @@ -9,7 +9,7 @@ on: jobs: project: - name: Project Checks + name: checks runs-on: ubuntu-24.04 timeout-minutes: 20 steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 26a6e42b37e..db893bd18a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: # Apparently does not # timeout-minutes: ${{ fromJSON(env.SHORT_TIMEOUT) }} timeout-minutes: 5 - name: unit ${{ matrix.goos }} + name: unit | ${{ matrix.goos }} runs-on: "${{ matrix.os }}" strategy: matrix: diff --git a/cmd/nerdctl/builder/builder_build_linux_test.go b/cmd/nerdctl/builder/builder_build_linux_test.go index 0f80066b0a2..277d7fb1665 100644 --- a/cmd/nerdctl/builder/builder_build_linux_test.go +++ b/cmd/nerdctl/builder/builder_build_linux_test.go @@ -18,17 +18,19 @@ package builder import ( "fmt" + "strings" "testing" "gotest.tools/v3/assert" - "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" + testhelpers "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 TestBuildContextWithOCILayout(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) + nerdtest.Setup() var dockerBuilderArgs []string if testutil.IsDocker() { @@ -38,48 +40,50 @@ func TestBuildContextWithOCILayout(t *testing.T) { dockerBuilderArgs = []string{"buildx", "--builder", builderName} } - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - ociLayout := "parent" - parentImageName := fmt.Sprintf("%s-%s", imageName, ociLayout) - - teardown := func() { - base.Cmd("rmi", parentImageName, imageName).Run() - } - t.Cleanup(teardown) - teardown() - - dockerfile := fmt.Sprintf(`FROM %s + testCase := &test.Case{ + Require: nerdtest.Build, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", fmt.Sprintf("%s-parent", data.Identifier())) + }, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s LABEL layer=oci-layout-parent CMD ["echo", "test-nerdctl-build-context-oci-layout-parent"]`, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - tarPath := fmt.Sprintf("%s/%s.tar", buildCtx, ociLayout) - - // Create OCI archive from parent image. - base.Cmd("build", buildCtx, "--tag", parentImageName).AssertOK() - base.Cmd("image", "save", "--output", tarPath, parentImageName).AssertOK() - - // Unpack OCI archive into OCI layout directory. - ociLayoutDir := t.TempDir() - err := helpers.ExtractTarFile(ociLayoutDir, tarPath) - assert.NilError(t, err) - dockerfile = fmt.Sprintf(`FROM %s -CMD ["echo", "test-nerdctl-build-context-oci-layout"]`, ociLayout) - buildCtx = helpers.CreateBuildContext(t, dockerfile) - - var buildArgs = []string{} - if testutil.IsDocker() { - buildArgs = dockerBuilderArgs - } - - buildArgs = append(buildArgs, "build", buildCtx, fmt.Sprintf("--build-context=%s=oci-layout://%s", ociLayout, ociLayoutDir), "--tag", imageName) - if testutil.IsDocker() { - // Need to load the container image from the builder to be able to run it. - buildArgs = append(buildArgs, "--load") + // FIXME: replace with a generic file creation helper - search for all occurrences of temp file creation + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + tarPath := fmt.Sprintf("%s/parent.tar", buildCtx) + + helpers.Ensure("build", buildCtx, "--tag", fmt.Sprintf("%s-parent", data.Identifier())) + helpers.Ensure("image", "save", "--output", tarPath, fmt.Sprintf("%s-parent", data.Identifier())) + helpers.Custom("tar", "Cxf", data.TempDir(), tarPath).Run(&test.Expected{}) + }, + + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + dockerfile := `FROM parent +CMD ["echo", "test-nerdctl-build-context-oci-layout"]` + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + var cmd test.TestableCommand + if testutil.IsDocker() { + cmd = helpers.Command(dockerBuilderArgs...) + } else { + cmd = helpers.Command() + } + cmd.WithArgs("build", buildCtx, fmt.Sprintf("--build-context=parent=oci-layout://%s", data.TempDir()), "--tag", data.Identifier()) + if testutil.IsDocker() { + // Need to load the container image from the builder to be able to run it. + cmd.WithArgs("--load") + } + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + assert.Assert(t, strings.Contains(helpers.Capture("run", "--rm", data.Identifier()), "test-nerdctl-build-context-oci-layout"), info) + }, + } + }, } - base.Cmd(buildArgs...).AssertOK() - base.Cmd("run", "--rm", imageName).AssertOutContains("test-nerdctl-build-context-oci-layout") + testCase.Run(t) } diff --git a/cmd/nerdctl/builder/builder_build_test.go b/cmd/nerdctl/builder/builder_build_test.go index 6b9052f9d12..93e21a1b1c5 100644 --- a/cmd/nerdctl/builder/builder_build_test.go +++ b/cmd/nerdctl/builder/builder_build_test.go @@ -17,154 +17,287 @@ package builder import ( + "errors" "fmt" "os" "path/filepath" + "runtime" "strings" "testing" "gotest.tools/v3/assert" - "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" + testhelpers "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" + "github.com/containerd/nerdctl/v2/pkg/platformutil" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) -func TestBuild(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - defer base.Cmd("rmi", imageName).Run() +func TestBuildBasics(t *testing.T) { + nerdtest.Setup() + + testCase := &test.Case{ + Require: nerdtest.Build, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-build-test-string"]`, testutil.CommonImage) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + data.Set("buildCtx", buildCtx) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + helpers.Anyhow("builder", "prune", "--all", "--force") + }, + SubTests: []*test.Case{ + { + Description: "Successfully build with 'tag first', 'buildctx second'", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", "-t", data.Identifier(), data.Get("buildCtx")) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Expected: test.Expects(0, nil, test.Equals("nerdctl-build-test-string\n")), + }, + { + Description: "Successfully build with 'buildctx first', 'tag second'", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "-t", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Expected: test.Expects(0, nil, test.Equals("nerdctl-build-test-string\n")), + }, + { + Description: "Successfully build with output docker, main tag still works", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "-t", data.Identifier(), "--output=type=docker,name="+data.Identifier("ignored")) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Expected: test.Expects(0, nil, test.Equals("nerdctl-build-test-string\n")), + }, + { + Description: "Successfully build with output docker, name cannot be used", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "-t", data.Identifier(), "--output=type=docker,name="+data.Identifier("ignored")) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier("ignored")) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Expected: test.Expects(-1, nil, nil), + }, + }, + } - dockerfile := fmt.Sprintf(`FROM %s -CMD ["echo", "nerdctl-build-test-string"] - `, testutil.CommonImage) + testCase.Run(t) +} - buildCtx := helpers.CreateBuildContext(t, dockerfile) +func TestCanBuildOnOtherPlatform(t *testing.T) { + nerdtest.Setup() - base.Cmd("build", "-t", imageName, buildCtx).AssertOK() - base.Cmd("build", buildCtx, "-t", imageName).AssertOK() - base.Cmd("run", "--rm", imageName).AssertOutExactly("nerdctl-build-test-string\n") + requireEmulation := &test.Requirement{ + Check: func(data test.Data, helpers test.Helpers) (bool, string) { + candidateArch := "arm64" + if runtime.GOARCH == "arm64" { + candidateArch = "amd64" + } + can, err := platformutil.CanExecProbably("linux/" + candidateArch) + assert.NilError(helpers.T(), err) - ignoredImageNamed := imageName + "-" + "ignored" - outputOpt := fmt.Sprintf("--output=type=docker,name=%s", ignoredImageNamed) - base.Cmd("build", buildCtx, "-t", imageName, outputOpt).AssertOK() + data.Set("OS", "linux") + data.Set("Architecture", candidateArch) + return can, "Current environment does not support emulation" + }, + } - base.Cmd("run", "--rm", imageName).AssertOutExactly("nerdctl-build-test-string\n") - base.Cmd("run", "--rm", ignoredImageNamed).AssertFail() + testCase := &test.Case{ + Require: test.Require( + nerdtest.Build, + requireEmulation, + ), + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s +RUN echo hello > /hello +CMD ["echo", "nerdctl-build-test-string"]`, testutil.CommonImage) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + data.Set("buildCtx", buildCtx) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", data.Get("buildCtx"), "--platform", fmt.Sprintf("%s/%s", data.Get("OS"), data.Get("Architecture")), "-t", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Expected: test.Expects(0, nil, nil), + } + + testCase.Run(t) } // TestBuildBaseImage tests if an image can be built on the previously built image. // This isn't currently supported by nerdctl with BuildKit OCI worker. func TestBuildBaseImage(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - defer base.Cmd("rmi", imageName).Run() - imageName2 := imageName + "-2" - defer base.Cmd("rmi", imageName2).Run() - - dockerfile := fmt.Sprintf(`FROM %s + nerdtest.Setup() + + testCase := &test.Case{ + Require: nerdtest.Build, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier("first")) + helpers.Anyhow("rmi", "-f", data.Identifier("second")) + }, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s RUN echo hello > /hello -CMD ["echo", "nerdctl-build-test-string"] - `, testutil.CommonImage) - - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - base.Cmd("build", "-t", imageName, buildCtx).AssertOK() - base.Cmd("build", buildCtx, "-t", imageName).AssertOK() +CMD ["echo", "nerdctl-build-test-string"]`, testutil.CommonImage) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + helpers.Ensure("build", "-t", data.Identifier("first"), buildCtx) - dockerfile2 := fmt.Sprintf(`FROM %s + dockerfileSecond := fmt.Sprintf(`FROM %s RUN echo hello2 > /hello2 -CMD ["cat", "/hello2"] - `, imageName) - - buildCtx2 := helpers.CreateBuildContext(t, dockerfile2) - - base.Cmd("build", "-t", imageName2, buildCtx2).AssertOK() - base.Cmd("build", buildCtx2, "-t", imageName2).AssertOK() +CMD ["cat", "/hello2"]`, data.Identifier("first")) + buildCtx = testhelpers.CreateBuildContext(t, dockerfileSecond) + helpers.Ensure("build", "-t", data.Identifier("second"), buildCtx) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier("second")) + }, + Expected: test.Expects(0, nil, test.Equals("hello2\n")), + } - base.Cmd("run", "--rm", imageName2).AssertOutExactly("hello2\n") + testCase.Run(t) } // TestBuildFromContainerd tests if an image can be built on an image pulled by nerdctl. // This isn't currently supported by nerdctl with BuildKit OCI worker. func TestBuildFromContainerd(t *testing.T) { - testutil.DockerIncompatible(t) - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - defer base.Cmd("rmi", imageName).Run() - imageName2 := imageName + "-2" - defer base.Cmd("rmi", imageName2).Run() - - // FIXME: BuildKit sometimes tries to use base image manifests of platforms that hasn't been - // pulled by `nerdctl pull`. This leads to "not found" error for the base image. - // To avoid this issue, images shared to BuildKit should always be pulled by manifest - // digest or `--all-platforms` needs to be added. - base.Cmd("pull", "--all-platforms", testutil.CommonImage).AssertOK() - base.Cmd("tag", testutil.CommonImage, imageName).AssertOK() - base.Cmd("rmi", testutil.CommonImage).AssertOK() - - dockerfile2 := fmt.Sprintf(`FROM %s + nerdtest.Setup() + + testCase := &test.Case{ + Require: test.Require( + nerdtest.Build, + test.Not(nerdtest.Docker), + ), + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier("first")) + helpers.Anyhow("rmi", "-f", data.Identifier("second")) + }, + Setup: func(data test.Data, helpers test.Helpers) { + // FIXME: BuildKit sometimes tries to use base image manifests of platforms that hasn't been + // pulled by `nerdctl pull`. This leads to "not found" error for the base image. + // To avoid this issue, images shared to BuildKit should always be pulled by manifest + // digest or `--all-platforms` needs to be added. + helpers.Ensure("pull", "--all-platforms", testutil.CommonImage) + helpers.Ensure("tag", testutil.CommonImage, data.Identifier("first")) + helpers.Ensure("rmi", testutil.CommonImage) + + dockerfile := fmt.Sprintf(`FROM %s RUN echo hello2 > /hello2 -CMD ["cat", "/hello2"] - `, imageName) - - buildCtx2 := helpers.CreateBuildContext(t, dockerfile2) - - base.Cmd("build", "-t", imageName2, buildCtx2).AssertOK() - base.Cmd("build", buildCtx2, "-t", imageName2).AssertOK() +CMD ["cat", "/hello2"]`, data.Identifier("first")) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + helpers.Ensure("build", "-t", data.Identifier("second"), buildCtx) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier("second")) + }, + Expected: test.Expects(0, nil, test.Equals("hello2\n")), + } - base.Cmd("run", "--rm", imageName2).AssertOutExactly("hello2\n") + testCase.Run(t) } func TestBuildFromStdin(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - defer base.Cmd("rmi", imageName).Run() - - dockerfile := fmt.Sprintf(`FROM %s -CMD ["echo", "nerdctl-build-test-stdin"] - `, testutil.CommonImage) + nerdtest.Setup() + + testCase := &test.Case{ + Require: nerdtest.Build, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-build-test-stdin"]`, testutil.CommonImage) + cmd := helpers.Command("build", "-t", data.Identifier(), "-f", "-", ".") + cmd.WithStdin(strings.NewReader(dockerfile)) + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + // Note: this is a cheat to match against stderr with the current tooling + Errors: []error{errors.New(data.Identifier())}, + } + }, + } - base.Cmd("build", "-t", imageName, "-f", "-", ".").CmdOption(testutil.WithStdin(strings.NewReader(dockerfile))).AssertCombinedOutContains(imageName) + testCase.Run(t) } func TestBuildWithDockerfile(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - defer base.Cmd("rmi", imageName).Run() - - dockerfile := fmt.Sprintf(`FROM %s + nerdtest.Setup() + + testCase := &test.Case{ + Require: nerdtest.Build, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s CMD ["echo", "nerdctl-build-test-dockerfile"] `, testutil.CommonImage) + buildCtx := filepath.Join(data.TempDir(), "test") + err := os.MkdirAll(buildCtx, 0755) + assert.NilError(helpers.T(), err) + err = os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0644) + assert.NilError(helpers.T(), err) + data.Set("buildCtx", buildCtx) + }, + SubTests: []*test.Case{ + { + Description: "Dockerfile ..", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("build", "-t", data.Identifier(), "-f", "Dockerfile", "..") + cmd.WithCwd(data.Get("buildCtx")) + return cmd + }, + Expected: test.Expects(0, nil, nil), + }, + { + Description: "Dockerfile .", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("build", "-t", data.Identifier(), "-f", "Dockerfile", ".") + cmd.WithCwd(data.Get("buildCtx")) + return cmd + }, + Expected: test.Expects(0, nil, nil), + }, + { + Description: "../Dockerfile .", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("build", "-t", data.Identifier(), "-f", "../Dockerfile", ".") + cmd.WithCwd(data.Get("buildCtx")) + return cmd + }, + Expected: test.Expects(1, nil, nil), + }, + }, + } - buildCtx := filepath.Join(t.TempDir(), "test") - err := os.MkdirAll(buildCtx, 0755) - assert.NilError(t, err) - err = os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0644) - assert.NilError(t, err) - - pwd, err := os.Getwd() - assert.NilError(t, err) - err = os.Chdir(buildCtx) - assert.NilError(t, err) - defer os.Chdir(pwd) - - // hack os.Getwd return "(unreachable)" on rootless - base.Env = append(base.Env, "PWD="+buildCtx) - - base.Cmd("build", "-t", imageName, "-f", "Dockerfile", "..").AssertOK() - base.Cmd("build", "-t", imageName, "-f", "Dockerfile", ".").AssertOK() - // fail err: no such file or directory - base.Cmd("build", "-t", imageName, "-f", "../Dockerfile", ".").AssertFail() + testCase.Run(t) } func TestBuildLocal(t *testing.T) { @@ -179,7 +312,7 @@ func TestBuildLocal(t *testing.T) { COPY %s /`, testFileName) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) if err := os.WriteFile(filepath.Join(buildCtx, testFileName), []byte(testContent), 0644); err != nil { t.Fatal(err) @@ -218,7 +351,7 @@ ENV TEST_STRING=$TEST_STRING CMD echo $TEST_STRING `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) base.Cmd("build", buildCtx, "-t", imageName).AssertOK() base.Cmd("run", "--rm", imageName).AssertOutExactly("1\n") @@ -261,7 +394,7 @@ func TestBuildWithIIDFile(t *testing.T) { CMD ["echo", "nerdctl-build-test-string"] `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) fileName := filepath.Join(t.TempDir(), "id.txt") base.Cmd("build", "-t", imageName, buildCtx, "--iidfile", fileName).AssertOK() @@ -284,7 +417,7 @@ func TestBuildWithLabels(t *testing.T) { LABEL name=nerdctl-build-test-label `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) base.Cmd("build", "-t", imageName, buildCtx, "--label", "label=test").AssertOK() defer base.Cmd("rmi", imageName).Run() @@ -307,7 +440,7 @@ func TestBuildMultipleTags(t *testing.T) { dockerfile := fmt.Sprintf(`FROM %s CMD ["echo", "%s"] `, testutil.CommonImage, output) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) base.Cmd("build", "-t", img, buildCtx).AssertOK() base.Cmd("build", buildCtx, "-t", img, "-t", imgWithNoTag, "-t", imgWithCustomTag).AssertOK() @@ -360,7 +493,7 @@ CMD ["echo", "dockerfile"] err = os.WriteFile(filepath.Join(tmpDir, "Containerfile"), []byte(containerfile), 0644) assert.NilError(t, err) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) base.Cmd("build", "-t", imageName, buildCtx).AssertOK() base.Cmd("run", "--rm", imageName).AssertOutExactly("dockerfile\n") @@ -375,7 +508,7 @@ func TestBuildNoTag(t *testing.T) { dockerfile := fmt.Sprintf(`FROM %s CMD ["echo", "nerdctl-build-notag-string"] `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) base.Cmd("build", buildCtx).AssertOK() base.Cmd("images").AssertOutContains("") @@ -390,7 +523,7 @@ func TestBuildContextDockerImageAlias(t *testing.T) { dockerfile := `FROM myorg/myapp CMD ["echo", "nerdctl-build-myorg/myapp"]` - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) base.Cmd("build", buildCtx, fmt.Sprintf("--build-context=myorg/myapp=docker-image://%s", testutil.CommonImage)).AssertOK() base.Cmd("images").AssertOutContains("") @@ -415,7 +548,7 @@ func TestBuildContextWithCopyFromDir(t *testing.T) { COPY --from=dir2 /%s /hello_from_dir2.txt RUN ["cat", "/hello_from_dir2.txt"]`, testutil.CommonImage, filename) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) base.Cmd("build", buildCtx, fmt.Sprintf("--build-context=dir2=%s", dir2)).AssertOK() base.Cmd("images").AssertOutContains("") @@ -436,7 +569,7 @@ RUN echo $SOURCE_DATE_EPOCH >/source-date-epoch CMD ["cat", "/source-date-epoch"] `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) const sourceDateEpochEnvStr = "1111111111" base.Env = append(base.Env, "SOURCE_DATE_EPOCH="+sourceDateEpochEnvStr) @@ -457,7 +590,7 @@ func TestBuildNetwork(t *testing.T) { RUN apk add --no-cache curl RUN curl -I http://google.com `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) validCases := []struct { name string @@ -513,7 +646,7 @@ func TestBuildAttestation(t *testing.T) { } dockerfile := "FROM " + testutil.NginxAlpineImage - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) // Test sbom outputSBOMDir := t.TempDir() diff --git a/cmd/nerdctl/builder/builder_linux_test.go b/cmd/nerdctl/builder/builder_linux_test.go index 862320142f9..7823c5f6130 100644 --- a/cmd/nerdctl/builder/builder_linux_test.go +++ b/cmd/nerdctl/builder/builder_linux_test.go @@ -18,136 +18,139 @@ package builder import ( "bytes" + "errors" "fmt" "os" "os/exec" - "path/filepath" "testing" "gotest.tools/v3/assert" - "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" - "github.com/containerd/nerdctl/v2/pkg/rootlessutil" + testhelpers "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 TestBuilderPrune(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) +func TestBuilder(t *testing.T) { + nerdtest.Setup() - base := testutil.NewBase(t) + // FIXME: this is a dirty hack to pass a function from Setup to Cleanup, which is not currently possible + var bkGC func() - dockerfile := fmt.Sprintf(`FROM %s + testCase := &test.Case{ + Require: nerdtest.Build, + SubTests: []*test.Case{ + { + Description: "PruneForce", + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s CMD ["echo", "nerdctl-test-builder-prune"]`, testutil.CommonImage) - - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - testCases := []struct { - name string - commandArgs []string - }{ - { - name: "TestBuilderPruneForce", - commandArgs: []string{"builder", "prune", "--force"}, - }, - { - name: "TestBuilderPruneForceAll", - commandArgs: []string{"builder", "prune", "--force", "--all"}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - base.Cmd("build", buildCtx).AssertOK() - base.Cmd(tc.commandArgs...).AssertOK() - }) - } -} - -func TestBuilderDebug(t *testing.T) { - testutil.DockerIncompatible(t) - base := testutil.NewBase(t) - - dockerfile := fmt.Sprintf(`FROM %s -CMD ["echo", "nerdctl-builder-debug-test-string"] - `, testutil.CommonImage) - - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - base.Cmd("builder", "debug", buildCtx).CmdOption(testutil.WithStdin(bytes.NewReader([]byte("c\n")))).AssertOK() -} - -func TestBuildWithPull(t *testing.T) { - testutil.DockerIncompatible(t) - if rootlessutil.IsRootless() { - t.Skipf("skipped because the test needs a custom buildkitd config") - } - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - - oldImage := testutil.BusyboxImage - oldImageSha := "141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47" - newImage := testutil.AlpineImage - - buildkitConfig := fmt.Sprintf(`[worker.oci] + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + helpers.Ensure("build", buildCtx) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("builder", "prune", "--all", "--force") + }, + Command: test.Command("builder", "prune", "--force"), + Expected: test.Expects(0, nil, nil), + }, + { + Description: "PruneForceAll", + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-test-builder-prune"]`, testutil.CommonImage) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + helpers.Ensure("build", buildCtx) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("builder", "prune", "--all", "--force") + }, + Command: test.Command("builder", "prune", "--force", "--all"), + Expected: test.Expects(0, nil, nil), + }, + { + Description: "Debug", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("builder", "prune", "--all", "--force") + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-builder-debug-test-string"]`, testutil.CommonImage) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + cmd := helpers.Command("builder", "debug", buildCtx) + cmd.WithStdin(bytes.NewReader([]byte("c\n"))) + return cmd + }, + Expected: test.Expects(0, nil, nil), + }, + { + Description: "WithPull", + Require: test.Require( + nerdtest.RootFul, + test.Not(nerdtest.Docker), + ), + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("builder", "prune", "--all", "--force") + if bkGC != nil { + bkGC() + } + }, + Setup: func(data test.Data, helpers test.Helpers) { + buildkitConfig := fmt.Sprintf(`[worker.oci] enabled = false [worker.containerd] enabled = true namespace = "%s"`, testutil.Namespace) - cleanup := useBuildkitConfig(t, buildkitConfig) - defer cleanup() - - testCases := []struct { - name string - pull string - }{ - { - name: "build with local image", - pull: "false", - }, - { - name: "build with newest image", - pull: "true", - }, - { - name: "build with buildkit default", - // buildkit default pulls from remote - pull: "default", + bkGC = useBuildkitConfig(t, buildkitConfig) + oldImage := testutil.BusyboxImage + oldImageSha := "141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47" + newImage := testutil.AlpineImage + + helpers.Ensure("pull", oldImage) + helpers.Ensure("tag", oldImage, newImage) + + dockerfile := fmt.Sprintf(`FROM %s`, newImage) + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + + data.Set("buildCtx", buildCtx) + data.Set("oldImageSha", oldImageSha) + }, + SubTests: []*test.Case{ + { + Description: "pull false", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", data.Get("buildCtx"), "--pull=false") + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 1, + Errors: []error{errors.New(data.Get("oldImageSha"))}, + } + }, + }, + { + Description: "pull true", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", data.Get("buildCtx"), "--pull=true") + }, + Expected: test.Expects(0, nil, nil), + }, + { + Description: "no pull", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", data.Get("buildCtx")) + }, + Expected: test.Expects(0, nil, nil), + }, + }, + }, }, } - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - base.Cmd("image", "prune", "--force", "--all").AssertOK() - - base.Cmd("pull", oldImage).Run() - base.Cmd("tag", oldImage, newImage).Run() - - dockerfile := fmt.Sprintf(`FROM %s`, newImage) - tmpDir := t.TempDir() - err := os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644) - assert.NilError(t, err) - - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - buildCmd := []string{"build", buildCtx} - switch tc.pull { - case "false": - buildCmd = append(buildCmd, "--pull=false") - base.Cmd(buildCmd...).AssertErrContains(oldImageSha) - case "true": - buildCmd = append(buildCmd, "--pull=true") - base.Cmd(buildCmd...).AssertErrNotContains(oldImageSha) - case "default": - base.Cmd(buildCmd...).AssertErrNotContains(oldImageSha) - } - }) - } + testCase.Run(t) } func useBuildkitConfig(t *testing.T, config string) (cleanup func()) { diff --git a/cmd/nerdctl/container/container_attach_linux_test.go b/cmd/nerdctl/container/container_attach_linux_test.go index 71a74eae59e..69c8cf3c6f6 100644 --- a/cmd/nerdctl/container/container_attach_linux_test.go +++ b/cmd/nerdctl/container/container_attach_linux_test.go @@ -24,81 +24,99 @@ import ( "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" ) -// skipAttachForDocker should be called by attach-related tests that assert 'read detach keys' in stdout. -func skipAttachForDocker(t *testing.T) { - t.Helper() - if testutil.GetTarget() == testutil.Docker { - t.Skip("When detaching from a container, for a session started with 'docker attach'" + - ", it prints 'read escape sequence', but for one started with 'docker (run|start)', it prints nothing." + - " However, the flag is called '--detach-keys' in all cases" + - ", so nerdctl prints 'read detach keys' for all cases" + - ", and that's why this test is skipped for Docker.") - } -} - -// prepareContainerToAttach spins up a container (entrypoint = shell) with `-it` and detaches from it -// so that it can be re-attached to later. -func prepareContainerToAttach(base *testutil.Base, containerName string) { - opts := []func(*testutil.Cmd){ - testutil.WithStdin(testutil.NewDelayOnceReader(bytes.NewReader( +func TestAttachDetachKeys(t *testing.T) { + testCase := nerdtest.Setup() + + setup := func(data test.Data, helpers test.Helpers) { + // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. + // unbuffer(1) can be installed with `apt-get install expect`. + // + // "-p" is needed because we need unbuffer to read from stdin, and from [1]: + // "Normally, unbuffer does not read from stdin. This simplifies use of unbuffer in some situations. + // To use unbuffer in a pipeline, use the -p flag." + // + // [1] https://linux.die.net/man/1/unbuffer + + si := testutil.NewDelayOnceReader(bytes.NewReader( []byte{16, 17}, // ctrl+p,ctrl+q, see https://www.physics.udel.edu/~watson/scen103/ascii.html - ))), + )) + + cmd := helpers. + Command("run", "-it", "--name", data.Identifier(), testutil.CommonImage) + cmd.WithWrapper("unbuffer", "-p") + cmd.WithStdin(si) + cmd.Run(&test.Expected{ + Output: test.All( + // NOTE: + // When detaching from a container, for a session started with 'docker attach', + // it prints 'read escape sequence', but for one started with 'docker (run|start)', it prints nothing. + // However, the flag is called '--detach-keys' in all cases, and nerdctl does print read detach keys + // in all cases. + // Disabling the contains test here allow both cli to run the test. + // test.Contains("read detach keys"), + func(stdout string, info string, t *testing.T) { + container := nerdtest.InspectContainer(helpers, data.Identifier()) + assert.Equal(t, container.State.Running, true, info) + }), + }) } - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - // - // "-p" is needed because we need unbuffer to read from stdin, and from [1]: - // "Normally, unbuffer does not read from stdin. This simplifies use of unbuffer in some situations. - // To use unbuffer in a pipeline, use the -p flag." - // - // [1] https://linux.die.net/man/1/unbuffer - base.CmdWithHelper([]string{"unbuffer", "-p"}, "run", "-it", "--name", containerName, testutil.CommonImage). - CmdOption(opts...).AssertOutContains("read detach keys") - container := base.InspectContainer(containerName) - assert.Equal(base.T, container.State.Running, true) -} - -func TestAttach(t *testing.T) { - t.Parallel() - - skipAttachForDocker(t) - - base := testutil.NewBase(t) - containerName := testutil.Identifier(t) - defer base.Cmd("container", "rm", "-f", containerName).AssertOK() - prepareContainerToAttach(base, containerName) - - opts := []func(*testutil.Cmd){ - testutil.WithStdin(testutil.NewDelayOnceReader(strings.NewReader("expr 1 + 1\nexit\n"))), + testCase.SubTests = []*test.Case{ + { + Description: "TestAttachDefaultKeys", + Require: test.Binary("unbuffer"), + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("container", "rm", "-f", data.Identifier()) + }, + Setup: setup, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + si := testutil.NewDelayOnceReader(strings.NewReader("expr 1 + 1\nexit\n")) + // `unbuffer -p` returns 0 even if the underlying nerdctl process returns a non-zero exit code, + // so the exit code cannot be easily tested here. + cmd := helpers.Command("attach", data.Identifier()) + cmd.WithStdin(si) + cmd.WithWrapper("unbuffer", "-p") + + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + container := nerdtest.InspectContainer(helpers, data.Identifier()) + assert.Equal(t, container.State.Running, false, info) + }, + } + }, + }, + { + Description: "TestAttachCustomKeys", + Require: test.Binary("unbuffer"), + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("container", "rm", "-f", data.Identifier()) + }, + Setup: setup, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + si := testutil.NewDelayOnceReader(bytes.NewReader([]byte{1, 2})) + cmd := helpers. + Command("attach", "--detach-keys=ctrl-a,ctrl-b", data.Identifier()) + cmd.WithStdin(si) + cmd.WithWrapper("unbuffer", "-p") + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + container := nerdtest.InspectContainer(helpers, data.Identifier()) + assert.Equal(t, container.State.Running, true, info) + }, + } + }, + }, } - // `unbuffer -p` returns 0 even if the underlying nerdctl process returns a non-zero exit code, - // so the exit code cannot be easily tested here. - base.CmdWithHelper([]string{"unbuffer", "-p"}, "attach", containerName).CmdOption(opts...).AssertOutContains("2") - container := base.InspectContainer(containerName) - assert.Equal(base.T, container.State.Running, false) -} - -func TestAttachDetachKeys(t *testing.T) { - t.Parallel() - skipAttachForDocker(t) - - base := testutil.NewBase(t) - containerName := testutil.Identifier(t) - - defer base.Cmd("container", "rm", "-f", containerName).AssertOK() - prepareContainerToAttach(base, containerName) - - opts := []func(*testutil.Cmd){ - testutil.WithStdin(testutil.NewDelayOnceReader(bytes.NewReader( - []byte{1, 2}, // https://www.physics.udel.edu/~watson/scen103/ascii.html - ))), - } - base.CmdWithHelper([]string{"unbuffer", "-p"}, "attach", "--detach-keys=ctrl-a,ctrl-b", containerName). - CmdOption(opts...).AssertOutContains("read detach keys") - container := base.InspectContainer(containerName) - assert.Equal(base.T, container.State.Running, true) + testCase.Run(t) } diff --git a/cmd/nerdctl/container/container_commit_linux_test.go b/cmd/nerdctl/container/container_commit_linux_test.go index 05802a1f7c9..f8936545b7a 100644 --- a/cmd/nerdctl/container/container_commit_linux_test.go +++ b/cmd/nerdctl/container/container_commit_linux_test.go @@ -32,6 +32,7 @@ func TestKubeCommitSave(t *testing.T) { testCase.Setup = func(data test.Data, helpers test.Helpers) { containerID := "" + nerdtest.KubeCtlCommand(helpers, "create", "namespace", string(nerdtest.Namespace)) nerdtest.KubeCtlCommand(helpers, "run", "--image", testutil.CommonImage, data.Identifier(), "--", "sleep", "Inf").Run(&test.Expected{}) nerdtest.KubeCtlCommand(helpers, "wait", "pod", data.Identifier(), "--for=condition=ready", "--timeout=1m").Run(&test.Expected{}) nerdtest.KubeCtlCommand(helpers, "exec", data.Identifier(), "--", "mkdir", "-p", "/tmp/whatever").Run(&test.Expected{}) diff --git a/cmd/nerdctl/container/container_commit_test.go b/cmd/nerdctl/container/container_commit_test.go index f9f553d9ca1..9382a782d7a 100644 --- a/cmd/nerdctl/container/container_commit_test.go +++ b/cmd/nerdctl/container/container_commit_test.go @@ -17,39 +17,64 @@ package container import ( - "fmt" "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 TestCommit(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - switch base.Info().CgroupDriver { - case "none", "": - t.Skip("requires cgroup (for pausing)") - } - testContainer := testutil.Identifier(t) - testImage := testutil.Identifier(t) + "-img" - defer base.Cmd("rm", "-f", testContainer).Run() - defer base.Cmd("rmi", testImage).Run() - - for _, pause := range []string{ - "true", - "false", - } { - base.Cmd("run", "-d", "--name", testContainer, testutil.CommonImage, "sleep", "infinity").AssertOK() - base.EnsureContainerStarted(testContainer) - base.Cmd("exec", testContainer, "sh", "-euxc", `echo hello-test-commit > /foo`).AssertOK() - base.Cmd( - "commit", - "-c", `CMD ["/foo"]`, - "-c", `ENTRYPOINT ["cat"]`, - fmt.Sprintf("--pause=%s", pause), - testContainer, testImage).AssertOK() - base.Cmd("run", "--rm", testImage).AssertOutExactly("hello-test-commit\n") - base.Cmd("rm", "-f", testContainer).Run() - base.Cmd("rmi", testImage).Run() + testCase := nerdtest.Setup() + + testCase.SubTests = []*test.Case{ + { + Description: "with pause", + Require: nerdtest.CGroup, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", "infinity") + nerdtest.EnsureContainerStarted(helpers, data.Identifier()) + helpers.Ensure("exec", data.Identifier(), "sh", "-euxc", `echo hello-test-commit > /foo`) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + helpers.Ensure( + "commit", + "-c", `CMD ["/foo"]`, + "-c", `ENTRYPOINT ["cat"]`, + "--pause=true", + data.Identifier(), data.Identifier()) + return helpers.Command("run", "--rm", data.Identifier()) + }, + Expected: test.Expects(0, nil, test.Equals("hello-test-commit\n")), + }, + { + Description: "no pause", + Require: test.Not(test.Windows), + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", "infinity") + nerdtest.EnsureContainerStarted(helpers, data.Identifier()) + helpers.Ensure("exec", data.Identifier(), "sh", "-euxc", `echo hello-test-commit > /foo`) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + helpers.Ensure( + "commit", + "-c", `CMD ["/foo"]`, + "-c", `ENTRYPOINT ["cat"]`, + "--pause=false", + data.Identifier(), data.Identifier()) + return helpers.Command("run", "--rm", data.Identifier()) + }, + Expected: test.Expects(0, nil, test.Equals("hello-test-commit\n")), + }, } + + testCase.Run(t) } diff --git a/cmd/nerdctl/container/container_cp_linux_test.go b/cmd/nerdctl/container/container_cp_linux_test.go index a94f46561b2..0534f10ba75 100644 --- a/cmd/nerdctl/container/container_cp_linux_test.go +++ b/cmd/nerdctl/container/container_cp_linux_test.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "syscall" "testing" @@ -28,221 +29,350 @@ import ( "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 TestCopyToContainer(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - testContainer := testutil.Identifier(t) - testStoppedContainer := "stopped-container-" + testutil.Identifier(t) + testCase := nerdtest.Setup() - base.Cmd("run", "-d", "--name", testContainer, testutil.CommonImage, "sleep", "1h").AssertOK() - defer base.Cmd("rm", "-f", testContainer).Run() + testCase.Require = nerdtest.RootFul - base.Cmd("run", "-d", "--name", testStoppedContainer, testutil.CommonImage, "sleep", "1h").AssertOK() - defer base.Cmd("rm", "-f", testStoppedContainer).Run() - // Stop container immediately after starting for testing copying into stopped container - base.Cmd("stop", testStoppedContainer).AssertOK() - srcUID := os.Geteuid() - srcDir := t.TempDir() - srcFile := filepath.Join(srcDir, "test-file") - srcFileContent := []byte("test-file-content") - err := os.WriteFile(srcFile, srcFileContent, 0o644) - assert.NilError(t, err) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + srcFileContent := "test-file-content" + srcFile := filepath.Join(data.TempDir(), "test-file") + err := os.WriteFile(srcFile, []byte(srcFileContent), 0o644) + assert.NilError(t, err) + + data.Set("srcFile", srcFile) + data.Set("srcUID", strconv.Itoa(os.Geteuid())) + data.Set("srcFileContent", srcFileContent) + } - assertCat := func(catPath string, testContainer string, stopped bool) { - if stopped { - base.Cmd("start", testContainer).AssertOK() - defer base.Cmd("stop", testContainer).AssertOK() + genSub := func(description string, customSetup func(data test.Data, helpers test.Helpers), stopped bool, success bool) *test.Case { + tc := &test.Case{ + Description: description, + Setup: func(data test.Data, helpers test.Helpers) { + if customSetup != nil { + customSetup(data, helpers) + } else { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + } + if stopped { + helpers.Ensure("stop", data.Identifier("container")) + } + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier("container")) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("cp", data.Get("srcPath"), data.Identifier("container")+":"+data.Get("destPath")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + exitCode := 0 + if !success { + exitCode = 1 + } + return &test.Expected{ + ExitCode: exitCode, + Output: func(stdout string, info string, t *testing.T) { + if !success { + return + } + + if stopped { + helpers.Ensure("start", data.Identifier("container")) + } + + so := helpers.Capture("exec", data.Identifier("container"), "sh", "-c", "--", fmt.Sprintf("cat %q; stat -c %%u %q", data.Get("catPath"), data.Get("catPath"))) + assert.Equal(t, so, fmt.Sprintf("%s%s\n", data.Get("srcFileContent"), data.Get("srcUID")), info) + }, + } + }, } - t.Logf("catPath=%q", catPath) - base.Cmd("exec", testContainer, "cat", catPath).AssertOutExactly(string(srcFileContent)) - base.Cmd("exec", testContainer, "stat", "-c", "%u", catPath).AssertOutExactly(fmt.Sprintf("%d\n", srcUID)) + return tc } // For the test matrix, see https://docs.docker.com/engine/reference/commandline/cp/ - t.Run("SRC_PATH specifies a file", func(t *testing.T) { - srcPath := srcFile - t.Run("DEST_PATH does not exist", func(t *testing.T) { - destPath := "/dest-no-exist-no-slash" - base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() - catPath := destPath - assertCat(catPath, testContainer, false) - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } - base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() - assertCat(catPath, testStoppedContainer, true) - }) - t.Run("DEST_PATH does not exist and ends with /", func(t *testing.T) { - destPath := "/dest-no-exist-with-slash/" - base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertFail() - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } - base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertFail() - }) - t.Run("DEST_PATH exists and is a file", func(t *testing.T) { - destPath := "/dest-file-exists" - base.Cmd("exec", testContainer, "touch", destPath).AssertOK() - base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() - catPath := destPath - assertCat(catPath, testContainer, false) - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } - base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() - assertCat(catPath, testStoppedContainer, true) - }) - t.Run("DEST_PATH exists and is a directory", func(t *testing.T) { - destPath := "/dest-dir-exists" - base.Cmd("exec", testContainer, "mkdir", "-p", destPath).AssertOK() - base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() - catPath := filepath.Join(destPath, filepath.Base(srcFile)) - assertCat(catPath, testContainer, false) - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } - base.Cmd("start", testStoppedContainer).AssertOK() - base.Cmd("exec", testStoppedContainer, "mkdir", "-p", destPath).AssertOK() - base.Cmd("stop", testStoppedContainer).AssertOK() - base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() - assertCat(catPath, testStoppedContainer, true) - }) - t.Run("DEST_PATH is in a volume", func(t *testing.T) { - // Create a volume - vol := "somevol" - base.Cmd("volume", "create", vol).AssertOK() - defer base.Cmd("volume", "rm", vol).Run() - con := fmt.Sprintf("%s-with-volume", testContainer) - mountDir := "/some_dir" - base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK() - defer base.Cmd("rm", "-f", con).Run() - catPath := filepath.Join(mountDir, filepath.Base(srcFile)) - // Running container test - base.Cmd("cp", srcPath, con+":"+mountDir).AssertOK() - assertCat(catPath, con, false) + testCase.SubTests = []*test.Case{ + { + Description: "SRC_PATH specifies a file", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("srcPath", data.Get("srcFile")) + }, + SubTests: []*test.Case{ + { + Description: "DEST_PATH does not exist", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("destPath", "/dest-no-exist-no-slash") + data.Set("catPath", "/dest-no-exist-no-slash") + }, + SubTests: []*test.Case{ + genSub("running", nil, false, true), + genSub("stopped", nil, true, true), + }, + }, + { + Description: "DEST_PATH does not exist and ends with /", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("destPath", "/dest-no-exist-with-slash/") + data.Set("catPath", "/dest-no-exist-with-slash/") + }, + SubTests: []*test.Case{ + genSub("running", nil, false, false), + genSub("stopped", nil, true, false), + }, + }, + { + Description: "DEST_PATH exist and is a file", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("destPath", "/dest-file-exists") + data.Set("catPath", "/dest-file-exists") + }, + SubTests: []*test.Case{ + genSub("running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "touch", data.Get("destPath")) + }, false, true), + genSub("stopped", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "touch", data.Get("destPath")) + }, true, true), + }, + }, + { + Description: "DEST_PATH exist and is a directory", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("destPath", "/dest-dir-exists") + data.Set("catPath", filepath.Join("/dest-dir-exists", filepath.Base(data.Get("srcPath")))) + }, + SubTests: []*test.Case{ + genSub("running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "mkdir", "-p", data.Get("destPath")) + }, false, true), + genSub("stopped", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "mkdir", "-p", data.Get("destPath")) + }, true, true), + }, + }, + { + Description: "DEST_PATH is the root of a volume", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("volume", "create", data.Identifier("volume")) + data.Set("destPath", "/in-a-volume") + data.Set("catPath", filepath.Join("/in-a-volume", filepath.Base(data.Get("srcPath")))) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("volume", "rm", data.Identifier("volume")) + }, + SubTests: []*test.Case{ + genSub("running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), "-v", fmt.Sprintf("%s:%s", data.Identifier("volume"), data.Get("destPath")), testutil.CommonImage, "sleep", "inf") + }, false, true), + genSub("running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), "-v", fmt.Sprintf("%s:%s", data.Identifier("volume"), data.Get("destPath")), testutil.CommonImage, "sleep", "inf") + }, true, true), + }, + }, + { + Description: "DEST_PATH is the root of a read-only volume", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("volume", "create", data.Identifier("volume")) + data.Set("destPath", "/in-a-volume") + data.Set("catPath", filepath.Join("/in-a-volume", filepath.Base(data.Get("srcPath")))) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("volume", "rm", data.Identifier("volume")) + }, + SubTests: []*test.Case{ + genSub("running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), "-v", fmt.Sprintf("%s:%s:ro", data.Identifier("volume"), data.Get("destPath")), testutil.CommonImage, "sleep", "inf") + }, false, false), + genSub("running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), "-v", fmt.Sprintf("%s:%s:ro", data.Identifier("volume"), data.Get("destPath")), testutil.CommonImage, "sleep", "inf") + }, true, false), + }, + }, + { + Description: "DEST_PATH is the root of /tmp (read-only)", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("volume", "create", data.Identifier("volume")) + data.Set("destPath", "/tmp") + data.Set("catPath", filepath.Join("/tmp", filepath.Base(data.Get("srcPath")))) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("volume", "rm", data.Identifier("volume")) + }, + SubTests: []*test.Case{ + genSub("running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), "-v", fmt.Sprintf("%s:%s:ro", data.Identifier("volume"), data.Get("destPath")), testutil.CommonImage, "sleep", "inf") + }, false, false), + genSub("running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), "-v", fmt.Sprintf("%s:%s:ro", data.Identifier("volume"), data.Get("destPath")), testutil.CommonImage, "sleep", "inf") + }, true, false), + }, + }, + }, + }, + { + Description: "SRC_PATH specifies a directory", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("srcPath", filepath.Dir(data.Get("srcFile"))) + }, + SubTests: []*test.Case{ + { + Description: "DEST_PATH does not exist", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("destPath", "/dest-no-exist-no-slash") + data.Set("catPath", filepath.Join("/dest-no-exist-no-slash", filepath.Base(data.Get("srcFile")))) + }, + SubTests: []*test.Case{ + genSub("running", nil, false, true), + genSub("stopped", nil, true, true), + }, + }, + { + Description: "DEST_PATH exist and is a file", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("destPath", "/dest-file-exists") + }, + SubTests: []*test.Case{ + genSub("running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "touch", data.Get("destPath")) + }, false, false), + genSub("stopped", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "touch", data.Get("destPath")) + }, true, false), + }, + }, + { + Description: "DEST_PATH exist and is a directory", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("destPath", "/dest-dir-exists") + data.Set("catPath", filepath.Join("/dest-dir-exists", filepath.Base(data.Get("srcPath")))) + }, + SubTests: []*test.Case{ + genSub("SRC_PATH does not end with `/.` running", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "mkdir", "-p", data.Get("destPath")) + }, false, true), + genSub("SRC_PATH does not end with `/.` stopped", func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "mkdir", "-p", data.Get("destPath")) + }, true, true), + genSub("SRC_PATH ends with `/.` running", func(data test.Data, helpers test.Helpers) { + data.Set("srcPath", data.Get("srcPath")+"/.") + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "mkdir", "-p", data.Get("destPath")) + }, false, true), + genSub("SRC_PATH ends with `/.` stopped", func(data test.Data, helpers test.Helpers) { + data.Set("srcPath", data.Get("srcPath")+"/.") + helpers.Ensure("run", "-d", "--name", data.Identifier("container"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("exec", data.Identifier("container"), "mkdir", "-p", data.Get("destPath")) + }, true, true), + }, + }, + }, + }, + } - // Skip for rootless - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } - // Stopped container test - // Delete previously copied file - base.Cmd("exec", con, "rm", catPath).AssertOK() - base.Cmd("stop", con).AssertOK() - base.Cmd("cp", srcPath, con+":"+mountDir).AssertOK() - assertCat(catPath, con, true) - }) - t.Run("Destination path is a read-only", func(t *testing.T) { - vol := "somevol" - base.Cmd("volume", "create", vol).AssertOK() - defer base.Cmd("volume", "rm", vol).Run() - con := fmt.Sprintf("%s-with-read-only-volume", testContainer) - mountDir := "/some_dir" - // Create container with read-only volume mounted - base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s:ro", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK() - defer base.Cmd("rm", "-f", con).Run() - base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail() + testCase.Run(t) +} - // Skip for rootless - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } +func TestCopyFromContainer(t *testing.T) { + testCase := nerdtest.Setup() - // Stopped container test - // Delete previously copied file - base.Cmd("stop", con).AssertOK() - base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail() - }) - t.Run("Destination path is a read-only and default tmpfs mount point", func(t *testing.T) { - vol := "somevol" - base.Cmd("volume", "create", vol).AssertOK() - defer base.Cmd("volume", "rm", vol).Run() - con := fmt.Sprintf("%s-with-read-only-volume", testContainer) + testCase.Require = nerdtest.RootFul - // /tmp is from rootfs of alpine - mountDir := "/tmp" - // Create container with read-only mounted volume mounted at /tmp - base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s:ro", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK() - defer base.Cmd("rm", "-f", con).Run() - base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail() + testCase.Setup = func(data test.Data, helpers test.Helpers) { + srcFileContent := "test-file-content" + srcFile := filepath.Join("/test-dir", "test-file") - // Skip for rootless - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } + data.Set("srcFile", srcFile) + data.Set("srcUID", "42") + data.Set("eUID", strconv.Itoa(os.Geteuid())) + data.Set("srcFileContent", srcFileContent) - // Stopped container test - // Delete previously copied file - base.Cmd("stop", con).AssertOK() - base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail() - }) - }) - t.Run("SRC_PATH specifies a directory", func(t *testing.T) { - srcPath := srcDir - t.Run("DEST_PATH does not exist", func(t *testing.T) { - destPath := "/dest2-no-exist" - base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() - catPath := filepath.Join(destPath, filepath.Base(srcFile)) - assertCat(catPath, testContainer, false) - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } - base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() - assertCat(catPath, testStoppedContainer, true) - }) - t.Run("DEST_PATH exists and is a file", func(t *testing.T) { - destPath := "/dest2-file-exists" - base.Cmd("exec", testContainer, "touch", destPath).AssertOK() - base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertFail() - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } - base.Cmd("start", testStoppedContainer).AssertOK() - base.Cmd("exec", testStoppedContainer, "touch", destPath).AssertOK() - base.Cmd("stop", testStoppedContainer).AssertOK() - base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertFail() - }) - t.Run("DEST_PATH exists and is a directory", func(t *testing.T) { - t.Run("SRC_PATH does not end with `/.`", func(t *testing.T) { - destPath := "/dest2-dir-exists" - base.Cmd("exec", testContainer, "mkdir", "-p", destPath).AssertOK() - base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() - catPath := filepath.Join(destPath, strings.TrimPrefix(srcFile, filepath.Dir(srcDir)+"/")) - assertCat(catPath, testContainer, false) - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") + helpers.Ensure("run", "-d", "--name", data.Identifier("running"), testutil.CommonImage, "sleep", "inf") + helpers.Ensure("run", "-d", "--name", data.Identifier("stopped"), testutil.CommonImage, "sleep", "inf") + + mkSrcScript := fmt.Sprintf("mkdir -p %q && echo -n %q >%q && chown %s %q", "/test-dir", srcFileContent, srcFile, data.Get("srcUID"), srcFile) + helpers.Ensure("exec", data.Identifier("running"), "sh", "-euc", mkSrcScript) + helpers.Ensure("exec", data.Identifier("stopped"), "sh", "-euc", mkSrcScript) + helpers.Ensure("stop", data.Identifier("stopped")) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier("running")) + helpers.Anyhow("rm", "-f", data.Identifier("stopped")) + } + + genSub := func(description string, customSetup func(data test.Data, helpers test.Helpers), stopped bool, success bool) *test.Case { + tc := &test.Case{ + Description: description, + Setup: customSetup, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cID := data.Get("running") + if stopped { + cID = data.Get("stopped") } - base.Cmd("start", testStoppedContainer).AssertOK() - base.Cmd("exec", testStoppedContainer, "mkdir", "-p", destPath).AssertOK() - base.Cmd("stop", testStoppedContainer).AssertOK() - base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() - assertCat(catPath, testStoppedContainer, true) - }) - t.Run("SRC_PATH does end with `/.`", func(t *testing.T) { - srcPath += "/." - destPath := "/dest2-dir2-exists" - base.Cmd("exec", testContainer, "mkdir", "-p", destPath).AssertOK() - base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() - catPath := filepath.Join(destPath, filepath.Base(srcFile)) - t.Logf("catPath=%q", catPath) - assertCat(catPath, testContainer, false) - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") + return helpers.Command("cp", cID+":"+data.Get("srcPath"), data.Get("destPath")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + if !success { + return &test.Expected{ + ExitCode: 1, + } } - base.Cmd("start", testStoppedContainer).AssertOK() - base.Cmd("exec", testStoppedContainer, "mkdir", "-p", destPath).AssertOK() - base.Cmd("stop", testStoppedContainer).AssertOK() - base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() - assertCat(catPath, testStoppedContainer, true) - }) - }) - }) + + got, err := os.ReadFile(data.Get("catPath")) + assert.NilError(helpers.T(), err) + assert.Equal(helpers.T(), data.Get("srcFileContent"), got) + st, err := os.Stat(data.Get("catPath")) + assert.NilError(helpers.T(), err) + stSys := st.Sys().(*syscall.Stat_t) + // stSys.Uid matches euid, not srcUID + e, _ := strconv.Atoi(data.Get("eUID")) + assert.Equal(helpers.T(), uint32(e), stSys.Uid) + return &test.Expected{} + }, + } + return tc + } + + testCase.SubTests = []*test.Case{ + { + Description: "SRC_PATH specifies a file", + Setup: func(data test.Data, helpers test.Helpers) { + data.Set("srcPath", data.Get("srcFile")) + }, + SubTests: []*test.Case{ + genSub("DEST_PATH does not exist - running", func(data test.Data, helpers test.Helpers) { + data.Set("destPath", filepath.Join(data.TempDir(), "dest-no-exist-no-slash")) + data.Set("catPath", filepath.Join(data.TempDir(), "dest-no-exist-no-slash")) + }, false, true), + genSub("DEST_PATH does not exist - stopped", func(data test.Data, helpers test.Helpers) { + data.Set("destPath", filepath.Join(data.TempDir(), "dest-no-exist-no-slash")) + data.Set("catPath", filepath.Join(data.TempDir(), "dest-no-exist-no-slash")) + }, true, true), + genSub("DEST_PATH does not exist ends / - running", func(data test.Data, helpers test.Helpers) { + data.Set("destPath", filepath.Join(data.TempDir(), "dest-no-exist-no-slash")+"/") + }, false, false), + genSub("DEST_PATH does not exist ends / - stopped", func(data test.Data, helpers test.Helpers) { + data.Set("destPath", filepath.Join(data.TempDir(), "dest-no-exist-no-slash")+"/") + }, true, false), + }, + }, + } + + testCase.Run(t) } -func TestCopyFromContainer(t *testing.T) { - t.Parallel() +func TestCopyFromContainerOld(t *testing.T) { base := testutil.NewBase(t) testContainer := testutil.Identifier(t) testStoppedContainer := "stopped-container-" + testutil.Identifier(t) @@ -279,25 +409,6 @@ func TestCopyFromContainer(t *testing.T) { // For the test matrix, see https://docs.docker.com/engine/reference/commandline/cp/ t.Run("SRC_PATH specifies a file", func(t *testing.T) { srcPath := srcFile - t.Run("DEST_PATH does not exist", func(t *testing.T) { - destPath := filepath.Join(td, "dest-no-exist-no-slash") - base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK() - catPath := destPath - assertCat(catPath) - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } - base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK() - assertCat(catPath) - }) - t.Run("DEST_PATH does not exist and ends with /", func(t *testing.T) { - destPath := td + "/dest-no-exist-with-slash/" // Avoid filepath.Join, to forcibly append "/" - base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertFail() - if rootlessutil.IsRootless() { - t.Skip("Test skipped in rootless mode for testStoppedContainer") - } - base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertFail() - }) t.Run("DEST_PATH exists and is a file", func(t *testing.T) { destPath := filepath.Join(td, "dest-file-exists") err := os.WriteFile(destPath, []byte(""), 0o644) diff --git a/cmd/nerdctl/container/container_create_linux_test.go b/cmd/nerdctl/container/container_create_linux_test.go index e0823ec3af0..b4ecbdf2508 100644 --- a/cmd/nerdctl/container/container_create_linux_test.go +++ b/cmd/nerdctl/container/container_create_linux_test.go @@ -35,18 +35,6 @@ import ( "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) -func TestCreate(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - tID := testutil.Identifier(t) - - base.Cmd("create", "--name", tID, testutil.CommonImage, "echo", "foo").AssertOK() - defer base.Cmd("rm", "-f", tID).Run() - base.Cmd("ps", "-a").AssertOutContains("Created") - base.Cmd("start", tID).AssertOK() - base.Cmd("logs", tID).AssertOutContains("foo") -} - func TestCreateWithLabel(t *testing.T) { t.Parallel() base := testutil.NewBase(t) @@ -190,7 +178,7 @@ func TestIssue2993(t *testing.T) { testCase.Require = test.Require( test.Not(nerdtest.Docker), // Maybe the use of a custom data root has an impact? - nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3518"), + // nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3518"), ) const ( @@ -218,8 +206,7 @@ func TestIssue2993(t *testing.T) { h := getAddrHash(defaults.DefaultAddress) dataStore := filepath.Join(dataRoot, h) - // FIXME: update with next tooling iteration to retrieve from the command - namespace := "nerdctl-test" + namespace := string(helpers.Read(nerdtest.Namespace)) containersPath := filepath.Join(dataStore, "containers", namespace) containersDirs, err := os.ReadDir(containersPath) @@ -266,8 +253,7 @@ func TestIssue2993(t *testing.T) { h := getAddrHash(defaults.DefaultAddress) dataStore := filepath.Join(dataRoot, h) - // FIXME: update with next tooling iteration to retrieve from the command - namespace := "nerdctl-test" + namespace := string(helpers.Read(nerdtest.Namespace)) containersPath := filepath.Join(dataStore, "containers", namespace) containersDirs, err := os.ReadDir(containersPath) diff --git a/cmd/nerdctl/container/container_create_test.go b/cmd/nerdctl/container/container_create_test.go new file mode 100644 index 00000000000..a363b76333f --- /dev/null +++ b/cmd/nerdctl/container/container_create_test.go @@ -0,0 +1,110 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package container + +import ( + "testing" + "time" + + "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" +) + +func TestCreate(t *testing.T) { + testCase := nerdtest.Setup() + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("create", "--name", data.Identifier("container"), testutil.CommonImage, "echo", "foo") + data.Set("cID", data.Identifier("container")) + } + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier("container")) + } + + testCase.SubTests = []*test.Case{ + { + Description: "ps -a", + NoParallel: true, + Command: test.Command("ps", "-a"), + // FIXME: this might get a false positive if other tests have created a container + Expected: test.Expects(0, nil, test.Contains("Created")), + }, + { + Description: "start", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("start", data.Get("cID")) + }, + Expected: test.Expects(0, nil, nil), + }, + { + Description: "logs", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("logs", data.Get("cID")) + }, + Expected: test.Expects(0, nil, test.Contains("foo")), + }, + } + + testCase.Run(t) +} + +func TestCreateHyperVContainer(t *testing.T) { + testCase := nerdtest.Setup() + + testCase.Require = test.Windows + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("create", "--isolation", "hyperv", "--name", data.Identifier("container"), testutil.CommonImage, "echo", "foo") + data.Set("cID", data.Identifier("container")) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier("container")) + } + + testCase.SubTests = []*test.Case{ + { + Description: "ps -a", + NoParallel: true, + Command: test.Command("ps", "-a"), + // FIXME: this might get a false positive if other tests have created a container + Expected: test.Expects(0, nil, test.Contains("Created")), + }, + { + Description: "start", + NoParallel: true, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("start", data.Get("cID")) + // hyperv containers take a few seconds to fire up, the test would fail without the sleep + // EnsureContainerStarted does not work + time.Sleep(10 * time.Second) + }, + }, + { + Description: "logs", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("logs", data.Get("cID")) + }, + Expected: test.Expects(0, nil, test.Contains("foo")), + }, + } + + testCase.Run(t) +} diff --git a/cmd/nerdctl/container/container_create_windows_test.go b/cmd/nerdctl/container/container_create_windows_test.go deleted file mode 100644 index d80f5bb6d1d..00000000000 --- a/cmd/nerdctl/container/container_create_windows_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package container - -import ( - "testing" - "time" - - "github.com/containerd/nerdctl/v2/pkg/testutil" -) - -func TestCreateProcessContainer(t *testing.T) { - base := testutil.NewBase(t) - tID := testutil.Identifier(t) - - base.Cmd("create", "--name", tID, testutil.CommonImage, "echo", "foo").AssertOK() - defer base.Cmd("rm", "-f", tID).Run() - base.Cmd("ps", "-a").AssertOutContains("Created") - base.Cmd("start", tID).AssertOK() - base.Cmd("logs", tID).AssertOutContains("foo") -} - -func TestCreateHyperVContainer(t *testing.T) { - base := testutil.NewBase(t) - tID := testutil.Identifier(t) - - if !testutil.HyperVSupported() { - t.Skip("HyperV is not enabled, skipping test") - } - - base.Cmd("create", "--isolation", "hyperv", "--name", tID, testutil.CommonImage, "echo", "foo").AssertOK() - defer base.Cmd("rm", "-f", tID).Run() - base.Cmd("ps", "-a").AssertOutContains("Created") - - base.Cmd("start", tID).AssertOK() - // hyperv containers take a few seconds to fire up, the test would fail without the sleep - // EnsureContainerStarted does not work - time.Sleep(10 * time.Second) - - base.Cmd("logs", tID).AssertOutContains("foo") -} diff --git a/cmd/nerdctl/container/container_diff_test.go b/cmd/nerdctl/container/container_diff_test.go index 6914dee5303..b47631dd600 100644 --- a/cmd/nerdctl/container/container_diff_test.go +++ b/cmd/nerdctl/container/container_diff_test.go @@ -17,11 +17,11 @@ package container import ( - "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" - "github.com/containerd/nerdctl/v2/pkg/testutil/test" "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 TestDiff(t *testing.T) { diff --git a/cmd/nerdctl/container/container_logs_test.go b/cmd/nerdctl/container/container_logs_test.go index ec9fbd2ce70..5f9e3d30aa6 100644 --- a/cmd/nerdctl/container/container_logs_test.go +++ b/cmd/nerdctl/container/container_logs_test.go @@ -95,6 +95,8 @@ func TestLogsWithInheritedFlags(t *testing.T) { base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage, "sh", "-euxc", "echo foo; echo bar").AssertOK() + // NOTE: seen with Docker: there are circumstances where this happens too fast and we get foo + time.Sleep(1 * time.Second) // test rootCmd alias `-n` already used in logs subcommand base.Cmd("logs", "-n", "1", containerName).AssertOutWithFunc(func(stdout string) error { if !(stdout == "bar\n" || stdout == "") { diff --git a/cmd/nerdctl/container/container_prune_linux_test.go b/cmd/nerdctl/container/container_prune_linux_test.go index 8912e8fb8dd..84979c780a6 100644 --- a/cmd/nerdctl/container/container_prune_linux_test.go +++ b/cmd/nerdctl/container/container_prune_linux_test.go @@ -20,36 +20,36 @@ 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 TestPruneContainer(t *testing.T) { - base := testutil.NewBase(t) - tID := testutil.Identifier(t) + testCase := nerdtest.Setup() - tearDown := func() { - defer base.Cmd("rm", "-f", tID+"-1").Run() - defer base.Cmd("rm", "-f", tID+"-2").Run() - } + testCase.Require = nerdtest.Private - tearUp := func() { - base.Cmd("run", "-d", "--name", tID+"-1", "-v", "/anonymous", testutil.CommonImage, "sleep", "infinity").AssertOK() - base.Cmd("exec", tID+"-1", "touch", "/anonymous/foo").AssertOK() - base.Cmd("create", "--name", tID+"-2", testutil.CommonImage, "sleep", "infinity").AssertOK() + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier("1")) + helpers.Anyhow("rm", "-f", data.Identifier("2")) } - tearDown() - t.Cleanup(tearDown) - tearUp() - - base.Cmd("container", "prune", "-f").AssertOK() - // tID-1 is still running, tID-2 is not - base.Cmd("inspect", tID+"-1").AssertOK() - base.Cmd("inspect", tID+"-2").AssertFail() + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("1"), "-v", "/anonymous", testutil.CommonImage, "sleep", "infinity") + helpers.Ensure("exec", data.Identifier("1"), "touch", "/anonymous/foo") + helpers.Ensure("create", "--name", data.Identifier("2"), testutil.CommonImage, "sleep", "infinity") + } - // https://github.com/containerd/nerdctl/issues/3134 - base.Cmd("exec", tID+"-1", "ls", "-lA", "/anonymous/foo").AssertOK() + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + helpers.Ensure("container", "prune", "-f") + helpers.Ensure("inspect", data.Identifier("1")) + helpers.Fail("inspect", data.Identifier("2")) + // https://github.com/containerd/nerdctl/issues/3134 + helpers.Ensure("exec", data.Identifier("1"), "ls", "-lA", "/anonymous/foo") + helpers.Ensure("kill", data.Identifier("1")) + helpers.Ensure("container", "prune", "-f") + return helpers.Command("inspect", data.Identifier("1")) + } - base.Cmd("kill", tID+"-1").AssertOK() - base.Cmd("container", "prune", "-f").AssertOK() - base.Cmd("inspect", tID+"-1").AssertFail() + testCase.Expected = test.Expects(1, nil, nil) } diff --git a/cmd/nerdctl/container/container_remove_test.go b/cmd/nerdctl/container/container_remove_test.go index 3f9eecfd679..626a4bead62 100644 --- a/cmd/nerdctl/container/container_remove_test.go +++ b/cmd/nerdctl/container/container_remove_test.go @@ -20,22 +20,29 @@ 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 TestRemoveContainer(t *testing.T) { - t.Parallel() + testCase := nerdtest.Setup() - base := testutil.NewBase(t) - tID := testutil.Identifier(t) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", "inf") + } - // ignore error - base.Cmd("rm", tID, "-f").AssertOK() + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + } - base.Cmd("run", "-d", "--name", tID, testutil.NginxAlpineImage).AssertOK() - defer base.Cmd("rm", tID, "-f").AssertOK() - base.Cmd("rm", tID).AssertFail() + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + helpers.Fail("rm", data.Identifier()) - // `kill` does return before the container actually stops - base.Cmd("stop", tID).AssertOK() - base.Cmd("rm", tID).AssertOK() + // `kill` does return before the container actually stops + helpers.Ensure("stop", data.Identifier()) + + return helpers.Command("rm", data.Identifier()) + } + + testCase.Expected = test.Expects(0, nil, nil) } diff --git a/cmd/nerdctl/container/container_run_linux_test.go b/cmd/nerdctl/container/container_run_linux_test.go index aca549d9446..a177ec0864e 100644 --- a/cmd/nerdctl/container/container_run_linux_test.go +++ b/cmd/nerdctl/container/container_run_linux_test.go @@ -36,9 +36,10 @@ import ( "gotest.tools/v3/icmd" "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" - "github.com/containerd/nerdctl/v2/pkg/rootlessutil" "github.com/containerd/nerdctl/v2/pkg/strutil" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) func TestRunCustomRootfs(t *testing.T) { @@ -458,64 +459,96 @@ func TestRunWithFluentdLogDriverWithLogOpt(t *testing.T) { } func TestRunWithOOMScoreAdj(t *testing.T) { - if rootlessutil.IsRootless() { - t.Skip("test skipped for rootless containers.") + nerdtest.Setup() + + testCase := &test.Case{ + Description: "TestStartDetachKeys", + Require: nerdtest.RootFul, + Command: test.Command("run", "--rm", "--oom-score-adj", "-42", testutil.AlpineImage, "cat", "/proc/self/oom_score_adj"), + Expected: test.Expects(0, nil, test.Contains("-42")), } - t.Parallel() - base := testutil.NewBase(t) - var score = "-42" - base.Cmd("run", "--rm", "--oom-score-adj", score, testutil.AlpineImage, "cat", "/proc/self/oom_score_adj").AssertOutContains(score) + testCase.Run(t) } -func TestRunWithDetachKeys(t *testing.T) { - t.Parallel() +func TestRunDetachKeys(t *testing.T) { + nerdtest.Setup() - if testutil.GetTarget() == testutil.Docker { - t.Skip("When detaching from a container, for a session started with 'docker attach'" + - ", it prints 'read escape sequence', but for one started with 'docker (run|start)', it prints nothing." + - " However, the flag is called '--detach-keys' in all cases" + - ", so nerdctl prints 'read detach keys' for all cases" + - ", and that's why this test is skipped for Docker.") + testCase := &test.Case{ + Description: "TestStartDetachKeys", + Require: test.Binary("unbuffer"), + 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 { + si := testutil.NewDelayOnceReader(bytes.NewReader([]byte{1, 2})) + cmd := helpers. + Command("run", "-it", "--detach-keys=ctrl-a,ctrl-b", "--name", data.Identifier(), testutil.CommonImage) + cmd.WithStdin(si) + cmd.WithWrapper("unbuffer", "-p") + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + container := nerdtest.InspectContainer(helpers, data.Identifier()) + assert.Equal(t, container.State.Running, true, info) + }, + } + }, } - base := testutil.NewBase(t) - containerName := testutil.Identifier(t) - opts := []func(*testutil.Cmd){ - testutil.WithStdin(testutil.NewDelayOnceReader(bytes.NewReader([]byte{1, 2}))), // https://www.physics.udel.edu/~watson/scen103/ascii.html - } - defer base.Cmd("container", "rm", "-f", containerName).AssertOK() - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - // - // "-p" is needed because we need unbuffer to read from stdin, and from [1]: - // "Normally, unbuffer does not read from stdin. This simplifies use of unbuffer in some situations. - // To use unbuffer in a pipeline, use the -p flag." - // - // [1] https://linux.die.net/man/1/unbuffer - base.CmdWithHelper([]string{"unbuffer", "-p"}, "run", "-it", "--detach-keys=ctrl-a,ctrl-b", "--name", containerName, testutil.CommonImage). - CmdOption(opts...).AssertOutContains("read detach keys") - container := base.InspectContainer(containerName) - assert.Equal(base.T, container.State.Running, true) + testCase.Run(t) } func TestRunWithTtyAndDetached(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("run", "-d", "--name", withoutTtyContainerName, imageName, "stty").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("run", "-d", "-t", "--name", withTtyContainerName, imageName, "stty").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: "without terminal", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("container", "rm", "-f", data.Identifier()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "stty") + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("logs", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Errors: []error{errors.New("stty: standard input: Not a tty")}, + Output: func(stdout string, info string, t *testing.T) { + container := nerdtest.InspectContainer(helpers, data.Identifier()) + assert.Equal(t, container.State.ExitCode, 1, info) + }, + } + }, + }, + { + Description: "with terminal", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("container", "rm", "-f", data.Identifier()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "-t", "--name", data.Identifier(), testutil.CommonImage, "stty") + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("logs", data.Identifier()) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.All( + test.Contains("speed 38400 baud; line = 0;"), + func(stdout string, info string, t *testing.T) { + container := nerdtest.InspectContainer(helpers, data.Identifier()) + assert.Equal(t, container.State.ExitCode, 0, info) + }), + } + }, + }, + } + + testCase.Run(t) } diff --git a/cmd/nerdctl/container/container_run_verify_linux_test.go b/cmd/nerdctl/container/container_run_verify_linux_test.go index 7d12342cbb3..9ec7256fc33 100644 --- a/cmd/nerdctl/container/container_run_verify_linux_test.go +++ b/cmd/nerdctl/container/container_run_verify_linux_test.go @@ -20,38 +20,56 @@ import ( "fmt" "testing" - "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" + testhelpers "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/testutil" - "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) func TestRunVerifyCosign(t *testing.T) { - testutil.RequireExecutable(t, "cosign") - testutil.DockerIncompatible(t) - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - t.Parallel() - - base := testutil.NewBase(t) - base.Env = append(base.Env, "COSIGN_PASSWORD=1") - - keyPair := helpers.NewCosignKeyPair(t, "cosign-key-pair", "1") - reg := testregistry.NewWithNoAuth(base, 0, false) - t.Cleanup(func() { - keyPair.Cleanup() - reg.Cleanup(nil) - }) - - tID := testutil.Identifier(t) - testImageRef := fmt.Sprintf("127.0.0.1:%d/%s", reg.Port, tID) - dockerfile := fmt.Sprintf(`FROM %s -CMD ["echo", "nerdctl-build-test-string"] - `, testutil.CommonImage) - - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - base.Cmd("build", "-t", testImageRef, buildCtx).AssertOK() - base.Cmd("push", testImageRef, "--sign=cosign", "--cosign-key="+keyPair.PrivateKey).AssertOK() - base.Cmd("run", "--rm", "--verify=cosign", "--cosign-key="+keyPair.PublicKey, testImageRef).AssertOK() - base.Cmd("run", "--rm", "--verify=cosign", "--cosign-key=dummy", testImageRef).AssertFail() + var keyPair *testhelpers.CosignKeyPair + var reg *registry.Server + + testCase := nerdtest.Setup() + + testCase.Require = test.Require( + test.Binary("cosign"), + test.Not(nerdtest.Docker), + nerdtest.Build, + ) + + testCase.Env = map[string]string{ + "COSIGN_PASSWORD": "1", + } + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + keyPair = testhelpers.NewCosignKeyPair(t, "cosign-key-pair", "1") + reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + if keyPair != nil { + keyPair.Cleanup() + } + if reg != nil { + reg.Cleanup(data, helpers) + } + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + testImageRef := fmt.Sprintf("127.0.0.1:%d/%s", reg.Port, data.Identifier()) + dockerfile := fmt.Sprintf(`FROM %s + CMD ["echo", "nerdctl-build-test-string"] + `, testutil.CommonImage) + + buildCtx := testhelpers.CreateBuildContext(t, dockerfile) + + helpers.Ensure("build", "-t", testImageRef, buildCtx) + helpers.Ensure("push", testImageRef, "--sign=cosign", "--cosign-key="+keyPair.PrivateKey) + helpers.Ensure("run", "--rm", "--verify=cosign", "--cosign-key="+keyPair.PublicKey, testImageRef) + return helpers.Command("run", "--rm", "--verify=cosign", "--cosign-key=dummy", testImageRef) + } + + testCase.Expected = test.Expects(1, nil, nil) } diff --git a/cmd/nerdctl/container/container_start_linux_test.go b/cmd/nerdctl/container/container_start_linux_test.go index 4fe6f2d249c..ac2da9d90ae 100644 --- a/cmd/nerdctl/container/container_start_linux_test.go +++ b/cmd/nerdctl/container/container_start_linux_test.go @@ -24,41 +24,52 @@ import ( "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 TestStartDetachKeys(t *testing.T) { - t.Parallel() + nerdtest.Setup() - skipAttachForDocker(t) + testCase := &test.Case{ + Description: "TestStartDetachKeys", + Require: test.Binary("unbuffer"), + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("container", "rm", "-f", data.Identifier()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + si := testutil.NewDelayOnceReader(strings.NewReader("exit\n")) - base := testutil.NewBase(t) - containerName := testutil.Identifier(t) - - defer base.Cmd("container", "rm", "-f", containerName).AssertOK() - opts := []func(*testutil.Cmd){ - // If NewDelayOnceReader is not used, - // the container state will be Created instead of Exited. - // Maybe `unbuffer` exits too early in that case? - testutil.WithStdin(testutil.NewDelayOnceReader(strings.NewReader("exit\n"))), + cmd := helpers. + Command("run", "-it", "--name", data.Identifier(), testutil.CommonImage) + cmd.WithWrapper("unbuffer", "-p") + cmd.WithStdin(si) + cmd.Run(&test.Expected{ + Output: test.All( + func(stdout string, info string, t *testing.T) { + container := nerdtest.InspectContainer(helpers, data.Identifier()) + assert.Equal(t, container.State.Running, false, info) + }), + }) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + si := testutil.NewDelayOnceReader(bytes.NewReader([]byte{1, 2})) + cmd := helpers. + Command("start", "-a", "--detach-keys=ctrl-a,ctrl-b", data.Identifier()) + cmd.WithStdin(si) + cmd.WithWrapper("unbuffer", "-p") + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + container := nerdtest.InspectContainer(helpers, data.Identifier()) + assert.Equal(t, container.State.Running, true, info) + }, + } + }, } - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - // - // "-p" is needed because we need unbuffer to read from stdin, and from [1]: - // "Normally, unbuffer does not read from stdin. This simplifies use of unbuffer in some situations. - // To use unbuffer in a pipeline, use the -p flag." - // - // [1] https://linux.die.net/man/1/unbuffer - base.CmdWithHelper([]string{"unbuffer", "-p"}, "run", "-it", "--name", containerName, testutil.CommonImage). - CmdOption(opts...).AssertOK() - container := base.InspectContainer(containerName) - assert.Equal(base.T, container.State.Running, false) - opts = []func(*testutil.Cmd){ - testutil.WithStdin(testutil.NewDelayOnceReader(bytes.NewReader([]byte{1, 2}))), // https://www.physics.udel.edu/~watson/scen103/ascii.html - } - base.CmdWithHelper([]string{"unbuffer", "-p"}, "start", "-a", "--detach-keys=ctrl-a,ctrl-b", containerName). - CmdOption(opts...).AssertOutContains("read detach keys") - container = base.InspectContainer(containerName) - assert.Equal(base.T, container.State.Running, true) + testCase.Run(t) + } diff --git a/cmd/nerdctl/container/container_stats_linux_test.go b/cmd/nerdctl/container/container_stats_linux_test.go index 5ea2017d949..0edf95aa7ee 100644 --- a/cmd/nerdctl/container/container_stats_linux_test.go +++ b/cmd/nerdctl/container/container_stats_linux_test.go @@ -17,12 +17,13 @@ package container import ( - "fmt" "testing" "github.com/containerd/nerdctl/v2/pkg/infoutil" "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 TestStats(t *testing.T) { @@ -31,17 +32,54 @@ func TestStats(t *testing.T) { if rootlessutil.IsRootless() && infoutil.CgroupsVersion() == "1" { t.Skip("test skipped for rootless containers on cgroup v1") } - testContainerName := testutil.Identifier(t)[:12] - exitedTestContainerName := fmt.Sprintf("%s-exited", testContainerName) - - base := testutil.NewBase(t) - defer base.Cmd("rm", "-f", testContainerName).Run() - defer base.Cmd("rm", "-f", exitedTestContainerName).Run() - base.Cmd("run", "--name", exitedTestContainerName, testutil.AlpineImage, "echo", "'exited'").AssertOK() - - base.Cmd("run", "-d", "--name", testContainerName, testutil.AlpineImage, "sleep", "10").AssertOK() - base.Cmd("stats", "--no-stream").AssertOutContains(testContainerName) - base.Cmd("stats", "--no-stream", testContainerName).AssertOK() - base.Cmd("container", "stats", "--no-stream").AssertOutContains(testContainerName) - base.Cmd("container", "stats", "--no-stream", testContainerName).AssertOK() + + testCase := nerdtest.Setup() + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()[:12]) + helpers.Anyhow("rm", "-f", data.Identifier()[:12]+"-exited") + } + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "--name", data.Identifier()[:12], testutil.AlpineImage, "sleep", "inf") + helpers.Ensure("run", "--name", data.Identifier()[:12]+"-exited", testutil.AlpineImage, "echo", "'exited'") + data.Set("id", data.Identifier()[:12]) + } + + testCase.SubTests = []*test.Case{ + { + Description: "stats", + Command: test.Command("stats", "--no-stream"), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("id")), + } + }, + }, + { + Description: "container stats", + Command: test.Command("container", "stats", "--no-stream"), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("id")), + } + }, + }, + { + Description: "stats ID", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("stats", "--no-stream", data.Get("id")) + }, + Expected: test.Expects(0, nil, nil), + }, + { + Description: "container stats ID", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("container", "stats", "--no-stream", data.Get("id")) + }, + Expected: test.Expects(0, nil, nil), + }, + } + + testCase.Run(t) } diff --git a/cmd/nerdctl/container/container_top_test.go b/cmd/nerdctl/container/container_top_test.go new file mode 100644 index 00000000000..bd1b66b4cb1 --- /dev/null +++ b/cmd/nerdctl/container/container_top_test.go @@ -0,0 +1,71 @@ +//go:build unix + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package container + +import ( + "runtime" + "testing" + + "github.com/containerd/nerdctl/v2/pkg/infoutil" + "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 TestTop(t *testing.T) { + //more details https://github.com/containerd/nerdctl/pull/223#issuecomment-851395178 + if runtime.GOOS == "linux" { + if rootlessutil.IsRootless() && infoutil.CgroupsVersion() == "1" { + t.Skip("test skipped for rootless containers on cgroup v1") + } + } + + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", "inf") + data.Set("cID", data.Identifier()) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + } + + testCase.SubTests = []*test.Case{ + { + Description: "with o pid,user,cmd", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("top", data.Get("cID"), "-o", "pid,user,cmd") + }, + + Expected: test.Expects(0, nil, nil), + }, + { + Description: "simple", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("top", data.Get("cID")) + }, + + Expected: test.Expects(0, nil, nil), + }, + } + + testCase.Run(t) +} diff --git a/cmd/nerdctl/container/container_top_unix_test.go b/cmd/nerdctl/container/container_top_unix_test.go deleted file mode 100644 index d68d42302ee..00000000000 --- a/cmd/nerdctl/container/container_top_unix_test.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build unix - -/* - Copyright The containerd Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package container - -import ( - "testing" - - "github.com/containerd/nerdctl/v2/pkg/infoutil" - "github.com/containerd/nerdctl/v2/pkg/rootlessutil" - "github.com/containerd/nerdctl/v2/pkg/testutil" -) - -func TestTop(t *testing.T) { - t.Parallel() - //more details https://github.com/containerd/nerdctl/pull/223#issuecomment-851395178 - if rootlessutil.IsRootless() && infoutil.CgroupsVersion() == "1" { - t.Skip("test skipped for rootless containers on cgroup v1") - } - testContainerName := testutil.Identifier(t) - - base := testutil.NewBase(t) - defer base.Cmd("rm", "-f", testContainerName).Run() - - base.Cmd("run", "-d", "--name", testContainerName, testutil.AlpineImage, "sleep", "5").AssertOK() - base.Cmd("top", testContainerName, "-o", "pid,user,cmd").AssertOK() - -} diff --git a/cmd/nerdctl/container/container_top_windows_test.go b/cmd/nerdctl/container/container_top_windows_test.go index 690e52d50f4..0eb13392a9a 100644 --- a/cmd/nerdctl/container/container_top_windows_test.go +++ b/cmd/nerdctl/container/container_top_windows_test.go @@ -20,28 +20,32 @@ 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 TestTopProcessContainer(t *testing.T) { - testContainerName := testutil.Identifier(t) - - base := testutil.NewBase(t) - defer base.Cmd("rm", "-f", testContainerName).Run() - - base.Cmd("run", "-d", "--name", testContainerName, testutil.WindowsNano, "sleep", "5").AssertOK() - base.Cmd("top", testContainerName).AssertOK() -} - func TestTopHyperVContainer(t *testing.T) { if !testutil.HyperVSupported() { t.Skip("HyperV is not enabled, skipping test") } - testContainerName := testutil.Identifier(t) + testCase := nerdtest.Setup() + + testCase.Require = test.Windows + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "--isolation", "hyperv", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", "inf") + } + + 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("top", data.Identifier()) + } - base := testutil.NewBase(t) - defer base.Cmd("rm", "-f", testContainerName).Run() + testCase.Expected = test.Expects(0, nil, nil) - base.Cmd("run", "--isolation", "hyperv", "-d", "--name", testContainerName, testutil.WindowsNano, "sleep", "5").AssertOK() - base.Cmd("top", testContainerName).AssertOK() + testCase.Run(t) } diff --git a/cmd/nerdctl/container/container_wait_test.go b/cmd/nerdctl/container/container_wait_test.go index d6a3203480a..ba24b0db70b 100644 --- a/cmd/nerdctl/container/container_wait_test.go +++ b/cmd/nerdctl/container/container_wait_test.go @@ -20,28 +20,31 @@ 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 TestWait(t *testing.T) { - t.Parallel() - tID := testutil.Identifier(t) - testContainerName1 := tID + "-1" - testContainerName2 := tID + "-2" - testContainerName3 := tID + "-3" + testCase := nerdtest.Setup() - const expected = `0 -0 -123 -` - base := testutil.NewBase(t) - defer base.Cmd("rm", "-f", testContainerName1, testContainerName2, testContainerName3).Run() - - base.Cmd("run", "-d", "--name", testContainerName1, testutil.CommonImage, "sleep", "1").AssertOK() + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier("1"), data.Identifier("2"), data.Identifier("3")) + } - base.Cmd("run", "-d", "--name", testContainerName2, testutil.CommonImage, "sleep", "1").AssertOK() + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier("1"), testutil.CommonImage, "sleep", "1") + helpers.Ensure("run", "-d", "--name", data.Identifier("2"), testutil.CommonImage, "sleep", "1") + helpers.Fail("run", "--name", data.Identifier("3"), testutil.CommonImage, "sh", "-euxc", "sleep 5; exit 123") + } - base.Cmd("run", "--name", testContainerName3, testutil.CommonImage, "sh", "-euxc", "sleep 5; exit 123").AssertExitCode(123) + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("wait", data.Identifier("1"), data.Identifier("2"), data.Identifier("3")) + } - base.Cmd("wait", testContainerName1, testContainerName2, testContainerName3).AssertOutExactly(expected) + testCase.Expected = test.Expects(0, nil, test.Equals(`0 +0 +123 +`)) + testCase.Run(t) } diff --git a/cmd/nerdctl/image/image_convert_linux_test.go b/cmd/nerdctl/image/image_convert_linux_test.go index 1b16d079bde..5b21487ce81 100644 --- a/cmd/nerdctl/image/image_convert_linux_test.go +++ b/cmd/nerdctl/image/image_convert_linux_test.go @@ -42,7 +42,7 @@ func TestImageConvert(t *testing.T) { { Description: "esgz", Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier("converted-image")) + helpers.Anyhow("rmi", "-f", data.Identifier("converted-image")) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { return helpers.Command("image", "convert", "--oci", "--estargz", @@ -56,7 +56,7 @@ func TestImageConvert(t *testing.T) { test.Binary("nydus-image"), ), Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier("converted-image")) + helpers.Anyhow("rmi", "-f", data.Identifier("converted-image")) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { return helpers.Command("image", "convert", "--oci", "--nydus", @@ -67,7 +67,7 @@ func TestImageConvert(t *testing.T) { { Description: "zstd", Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier("converted-image")) + helpers.Anyhow("rmi", "-f", data.Identifier("converted-image")) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { return helpers.Command("image", "convert", "--oci", "--zstd", "--zstd-compression-level", "3", @@ -78,7 +78,7 @@ func TestImageConvert(t *testing.T) { { Description: "zstdchunked", Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier("converted-image")) + helpers.Anyhow("rmi", "-f", data.Identifier("converted-image")) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { return helpers.Command("image", "convert", "--oci", "--zstdchunked", "--zstdchunked-compression-level", "3", @@ -120,10 +120,10 @@ func TestImageConvertNydusVerify(t *testing.T) { helpers.Ensure("push", data.Get(remoteImageKey)) }, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier("converted-image")) + helpers.Anyhow("rmi", "-f", data.Identifier("converted-image")) if registry != nil { registry.Cleanup(nil) - helpers.Anyhow("rmi", data.Get(remoteImageKey)) + helpers.Anyhow("rmi", "-f", data.Get(remoteImageKey)) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { diff --git a/cmd/nerdctl/image/image_list_test.go b/cmd/nerdctl/image/image_list_test.go index 71e754484bf..150c5149662 100644 --- a/cmd/nerdctl/image/image_list_test.go +++ b/cmd/nerdctl/image/image_list_test.go @@ -143,9 +143,9 @@ RUN echo "actually creating a layer so that docker sets the createdAt time" data.Set("buildCtx", buildCtx) }, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", "taggedimage:one-fragment-one") - helpers.Anyhow("rmi", "taggedimage:two-fragment-two") - helpers.Anyhow("rmi", data.Identifier()) + helpers.Anyhow("rmi", "-f", "taggedimage:one-fragment-one") + helpers.Anyhow("rmi", "-f", "taggedimage:two-fragment-two") + helpers.Anyhow("rmi", "-f", data.Identifier()) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { data.Set("builtImageID", data.Identifier()) @@ -181,7 +181,7 @@ RUN echo "actually creating a layer so that docker sets the createdAt time" }, }, { - Description: "label=foo=bar label=version=0.1", + Description: "label=foo=bar label=version=0.2", Command: test.Command("images", "--filter", "label=foo=bar", "--filter", "label=version=0.2"), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ @@ -191,7 +191,6 @@ RUN echo "actually creating a layer so that docker sets the createdAt time" }, { Description: "label=version", - Require: nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3512"), Command: test.Command("images", "--filter", "label=version"), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ diff --git a/cmd/nerdctl/image/image_load_test.go b/cmd/nerdctl/image/image_load_test.go index 97f31c04b6e..452a0e18729 100644 --- a/cmd/nerdctl/image/image_load_test.go +++ b/cmd/nerdctl/image/image_load_test.go @@ -43,7 +43,7 @@ func TestLoadStdinFromPipe(t *testing.T) { helpers.Ensure("rmi", "-f", data.Identifier()) }, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Identifier()) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { cmd := helpers.Command("load") diff --git a/cmd/nerdctl/image/image_prune_test.go b/cmd/nerdctl/image/image_prune_test.go index c6534253698..e3c66b8bf10 100644 --- a/cmd/nerdctl/image/image_prune_test.go +++ b/cmd/nerdctl/image/image_prune_test.go @@ -53,7 +53,7 @@ func TestImagePrune(t *testing.T) { nerdtest.Build, ), Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Identifier()) }, Setup: func(data test.Data, helpers test.Helpers) { dockerfile := fmt.Sprintf(`FROM %s @@ -95,7 +95,7 @@ func TestImagePrune(t *testing.T) { // Cannot use a custom namespace with buildkitd right now, so, no parallel it is NoParallel: true, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Identifier()) helpers.Anyhow("rm", "-f", data.Identifier()) }, Setup: func(data test.Data, helpers test.Helpers) { @@ -138,7 +138,7 @@ func TestImagePrune(t *testing.T) { // Cannot use a custom namespace with buildkitd right now, so, no parallel it is NoParallel: true, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Identifier()) }, Setup: func(data test.Data, helpers test.Helpers) { dockerfile := fmt.Sprintf(`FROM %s @@ -177,7 +177,7 @@ LABEL version=0.1`, testutil.CommonImage) // Cannot use a custom namespace with buildkitd right now, so, no parallel it is NoParallel: true, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Identifier()) }, Setup: func(data test.Data, helpers test.Helpers) { dockerfile := fmt.Sprintf(`FROM %s diff --git a/cmd/nerdctl/image/image_pull_linux_test.go b/cmd/nerdctl/image/image_pull_linux_test.go index ff9ee84fdd8..d2d624671fd 100644 --- a/cmd/nerdctl/image/image_pull_linux_test.go +++ b/cmd/nerdctl/image/image_pull_linux_test.go @@ -176,7 +176,7 @@ func TestImagePullSoci(t *testing.T) { helpers.Ensure("--snapshotter=soci", "pull", testutil.FfmpegSociImage) }, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", testutil.FfmpegSociImage) + helpers.Anyhow("rmi", "-f", testutil.FfmpegSociImage) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { return helpers.Custom("mount") @@ -210,7 +210,7 @@ func TestImagePullSoci(t *testing.T) { helpers.Ensure("--snapshotter=soci", "pull", testutil.FfmpegSociImage) }, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", testutil.FfmpegSociImage) + helpers.Anyhow("rmi", "-f", testutil.FfmpegSociImage) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { return helpers.Custom("mount") diff --git a/cmd/nerdctl/image/image_push_linux_test.go b/cmd/nerdctl/image/image_push_linux_test.go index 9d3ae4d8598..1cd38bba7fd 100644 --- a/cmd/nerdctl/image/image_push_linux_test.go +++ b/cmd/nerdctl/image/image_push_linux_test.go @@ -72,7 +72,7 @@ func TestPush(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get("testImageRef") != "" { - helpers.Anyhow("rmi", data.Get("testImageRef")) + helpers.Anyhow("rmi", "-f", data.Get("testImageRef")) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -92,7 +92,7 @@ func TestPush(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get("testImageRef") != "" { - helpers.Anyhow("rmi", data.Get("testImageRef")) + helpers.Anyhow("rmi", "-f", data.Get("testImageRef")) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -126,7 +126,7 @@ func TestPush(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get("testImageRef") != "" { - helpers.Anyhow("rmi", data.Get("testImageRef")) + helpers.Anyhow("rmi", "-f", data.Get("testImageRef")) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -149,7 +149,7 @@ func TestPush(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get("testImageRef") != "" { - helpers.Anyhow("rmi", data.Get("testImageRef")) + helpers.Anyhow("rmi", "-f", data.Get("testImageRef")) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -172,7 +172,7 @@ func TestPush(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get("testImageRef") != "" { - helpers.Anyhow("rmi", data.Get("testImageRef")) + helpers.Anyhow("rmi", "-f", data.Get("testImageRef")) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -192,7 +192,7 @@ func TestPush(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get("testImageRef") != "" { - helpers.Anyhow("rmi", data.Get("testImageRef")) + helpers.Anyhow("rmi", "-f", data.Get("testImageRef")) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -224,7 +224,7 @@ func TestPush(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get("testImageRef") != "" { - helpers.Anyhow("rmi", data.Get("testImageRef")) + helpers.Anyhow("rmi", "-f", data.Get("testImageRef")) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -259,7 +259,7 @@ func TestPush(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get("testImageRef") != "" { - helpers.Anyhow("rmi", data.Get("testImageRef")) + helpers.Anyhow("rmi", "-f", data.Get("testImageRef")) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { diff --git a/cmd/nerdctl/ipfs/ipfs_compose_linux_test.go b/cmd/nerdctl/ipfs/ipfs_compose_linux_test.go index 10d56dd0d19..6558019111a 100644 --- a/cmd/nerdctl/ipfs/ipfs_compose_linux_test.go +++ b/cmd/nerdctl/ipfs/ipfs_compose_linux_test.go @@ -46,7 +46,7 @@ func TestIPFSCompNoBuild(t *testing.T) { test.Not(nerdtest.Docker), nerdtest.Registry, nerdtest.IPFS, - nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3510"), + nerdtest.IsFlaky("compose implementation is currently flaky"), // See note below // nerdtest.Private, ) @@ -72,9 +72,8 @@ func TestIPFSCompNoBuild(t *testing.T) { if ipfsRegistry != nil { ipfsRegistry.Cleanup(data, helpers) } - // Speeding up repeat tests... - helpers.Anyhow("rmi", testutil.WordpressImage) - helpers.Anyhow("rmi", testutil.MariaDBImage) + helpers.Anyhow("rmi", "-f", testutil.WordpressImage) + helpers.Anyhow("rmi", "-f", testutil.MariaDBImage) } testCase.Run(t) @@ -128,8 +127,8 @@ func subtestTestIPFSCompNoB(t *testing.T, stargz bool, byAddr bool) *test.Case { // Deliberately electing to not remove them here so that we can parallelize and cut down the running time /* if data.Get(mariaImageCIDKey) != "" { - helpers.Anyhow("rmi", data.Get(mariaImageCIDKey)) - helpers.Anyhow("rmi", data.Get(wordpressImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(mariaImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(wordpressImageCIDKey)) } */ } @@ -234,7 +233,7 @@ COPY index.html /usr/share/nginx/html/index.html testCase.Cleanup = func(data test.Data, helpers test.Helpers) { if ipfsServer != nil { // Close the server once done - helpers.Anyhow("rmi", data.Get(mainImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(mainImageCIDKey)) ipfsServer.Run(nil) } if comp != nil { diff --git a/cmd/nerdctl/ipfs/ipfs_kubo_linux_test.go b/cmd/nerdctl/ipfs/ipfs_kubo_linux_test.go index 3738ceb4514..c65883e7681 100644 --- a/cmd/nerdctl/ipfs/ipfs_kubo_linux_test.go +++ b/cmd/nerdctl/ipfs/ipfs_kubo_linux_test.go @@ -68,7 +68,7 @@ func TestIPFSAddrWithKubo(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get(mainImageCIDKey) != "" { - helpers.Anyhow("rmi", data.Get(mainImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(mainImageCIDKey)) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -91,7 +91,7 @@ func TestIPFSAddrWithKubo(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get(mainImageCIDKey) != "" { - helpers.Anyhow("rmi", data.Get(mainImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(mainImageCIDKey)) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { diff --git a/cmd/nerdctl/ipfs/ipfs_registry_linux_test.go b/cmd/nerdctl/ipfs/ipfs_registry_linux_test.go index 63a461361cc..b6b032dc965 100644 --- a/cmd/nerdctl/ipfs/ipfs_registry_linux_test.go +++ b/cmd/nerdctl/ipfs/ipfs_registry_linux_test.go @@ -89,7 +89,7 @@ func TestIPFSNerdctlRegistry(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get(ipfsImageURLKey) != "" { - helpers.Anyhow("rmi", data.Get(ipfsImageURLKey)) + helpers.Anyhow("rmi", "-f", data.Get(ipfsImageURLKey)) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -107,7 +107,7 @@ func TestIPFSNerdctlRegistry(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get(ipfsImageURLKey) != "" { - helpers.Anyhow("rmi", data.Get(ipfsImageURLKey)) + helpers.Anyhow("rmi", "-f", data.Get(ipfsImageURLKey)) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -120,9 +120,9 @@ func TestIPFSNerdctlRegistry(t *testing.T) { NoParallel: true, Require: nerdtest.Build, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rmi", data.Identifier("built-image")) + helpers.Anyhow("rmi", "-f", data.Identifier("built-image")) if data.Get(ipfsImageURLKey) != "" { - helpers.Anyhow("rmi", data.Get(ipfsImageURLKey)) + helpers.Anyhow("rmi", "-f", data.Get(ipfsImageURLKey)) } }, Setup: func(data test.Data, helpers test.Helpers) { diff --git a/cmd/nerdctl/ipfs/ipfs_simple_linux_test.go b/cmd/nerdctl/ipfs/ipfs_simple_linux_test.go index e18acc332ee..5c7b5697464 100644 --- a/cmd/nerdctl/ipfs/ipfs_simple_linux_test.go +++ b/cmd/nerdctl/ipfs/ipfs_simple_linux_test.go @@ -55,7 +55,7 @@ func TestIPFSSimple(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get(mainImageCIDKey) != "" { - helpers.Anyhow("rmi", data.Get(mainImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(mainImageCIDKey)) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -76,7 +76,7 @@ func TestIPFSSimple(t *testing.T) { }, Cleanup: func(data test.Data, helpers test.Helpers) { if data.Get(mainImageCIDKey) != "" { - helpers.Anyhow("rmi", data.Get(mainImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(mainImageCIDKey)) } }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { @@ -104,11 +104,11 @@ func TestIPFSSimple(t *testing.T) { helpers.Ensure("pull", "ipfs://"+data.Get(transformedImageCIDKey)) }, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", data.Identifier("commit-container")) - helpers.Anyhow("rmi", data.Identifier("commit-image")) + helpers.Anyhow("rm", "-f", data.Identifier("commit-container")) + helpers.Anyhow("rmi", "-f", data.Identifier("commit-image")) if data.Get(mainImageCIDKey) != "" { - helpers.Anyhow("rmi", data.Get(mainImageCIDKey)) - helpers.Anyhow("rmi", data.Get(transformedImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(mainImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(transformedImageCIDKey)) } }, @@ -142,11 +142,11 @@ func TestIPFSSimple(t *testing.T) { helpers.Ensure("pull", "ipfs://"+data.Get(transformedImageCIDKey)) }, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", data.Identifier("commit-container")) - helpers.Anyhow("rmi", data.Identifier("commit-image")) + helpers.Anyhow("rm", "-f", data.Identifier("commit-container")) + helpers.Anyhow("rmi", "-f", data.Identifier("commit-image")) if data.Get(mainImageCIDKey) != "" { - helpers.Anyhow("rmi", data.Get(mainImageCIDKey)) - helpers.Anyhow("rmi", data.Get(transformedImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(mainImageCIDKey)) + helpers.Anyhow("rmi", "-f", data.Get(transformedImageCIDKey)) } }, diff --git a/cmd/nerdctl/login/login_linux_test.go b/cmd/nerdctl/login/login_linux_test.go index 0a53a6a623e..2ac21fa374f 100644 --- a/cmd/nerdctl/login/login_linux_test.go +++ b/cmd/nerdctl/login/login_linux_test.go @@ -31,6 +31,7 @@ import ( "github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" "github.com/containerd/nerdctl/v2/pkg/testutil/testca" "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" ) @@ -108,8 +109,8 @@ func TestLoginPersistence(t *testing.T) { t.Run(fmt.Sprintf("Server %s", tc.auth), func(t *testing.T) { t.Parallel() - username := testregistry.SafeRandomString(30) + "∞" - password := testregistry.SafeRandomString(30) + ":∞" + username := test.RandomStringBase64(30) + "∞" + password := test.RandomStringBase64(30) + ":∞" // Add the requested authentication var auth testregistry.Auth @@ -297,8 +298,8 @@ func TestLoginAgainstVariants(t *testing.T) { } // Generate credentials that are specific to each registry, so that we never cross hit another one - username := testregistry.SafeRandomString(30) + "∞" - password := testregistry.SafeRandomString(30) + ":∞" + username := test.RandomStringBase64(30) + "∞" + password := test.RandomStringBase64(30) + ":∞" // Get a CA if we want TLS var ca *testca.CA diff --git a/cmd/nerdctl/network/network_create_linux_test.go b/cmd/nerdctl/network/network_create_linux_test.go index 2d1274f19ea..596375f33db 100644 --- a/cmd/nerdctl/network/network_create_linux_test.go +++ b/cmd/nerdctl/network/network_create_linux_test.go @@ -35,8 +35,6 @@ func TestNetworkCreate(t *testing.T) { testCase.SubTests = []*test.Case{ { Description: "vanilla", - // #3491 and #3508 may have helped - commenting this out for now - // Require: nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3086"), Setup: func(data test.Data, helpers test.Helpers) { helpers.Ensure("network", "create", data.Identifier()) netw := nerdtest.InspectNetwork(helpers, data.Identifier()) @@ -66,8 +64,6 @@ func TestNetworkCreate(t *testing.T) { }, { Description: "with MTU", - // #3491 and #3508 may have helped - commenting this out for now - // Require: nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3086"), Setup: func(data test.Data, helpers test.Helpers) { helpers.Ensure("network", "create", data.Identifier(), "--driver", "bridge", "--opt", "com.docker.network.driver.mtu=9216") }, @@ -81,9 +77,7 @@ func TestNetworkCreate(t *testing.T) { }, { Description: "with ipv6", - // #3491 and #3508 may have helped - commenting this out for now - // Require: nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3086"), - Require: nerdtest.OnlyIPv6, + Require: nerdtest.OnlyIPv6, Setup: func(data test.Data, helpers test.Helpers) { subnetStr := "2001:db8:8::/64" data.Set("subnetStr", subnetStr) diff --git a/cmd/nerdctl/network/network_list_linux_test.go b/cmd/nerdctl/network/network_list_linux_test.go index af8c8d1ca0b..22620287a66 100644 --- a/cmd/nerdctl/network/network_list_linux_test.go +++ b/cmd/nerdctl/network/network_list_linux_test.go @@ -29,9 +29,6 @@ import ( func TestNetworkLsFilter(t *testing.T) { testCase := nerdtest.Setup() - // #3491 and #3508 may have helped - commenting this out for now - // testCase.Require = nerdtest.IsFlaky("https://github.com/containerd/nerdctl/issues/3086"), - testCase.Setup = func(data test.Data, helpers test.Helpers) { data.Set("identifier", data.Identifier()) data.Set("label", "mylabel=label-1") diff --git a/pkg/testutil/test/helpers.go b/pkg/testutil/test/helpers.go index 99d769d7c3e..06411df8e99 100644 --- a/pkg/testutil/test/helpers.go +++ b/pkg/testutil/test/helpers.go @@ -70,7 +70,7 @@ func (help *helpersInternal) Anyhow(args ...string) { // Fail will run a command and make sure it does fail func (help *helpersInternal) Fail(args ...string) { help.Command(args...).Run(&Expected{ - ExitCode: 1, + ExitCode: -1, }) }