From 8f21c9d38e5c814259554e9e78d1de0819470c28 Mon Sep 17 00:00:00 2001 From: apostasie Date: Tue, 15 Oct 2024 22:05:53 -0700 Subject: [PATCH] Builder tests rewrite Signed-off-by: apostasie --- .../builder/builder_build_linux_test.go | 85 -- .../builder/builder_build_oci_layout_test.go | 107 ++ cmd/nerdctl/builder/builder_build_test.go | 1072 +++++++++++------ cmd/nerdctl/builder/builder_builder_test.go | 152 +++ cmd/nerdctl/builder/builder_linux_test.go | 168 --- .../completion/completion_linux_test.go | 201 ---- cmd/nerdctl/completion/completion_test.go | 186 +++ .../container/container_commit_test.go | 4 + pkg/testutil/nerdtest/requirements.go | 7 +- pkg/testutil/testutil.go | 18 - pkg/testutil/testutil_freebsd.go | 1 + pkg/testutil/testutil_windows.go | 4 +- 12 files changed, 1179 insertions(+), 826 deletions(-) delete mode 100644 cmd/nerdctl/builder/builder_build_linux_test.go create mode 100644 cmd/nerdctl/builder/builder_build_oci_layout_test.go create mode 100644 cmd/nerdctl/builder/builder_builder_test.go delete mode 100644 cmd/nerdctl/builder/builder_linux_test.go delete mode 100644 cmd/nerdctl/completion/completion_linux_test.go diff --git a/cmd/nerdctl/builder/builder_build_linux_test.go b/cmd/nerdctl/builder/builder_build_linux_test.go deleted file mode 100644 index 0f80066b0a2..00000000000 --- a/cmd/nerdctl/builder/builder_build_linux_test.go +++ /dev/null @@ -1,85 +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 builder - -import ( - "fmt" - "testing" - - "gotest.tools/v3/assert" - - "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" - "github.com/containerd/nerdctl/v2/pkg/testutil" -) - -func TestBuildContextWithOCILayout(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - - var dockerBuilderArgs []string - if testutil.IsDocker() { - // Default docker driver does not support OCI exporter. - // Reference: https://docs.docker.com/build/exporters/oci-docker/ - builderName := testutil.SetupDockerContainerBuilder(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 -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") - } - - base.Cmd(buildArgs...).AssertOK() - base.Cmd("run", "--rm", imageName).AssertOutContains("test-nerdctl-build-context-oci-layout") -} diff --git a/cmd/nerdctl/builder/builder_build_oci_layout_test.go b/cmd/nerdctl/builder/builder_build_oci_layout_test.go new file mode 100644 index 00000000000..a3fea9b5fa5 --- /dev/null +++ b/cmd/nerdctl/builder/builder_build_oci_layout_test.go @@ -0,0 +1,107 @@ +/* + 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 builder + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "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 TestBuildContextWithOCILayout(t *testing.T) { + nerdtest.Setup() + + var dockerBuilderArgs []string + + testCase := &test.Case{ + Require: test.Require( + nerdtest.Build, + test.Not(test.Windows), + ), + Cleanup: func(data test.Data, helpers test.Helpers) { + if nerdtest.IsDocker() { + helpers.Anyhow("buildx", "stop", data.Identifier("-container")) + helpers.Anyhow("buildx", "rm", "--force", data.Identifier("-container")) + } + helpers.Anyhow("rmi", "-f", data.Identifier("-parent")) + helpers.Anyhow("rmi", "-f", data.Identifier("-child")) + }, + Setup: func(data test.Data, helpers test.Helpers) { + // Default docker driver does not support OCI exporter. + // Reference: https://docs.docker.com/build/exporters/oci-docker/ + if nerdtest.IsDocker() { + name := data.Identifier("-container") + helpers.Ensure("buildx", "create", "--name", name, "--driver=docker-container") + dockerBuilderArgs = []string{"buildx", "--builder", name} + } + + dockerfile := fmt.Sprintf(`FROM %s +LABEL layer=oci-layout-parent +CMD ["echo", "test-nerdctl-build-context-oci-layout-parent"]`, testutil.CommonImage) + + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + + tarPath := filepath.Join(buildCtx, "parent.tar") + dest := filepath.Join(buildCtx, "parent") + assert.NilError(helpers.T(), os.MkdirAll(dest, 0o700)) + helpers.Ensure("build", buildCtx, "--tag", data.Identifier("-parent")) + helpers.Ensure("image", "save", "--output", tarPath, data.Identifier("-parent")) + helpers.Custom("tar", "Cxf", dest, 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 := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + + var cmd test.TestableCommand + if nerdtest.IsDocker() { + cmd = helpers.Command(dockerBuilderArgs...) + } else { + cmd = helpers.Command() + } + cmd.WithArgs("build", buildCtx, fmt.Sprintf("--build-context=parent=oci-layout://%s", filepath.Join(buildCtx, "parent")), "--tag", data.Identifier("-child")) + if nerdtest.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("-child")), "test-nerdctl-build-context-oci-layout"), info) + }, + } + }, + } + + testCase.Run(t) +} diff --git a/cmd/nerdctl/builder/builder_build_test.go b/cmd/nerdctl/builder/builder_build_test.go index 6b9052f9d12..e14446254b7 100644 --- a/cmd/nerdctl/builder/builder_build_test.go +++ b/cmd/nerdctl/builder/builder_build_test.go @@ -17,481 +17,844 @@ package builder import ( + "errors" "fmt" "os" "path/filepath" + "runtime" "strings" "testing" "gotest.tools/v3/assert" - "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) + err := os.WriteFile(filepath.Join(data.TempDir(), "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + data.Set("buildCtx", data.TempDir()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + 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) + err := os.WriteFile(filepath.Join(data.TempDir(), "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + data.Set("buildCtx", data.TempDir()) + }, + 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) + err := os.WriteFile(filepath.Join(data.TempDir(), "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", "-t", data.Identifier("first"), data.TempDir()) - 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")) + err = os.WriteFile(filepath.Join(data.TempDir(), "Dockerfile"), []byte(dockerfileSecond), 0644) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", "-t", data.Identifier("second"), data.TempDir()) + }, + 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) { + helpers.Ensure("pull", "--quiet", testutil.CommonImage) + helpers.Ensure("tag", testutil.CommonImage, data.Identifier("first")) + + 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")) + err := os.WriteFile(filepath.Join(data.TempDir(), "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", "-t", data.Identifier("second"), data.TempDir()) + }, + 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{ + 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), 0o600) + assert.NilError(helpers.T(), err) + data.Set("buildCtx", buildCtx) + }, + SubTests: []*test.Case{ + { + Description: "Dockerfile ..", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + 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 .", + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + 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) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) + nerdtest.Setup() + const testFileName = "nerdctl-build-test" const testContent = "nerdctl" - outputDir := t.TempDir() - - dockerfile := fmt.Sprintf(`FROM scratch -COPY %s /`, - testFileName) - - buildCtx := helpers.CreateBuildContext(t, dockerfile) - if err := os.WriteFile(filepath.Join(buildCtx, testFileName), []byte(testContent), 0644); err != nil { - t.Fatal(err) + testCase := &test.Case{ + Require: nerdtest.Build, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM scratch +COPY %s /`, testFileName) + + err := os.WriteFile(filepath.Join(data.TempDir(), "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + + err = os.WriteFile(filepath.Join(data.TempDir(), testFileName), []byte(testContent), 0644) + assert.NilError(helpers.T(), err) + + data.Set("buildCtx", data.TempDir()) + }, + SubTests: []*test.Case{ + { + Description: "destination 1", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", "-o", fmt.Sprintf("type=local,dest=%s", data.TempDir()), data.Get("buildCtx")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + testFilePath := filepath.Join(data.TempDir(), testFileName) + _, err := os.Stat(testFilePath) + assert.NilError(helpers.T(), err, info) + dt, err := os.ReadFile(testFilePath) + assert.NilError(helpers.T(), err, info) + assert.Equal(helpers.T(), string(dt), testContent, info) + }, + } + }, + }, + { + Description: "destination 2", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", "-o", data.TempDir(), data.Get("buildCtx")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + testFilePath := filepath.Join(data.TempDir(), testFileName) + _, err := os.Stat(testFilePath) + assert.NilError(helpers.T(), err, info) + dt, err := os.ReadFile(testFilePath) + assert.NilError(helpers.T(), err, info) + assert.Equal(helpers.T(), string(dt), testContent, info) + }, + } + }, + }, + }, } - testFilePath := filepath.Join(outputDir, testFileName) - base.Cmd("build", "-o", fmt.Sprintf("type=local,dest=%s", outputDir), buildCtx).AssertOK() - if _, err := os.Stat(testFilePath); err != nil { - t.Fatal(err) - } - data, err := os.ReadFile(testFilePath) - assert.NilError(t, err) - assert.Equal(t, string(data), testContent) - - aliasOutputDir := t.TempDir() - testAliasFilePath := filepath.Join(aliasOutputDir, testFileName) - base.Cmd("build", "-o", aliasOutputDir, buildCtx).AssertOK() - if _, err := os.Stat(testAliasFilePath); err != nil { - t.Fatal(err) - } - data, err = os.ReadFile(testAliasFilePath) - assert.NilError(t, err) - assert.Equal(t, string(data), testContent) + testCase.Run(t) } func TestBuildWithBuildArg(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 ARG TEST_STRING=1 ENV TEST_STRING=$TEST_STRING CMD echo $TEST_STRING `, testutil.CommonImage) - - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - base.Cmd("build", buildCtx, "-t", imageName).AssertOK() - base.Cmd("run", "--rm", imageName).AssertOutExactly("1\n") - - validCases := []struct { - name string - arg string - envValue string - envSet bool - expected string - }{ - {"ArgValueOverridesDefault", "TEST_STRING=2", "", false, "2\n"}, - {"EmptyArgValueOverridesDefault", "TEST_STRING=", "", false, "\n"}, - {"UnsetArgKeyPreservesDefault", "TEST_STRING", "", false, "1\n"}, - {"EnvValueOverridesDefault", "TEST_STRING", "3", true, "3\n"}, - {"EmptyEnvValueOverridesDefault", "TEST_STRING", "", true, "\n"}, + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + data.Set("buildCtx", buildCtx) + }, + SubTests: []*test.Case{ + { + Description: "No args", + 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("1\n")), + }, + { + Description: "ArgValueOverridesDefault", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "--build-arg", "TEST_STRING=2", "-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("2\n")), + }, + { + Description: "EmptyArgValueOverridesDefault", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "--build-arg", "TEST_STRING=", "-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("\n")), + }, + { + Description: "UnsetArgKeyPreservesDefault", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "--build-arg", "TEST_STRING", "-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("1\n")), + }, + { + Description: "EnvValueOverridesDefault", + Env: map[string]string{ + "TEST_STRING": "3", + }, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "--build-arg", "TEST_STRING", "-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("3\n")), + }, + { + Description: "EmptyEnvValueOverridesDefault", + Env: map[string]string{ + "TEST_STRING": "", + }, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "--build-arg", "TEST_STRING", "-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("\n")), + }, + }, } - for _, tc := range validCases { - t.Run(tc.name, func(t *testing.T) { - subBase := testutil.NewBase(t) - if tc.envSet { - subBase.Env = append(base.Env, "TEST_STRING="+tc.envValue) - } - - subBase.Cmd("build", buildCtx, "-t", imageName, "--build-arg", tc.arg).AssertOK() - subBase.Cmd("run", "--rm", imageName).AssertOutExactly(tc.expected) - }) - } + testCase.Run(t) } func TestBuildWithIIDFile(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-string"] `, testutil.CommonImage) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", buildCtx, "--iidfile", filepath.Join(data.TempDir(), "id.txt"), "-t", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + imageID, err := os.ReadFile(filepath.Join(data.TempDir(), "id.txt")) + assert.NilError(helpers.T(), err) + return helpers.Command("run", "--rm", string(imageID)) + }, + + Expected: test.Expects(0, nil, test.Equals("nerdctl-build-test-string\n")), + } - buildCtx := helpers.CreateBuildContext(t, dockerfile) - fileName := filepath.Join(t.TempDir(), "id.txt") - - base.Cmd("build", "-t", imageName, buildCtx, "--iidfile", fileName).AssertOK() - base.Cmd("build", buildCtx, "-t", imageName, "--iidfile", fileName).AssertOK() - defer os.Remove(fileName) - - imageID, err := os.ReadFile(fileName) - assert.NilError(t, err) - - base.Cmd("run", "--rm", string(imageID)).AssertOutExactly("nerdctl-build-test-string\n") + testCase.Run(t) } func TestBuildWithLabels(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - - 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 LABEL name=nerdctl-build-test-label `, testutil.CommonImage) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", buildCtx, "--label", "label=test", "-t", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("inspect", data.Identifier(), "--format", "{{json .Config.Labels }}") + }, + + Expected: test.Expects(0, nil, test.Equals("{\"label\":\"test\",\"name\":\"nerdctl-build-test-label\"}\n")), + } - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - base.Cmd("build", "-t", imageName, buildCtx, "--label", "label=test").AssertOK() - defer base.Cmd("rmi", imageName).Run() - - base.Cmd("inspect", imageName, "--format", "{{json .Config.Labels }}").AssertOutExactly("{\"label\":\"test\",\"name\":\"nerdctl-build-test-label\"}\n") + testCase.Run(t) } func TestBuildMultipleTags(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - img := testutil.Identifier(t) - imgWithNoTag, imgWithCustomTag := fmt.Sprintf("%s%d", img, 2), fmt.Sprintf("%s%d:hello", img, 3) - defer base.Cmd("rmi", img).AssertOK() - defer base.Cmd("rmi", imgWithNoTag).AssertOK() - defer base.Cmd("rmi", imgWithCustomTag).AssertOK() - - buildOutputs := []string{"nerdctl-build-test-string", "nerdctl-build-test-string-rebuild"} - for _, output := range buildOutputs { - dockerfile := fmt.Sprintf(`FROM %s - CMD ["echo", "%s"] - `, testutil.CommonImage, output) - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - base.Cmd("build", "-t", img, buildCtx).AssertOK() - base.Cmd("build", buildCtx, "-t", img, "-t", imgWithNoTag, "-t", imgWithCustomTag).AssertOK() - base.Cmd("run", "--rm", img).AssertOutExactly(output + "\n") - base.Cmd("run", "--rm", imgWithNoTag).AssertOutExactly(output + "\n") - base.Cmd("run", "--rm", imgWithCustomTag).AssertOutExactly(output + "\n") + nerdtest.Setup() + + testCase := &test.Case{ + Require: nerdtest.Build, + Data: test.WithData("i1", "image"). + Set("i2", "image2"). + Set("i3", "image3:hello"), + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Get("i1")) + helpers.Anyhow("rmi", "-f", data.Get("i2")) + helpers.Anyhow("rmi", "-f", data.Get("i3")) + }, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-build-test-string"] + `, testutil.CommonImage) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", buildCtx, "-t", data.Get("i1"), "-t", data.Get("i2"), "-t", data.Get("i3")) + }, + SubTests: []*test.Case{ + { + Description: "i1", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Get("i1")) + }, + + Expected: test.Expects(0, nil, test.Equals("nerdctl-build-test-string\n")), + }, + { + Description: "i2", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Get("i2")) + }, + + Expected: test.Expects(0, nil, test.Equals("nerdctl-build-test-string\n")), + }, + { + Description: "i3", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Get("i3")) + }, + + Expected: test.Expects(0, nil, test.Equals("nerdctl-build-test-string\n")), + }, + }, } + + testCase.Run(t) } func TestBuildWithContainerfile(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() - - containerfile := 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()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s CMD ["echo", "nerdctl-build-test-string"] `, testutil.CommonImage) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Containerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", buildCtx, "-t", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier()) + }, + Expected: test.Expects(0, nil, test.Equals("nerdctl-build-test-string\n")), + } - buildCtx := t.TempDir() - - var err = os.WriteFile(filepath.Join(buildCtx, "Containerfile"), []byte(containerfile), 0644) - assert.NilError(t, err) - base.Cmd("build", "-t", imageName, buildCtx).AssertOK() - base.Cmd("run", "--rm", imageName).AssertOutExactly("nerdctl-build-test-string\n") + testCase.Run(t) } func TestBuildWithDockerFileAndContainerfile(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", "dockerfile"] `, testutil.CommonImage) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + dockerfile = fmt.Sprintf(`FROM %s +CMD ["echo", "containerfile"] + `, testutil.CommonImage) + err = os.WriteFile(filepath.Join(buildCtx, "Containerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", buildCtx, "-t", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier()) + }, + Expected: test.Expects(0, nil, test.Equals("dockerfile\n")), + } - containerfile := fmt.Sprintf(`FROM %s - CMD ["echo", "containerfile"] - `, testutil.CommonImage) - - tmpDir := t.TempDir() - - var err = os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644) - assert.NilError(t, err) - - err = os.WriteFile(filepath.Join(tmpDir, "Containerfile"), []byte(containerfile), 0644) - assert.NilError(t, err) - - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - base.Cmd("build", "-t", imageName, buildCtx).AssertOK() - base.Cmd("run", "--rm", imageName).AssertOutExactly("dockerfile\n") + testCase.Run(t) } func TestBuildNoTag(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - base.Cmd("image", "prune", "--force", "--all").AssertOK() - - dockerfile := fmt.Sprintf(`FROM %s -CMD ["echo", "nerdctl-build-notag-string"] + nerdtest.Setup() + + // FIXME: this test should be rewritten and instead get the image id from the build, then query the image explicitly - instead of pruning / noparallel + testCase := &test.Case{ + NoParallel: true, + Require: nerdtest.Build, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("image", "prune", "--force", "--all") + }, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-build-test-string"] `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", buildCtx) + }, + Command: test.Command("images"), + Expected: test.Expects(0, nil, test.Contains("")), + } - base.Cmd("build", buildCtx).AssertOK() - base.Cmd("images").AssertOutContains("") - base.Cmd("image", "prune", "--force", "--all").AssertOK() + testCase.Run(t) } func TestBuildContextDockerImageAlias(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - base.Cmd("image", "prune", "--force", "--all").AssertOK() - - dockerfile := `FROM myorg/myapp + 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 := `FROM myorg/myapp CMD ["echo", "nerdctl-build-myorg/myapp"]` - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + data.Set("buildCtx", buildCtx) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", "-t", data.Identifier(), data.Get("buildCtx"), fmt.Sprintf("--build-context=myorg/myapp=docker-image://%s", testutil.CommonImage)) + }, + Expected: test.Expects(0, nil, nil), + } - base.Cmd("build", buildCtx, fmt.Sprintf("--build-context=myorg/myapp=docker-image://%s", testutil.CommonImage)).AssertOK() - base.Cmd("images").AssertOutContains("") - base.Cmd("image", "prune", "--force", "--all").AssertOK() + testCase.Run(t) } func TestBuildContextWithCopyFromDir(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - base.Cmd("image", "prune", "--force", "--all").AssertOK() + nerdtest.Setup() content := "hello_from_dir_2" filename := "hello.txt" - dir2 := t.TempDir() - filePath := filepath.Join(dir2, filename) - err := os.WriteFile(filePath, []byte(content), 0644) - assert.NilError(t, err) - - dockerfile := fmt.Sprintf(`FROM %s + 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()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + dir2 := helpers.T().TempDir() + filePath := filepath.Join(dir2, filename) + err := os.WriteFile(filePath, []byte(content), 0o600) + assert.NilError(helpers.T(), err) + dockerfile := fmt.Sprintf(`FROM %s COPY --from=dir2 /%s /hello_from_dir2.txt RUN ["cat", "/hello_from_dir2.txt"]`, testutil.CommonImage, filename) + buildCtx := data.TempDir() + err = os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + data.Set("buildCtx", buildCtx) + data.Set("dir2", dir2) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", "-t", data.Identifier(), data.Get("buildCtx"), fmt.Sprintf("--build-context=dir2=%s", data.Get("dir2"))) + }, + Expected: test.Expects(0, nil, nil), + } - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - base.Cmd("build", buildCtx, fmt.Sprintf("--build-context=dir2=%s", dir2)).AssertOK() - base.Cmd("images").AssertOutContains("") - base.Cmd("image", "prune", "--force", "--all").AssertOK() + testCase.Run(t) } // TestBuildSourceDateEpoch tests that $SOURCE_DATE_EPOCH is propagated from the client env // https://github.com/docker/buildx/pull/1482 func TestBuildSourceDateEpoch(t *testing.T) { - testutil.RequiresBuild(t) - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - defer base.Cmd("rmi", imageName).AssertOK() - - dockerfile := 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()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s ARG SOURCE_DATE_EPOCH RUN echo $SOURCE_DATE_EPOCH >/source-date-epoch CMD ["cat", "/source-date-epoch"] `, testutil.CommonImage) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + data.Set("buildCtx", buildCtx) + }, + SubTests: []*test.Case{ + { + Description: "1111111111", + Env: map[string]string{ + "SOURCE_DATE_EPOCH": "1111111111", + }, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "-t", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier()) + }, + Expected: test.Expects(0, nil, test.Equals("1111111111\n")), + }, + { + Description: "2222222222", + Env: map[string]string{ + "SOURCE_DATE_EPOCH": "1111111111", + }, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("build", data.Get("buildCtx"), "--build-arg", "SOURCE_DATE_EPOCH=2222222222", "-t", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Identifier()) + }, + Expected: test.Expects(0, nil, test.Equals("2222222222\n")), + }, + }, + } - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - const sourceDateEpochEnvStr = "1111111111" - base.Env = append(base.Env, "SOURCE_DATE_EPOCH="+sourceDateEpochEnvStr) - base.Cmd("build", "-t", imageName, buildCtx).AssertOK() - base.Cmd("run", "--rm", imageName).AssertOutExactly(sourceDateEpochEnvStr + "\n") - - const sourceDateEpochArgStr = "2222222222" - base.Cmd("build", "-t", imageName, "--build-arg", "SOURCE_DATE_EPOCH="+sourceDateEpochArgStr, buildCtx).AssertOK() - base.Cmd("run", "--rm", imageName).AssertOutExactly(sourceDateEpochArgStr + "\n") + testCase.Run(t) } func TestBuildNetwork(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - - dockerfile := 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()) + }, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s RUN apk add --no-cache curl RUN curl -I http://google.com `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - validCases := []struct { - name string - network string - exitCode int - }{ - // When network=none, can't connect to internet, therefore cannot download packages in the dockerfile - // Order is important here, test fails for `-test.target=docker` in CI - {"test_with_no_network", "none", 1}, - {"test_with_empty_network", "", 0}, - {"test_with_default_network", "default", 0}, - } - - for _, tc := range validCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - // --no-cache is intentional here for `-test.target=docker` - base.Cmd("build", buildCtx, "-t", tc.name, "--no-cache", "--network", tc.network).AssertExitCode(tc.exitCode) - if tc.exitCode != 1 { - defer base.Cmd("rmi", tc.name).AssertOK() - } - }) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + data.Set("buildCtx", buildCtx) + }, + SubTests: []*test.Case{ + { + Description: "none", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", data.Get("buildCtx"), "-t", data.Identifier(), "--no-cache", "--network", "none") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Expected: test.Expects(1, nil, nil), + }, + { + Description: "empty", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", data.Get("buildCtx"), "-t", data.Identifier(), "--no-cache", "--network", "") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Expected: test.Expects(0, nil, nil), + }, + { + Description: "default", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", data.Get("buildCtx"), "-t", data.Identifier(), "--no-cache", "--network", "default") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", data.Identifier()) + }, + Expected: test.Expects(0, nil, nil), + }, + }, } -} -func TestBuildNetworkShellCompletion(t *testing.T) { - testutil.DockerIncompatible(t) - base := testutil.NewBase(t) - const gsc = "__complete" - // Tests with build network - networkName := "default" - base.Cmd(gsc, "build", "--network", "").AssertOutContains(networkName) + testCase.Run(t) } +/* func buildWithNamedBuilder(base *testutil.Base, builderName string, args ...string) *testutil.Cmd { buildArgs := []string{"build"} if testutil.GetTarget() == testutil.Docker { @@ -513,10 +876,12 @@ func TestBuildAttestation(t *testing.T) { } dockerfile := "FROM " + testutil.NginxAlpineImage - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) // Test sbom - outputSBOMDir := t.TempDir() + outputSBOMDir := data.TempDir() buildWithNamedBuilder(base, builderName, "--sbom=true", "-o", fmt.Sprintf("type=local,dest=%s", outputSBOMDir), buildCtx).AssertOK() const testSBOMFileName = "sbom.spdx.json" testSBOMFilePath := filepath.Join(outputSBOMDir, testSBOMFileName) @@ -525,7 +890,7 @@ func TestBuildAttestation(t *testing.T) { } // Test provenance - outputProvenanceDir := t.TempDir() + outputProvenanceDir := data.TempDir() buildWithNamedBuilder(base, builderName, "--provenance=mode=min", "-o", fmt.Sprintf("type=local,dest=%s", outputProvenanceDir), buildCtx).AssertOK() const testProvenanceFileName = "provenance.json" testProvenanceFilePath := filepath.Join(outputProvenanceDir, testProvenanceFileName) @@ -534,7 +899,7 @@ func TestBuildAttestation(t *testing.T) { } // Test attestation - outputAttestationDir := t.TempDir() + outputAttestationDir := data.TempDir() buildWithNamedBuilder(base, builderName, "--attest=type=provenance,mode=min", "--attest=type=sbom", "-o", fmt.Sprintf("type=local,dest=%s", outputAttestationDir), buildCtx).AssertOK() testSBOMFilePath = filepath.Join(outputAttestationDir, testSBOMFileName) testProvenanceFilePath = filepath.Join(outputAttestationDir, testProvenanceFileName) @@ -545,3 +910,6 @@ func TestBuildAttestation(t *testing.T) { t.Fatal(err) } } + + +*/ diff --git a/cmd/nerdctl/builder/builder_builder_test.go b/cmd/nerdctl/builder/builder_builder_test.go new file mode 100644 index 00000000000..9be5e185faa --- /dev/null +++ b/cmd/nerdctl/builder/builder_builder_test.go @@ -0,0 +1,152 @@ +/* + 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 builder + +import ( + "bytes" + "errors" + "fmt" + "os" + "path/filepath" + "testing" + + "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 TestBuilder(t *testing.T) { + nerdtest.Setup() + + testCase := &test.Case{ + NoParallel: true, + Require: test.Require( + nerdtest.Build, + test.Not(test.Windows), + ), + SubTests: []*test.Case{ + { + Description: "PruneForce", + NoParallel: true, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-test-builder-prune"]`, testutil.CommonImage) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", buildCtx) + }, + Command: test.Command("builder", "prune", "--force"), + Expected: test.Expects(0, nil, nil), + }, + { + Description: "PruneForceAll", + NoParallel: true, + Setup: func(data test.Data, helpers test.Helpers) { + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-test-builder-prune"]`, testutil.CommonImage) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + helpers.Ensure("build", buildCtx) + }, + Command: test.Command("builder", "prune", "--force", "--all"), + Expected: test.Expects(0, nil, nil), + }, + { + Description: "Debug", + NoParallel: true, + 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 := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + cmd := helpers.Command("builder", "debug", buildCtx) + cmd.WithStdin(bytes.NewReader([]byte("c\n"))) + return cmd + }, + Expected: test.Expects(0, nil, nil), + }, + { + Description: "WithPull", + Setup: func(data test.Data, helpers test.Helpers) { + // FIXME: this test should be rewritten to dynamically retrieve the ids, and use images + // available on all platforms + oldImage := testutil.BusyboxImage + oldImageSha := "141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47" + newImage := testutil.AlpineImage + newImageSha := "ec14c7992a97fc11425907e908340c6c3d6ff602f5f13d899e6b7027c9b4133a" + + helpers.Ensure("pull", "--quiet", oldImage) + helpers.Ensure("tag", oldImage, newImage) + + dockerfile := fmt.Sprintf(`FROM %s`, newImage) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) + + data.Set("buildCtx", buildCtx) + data.Set("oldImageSha", oldImageSha) + data.Set("newImageSha", newImageSha) + }, + SubTests: []*test.Case{ + { + Description: "pull false", + NoParallel: true, + 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{ + Errors: []error{errors.New(data.Get("oldImageSha"))}, + } + }, + }, + { + Description: "pull true", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", data.Get("buildCtx"), "--pull=true") + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Errors: []error{errors.New(data.Get("newImageSha"))}, + } + }, + }, + { + Description: "no pull", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("build", data.Get("buildCtx")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Errors: []error{errors.New(data.Get("newImageSha"))}, + } + }, + }, + }, + }, + }, + } + + testCase.Run(t) +} diff --git a/cmd/nerdctl/builder/builder_linux_test.go b/cmd/nerdctl/builder/builder_linux_test.go deleted file mode 100644 index 862320142f9..00000000000 --- a/cmd/nerdctl/builder/builder_linux_test.go +++ /dev/null @@ -1,168 +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 builder - -import ( - "bytes" - "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" - "github.com/containerd/nerdctl/v2/pkg/testutil" -) - -func TestBuilderPrune(t *testing.T) { - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - - base := testutil.NewBase(t) - - 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] -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", - }, - } - - 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) - } - }) - } -} - -func useBuildkitConfig(t *testing.T, config string) (cleanup func()) { - buildkitConfigPath := "/etc/buildkit/buildkitd.toml" - - currConfig, err := exec.Command("cat", buildkitConfigPath).Output() - assert.NilError(t, err) - - os.WriteFile(buildkitConfigPath, []byte(config), 0644) - _, err = exec.Command("systemctl", "restart", "buildkit").Output() - assert.NilError(t, err) - - return func() { - assert.NilError(t, os.WriteFile(buildkitConfigPath, currConfig, 0644)) - _, err = exec.Command("systemctl", "restart", "buildkit").Output() - assert.NilError(t, err) - } -} diff --git a/cmd/nerdctl/completion/completion_linux_test.go b/cmd/nerdctl/completion/completion_linux_test.go deleted file mode 100644 index 8fee86e9ad5..00000000000 --- a/cmd/nerdctl/completion/completion_linux_test.go +++ /dev/null @@ -1,201 +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 completion - -import ( - "testing" - - "github.com/containerd/nerdctl/v2/pkg/testutil" - "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" - "github.com/containerd/nerdctl/v2/pkg/testutil/test" -) - -func TestCompletion(t *testing.T) { - nerdtest.Setup() - - testCase := &test.Case{ - Require: test.Not(nerdtest.Docker), - Setup: func(data test.Data, helpers test.Helpers) { - helpers.Ensure("pull", "--quiet", testutil.AlpineImage) - helpers.Ensure("network", "create", data.Identifier()) - helpers.Ensure("volume", "create", data.Identifier()) - data.Set("identifier", data.Identifier()) - }, - Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("network", "rm", data.Identifier()) - helpers.Anyhow("volume", "rm", data.Identifier()) - }, - SubTests: []*test.Case{ - { - Description: "--cgroup-manager", - Command: test.Command("__complete", "--cgroup-manager", ""), - Expected: test.Expects(0, nil, test.Contains("cgroupfs\n")), - }, - { - Description: "--snapshotter", - Command: test.Command("__complete", "--snapshotter", ""), - Expected: test.Expects(0, nil, test.Contains("native\n")), - }, - { - Description: "empty", - Command: test.Command("__complete", ""), - Expected: test.Expects(0, nil, test.Contains("run\t")), - }, - { - Description: "run -", - Command: test.Command("__complete", "run", "-"), - Expected: test.Expects(0, nil, test.Contains("--network\t")), - }, - { - Description: "run --n", - Command: test.Command("__complete", "run", "--n"), - Expected: test.Expects(0, nil, test.Contains("--network\t")), - }, - { - Description: "run --ne", - Command: test.Command("__complete", "run", "--ne"), - Expected: test.Expects(0, nil, test.Contains("--network\t")), - }, - { - Description: "run --net", - Command: test.Command("__complete", "run", "--net", ""), - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - Output: test.All( - test.Contains("host\n"), - test.Contains(data.Get("identifier")+"\n"), - ), - } - }, - }, - { - Description: "run -it --net", - Command: test.Command("__complete", "run", "-it", "--net", ""), - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - Output: test.All( - test.Contains("host\n"), - test.Contains(data.Get("identifier")+"\n"), - ), - } - }, - }, - { - Description: "run -ti --rm --net", - Command: test.Command("__complete", "run", "-it", "--rm", "--net", ""), - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - Output: test.All( - test.Contains("host\n"), - test.Contains(data.Get("identifier")+"\n"), - ), - } - }, - }, - { - Description: "run --restart", - Command: test.Command("__complete", "run", "--restart", ""), - Expected: test.Expects(0, nil, test.Contains("always\n")), - }, - { - Description: "network --rm", - Command: test.Command("__complete", "network", "rm", ""), - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - Output: test.All( - test.DoesNotContain("host\n"), - test.Contains(data.Get("identifier")+"\n"), - ), - } - }, - }, - { - Description: "run --cap-add", - Command: test.Command("__complete", "run", "--cap-add", ""), - Expected: test.Expects(0, nil, test.All( - test.Contains("sys_admin\n"), - test.DoesNotContain("CAP_SYS_ADMIN\n"), - )), - }, - { - Description: "volume inspect", - Command: test.Command("__complete", "volume", "inspect", ""), - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - Output: test.Contains(data.Get("identifier") + "\n"), - } - }, - }, - { - Description: "volume rm", - Command: test.Command("__complete", "volume", "rm", ""), - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - Output: test.Contains(data.Get("identifier") + "\n"), - } - }, - }, - { - Description: "no namespace --cgroup-manager", - Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - return helpers.Custom("nerdctl", "__complete", "--cgroup-manager", "") - }, - Expected: test.Expects(0, nil, test.Contains("cgroupfs\n")), - }, - { - Description: "no namespace empty", - Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - return helpers.Custom("nerdctl", "__complete", "") - }, - Expected: test.Expects(0, nil, test.Contains("run\t")), - }, - { - Description: "namespace space empty", - Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - // mind {"--namespace=nerdctl-test"} vs {"--namespace", "nerdctl-test"} - return helpers.Custom("nerdctl", "__complete", "--namespace", string(helpers.Read(nerdtest.Namespace)), "") - }, - Expected: test.Expects(0, nil, test.Contains("run\t")), - }, - { - Description: "run -i", - Command: test.Command("__complete", "run", "-i", ""), - Expected: test.Expects(0, nil, test.Contains(testutil.AlpineImage)), - }, - { - Description: "run -it", - Command: test.Command("__complete", "run", "-it", ""), - Expected: test.Expects(0, nil, test.Contains(testutil.AlpineImage)), - }, - { - Description: "run -it --rm", - Command: test.Command("__complete", "run", "-it", "--rm", ""), - Expected: test.Expects(0, nil, test.Contains(testutil.AlpineImage)), - }, - { - Description: "namespace run -i", - Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - // mind {"--namespace=nerdctl-test"} vs {"--namespace", "nerdctl-test"} - return helpers.Custom("nerdctl", "__complete", "--namespace", string(helpers.Read(nerdtest.Namespace)), "run", "-i", "") - }, - Expected: test.Expects(0, nil, test.Contains(testutil.AlpineImage+"\n")), - }, - }, - } - - testCase.Run(t) -} diff --git a/cmd/nerdctl/completion/completion_test.go b/cmd/nerdctl/completion/completion_test.go index f37df496baf..6eb7612f8bc 100644 --- a/cmd/nerdctl/completion/completion_test.go +++ b/cmd/nerdctl/completion/completion_test.go @@ -20,8 +20,194 @@ 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 TestMain(m *testing.M) { testutil.M(m) } + +func TestCompletion(t *testing.T) { + nerdtest.Setup() + + testCase := &test.Case{ + Require: test.Not(nerdtest.Docker), + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("pull", "--quiet", testutil.CommonImage) + helpers.Ensure("network", "create", data.Identifier()) + helpers.Ensure("volume", "create", data.Identifier()) + data.Set("identifier", data.Identifier()) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "rm", data.Identifier()) + helpers.Anyhow("volume", "rm", data.Identifier()) + }, + SubTests: []*test.Case{ + { + Description: "--cgroup-manager", + Require: test.Not(test.Windows), + Command: test.Command("__complete", "--cgroup-manager", ""), + Expected: test.Expects(0, nil, test.Contains("cgroupfs\n")), + }, + { + Description: "--snapshotter", + Command: test.Command("__complete", "--snapshotter", ""), + Expected: test.Expects(0, nil, test.Contains("native\n")), + }, + { + Description: "empty", + Command: test.Command("__complete", ""), + Expected: test.Expects(0, nil, test.Contains("run\t")), + }, + { + Description: "build --network", + Command: test.Command("__complete", "build", "--network", ""), + Expected: test.Expects(0, nil, test.Contains("default\n")), + }, + { + Description: "run -", + Command: test.Command("__complete", "run", "-"), + Expected: test.Expects(0, nil, test.Contains("--network\t")), + }, + { + Description: "run --n", + Command: test.Command("__complete", "run", "--n"), + Expected: test.Expects(0, nil, test.Contains("--network\t")), + }, + { + Description: "run --ne", + Command: test.Command("__complete", "run", "--ne"), + Expected: test.Expects(0, nil, test.Contains("--network\t")), + }, + { + Description: "run --net", + Command: test.Command("__complete", "run", "--net", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.All( + test.Contains("host\n"), + test.Contains(data.Get("identifier")+"\n"), + ), + } + }, + }, + { + Description: "run -it --net", + Command: test.Command("__complete", "run", "-it", "--net", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.All( + test.Contains("host\n"), + test.Contains(data.Get("identifier")+"\n"), + ), + } + }, + }, + { + Description: "run -ti --rm --net", + Command: test.Command("__complete", "run", "-it", "--rm", "--net", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.All( + test.Contains("host\n"), + test.Contains(data.Get("identifier")+"\n"), + ), + } + }, + }, + { + Description: "run --restart", + Command: test.Command("__complete", "run", "--restart", ""), + Expected: test.Expects(0, nil, test.Contains("always\n")), + }, + { + Description: "network --rm", + Command: test.Command("__complete", "network", "rm", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.All( + test.DoesNotContain("host\n"), + test.Contains(data.Get("identifier")+"\n"), + ), + } + }, + }, + { + Description: "run --cap-add", + Require: test.Not(test.Windows), + Command: test.Command("__complete", "run", "--cap-add", ""), + Expected: test.Expects(0, nil, test.All( + test.Contains("sys_admin\n"), + test.DoesNotContain("CAP_SYS_ADMIN\n"), + )), + }, + { + Description: "volume inspect", + Command: test.Command("__complete", "volume", "inspect", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("identifier") + "\n"), + } + }, + }, + { + Description: "volume rm", + Command: test.Command("__complete", "volume", "rm", ""), + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: test.Contains(data.Get("identifier") + "\n"), + } + }, + }, + { + Description: "no namespace --cgroup-manager", + Require: test.Not(test.Windows), + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Custom("nerdctl", "__complete", "--cgroup-manager", "") + }, + Expected: test.Expects(0, nil, test.Contains("cgroupfs\n")), + }, + { + Description: "no namespace empty", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Custom("nerdctl", "__complete", "") + }, + Expected: test.Expects(0, nil, test.Contains("run\t")), + }, + { + Description: "namespace space empty", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + // mind {"--namespace=nerdctl-test"} vs {"--namespace", "nerdctl-test"} + return helpers.Custom("nerdctl", "__complete", "--namespace", string(helpers.Read(nerdtest.Namespace)), "") + }, + Expected: test.Expects(0, nil, test.Contains("run\t")), + }, + { + Description: "run -i", + Command: test.Command("__complete", "run", "-i", ""), + Expected: test.Expects(0, nil, test.Contains(testutil.CommonImage)), + }, + { + Description: "run -it", + Command: test.Command("__complete", "run", "-it", ""), + Expected: test.Expects(0, nil, test.Contains(testutil.CommonImage)), + }, + { + Description: "run -it --rm", + Command: test.Command("__complete", "run", "-it", "--rm", ""), + Expected: test.Expects(0, nil, test.Contains(testutil.CommonImage)), + }, + { + Description: "namespace run -i", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + // mind {"--namespace=nerdctl-test"} vs {"--namespace", "nerdctl-test"} + return helpers.Custom("nerdctl", "__complete", "--namespace", string(helpers.Read(nerdtest.Namespace)), "run", "-i", "") + }, + Expected: test.Expects(0, nil, test.Contains(testutil.CommonImage+"\n")), + }, + }, + } + + testCase.Run(t) +} diff --git a/cmd/nerdctl/container/container_commit_test.go b/cmd/nerdctl/container/container_commit_test.go index 9382a782d7a..b1cfc150b20 100644 --- a/cmd/nerdctl/container/container_commit_test.go +++ b/cmd/nerdctl/container/container_commit_test.go @@ -27,6 +27,10 @@ import ( func TestCommit(t *testing.T) { testCase := nerdtest.Setup() + if nerdtest.IsDocker() { + testCase.Require = nerdtest.IsFlaky("no issue linked") + } + testCase.SubTests = []*test.Case{ { Description: "with pause", diff --git a/pkg/testutil/nerdtest/requirements.go b/pkg/testutil/nerdtest/requirements.go index 4bfcb57d8bf..3c365095b03 100644 --- a/pkg/testutil/nerdtest/requirements.go +++ b/pkg/testutil/nerdtest/requirements.go @@ -276,7 +276,12 @@ var Build = &test.Requirement{ return ret, mess }, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("builder", "prune", "--all", "--force") + // Previously, every build test was sequential, and was purging the build cache. + // Running this in parallel of any other test depending on build will trash it. + // The only way to parallelize any test involving build is indeed to disable this. + // The price to pay is that we might get cache from another test. + // This can be avoided individually by passing --no-cache if and when necessary + // helpers.Anyhow("builder", "prune", "--all", "--force") }, } diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index fe3fb0004ee..525225fb996 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -839,21 +839,3 @@ func RegisterBuildCacheCleanup(t *testing.T) { NewBase(t).Cmd("builder", "prune", "--all", "--force").Run() }) } - -// SetupDockerContainerBuilder creates a Docker builder using the docker-container driver -// and adds cleanup steps to test cleanup. The builder name is returned as output. -// -// If not docker, this function returns an empty string as the builder name. -func SetupDockerContainerBuilder(t *testing.T) string { - var name string - if IsDocker() { - name = fmt.Sprintf("%s-container", Identifier(t)) - base := NewBase(t) - base.Cmd("buildx", "create", "--name", name, "--driver=docker-container").AssertOK() - t.Cleanup(func() { - base.Cmd("buildx", "stop", name).AssertOK() - base.Cmd("buildx", "rm", "--force", name).AssertOK() - }) - } - return name -} diff --git a/pkg/testutil/testutil_freebsd.go b/pkg/testutil/testutil_freebsd.go index 5c0fb9ba293..ae85176fda7 100644 --- a/pkg/testutil/testutil_freebsd.go +++ b/pkg/testutil/testutil_freebsd.go @@ -30,6 +30,7 @@ const ( ) var ( + BusyboxImage = "ghcr.io/containerd/busybox:1.28" AlpineImage = mirrorOf("alpine:3.13") NginxAlpineImage = mirrorOf("nginx:1.19-alpine") GolangImage = mirrorOf("golang:1.18") diff --git a/pkg/testutil/testutil_windows.go b/pkg/testutil/testutil_windows.go index edf86ba9143..69934ae1a51 100644 --- a/pkg/testutil/testutil_windows.go +++ b/pkg/testutil/testutil_windows.go @@ -43,7 +43,9 @@ const ( NginxAlpineImage = "registry.k8s.io/e2e-test-images/nginx:1.14-2" NginxAlpineIndexHTMLSnippet = "Welcome to nginx!" - GolangImage = "fixme-test-using-this-image-is-disabled-on-windows" + GolangImage = "fixme-test-using-this-image-is-disabled-on-windows" + BusyboxImage = "fixme-test-using-this-image-is-disabled-on-windows" + AlpineImage = "fixme-test-using-this-image-is-disabled-on-windows" // This error string is expected when attempting to connect to a TCP socket // for a service which actively refuses the connection.