From b2493b3b4bb6b3a90b1f53efaeb1b8232921bbcc 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 | 484 ++++++++++++------ cmd/nerdctl/builder/builder_linux_test.go | 250 ++++----- pkg/testutil/testutil.go | 18 - 5 files changed, 573 insertions(+), 371 deletions(-) delete mode 100644 cmd/nerdctl/builder/builder_build_linux_test.go create mode 100644 cmd/nerdctl/builder/builder_build_oci_layout_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..43ac15a2a37 --- /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.Ensure("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..a6c6533b51c 100644 --- a/cmd/nerdctl/builder/builder_build_test.go +++ b/cmd/nerdctl/builder/builder_build_test.go @@ -17,194 +17,357 @@ 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) +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()) - buildCtx := helpers.CreateBuildContext(t, dockerfile) - - base.Cmd("build", "-t", imageName, buildCtx).AssertOK() - base.Cmd("build", buildCtx, "-t", imageName).AssertOK() - - 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) - } - 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) + 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) + }, + } + }, + }, + }, } - 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) @@ -218,7 +381,9 @@ ENV TEST_STRING=$TEST_STRING CMD echo $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) base.Cmd("build", buildCtx, "-t", imageName).AssertOK() base.Cmd("run", "--rm", imageName).AssertOutExactly("1\n") @@ -261,7 +426,9 @@ func TestBuildWithIIDFile(t *testing.T) { 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) fileName := filepath.Join(t.TempDir(), "id.txt") base.Cmd("build", "-t", imageName, buildCtx, "--iidfile", fileName).AssertOK() @@ -284,7 +451,9 @@ func TestBuildWithLabels(t *testing.T) { LABEL name=nerdctl-build-test-label `, 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) base.Cmd("build", "-t", imageName, buildCtx, "--label", "label=test").AssertOK() defer base.Cmd("rmi", imageName).Run() @@ -307,7 +476,9 @@ func TestBuildMultipleTags(t *testing.T) { dockerfile := fmt.Sprintf(`FROM %s CMD ["echo", "%s"] `, testutil.CommonImage, output) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) base.Cmd("build", "-t", img, buildCtx).AssertOK() base.Cmd("build", buildCtx, "-t", img, "-t", imgWithNoTag, "-t", imgWithCustomTag).AssertOK() @@ -354,13 +525,15 @@ CMD ["echo", "dockerfile"] tmpDir := t.TempDir() - var err = os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644) + var err = os.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0o600) assert.NilError(t, err) err = os.WriteFile(filepath.Join(tmpDir, "Containerfile"), []byte(containerfile), 0644) assert.NilError(t, err) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + buildCtx := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) base.Cmd("build", "-t", imageName, buildCtx).AssertOK() base.Cmd("run", "--rm", imageName).AssertOutExactly("dockerfile\n") @@ -375,7 +548,9 @@ func TestBuildNoTag(t *testing.T) { dockerfile := fmt.Sprintf(`FROM %s CMD ["echo", "nerdctl-build-notag-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) base.Cmd("build", buildCtx).AssertOK() base.Cmd("images").AssertOutContains("") @@ -390,7 +565,9 @@ func TestBuildContextDockerImageAlias(t *testing.T) { 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) base.Cmd("build", buildCtx, fmt.Sprintf("--build-context=myorg/myapp=docker-image://%s", testutil.CommonImage)).AssertOK() base.Cmd("images").AssertOutContains("") @@ -415,7 +592,9 @@ 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 := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) base.Cmd("build", buildCtx, fmt.Sprintf("--build-context=dir2=%s", dir2)).AssertOK() base.Cmd("images").AssertOutContains("") @@ -436,7 +615,9 @@ RUN echo $SOURCE_DATE_EPOCH >/source-date-epoch CMD ["cat", "/source-date-epoch"] `, 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) const sourceDateEpochEnvStr = "1111111111" base.Env = append(base.Env, "SOURCE_DATE_EPOCH="+sourceDateEpochEnvStr) @@ -457,7 +638,9 @@ 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 := data.TempDir() + err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) + assert.NilError(helpers.T(), err) validCases := []struct { name string @@ -513,7 +696,9 @@ 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() @@ -545,3 +730,6 @@ func TestBuildAttestation(t *testing.T) { t.Fatal(err) } } + + +*/ diff --git a/cmd/nerdctl/builder/builder_linux_test.go b/cmd/nerdctl/builder/builder_linux_test.go index 862320142f9..3aee8ee7064 100644 --- a/cmd/nerdctl/builder/builder_linux_test.go +++ b/cmd/nerdctl/builder/builder_linux_test.go @@ -18,151 +18,161 @@ 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" "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) - - base := testutil.NewBase(t) - - dockerfile := fmt.Sprintf(`FROM %s +func TestBuilder(t *testing.T) { + nerdtest.Setup() + + testCase := &test.Case{ + Require: test.Require( + nerdtest.Build, + test.Not(test.Windows), + ), + 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 := 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", + 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", + 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), + }, + { + // FIXME: there is no reason to believe the config change is necessary in any way. + Description: "WithPull", + Require: test.Require( + nerdtest.Rootful, + test.Not(nerdtest.Docker), + ), + 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", + helpers.T().Cleanup(useBuildkitConfig(helpers, buildkitConfig)) + oldImage := testutil.BusyboxImage + oldImageSha := "141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47" + newImage := testutil.AlpineImage + + 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) + }, + 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{ + ExitCode: 1, + 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{ + Output: test.All( + test.DoesNotContain(data.Get("oldImageSha")), + )} + }, + }, + { + 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{Output: test.All( + test.DoesNotContain(data.Get("oldImageSha")), + )} + }, + }, + }, + }, }, } - 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()) { +// FIXME: this is clearly not safe to use concurrently +func useBuildkitConfig(helpers test.Helpers, config string) (cleanup func()) { buildkitConfigPath := "/etc/buildkit/buildkitd.toml" + var currConfig string + helpers.Custom("cat", buildkitConfigPath).Run(&test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + currConfig = stdout + }, + }) - 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) + assert.NilError(helpers.T(), os.WriteFile(buildkitConfigPath, []byte(config), 0o644)) + helpers.Ensure("systemctl", "restart", "buildkit") return func() { - assert.NilError(t, os.WriteFile(buildkitConfigPath, currConfig, 0644)) - _, err = exec.Command("systemctl", "restart", "buildkit").Output() - assert.NilError(t, err) + assert.NilError(helpers.T(), os.WriteFile(buildkitConfigPath, []byte(currConfig), 0o644)) + helpers.Ensure("systemctl", "restart", "buildkit") } } 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 -}