diff --git a/cmd/nerdctl/compose/compose_build_linux_test.go b/cmd/nerdctl/compose/compose_build_linux_test.go index 80cc04d4c35..6ea0663240a 100644 --- a/cmd/nerdctl/compose/compose_build_linux_test.go +++ b/cmd/nerdctl/compose/compose_build_linux_test.go @@ -17,17 +17,31 @@ package compose import ( + "errors" "fmt" "testing" + "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) func TestComposeBuild(t *testing.T) { - const imageSvc0 = "composebuild_svc0" - const imageSvc1 = "composebuild_svc1" + dockerfile := "FROM " + testutil.AlpineImage + + testCase := nerdtest.Setup() + + testCase.Require = nerdtest.Build + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + // Make sure we shard the image name to something unique to the test to avoid conflicts with other tests + imageSvc0 := data.Identifier("svc0") + imageSvc1 := data.Identifier("svc1") - dockerComposeYAML := fmt.Sprintf(` + // We are not going to run them, so, ports conflicts should not matter here + dockerComposeYAML := fmt.Sprintf(` services: svc0: build: . @@ -43,31 +57,81 @@ services: - 8081:80 `, imageSvc0, imageSvc1) - dockerfile := fmt.Sprintf(`FROM %s`, testutil.AlpineImage) - - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - comp.WriteFile("Dockerfile", dockerfile) - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - - defer base.Cmd("rmi", imageSvc0).Run() - defer base.Cmd("rmi", imageSvc1).Run() - - // 1. build only 1 service without triggering the dependency service build - base.ComposeCmd("-f", comp.YAMLFullPath(), "build", "svc0").AssertOK() - base.Cmd("images").AssertOutContains(imageSvc0) - base.Cmd("images").AssertOutNotContains(imageSvc1) - // 2. build multiple services - base.ComposeCmd("-f", comp.YAMLFullPath(), "build", "svc0", "svc1").AssertOK() - base.Cmd("images").AssertOutContains(imageSvc0) - base.Cmd("images").AssertOutContains(imageSvc1) - // 3. build all if no args are given - base.ComposeCmd("-f", comp.YAMLFullPath(), "build").AssertOK() - // 4. fail if some services args not exist in compose.yml - base.ComposeCmd("-f", comp.YAMLFullPath(), "build", "svc0", "svc100").AssertFail() + data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Temp().Save(dockerfile, "Dockerfile") + + data.Labels().Set("composeYaml", data.Temp().Path("compose.yaml")) + data.Labels().Set("imageSvc0", imageSvc0) + data.Labels().Set("imageSvc1", imageSvc1) + } + + testCase.SubTests = []*test.Case{ + { + Description: "build svc0", + NoParallel: true, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("compose", "-f", data.Labels().Get("composeYaml"), "build", "svc0") + }, + + Command: test.Command("images"), + + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: expect.All( + expect.Contains(data.Labels().Get("imageSvc0")), + expect.DoesNotContain(data.Labels().Get("imageSvc1")), + ), + } + }, + }, + { + Description: "build svc0 and svc1", + NoParallel: true, + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("compose", "-f", data.Labels().Get("composeYaml"), "build", "svc0", "svc1") + }, + + Command: test.Command("images"), + + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: expect.All( + expect.Contains(data.Labels().Get("imageSvc0")), + expect.Contains(data.Labels().Get("imageSvc1")), + ), + } + }, + }, + { + Description: "build no arg", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "build") + }, + + Expected: test.Expects(expect.ExitCodeSuccess, nil, nil), + }, + { + Description: "build bogus", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "compose", + "-f", + data.Labels().Get("composeYaml"), + "build", + "svc0", + "svc100", + ) + }, + + Expected: test.Expects(1, []error{errors.New("no such service: svc100")}, nil), + }, + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + if data.Labels().Get("imageSvc0") != "" { + helpers.Anyhow("rmi", data.Labels().Get("imageSvc0"), data.Labels().Get("imageSvc1")) + } + } + + testCase.Run(t) } diff --git a/cmd/nerdctl/compose/compose_config_test.go b/cmd/nerdctl/compose/compose_config_test.go index 18dd728da5a..5df0c9eeebb 100644 --- a/cmd/nerdctl/compose/compose_config_test.go +++ b/cmd/nerdctl/compose/compose_config_test.go @@ -18,141 +18,275 @@ package compose import ( "fmt" - "os" - "path/filepath" "testing" "gotest.tools/v3/assert" - "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/test" + + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) func TestComposeConfig(t *testing.T) { - base := testutil.NewBase(t) - - var dockerComposeYAML = ` + const dockerComposeYAML = ` services: hello: image: alpine:3.13 ` + testCase := nerdtest.Setup() - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - - base.ComposeCmd("-f", comp.YAMLFullPath(), "config").AssertOutContains("hello:") -} - -func TestComposeConfigWithPrintService(t *testing.T) { - base := testutil.NewBase(t) - - var dockerComposeYAML = ` -services: - hello1: - image: alpine:3.13 -` + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYaml", data.Temp().Path("compose.yaml")) + } - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() + testCase.SubTests = []*test.Case{ + { + Description: "config contains service name", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "config") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("hello:")), + }, + { + Description: "config --services is exactly service name", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "compose", + "-f", + data.Labels().Get("composeYaml"), + "config", + "--services", + ) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals("hello\n")), + }, + { + Description: "config --hash=* contains service name", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "config", "--hash=*") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("hello")), + }, + } - base.ComposeCmd("-f", comp.YAMLFullPath(), "config", "--services").AssertOutExactly("hello1\n") + testCase.Run(t) } func TestComposeConfigWithPrintServiceHash(t *testing.T) { - base := testutil.NewBase(t) - - var dockerComposeYAML = ` + const dockerComposeYAML = ` services: - hello1: + hello: image: alpine:%s ` + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(fmt.Sprintf(dockerComposeYAML, "3.13"), "compose.yaml") + + hash := helpers.Capture( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "config", + "--hash=hello", + ) - comp := testutil.NewComposeDir(t, fmt.Sprintf(dockerComposeYAML, "3.13")) - defer comp.CleanUp() + data.Labels().Set("hash", hash) - // `--hash=*` is broken in Docker Compose v2.23.0: https://github.com/docker/compose/issues/11145 - if base.Target == testutil.Nerdctl { - base.ComposeCmd("-f", comp.YAMLFullPath(), "config", "--hash=*").AssertOutContains("hello1") + data.Temp().Save(fmt.Sprintf(dockerComposeYAML, "3.14"), "compose.yaml") } - hash := base.ComposeCmd("-f", comp.YAMLFullPath(), "config", "--hash=hello1").Out() + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "config", + "--hash=hello", + ) + } - newComp := testutil.NewComposeDir(t, fmt.Sprintf(dockerComposeYAML, "3.14")) - defer newComp.CleanUp() + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Output: func(stdout, info string, t *testing.T) { + assert.Assert(t, data.Labels().Get("hash") != stdout, "hash should be different") + }, + } + } - newHash := base.ComposeCmd("-f", newComp.YAMLFullPath(), "config", "--hash=hello1").Out() - assert.Assert(t, hash != newHash) + testCase.Run(t) } func TestComposeConfigWithMultipleFile(t *testing.T) { - base := testutil.NewBase(t) - - var dockerComposeYAML = ` + const dockerComposeBase = ` services: hello1: image: alpine:3.13 ` - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - - comp.WriteFile("docker-compose.test.yml", ` + const dockerComposeTest = ` services: hello2: image: alpine:3.14 -`) - comp.WriteFile("docker-compose.override.yml", ` +` + + const dockerComposeOverride = ` services: hello1: image: alpine:3.14 -`) +` + + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeBase, "compose.yaml") + data.Temp().Save(dockerComposeTest, "docker-compose.test.yml") + data.Temp().Save(dockerComposeOverride, "docker-compose.override.yml") + + data.Labels().Set("composeDir", data.Temp().Path()) + data.Labels().Set("composeYaml", data.Temp().Path("compose.yaml")) + data.Labels().Set("composeYamlTest", data.Temp().Path("docker-compose.test.yml")) + } + + testCase.SubTests = []*test.Case{ + { + Description: "config override", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "compose", + "-f", data.Labels().Get("composeYaml"), + "-f", data.Labels().Get("composeYamlTest"), + "config", + ) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All( + expect.Contains("alpine:3.13"), + expect.Contains("alpine:3.14"), + expect.Contains("hello1"), + expect.Contains("hello2"), + )), + }, + { + Description: "project dir", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "compose", + "--project-directory", data.Labels().Get("composeDir"), "config", + ) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("alpine:3.14")), + }, + { + Description: "project dir services", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "compose", + "--project-directory", data.Labels().Get("composeDir"), "config", "--services", + ) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals("hello1\n")), + }, + } - base.ComposeCmd("-f", comp.YAMLFullPath(), "-f", filepath.Join(comp.Dir(), "docker-compose.test.yml"), "config").AssertOutContains("alpine:3.14") - base.ComposeCmd("--project-directory", comp.Dir(), "config", "--services").AssertOutExactly("hello1\n") - base.ComposeCmd("--project-directory", comp.Dir(), "config").AssertOutContains("alpine:3.14") + testCase.Run(t) } func TestComposeConfigWithComposeFileEnv(t *testing.T) { - base := testutil.NewBase(t) - - var dockerComposeYAML = ` + const dockerComposeBase = ` services: hello1: image: alpine:3.13 ` - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - - comp.WriteFile("docker-compose.test.yml", ` + const dockerComposeTest = ` services: hello2: image: alpine:3.14 -`) +` + + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeBase, "compose.yaml") + data.Temp().Save(dockerComposeTest, "docker-compose.test.yml") - base.Env = append(base.Env, "COMPOSE_FILE="+comp.YAMLFullPath()+","+filepath.Join(comp.Dir(), "docker-compose.test.yml"), "COMPOSE_PATH_SEPARATOR=,") + data.Labels().Set("composeDir", data.Temp().Path()) + data.Labels().Set("composeYaml", data.Temp().Path("compose.yaml")) + data.Labels().Set("composeYamlTest", data.Temp().Path("docker-compose.test.yml")) + } + + testCase.SubTests = []*test.Case{ + { + Description: "env config", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "config", + ) + cmd.Setenv("COMPOSE_FILE", data.Labels().Get("composeYaml")+","+data.Labels().Get("composeYamlTest")) + cmd.Setenv("COMPOSE_PATH_SEPARATOR", ",") + return cmd + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All( + expect.Contains("alpine:3.13"), + expect.Contains("alpine:3.14"), + expect.Contains("hello1"), + expect.Contains("hello2"), + )), + }, + { + Description: "env with project dir", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "--project-directory", data.Labels().Get("composeDir"), + "config", + ) + cmd.Setenv("COMPOSE_FILE", data.Labels().Get("composeYaml")+","+data.Labels().Get("composeYamlTest")) + cmd.Setenv("COMPOSE_PATH_SEPARATOR", ",") + return cmd + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All( + expect.Contains("alpine:3.13"), + expect.Contains("alpine:3.14"), + expect.Contains("hello1"), + expect.Contains("hello2"), + )), + }, + } - base.ComposeCmd("config").AssertOutContains("alpine:3.14") - base.ComposeCmd("--project-directory", comp.Dir(), "config", "--services").AssertOutContainsAll("hello1\n", "hello2\n") - base.ComposeCmd("--project-directory", comp.Dir(), "config").AssertOutContains("alpine:3.14") + testCase.Run(t) } func TestComposeConfigWithEnvFile(t *testing.T) { - base := testutil.NewBase(t) - const dockerComposeYAML = ` services: hello: image: ${image} ` - - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - - envFile := filepath.Join(comp.Dir(), "env") const envFileContent = ` image: hello-world ` - assert.NilError(t, os.WriteFile(envFile, []byte(envFileContent), 0644)) - base.ComposeCmd("-f", comp.YAMLFullPath(), "--env-file", envFile, "config").AssertOutContains("image: hello-world") + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Temp().Save(envFileContent, "env") + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", + "-f", data.Temp().Path("compose.yaml"), + "--env-file", data.Temp().Path("env"), + "config", + ) + } + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("image: hello-world")) + + testCase.Run(t) } diff --git a/cmd/nerdctl/compose/compose_cp_linux_test.go b/cmd/nerdctl/compose/compose_cp_linux_test.go index 605210d8946..7d5dea8502c 100644 --- a/cmd/nerdctl/compose/compose_cp_linux_test.go +++ b/cmd/nerdctl/compose/compose_cp_linux_test.go @@ -18,18 +18,18 @@ package compose import ( "fmt" - "os" - "path/filepath" "testing" "gotest.tools/v3/assert" + "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) func TestComposeCopy(t *testing.T) { - base := testutil.NewBase(t) - var dockerComposeYAML = fmt.Sprintf(` version: '3.1' @@ -39,31 +39,54 @@ services: command: "sleep infinity" `, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - - base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK() - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK() - - // gernetate test file - srcDir := t.TempDir() - srcFile := filepath.Join(srcDir, "test-file") - srcFileContent := []byte("test-file-content") - err := os.WriteFile(srcFile, srcFileContent, 0o644) - assert.NilError(t, err) - - // test copy to service - destPath := "/dest-no-exist-no-slash" - base.ComposeCmd("-f", comp.YAMLFullPath(), "cp", srcFile, "svc0:"+destPath).AssertOK() - - // test copy from service - destFile := filepath.Join(srcDir, "test-file2") - base.ComposeCmd("-f", comp.YAMLFullPath(), "cp", "svc0:"+destPath, destFile).AssertOK() - - destFileContent, err := os.ReadFile(destFile) - assert.NilError(t, err) - assert.DeepEqual(t, srcFileContent, destFileContent) - + const testFileContent = "test-file-content" + + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + compYamlPath := data.Temp().Save(dockerComposeYAML, "compose.yaml") + helpers.Ensure("compose", "-f", compYamlPath, "up", "-d") + + srcFilePath := data.Temp().Save(testFileContent, "test-file") + + data.Labels().Set("composeYaml", compYamlPath) + data.Labels().Set("srcFile", srcFilePath) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + testCase.SubTests = []*test.Case{ + { + Description: "test copy to service /dest-no-exist-no-slash", + // These are expected to run in sequence + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", + "-f", data.Labels().Get("composeYaml"), + "cp", data.Labels().Get("srcFile"), "svc0:/dest-no-exist-no-slash") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, nil), + }, + { + Description: "test copy from service test-file2", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", + "-f", data.Labels().Get("composeYaml"), + "cp", "svc0:/dest-no-exist-no-slash", data.Temp().Path("test-file2")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout, info string, t *testing.T) { + copied := data.Temp().Load("test-file2") + assert.Equal(t, copied, testFileContent) + }, + } + }, + }, + } + + testCase.Run(t) } diff --git a/cmd/nerdctl/compose/compose_create_linux_test.go b/cmd/nerdctl/compose/compose_create_linux_test.go index 43f2dc40067..c1a94dfd2c6 100644 --- a/cmd/nerdctl/compose/compose_create_linux_test.go +++ b/cmd/nerdctl/compose/compose_create_linux_test.go @@ -18,13 +18,19 @@ package compose import ( "fmt" + "strings" "testing" + "gotest.tools/v3/assert" + + "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) func TestComposeCreate(t *testing.T) { - base := testutil.NewBase(t) var dockerComposeYAML = fmt.Sprintf(` version: '3.1' @@ -33,22 +39,53 @@ services: image: %s `, testutil.AlpineImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK() - - // 1.1 `compose create` should create service container (in `created` status) - base.ComposeCmd("-f", comp.YAMLFullPath(), "create").AssertOK() - base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc0", "-a").AssertOutContainsAny("Created", "created") - // 1.2 created container can be started by `compose start` - base.ComposeCmd("-f", comp.YAMLFullPath(), "start").AssertOK() + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + compYamlPath := data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYaml", compYamlPath) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + testCase.SubTests = []*test.Case{ + { + Description: "`compose create` should work", + // These are sequential + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "create") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, nil), + }, + { + Description: "`compose create` should have created service container (in `created` status)", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "svc0", "-a") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout, info string, t *testing.T) { + assert.Assert(t, + strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"), + "stdout should contain `created`") + }), + }, + { + Description: "`created container can be started by `compose start`", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "start") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, nil), + }, + } + + testCase.Run(t) } func TestComposeCreateDependency(t *testing.T) { - base := testutil.NewBase(t) var dockerComposeYAML = fmt.Sprintf(` version: '3.1' @@ -61,17 +98,54 @@ services: image: %s `, testutil.CommonImage, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK() - - // `compose create` should create containers for both services and their dependencies - base.ComposeCmd("-f", comp.YAMLFullPath(), "create", "svc0").AssertOK() - base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc0", "-a").AssertOutContainsAny("Created", "created") - base.ComposeCmd("-f", comp.YAMLFullPath(), "ps", "svc1", "-a").AssertOutContainsAny("Created", "created") + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + compYamlPath := data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("composeYaml", compYamlPath) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + testCase.SubTests = []*test.Case{ + { + Description: "`compose create` should work", + // These are sequential + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "create") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, nil), + }, + { + Description: "`compose create` should have created svc0 (in `created` status)", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "svc0", "-a") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout, info string, t *testing.T) { + assert.Assert(t, + strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"), + "stdout should contain `created`") + }), + }, + { + Description: "`compose create` should have created svc1 (in `created` status)", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("composeYaml"), "ps", "svc1", "-a") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout, info string, t *testing.T) { + assert.Assert(t, + strings.Contains(stdout, "created") || strings.Contains(stdout, "Created"), + "stdout should contain `created`") + }), + }, + } + + testCase.Run(t) } func TestComposeCreatePull(t *testing.T) { diff --git a/cmd/nerdctl/compose/compose_exec_linux_test.go b/cmd/nerdctl/compose/compose_exec_linux_test.go index 0546ebed6f5..be437cb94d0 100644 --- a/cmd/nerdctl/compose/compose_exec_linux_test.go +++ b/cmd/nerdctl/compose/compose_exec_linux_test.go @@ -17,20 +17,23 @@ package compose import ( - "errors" "fmt" "net" + "path/filepath" "strings" "testing" "gotest.tools/v3/assert" + "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) func TestComposeExec(t *testing.T) { - base := testutil.NewBase(t) - var dockerComposeYAML = fmt.Sprintf(` + dockerComposeYAML := fmt.Sprintf(` version: '3.1' services: @@ -42,107 +45,108 @@ services: command: "sleep infinity" `, testutil.CommonImage, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - - base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d", "svc0").AssertOK() - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK() - - // test basic functionality and `--workdir` flag - base.ComposeCmd("-f", comp.YAMLFullPath(), "exec", "-i=false", "--no-TTY", "svc0", "echo", "success").AssertOutExactly("success\n") - base.ComposeCmd("-f", comp.YAMLFullPath(), "exec", "-i=false", "--no-TTY", "--workdir", "/tmp", "svc0", "pwd").AssertOutExactly("/tmp\n") - // cannot `exec` on non-running service - base.ComposeCmd("-f", comp.YAMLFullPath(), "exec", "svc1", "echo", "success").AssertFail() -} - -func TestComposeExecWithEnv(t *testing.T) { - base := testutil.NewBase(t) - var dockerComposeYAML = fmt.Sprintf(` -version: '3.1' - -services: - svc0: - image: %s - command: "sleep infinity" -`, testutil.CommonImage) - - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - - base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK() - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK() - - // FYI: https://github.com/containerd/nerdctl/blob/e4b2b6da56555dc29ed66d0fd8e7094ff2bc002d/cmd/nerdctl/run_test.go#L177 - base.Env = append(base.Env, "CORGE=corge-value-in-host", "GARPLY=garply-value-in-host") - base.ComposeCmd("-f", comp.YAMLFullPath(), "exec", "-i=false", "--no-TTY", - "--env", "FOO=foo1,foo2", - "--env", "BAR=bar1 bar2", - "--env", "BAZ=", - "--env", "QUX", // not exported in OS - "--env", "QUUX=quux1", - "--env", "QUUX=quux2", - "--env", "CORGE", // OS exported - "--env", "GRAULT=grault_key=grault_value", // value contains `=` char - "--env", "GARPLY=", // OS exported - "--env", "WALDO=", // not exported in OS - - "svc0", "env").AssertOutWithFunc(func(stdout string) error { - if !strings.Contains(stdout, "\nFOO=foo1,foo2\n") { - return errors.New("got bad FOO") - } - if !strings.Contains(stdout, "\nBAR=bar1 bar2\n") { - return errors.New("got bad BAR") - } - if !strings.Contains(stdout, "\nBAZ=\n") { - return errors.New("got bad BAZ") - } - if strings.Contains(stdout, "QUX") { - return errors.New("got bad QUX (should not be set)") - } - if !strings.Contains(stdout, "\nQUUX=quux2\n") { - return errors.New("got bad QUUX") - } - if !strings.Contains(stdout, "\nCORGE=corge-value-in-host\n") { - return errors.New("got bad CORGE") - } - if !strings.Contains(stdout, "\nGRAULT=grault_key=grault_value\n") { - return errors.New("got bad GRAULT") - } - if !strings.Contains(stdout, "\nGARPLY=\n") { - return errors.New("got bad GARPLY") - } - if !strings.Contains(stdout, "\nWALDO=\n") { - return errors.New("got bad WALDO") - } - - return nil - }) -} + testCase := nerdtest.Setup() -func TestComposeExecWithUser(t *testing.T) { - base := testutil.NewBase(t) - var dockerComposeYAML = fmt.Sprintf(` -version: '3.1' + testCase.Setup = func(data test.Data, helpers test.Helpers) { + yamlPath := data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("YAMLPath", yamlPath) + helpers.Ensure("compose", "-f", yamlPath, "up", "-d", "svc0") + } -services: - svc0: - image: %s - command: "sleep infinity" -`, testutil.CommonImage) + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) + testCase.SubTests = []*test.Case{ + { + Description: "exec no tty", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "compose", + "-f", + data.Labels().Get("YAMLPath"), + "exec", + "-i=false", + "--no-TTY", + "svc0", + "echo", + "success", + ) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals("success\n")), + }, + { + Description: "exec no tty with workdir", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "compose", + "-f", + data.Labels().Get("YAMLPath"), + "exec", + "-i=false", + "--no-TTY", + "--workdir", + "/tmp", + "svc0", + "pwd", + ) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals("/tmp\n")), + }, + { + Description: "cannot exec on non-running service", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("compose", "-f", data.Labels().Get("YAMLPath"), "exec", "svc1", "echo", "success") + }, + Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), + }, + { + Description: "with env", + Env: map[string]string{ + "CORGE": "corge-value-in-host", + "GARPLY": "garply-value-in-host", + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "compose", + "-f", + data.Labels().Get("YAMLPath"), + "exec", + "-i=false", + "--no-TTY", + "--env", "FOO=foo1,foo2", + "--env", "BAR=bar1 bar2", + "--env", "BAZ=", + "--env", "QUX", // not exported in OS + "--env", "QUUX=quux1", + "--env", "QUUX=quux2", + "--env", "CORGE", // OS exported + "--env", "GRAULT=grault_key=grault_value", // value contains `=` char + "--env", "GARPLY=", // OS exported + "--env", "WALDO=", // not exported in OS + "svc0", + "env") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All( + expect.Contains("\nFOO=foo1,foo2\n"), + expect.Contains("\nBAR=bar1 bar2\n"), + expect.Contains("\nBAZ=\n"), + expect.DoesNotContain("QUX"), + expect.Contains("\nQUUX=quux2\n"), + expect.Contains("\nCORGE=corge-value-in-host\n"), + expect.Contains("\nGRAULT=grault_key=grault_value\n"), + expect.Contains("\nGARPLY=\n"), + expect.Contains("\nWALDO=\n"), + )), + }, + } - base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d").AssertOK() - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK() + userSubTest := &test.Case{ + Description: "with user", + SubTests: []*test.Case{}, + } - testCases := map[string]string{ + userCasesMap := map[string]string{ "": "uid=0(root) gid=0(root)", "1000": "uid=1000 gid=0(root)", "1000:users": "uid=1000 gid=100(users)", @@ -151,21 +155,29 @@ services: "nobody:users": "uid=65534(nobody) gid=100(users)", } - for userStr, expected := range testCases { - args := []string{"-f", comp.YAMLFullPath(), "exec", "-i=false", "--no-TTY"} - if userStr != "" { - args = append(args, "--user", userStr) - } - args = append(args, "svc0", "id") - base.ComposeCmd(args...).AssertOutContains(expected) + for k, v := range userCasesMap { + userSubTest.SubTests = append(userSubTest.SubTests, &test.Case{ + Description: k + " " + v, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + args := []string{"compose", "-f", data.Labels().Get("YAMLPath"), "exec", "-i=false", "--no-TTY"} + if k != "" { + args = append(args, "--user", k) + } + args = append(args, "svc0", "id") + return helpers.Command(args...) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(v)), + }) } + + testCase.SubTests = append(testCase.SubTests, userSubTest) + + testCase.Run(t) } func TestComposeExecTTY(t *testing.T) { - // `-i` in `compose run & exec` is only supported in compose v2. - base := testutil.NewBase(t) - - var dockerComposeYAML = fmt.Sprintf(` + const expectedOutput = "speed 38400 baud" + dockerComposeYAML := fmt.Sprintf(` version: '3.1' services: @@ -175,29 +187,85 @@ services: image: %s `, testutil.CommonImage, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - - testContainer := testutil.Identifier(t) - base.ComposeCmd("-f", comp.YAMLFullPath(), "run", "-d", "-i=false", "--name", testContainer, "svc0", "sleep", "1h").AssertOK() - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK() - base.EnsureContainerStarted(testContainer) - - const sttyPartialOutput = "speed 38400 baud" - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "exec", "svc0", "stty").AssertOutContains(sttyPartialOutput) // `-it` - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "exec", "-i=false", "svc0", "stty").AssertOutContains(sttyPartialOutput) // `-t` - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "exec", "--no-TTY", "svc0", "stty").AssertFail() // `-i` - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "exec", "-i=false", "--no-TTY", "svc0", "stty").AssertFail() + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + yamlPath := data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("YAMLPath", yamlPath) + helpers.Ensure( + "compose", + "-f", + yamlPath, + "run", + "-d", + "-i=false", + "--name", + data.Identifier(), + "svc0", + "sleep", + "1h", + ) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + // FIXME? + // similar, other test does *also* remove the container + helpers.Anyhow("compose", "-f", data.Labels().Get("YAMLPath"), "down", "-v") + } + + testCase.SubTests = []*test.Case{ + { + Description: "stty exec", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("compose", "-f", data.Labels().Get("YAMLPath"), "exec", "svc0", "stty") + cmd.WithPseudoTTY() + return cmd + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(expectedOutput)), + }, + { + Description: "-i=false stty exec", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("compose", "-f", data.Labels().Get("YAMLPath"), "exec", "-i=false", "svc0", "stty") + cmd.WithPseudoTTY() + return cmd + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(expectedOutput)), + }, + { + Description: "--no-TTY stty exec", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("compose", "-f", data.Labels().Get("YAMLPath"), "exec", "--no-TTY", "svc0", "stty") + cmd.WithPseudoTTY() + return cmd + }, + Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), + }, + { + Description: "-i=false --no-TTY stty exec", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "-f", + data.Labels().Get("YAMLPath"), + "exec", + "-i=false", + "--no-TTY", + "svc0", + "stty", + ) + cmd.WithPseudoTTY() + return cmd + }, + Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), + }, + } + + testCase.Run(t) } func TestComposeExecWithIndex(t *testing.T) { - base := testutil.NewBase(t) - var dockerComposeYAML = fmt.Sprintf(` + dockerComposeYAML := fmt.Sprintf(` version: '3.1' services: @@ -208,39 +276,52 @@ services: replicas: 3 `, testutil.CommonImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - t.Cleanup(func() { - comp.CleanUp() - }) - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - - base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d", "svc0").AssertOK() - t.Cleanup(func() { - base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").AssertOK() - }) - - // try 5 times to ensure that results are stable - for i := 0; i < 5; i++ { - for _, j := range []string{"1", "2", "3"} { - name := fmt.Sprintf("%s-svc0-%s", projectName, j) - host := fmt.Sprintf("%s.%s_default", name, projectName) - var ( - expectIP string - realIP string - ) - // docker and nerdctl have different DNS resolution behaviors. - // it uses the ID in the /etc/hosts file, so we need to fetch the ID first. - if testutil.GetTarget() == testutil.Docker { - base.Cmd("ps", "--filter", fmt.Sprintf("name=%s", name), "--format", "{{.ID}}").AssertOutWithFunc(func(stdout string) error { - host = strings.TrimSpace(stdout) - return nil - }) - } - cmds := []string{"-f", comp.YAMLFullPath(), "exec", "-i=false", "--no-TTY", "--index", j, "svc0"} - base.ComposeCmd(append(cmds, "cat", "/etc/hosts")...). - AssertOutWithFunc(func(stdout string) error { - lines := strings.Split(stdout, "\n") + testCase := nerdtest.Setup() + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + yamlPath := data.Temp().Save(dockerComposeYAML, "compose.yaml") + data.Labels().Set("YAMLPath", yamlPath) + data.Labels().Set("projectName", strings.ToLower(filepath.Base(data.Temp().Dir()))) + + helpers.Ensure("compose", "-f", yamlPath, "up", "-d", "svc0") + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + } + + for _, index := range []string{"1", "2", "3"} { + testCase.SubTests = append(testCase.SubTests, &test.Case{ + Description: index, + Setup: func(data test.Data, helpers test.Helpers) { + // try 5 times to ensure that results are stable + for range 5 { + cmds := []string{ + "compose", + "-f", + data.Labels().Get("YAMLPath"), + "exec", + "-i=false", + "--no-TTY", + "--index", + index, + "svc0", + } + + hsts := helpers.Capture(append(cmds, "cat", "/etc/hosts")...) + ips := helpers.Capture(append(cmds, "ip", "addr", "show", "dev", "eth0")...) + + var ( + expectIP string + realIP string + ) + name := fmt.Sprintf("%s-svc0-%s", data.Labels().Get("projectName"), index) + host := fmt.Sprintf("%s.%s_default", name, data.Labels().Get("projectName")) + if nerdtest.IsDocker() { + host = strings.TrimSpace(helpers.Capture("ps", "--filter", "name="+name, "--format", "{{.ID}}")) + } + + lines := strings.Split(hsts, "\n") for _, line := range lines { if !strings.Contains(line, host) { continue @@ -250,37 +331,32 @@ services: continue } expectIP = fields[0] - return nil } - return errors.New("fail to get the expected ip address") - }) - base.ComposeCmd(append(cmds, "ip", "addr", "show", "dev", "eth0")...). - AssertOutWithFunc(func(stdout string) error { - ip := findIP(stdout) - if ip == nil { - return errors.New("fail to get the real ip address") + + var ip string + lines = strings.Split(ips, "\n") + for _, line := range lines { + if !strings.Contains(line, "inet ") { + continue + } + fields := strings.Fields(line) + if len(fields) <= 1 { + continue + } + ip = strings.Split(fields[1], "/")[0] + break } - realIP = ip.String() - return nil - }) - assert.Equal(t, realIP, expectIP) - } - } -} -func findIP(output string) net.IP { - var ip string - lines := strings.Split(output, "\n") - for _, line := range lines { - if !strings.Contains(line, "inet ") { - continue - } - fields := strings.Fields(line) - if len(fields) <= 1 { - continue - } - ip = strings.Split(fields[1], "/")[0] - break + pip := net.ParseIP(ip) + + assert.Assert(helpers.T(), pip != nil, "fail to get the real ip address") + realIP = pip.String() + + assert.Equal(helpers.T(), realIP, expectIP) + } + }, + }) } - return net.ParseIP(ip) + + testCase.Run(t) } diff --git a/cmd/nerdctl/compose/compose_run_linux_test.go b/cmd/nerdctl/compose/compose_run_linux_test.go index 65b36e7ffb6..bd606299bfe 100644 --- a/cmd/nerdctl/compose/compose_run_linux_test.go +++ b/cmd/nerdctl/compose/compose_run_linux_test.go @@ -26,48 +26,18 @@ import ( "gotest.tools/v3/assert" "github.com/containerd/log" + "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/test" "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil" "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" ) func TestComposeRun(t *testing.T) { - base := testutil.NewBase(t) - // specify the name of container in order to remove - // TODO: when `compose rm` is implemented, replace it. - containerName := testutil.Identifier(t) - - dockerComposeYAML := fmt.Sprintf(` -version: '3.1' -services: - alpine: - image: %s - entrypoint: - - stty -`, testutil.AlpineImage) - - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() - - defer base.Cmd("rm", "-f", "-v", containerName).Run() - const sttyPartialOutput = "speed 38400 baud" - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "--name", containerName, "alpine").AssertOutContains(sttyPartialOutput) -} - -func TestComposeRunWithRM(t *testing.T) { - base := testutil.NewBase(t) - // specify the name of container in order to remove - // TODO: when `compose rm` is implemented, replace it. - containerName := testutil.Identifier(t) + const expectedOutput = "speed 38400 baud" dockerComposeYAML := fmt.Sprintf(` version: '3.1' @@ -78,29 +48,69 @@ services: - stty `, testutil.AlpineImage) - comp := testutil.NewComposeDir(t, dockerComposeYAML) - defer comp.CleanUp() - projectName := comp.ProjectName() - t.Logf("projectName=%q", projectName) - defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() - - defer base.Cmd("rm", "-f", "-v", containerName).Run() - const sttyPartialOutput = "speed 38400 baud" - // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. - // unbuffer(1) can be installed with `apt-get install expect`. - unbuffer := []string{"unbuffer"} - base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), - "run", "--name", containerName, "--rm", "alpine").AssertOutContains(sttyPartialOutput) - - psCmd := base.Cmd("ps", "-a", "--format=\"{{.Names}}\"") - result := psCmd.Run() - stdoutContent := result.Stdout() + result.Stderr() - assert.Assert(psCmd.Base.T, result.ExitCode == 0, stdoutContent) - if strings.Contains(stdoutContent, containerName) { - log.L.Errorf("test failed, the container %s is not removed", stdoutContent) - t.Fail() - return + testCase := nerdtest.Setup() + + testCase.SubTests = []*test.Case{ + { + Description: "pty run", + Setup: func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "--name", + data.Identifier(), + "alpine", + ) + cmd.WithPseudoTTY() + return cmd + }, + Expected: test.Expects(0, nil, expect.Contains(expectedOutput)), + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", "-v", data.Identifier()) + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + }, + }, + { + Description: "pty run with --rm", + Setup: func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerComposeYAML, "compose.yaml") + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command( + "compose", + "-f", + data.Temp().Path("compose.yaml"), + "run", + "--name", + data.Identifier(), + "--rm", + "alpine", + ) + cmd.WithPseudoTTY() + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + // Ensure the container has been removed + capt := helpers.Capture("ps", "-a", "--format=\"{{.Names}}\"") + assert.Assert(t, !strings.Contains(capt, data.Identifier()), capt) + + return &test.Expected{ + Output: expect.Contains(expectedOutput), + } + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", "-v", data.Identifier()) + helpers.Anyhow("compose", "-f", data.Temp().Path("compose.yaml"), "down", "-v") + }, + }, } + + testCase.Run(t) } func TestComposeRunWithServicePorts(t *testing.T) { diff --git a/cmd/nerdctl/container/container_logs_test.go b/cmd/nerdctl/container/container_logs_test.go index 94c561fafb4..05b6ea67190 100644 --- a/cmd/nerdctl/container/container_logs_test.go +++ b/cmd/nerdctl/container/container_logs_test.go @@ -17,6 +17,7 @@ package container import ( + "errors" "fmt" "os/exec" "runtime" @@ -76,16 +77,29 @@ bar` // Tests whether `nerdctl logs` properly separates stdout/stderr output // streams for containers using the jsonfile logging driver: func TestLogsOutStreamsSeparated(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - containerName := testutil.Identifier(t) + testCase := nerdtest.Setup() - defer base.Cmd("rm", containerName).Run() - base.Cmd("run", "-d", "--name", containerName, testutil.CommonImage, - "sh", "-euc", "echo stdout1; echo stderr1 >&2; echo stdout2; echo stderr2 >&2").AssertOK() - time.Sleep(3 * time.Second) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "-d", "--name", data.Identifier(), testutil.CommonImage, + "sh", "-euc", "echo stdout1; echo stderr1 >&2; echo stdout2; echo stderr2 >&2") + } - base.Cmd("logs", containerName).AssertOutStreamsExactly("stdout1\nstdout2\n", "stderr1\nstderr2\n") + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + // Arbitrary, but we need to wait until the logs show up + time.Sleep(3 * time.Second) + return helpers.Command("logs", data.Identifier()) + } + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, []error{ + //revive:disable:error-strings + errors.New("stderr1\nstderr2\n"), + }, expect.Equals("stdout1\nstdout2\n")) + + testCase.Run(t) } func TestLogsWithInheritedFlags(t *testing.T) { diff --git a/cmd/nerdctl/container/container_run_network_linux_test.go b/cmd/nerdctl/container/container_run_network_linux_test.go index 853e14e8ab5..46b9057e11a 100644 --- a/cmd/nerdctl/container/container_run_network_linux_test.go +++ b/cmd/nerdctl/container/container_run_network_linux_test.go @@ -41,7 +41,6 @@ import ( "github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/test" - "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" @@ -682,27 +681,18 @@ func TestSharedNetworkWithNone(t *testing.T) { testCase := &test.Case{ Require: require.Not(require.Windows), Setup: func(data test.Data, helpers test.Helpers) { - data.Labels().Set("containerName1", data.Identifier("-container1")) - containerName1 := data.Labels().Get("containerName1") - helpers.Ensure("run", "-d", "--name", containerName1, "--network", "none", + helpers.Ensure("run", "-d", "--name", data.Identifier("container1"), "--network", "none", testutil.NginxAlpineImage) }, Cleanup: func(data test.Data, helpers test.Helpers) { - helpers.Anyhow("rm", "-f", data.Labels().Get("containerName1")) + helpers.Anyhow("rm", "-f", data.Identifier("container1")) + helpers.Anyhow("rm", "-f", data.Identifier("container2")) }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - containerName2 := data.Identifier() - cmd := helpers.Command() - cmd.WithArgs("run", "-d", "--name", containerName2, - "--network=container:"+data.Labels().Get("containerName1"), - testutil.NginxAlpineImage) - return cmd - }, - Expected: func(data test.Data, helpers test.Helpers) *test.Expected { - return &test.Expected{ - ExitCode: 0, - } + return helpers.Command("run", "-d", "--name", data.Identifier("container2"), + "--network=container:"+data.Identifier("container1"), testutil.NginxAlpineImage) }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, nil), } testCase.Run(t) } @@ -905,7 +895,7 @@ func TestRunContainerWithStaticIP6(t *testing.T) { return } cmd.AssertOutWithFunc(func(stdout string) error { - ip := helpers.FindIPv6(stdout) + ip := nerdtest.FindIPv6(stdout) if !subnet.Contains(ip) { return fmt.Errorf("expected subnet %s include ip %s", subnet, ip) } diff --git a/cmd/nerdctl/container/container_run_soci_linux_test.go b/cmd/nerdctl/container/container_run_soci_linux_test.go index 57cf0599525..670a15dc7de 100644 --- a/cmd/nerdctl/container/container_run_soci_linux_test.go +++ b/cmd/nerdctl/container/container_run_soci_linux_test.go @@ -17,60 +17,63 @@ package container import ( - "os/exec" + "strconv" "strings" "testing" - "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" + "gotest.tools/v3/assert" + + "github.com/containerd/nerdctl/mod/tigron/require" + "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) func TestRunSoci(t *testing.T) { - testutil.DockerIncompatible(t) - tests := []struct { - name string - image string - remoteSnapshotsExpectedCount int - }{ - { - name: "Run with SOCI", - image: testutil.FfmpegSociImage, - remoteSnapshotsExpectedCount: 11, - }, - } + testCase := nerdtest.Setup() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - base := testutil.NewBase(t) - helpers.RequiresSoci(base) + testCase.Require = require.All( + require.Not(nerdtest.Docker), + nerdtest.Soci, + ) - //counting initial snapshot mounts - initialMounts, err := exec.Command("mount").Output() - if err != nil { - t.Fatal(err) - } + // Tests relying on the output of "mount" cannot be run in parallel obviously + testCase.NoParallel = true - remoteSnapshotsInitialCount := strings.Count(string(initialMounts), "fuse.rawBridge") + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Custom("mount").Run(&test.Expected{ + ExitCode: 0, + Output: func(stdout, info string, t *testing.T) { + data.Labels().Set("beforeCount", strconv.Itoa(strings.Count(stdout, "fuse.rawBridge"))) + }, + }) + } - runOutput := base.Cmd("--snapshotter=soci", "run", "--rm", testutil.FfmpegSociImage).Out() - base.T.Logf("run output: %s", runOutput) + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rmi", "-f", testutil.FfmpegSociImage) + } - actualMounts, err := exec.Command("mount").Output() - if err != nil { - t.Fatal(err) - } - remoteSnapshotsActualCount := strings.Count(string(actualMounts), "fuse.rawBridge") - base.T.Logf("number of actual mounts: %v", remoteSnapshotsActualCount) + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("--snapshotter=soci", "run", "--rm", testutil.FfmpegSociImage) + } - rmiOutput := base.Cmd("rmi", testutil.FfmpegSociImage).Out() - base.T.Logf("rmi output: %s", rmiOutput) + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout, info string, t *testing.T) { + var afterCount int + beforeCount, _ := strconv.Atoi(data.Labels().Get("beforeCount")) - base.T.Logf("number of expected mounts: %v", tt.remoteSnapshotsExpectedCount) + helpers.Custom("mount").Run(&test.Expected{ + Output: func(stdout, info string, t *testing.T) { + afterCount = strings.Count(stdout, "fuse.rawBridge") + }, + }) - if tt.remoteSnapshotsExpectedCount != (remoteSnapshotsActualCount - remoteSnapshotsInitialCount) { - t.Fatalf("incorrect number of remote snapshots; expected=%d, actual=%d", - tt.remoteSnapshotsExpectedCount, remoteSnapshotsActualCount-remoteSnapshotsInitialCount) - } - }) + assert.Equal(t, 11, afterCount-beforeCount, "expected the number of fuse.rawBridge") + }, + } } + + testCase.Run(t) } diff --git a/cmd/nerdctl/container/container_run_test.go b/cmd/nerdctl/container/container_run_test.go index 97eac527b8d..eb0d22c9674 100644 --- a/cmd/nerdctl/container/container_run_test.go +++ b/cmd/nerdctl/container/container_run_test.go @@ -34,215 +34,274 @@ import ( "gotest.tools/v3/icmd" "gotest.tools/v3/poll" + "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/require" + "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) func TestRunEntrypointWithBuild(t *testing.T) { - t.Parallel() - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - base := testutil.NewBase(t) - imageName := testutil.Identifier(t) - defer base.Cmd("rmi", imageName).Run() + nerdtest.Setup() dockerfile := fmt.Sprintf(`FROM %s ENTRYPOINT ["echo", "foo"] CMD ["echo", "bar"] `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + testCase := &test.Case{ + Require: nerdtest.Build, + Setup: func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerfile, "Dockerfile") + data.Labels().Set("image", data.Identifier()) + helpers.Ensure("build", "-t", data.Labels().Get("image"), data.Temp().Path()) + }, + SubTests: []*test.Case{ + { + Description: "Run image", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", data.Labels().Get("image")) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals("foo echo bar\n")), + }, + { + Description: "Run image empty entrypoint", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", "--entrypoint", "", data.Labels().Get("image")) + }, + Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), + }, + { + Description: "Run image time entrypoint", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", "--entrypoint", "time", data.Labels().Get("image")) + }, + Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), + }, + { + Description: "Run image empty entrypoint custom command", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", "--entrypoint", "", data.Labels().Get("image"), "echo", "blah") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All( + expect.Contains("blah"), + expect.DoesNotContain("foo"), + expect.DoesNotContain("bar"), + )), + }, + { + Description: "Run image time entrypoint custom command", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", "--entrypoint", "time", data.Labels().Get("image"), "echo", "blah") + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.All( + expect.Contains("blah"), + expect.DoesNotContain("foo"), + expect.DoesNotContain("bar"), + )), + }, + }, + } - base.Cmd("build", "-t", imageName, buildCtx).AssertOK() - base.Cmd("run", "--rm", imageName).AssertOutExactly("foo echo bar\n") - base.Cmd("run", "--rm", "--entrypoint", "", imageName).AssertFail() - base.Cmd("run", "--rm", "--entrypoint", "", imageName, "echo", "blah").AssertOutWithFunc(func(stdout string) error { - if !strings.Contains(stdout, "blah") { - return errors.New("echo blah was not executed?") - } - if strings.Contains(stdout, "bar") { - return errors.New("echo bar should not be executed") - } - if strings.Contains(stdout, "foo") { - return errors.New("echo foo should not be executed") - } - return nil - }) - base.Cmd("run", "--rm", "--entrypoint", "time", imageName).AssertFail() - base.Cmd("run", "--rm", "--entrypoint", "time", imageName, "echo", "blah").AssertOutWithFunc(func(stdout string) error { - if !strings.Contains(stdout, "blah") { - return errors.New("echo blah was not executed?") - } - if strings.Contains(stdout, "bar") { - return errors.New("echo bar should not be executed") - } - if strings.Contains(stdout, "foo") { - return errors.New("echo foo should not be executed") - } - return nil - }) + testCase.Run(t) } func TestRunWorkdir(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) + testCase := nerdtest.Setup() + dir := "/foo" if runtime.GOOS == "windows" { dir = "c:" + dir } - cmd := base.Cmd("run", "--rm", "--workdir="+dir, testutil.CommonImage, "pwd") - cmd.AssertOutContains("/foo") + + testCase.Command = test.Command("run", "--rm", "--workdir="+dir, testutil.CommonImage, "pwd") + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(dir)) + + testCase.Run(t) } func TestRunWithDoubleDash(t *testing.T) { - t.Parallel() - testutil.DockerIncompatible(t) - base := testutil.NewBase(t) - base.Cmd("run", "--rm", testutil.CommonImage, "--", "sh", "-euxc", "exit 0").AssertOK() + testCase := nerdtest.Setup() + + testCase.Require = require.Not(nerdtest.Docker) + + testCase.Command = test.Command("run", "--rm", testutil.CommonImage, "--", "sh", "-euxc", "exit 0") + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, nil) + + testCase.Run(t) } func TestRunExitCode(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - tID := testutil.Identifier(t) - testContainer0 := tID + "-0" - testContainer123 := tID + "-123" - defer base.Cmd("rm", "-f", testContainer0, testContainer123).Run() - - base.Cmd("run", "--name", testContainer0, testutil.CommonImage, "sh", "-euxc", "exit 0").AssertOK() - base.Cmd("run", "--name", testContainer123, testutil.CommonImage, "sh", "-euxc", "exit 123").AssertExitCode(123) - base.Cmd("ps", "-a").AssertOutWithFunc(func(stdout string) error { - if !strings.Contains(stdout, "Exited (0)") { - return fmt.Errorf("no entry for %q", testContainer0) - } - if !strings.Contains(stdout, "Exited (123)") { - return fmt.Errorf("no entry for %q", testContainer123) - } - return nil - }) + testCase := nerdtest.Setup() - inspect0 := base.InspectContainer(testContainer0) - assert.Equal(base.T, "exited", inspect0.State.Status) - assert.Equal(base.T, 0, inspect0.State.ExitCode) + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier("exit0")) + helpers.Anyhow("rm", "-f", data.Identifier("exit123")) + } + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "--name", data.Identifier("exit0"), testutil.CommonImage, "sh", "-euxc", "exit 0") + helpers.Command("run", "--name", data.Identifier("exit123"), testutil.CommonImage, "sh", "-euxc", "exit 123"). + Run(&test.Expected{ExitCode: 123}) + } + + testCase.Command = test.Command("ps", "-a") + + testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: expect.ExitCodeSuccess, + Errors: nil, + Output: expect.All( + expect.Match(regexp.MustCompile("Exited [(]123[)][A-Za-z0-9 ]+"+data.Identifier("exit123"))), + expect.Match(regexp.MustCompile("Exited [(]0[)][A-Za-z0-9 ]+"+data.Identifier("exit0"))), + func(stdout, info string, t *testing.T) { + assert.Equal(t, nerdtest.InspectContainer(helpers, data.Identifier("exit0")).State.Status, "exited") + assert.Equal(t, nerdtest.InspectContainer(helpers, data.Identifier("exit123")).State.Status, "exited") + }, + ), + } + } - inspect123 := base.InspectContainer(testContainer123) - assert.Equal(base.T, "exited", inspect123.State.Status) - assert.Equal(base.T, 123, inspect123.State.ExitCode) + testCase.Run(t) } func TestRunCIDFile(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - fileName := filepath.Join(t.TempDir(), "cid.file") + testCase := nerdtest.Setup() - base.Cmd("run", "--rm", "--cidfile", fileName, testutil.CommonImage).AssertOK() - defer os.Remove(fileName) + testCase.Setup = func(data test.Data, helpers test.Helpers) { + helpers.Ensure("run", "--rm", "--cidfile", data.Temp().Path("cid-file"), testutil.CommonImage) + data.Temp().Exists("cid-file") + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", "--cidfile", data.Temp().Path("cid-file"), testutil.CommonImage) + } - _, err := os.Stat(fileName) - assert.NilError(base.T, err) + // Docker will return 125 while nerdctl returns 1, so, generic fail instead of specific exit code + testCase.Expected = test.Expects(expect.ExitCodeGenericFail, []error{errors.New("container ID file found")}, nil) - base.Cmd("run", "--rm", "--cidfile", fileName, testutil.CommonImage).AssertFail() + testCase.Run(t) } func TestRunEnvFile(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - base.Env = append(base.Env, "HOST_ENV=ENV-IN-HOST") - - tID := testutil.Identifier(t) - file1, err := os.CreateTemp("", tID) - assert.NilError(base.T, err) - path1 := file1.Name() - defer file1.Close() - defer os.Remove(path1) - err = os.WriteFile(path1, []byte("# this is a comment line\nTESTKEY1=TESTVAL1"), 0666) - assert.NilError(base.T, err) - - file2, err := os.CreateTemp("", tID) - assert.NilError(base.T, err) - path2 := file2.Name() - defer file2.Close() - defer os.Remove(path2) - err = os.WriteFile(path2, []byte("# this is a comment line\nTESTKEY2=TESTVAL2\nHOST_ENV"), 0666) - assert.NilError(base.T, err) - - base.Cmd("run", "--rm", "--env-file", path1, "--env-file", path2, testutil.CommonImage, "sh", "-c", "echo -n $TESTKEY1").AssertOutExactly("TESTVAL1") - base.Cmd("run", "--rm", "--env-file", path1, "--env-file", path2, testutil.CommonImage, "sh", "-c", "echo -n $TESTKEY2").AssertOutExactly("TESTVAL2") - base.Cmd("run", "--rm", "--env-file", path1, "--env-file", path2, testutil.CommonImage, "sh", "-c", "echo -n $HOST_ENV").AssertOutExactly("ENV-IN-HOST") + testCase := nerdtest.Setup() + + testCase.Env = map[string]string{ + "HOST_ENV": "ENV-IN-HOST", + } + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save("# this is a comment line\nTESTKEY1=TESTVAL1", "env1-file") + data.Temp().Save("# this is a comment line\nTESTKEY2=TESTVAL2\nHOST_ENV", "env2-file") + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command( + "run", "--rm", + "--env-file", data.Temp().Path("env1-file"), + "--env-file", data.Temp().Path("env2-file"), + testutil.CommonImage, "env") + } + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.All( + expect.Contains("TESTKEY1=TESTVAL1"), + expect.Contains("TESTKEY2=TESTVAL2"), + expect.Contains("HOST_ENV=ENV-IN-HOST"), + )) + + testCase.Run(t) } func TestRunEnv(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - base.Env = append(base.Env, "CORGE=corge-value-in-host", "GARPLY=garply-value-in-host") - base.Cmd("run", "--rm", - "--env", "FOO=foo1,foo2", - "--env", "BAR=bar1 bar2", - "--env", "BAZ=", - "--env", "QUX", // not exported in OS - "--env", "QUUX=quux1", - "--env", "QUUX=quux2", - "--env", "CORGE", // OS exported - "--env", "GRAULT=grault_key=grault_value", // value contains `=` char - "--env", "GARPLY=", // OS exported - "--env", "WALDO=", // not exported in OS - - testutil.CommonImage, "env").AssertOutWithFunc(func(stdout string) error { - if !strings.Contains(stdout, "\nFOO=foo1,foo2\n") { - return errors.New("got bad FOO") - } - if !strings.Contains(stdout, "\nBAR=bar1 bar2\n") { - return errors.New("got bad BAR") - } - if !strings.Contains(stdout, "\nBAZ=\n") && runtime.GOOS != "windows" { - return errors.New("got bad BAZ") - } - if strings.Contains(stdout, "QUX") { - return errors.New("got bad QUX (should not be set)") - } - if !strings.Contains(stdout, "\nQUUX=quux2\n") { - return errors.New("got bad QUUX") - } - if !strings.Contains(stdout, "\nCORGE=corge-value-in-host\n") { - return errors.New("got bad CORGE") - } - if !strings.Contains(stdout, "\nGRAULT=grault_key=grault_value\n") { - return errors.New("got bad GRAULT") - } - if !strings.Contains(stdout, "\nGARPLY=\n") && runtime.GOOS != "windows" { - return errors.New("got bad GARPLY") - } - if !strings.Contains(stdout, "\nWALDO=\n") && runtime.GOOS != "windows" { - return errors.New("got bad WALDO") - } + testCase := nerdtest.Setup() - return nil - }) + testCase.Env = map[string]string{ + "CORGE": "corge-value-in-host", + "GARPLY": "garply-value-in-host", + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("run", "--rm", + "--env", "FOO=foo1,foo2", + "--env", "BAR=bar1 bar2", + "--env", "BAZ=", + "--env", "QUX", // not exported in OS + "--env", "QUUX=quux1", + "--env", "QUUX=quux2", + "--env", "CORGE", // OS exported + "--env", "GRAULT=grault_key=grault_value", // value contains `=` char + "--env", "GARPLY=", // OS exported + "--env", "WALDO=", // not exported in OS + testutil.CommonImage, "env") + } + + validate := []test.Comparator{ + expect.Contains("\nFOO=foo1,foo2\n"), + expect.Contains("\nBAR=bar1 bar2\n"), + expect.DoesNotContain("QUX"), + expect.Contains("\nQUUX=quux2\n"), + expect.Contains("\nCORGE=corge-value-in-host\n"), + expect.Contains("\nGRAULT=grault_key=grault_value\n"), + } + + if runtime.GOOS != "windows" { + validate = append( + validate, + expect.Contains("\nBAZ=\n"), + expect.Contains("\nGARPLY=\n"), + expect.Contains("\nWALDO=\n"), + ) + } + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.All(validate...)) + + testCase.Run(t) } -func TestRunHostnameEnv(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) - base.Cmd("run", "-i", "--rm", testutil.CommonImage). - CmdOption(testutil.WithStdin(strings.NewReader(`[[ "HOSTNAME=$(hostname)" == "$(env | grep HOSTNAME)" ]]`))). - AssertOK() +func TestRunHostnameEnv(t *testing.T) { + testCase := nerdtest.Setup() - if runtime.GOOS == "windows" { - t.Skip("run --hostname not implemented on Windows yet") + testCase.SubTests = []*test.Case{ + { + Description: "default hostname", + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("run", "--rm", "--quiet", testutil.CommonImage) + // Note: on Windows, just straight passing the command will not work (some cmd escaping weirdness?) + cmd.Feed(strings.NewReader(`[[ "HOSTNAME=$(hostname)" == "$(env | grep HOSTNAME)" ]]`)) + return cmd + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, nil), + }, + { + Description: "with --hostname", + // Windows does not support --hostname + Require: require.Not(require.Windows), + Command: test.Command("run", "--rm", "--quiet", "--hostname", "foobar", testutil.CommonImage, "env"), + Expected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains("HOSTNAME=foobar")), + }, } - base.Cmd("run", "--rm", "--hostname", "foobar", testutil.CommonImage, "env").AssertOutContains("HOSTNAME=foobar") + + testCase.Run(t) } func TestRunStdin(t *testing.T) { - t.Parallel() - base := testutil.NewBase(t) + testCase := nerdtest.Setup() const testStr = "test-run-stdin" - opts := []func(*testutil.Cmd){ - testutil.WithStdin(strings.NewReader(testStr)), + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("run", "--rm", "-i", testutil.CommonImage, "cat") + cmd.Feed(strings.NewReader(testStr)) + return cmd } - base.Cmd("run", "--rm", "-i", testutil.CommonImage, "cat").CmdOption(opts...).AssertOutExactly(testStr) + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(testStr)) + + testCase.Run(t) } func TestRunWithJsonFileLogDriver(t *testing.T) { @@ -493,7 +552,7 @@ COPY --from=builder /go/src/logger/logger / } // history: There was a bug that the --add-host items disappear when the another container created. -// This case ensures that it's doesn't happen. +// This test ensures that it doesn't happen. // (https://github.com/containerd/nerdctl/issues/2560) func TestRunAddHostRemainsWhenAnotherContainerCreated(t *testing.T) { if runtime.GOOS == "windows" { diff --git a/cmd/nerdctl/container/container_run_verify_linux_test.go b/cmd/nerdctl/container/container_run_verify_linux_test.go index 7d12342cbb3..e5d56373089 100644 --- a/cmd/nerdctl/container/container_run_verify_linux_test.go +++ b/cmd/nerdctl/container/container_run_verify_linux_test.go @@ -20,38 +20,66 @@ import ( "fmt" "testing" - "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" + "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/require" + "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/v2/pkg/testutil" - "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry" ) func TestRunVerifyCosign(t *testing.T) { - testutil.RequireExecutable(t, "cosign") - testutil.DockerIncompatible(t) - testutil.RequiresBuild(t) - testutil.RegisterBuildCacheCleanup(t) - t.Parallel() - - base := testutil.NewBase(t) - base.Env = append(base.Env, "COSIGN_PASSWORD=1") - - keyPair := helpers.NewCosignKeyPair(t, "cosign-key-pair", "1") - reg := testregistry.NewWithNoAuth(base, 0, false) - t.Cleanup(func() { - keyPair.Cleanup() - reg.Cleanup(nil) - }) - - tID := testutil.Identifier(t) - testImageRef := fmt.Sprintf("127.0.0.1:%d/%s", reg.Port, tID) dockerfile := fmt.Sprintf(`FROM %s CMD ["echo", "nerdctl-build-test-string"] `, testutil.CommonImage) - buildCtx := helpers.CreateBuildContext(t, dockerfile) + testCase := nerdtest.Setup() + + var reg *registry.Server + + testCase.Require = require.All( + require.Binary("cosign"), + require.Not(nerdtest.Docker), + nerdtest.Build, + nerdtest.Registry, + ) + + testCase.Env["COSIGN_PASSWORD"] = "1" + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerfile, "Dockerfile") + pri, pub := nerdtest.GenerateCosignKeyPair(data, helpers, "1") + reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false) + reg.Setup(data, helpers) + + testImageRef := fmt.Sprintf("127.0.0.1:%d/%s", reg.Port, data.Identifier("push-cosign-image")) + helpers.Ensure("build", "-t", testImageRef, data.Temp().Path()) + helpers.Ensure("push", testImageRef, "--sign=cosign", "--cosign-key="+pri) + + data.Labels().Set("public_key", pub) + data.Labels().Set("image_ref", testImageRef) + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + if reg != nil { + reg.Cleanup(data, helpers) + } + } + + testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand { + helpers.Fail( + "run", "--rm", "--verify=cosign", + "--cosign-key=dummy", + data.Labels().Get("image_ref")) + + return helpers.Command( + "run", "--rm", "--verify=cosign", + "--cosign-key="+data.Labels().Get("public_key"), + data.Labels().Get("image_ref")) + } + + testCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, nil) - base.Cmd("build", "-t", testImageRef, buildCtx).AssertOK() - base.Cmd("push", testImageRef, "--sign=cosign", "--cosign-key="+keyPair.PrivateKey).AssertOK() - base.Cmd("run", "--rm", "--verify=cosign", "--cosign-key="+keyPair.PublicKey, testImageRef).AssertOK() - base.Cmd("run", "--rm", "--verify=cosign", "--cosign-key=dummy", testImageRef).AssertFail() + testCase.Run(t) } diff --git a/cmd/nerdctl/helpers/testing_linux.go b/cmd/nerdctl/helpers/testing_linux.go index dd5901177de..bf63686f0c8 100644 --- a/cmd/nerdctl/helpers/testing_linux.go +++ b/cmd/nerdctl/helpers/testing_linux.go @@ -19,7 +19,6 @@ package helpers import ( "fmt" "io" - "net" "os" "os/exec" "path/filepath" @@ -33,64 +32,6 @@ import ( "github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil" ) -func FindIPv6(output string) net.IP { - var ipv6 string - lines := strings.Split(output, "\n") - for _, line := range lines { - if strings.Contains(line, "inet6") { - fields := strings.Fields(line) - if len(fields) > 1 { - ipv6 = strings.Split(fields[1], "/")[0] - break - } - } - } - return net.ParseIP(ipv6) -} - -type JweKeyPair struct { - Prv string - Pub string - Cleanup func() -} - -func NewJWEKeyPair(t testing.TB) *JweKeyPair { - testutil.RequireExecutable(t, "openssl") - td, err := os.MkdirTemp(t.TempDir(), "jwe-key-pair") - assert.NilError(t, err) - prv := filepath.Join(td, "mykey.pem") - pub := filepath.Join(td, "mypubkey.pem") - cmds := [][]string{ - // Exec openssl commands to ensure that nerdctl is compatible with the output of openssl commands. - // Do NOT refactor this function to use "crypto/rsa" stdlib. - {"openssl", "genrsa", "-out", prv}, - {"openssl", "rsa", "-in", prv, "-pubout", "-out", pub}, - } - for _, f := range cmds { - cmd := exec.Command(f[0], f[1:]...) - if out, err := cmd.CombinedOutput(); err != nil { - t.Fatalf("failed to run %v: %v (%q)", cmd.Args, err, string(out)) - } - } - return &JweKeyPair{ - Prv: prv, - Pub: pub, - Cleanup: func() { - _ = os.RemoveAll(td) - }, - } -} - -func RequiresSoci(base *testutil.Base) { - info := base.Info() - for _, p := range info.Plugins.Storage { - if p == "soci" { - return - } - } - base.T.Skip("test requires soci") -} - type CosignKeyPair struct { PublicKey string PrivateKey string diff --git a/cmd/nerdctl/image/image_encrypt_linux_test.go b/cmd/nerdctl/image/image_encrypt_linux_test.go index f8d34dc03cb..40cb742a10c 100644 --- a/cmd/nerdctl/image/image_encrypt_linux_test.go +++ b/cmd/nerdctl/image/image_encrypt_linux_test.go @@ -26,7 +26,6 @@ import ( "github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/test" - testhelpers "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" @@ -36,7 +35,6 @@ func TestImageEncryptJWE(t *testing.T) { nerdtest.Setup() var registry *testregistry.RegistryServer - var keyPair *testhelpers.JweKeyPair const remoteImageKey = "remoteImageKey" @@ -50,18 +48,20 @@ func TestImageEncryptJWE(t *testing.T) { Cleanup: func(data test.Data, helpers test.Helpers) { if registry != nil { registry.Cleanup(nil) - keyPair.Cleanup() helpers.Anyhow("rmi", "-f", data.Labels().Get(remoteImageKey)) } helpers.Anyhow("rmi", "-f", data.Identifier("decrypted")) }, Setup: func(data test.Data, helpers test.Helpers) { + pri, pub := nerdtest.GenerateJWEKeyPair(data, helpers) + data.Labels().Set("private", pri) + data.Labels().Set("public", pub) + base := testutil.NewBase(t) registry = testregistry.NewWithNoAuth(base, 0, false) - keyPair = testhelpers.NewJWEKeyPair(t) helpers.Ensure("pull", "--quiet", testutil.CommonImage) encryptImageRef := fmt.Sprintf("127.0.0.1:%d/%s:encrypted", registry.Port, data.Identifier()) - helpers.Ensure("image", "encrypt", "--recipient=jwe:"+keyPair.Pub, testutil.CommonImage, encryptImageRef) + helpers.Ensure("image", "encrypt", "--recipient=jwe:"+pub, testutil.CommonImage, encryptImageRef) inspector := helpers.Capture("image", "inspect", "--mode=native", "--format={{len .Index.Manifests}}", encryptImageRef) assert.Equal(t, inspector, "1\n") inspector = helpers.Capture("image", "inspect", "--mode=native", "--format={{json .Manifest.Layers}}", encryptImageRef) @@ -74,8 +74,8 @@ func TestImageEncryptJWE(t *testing.T) { Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { helpers.Fail("pull", data.Labels().Get(remoteImageKey)) helpers.Ensure("pull", "--quiet", "--unpack=false", data.Labels().Get(remoteImageKey)) - helpers.Fail("image", "decrypt", "--key="+keyPair.Pub, data.Labels().Get(remoteImageKey), data.Identifier("decrypted")) // decryption needs prv key, not pub key - return helpers.Command("image", "decrypt", "--key="+keyPair.Prv, data.Labels().Get(remoteImageKey), data.Identifier("decrypted")) + helpers.Fail("image", "decrypt", "--key="+data.Labels().Get("public"), data.Labels().Get(remoteImageKey), data.Identifier("decrypted")) // decryption needs prv key, not pub key + return helpers.Command("image", "decrypt", "--key="+data.Labels().Get("private"), data.Labels().Get(remoteImageKey), data.Identifier("decrypted")) }, Expected: test.Expects(0, nil, nil), } diff --git a/cmd/nerdctl/image/image_pull_linux_test.go b/cmd/nerdctl/image/image_pull_linux_test.go index 6156b82a3e2..6dd12b34aba 100644 --- a/cmd/nerdctl/image/image_pull_linux_test.go +++ b/cmd/nerdctl/image/image_pull_linux_test.go @@ -18,8 +18,6 @@ package image import ( "fmt" - "os" - "path/filepath" "strconv" "strings" "testing" @@ -30,17 +28,19 @@ import ( "github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/test" - testhelpers "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" - "github.com/containerd/nerdctl/v2/pkg/testutil/testregistry" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry" ) func TestImagePullWithCosign(t *testing.T) { + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-build-test-string"] + `, testutil.CommonImage) + nerdtest.Setup() - var registry *testregistry.RegistryServer - var keyPair *testhelpers.CosignKeyPair + var reg *registry.Server testCase := &test.Case{ Require: require.All( @@ -48,45 +48,47 @@ func TestImagePullWithCosign(t *testing.T) { nerdtest.Build, require.Binary("cosign"), require.Not(nerdtest.Docker), + nerdtest.Registry, ), + Env: map[string]string{ "COSIGN_PASSWORD": "1", }, - Setup: func(data test.Data, helpers test.Helpers) { - keyPair = testhelpers.NewCosignKeyPair(t, "cosign-key-pair", "1") - base := testutil.NewBase(t) - registry = testregistry.NewWithNoAuth(base, 0, false) - testImageRef := fmt.Sprintf("%s:%d/%s", "127.0.0.1", registry.Port, data.Identifier()) - dockerfile := fmt.Sprintf(`FROM %s -CMD ["echo", "nerdctl-build-test-string"] - `, testutil.CommonImage) + Setup: func(data test.Data, helpers test.Helpers) { + data.Temp().Save(dockerfile, "Dockerfile") + pri, pub := nerdtest.GenerateCosignKeyPair(data, helpers, "1") + reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false) + reg.Setup(data, helpers) + testImageRef := fmt.Sprintf("%s:%d/%s", "127.0.0.1", reg.Port, data.Identifier()) buildCtx := data.Temp().Path() - err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) - assert.NilError(helpers.T(), err) + helpers.Ensure("build", "-t", testImageRef+":one", buildCtx) helpers.Ensure("build", "-t", testImageRef+":two", buildCtx) - helpers.Ensure("push", "--sign=cosign", "--cosign-key="+keyPair.PrivateKey, testImageRef+":one") - helpers.Ensure("push", "--sign=cosign", "--cosign-key="+keyPair.PrivateKey, testImageRef+":two") - helpers.Ensure("rmi", "-f", testImageRef) - data.Labels().Set("imageref", testImageRef) + helpers.Ensure("push", "--sign=cosign", "--cosign-key="+pri, testImageRef+":one") + helpers.Ensure("push", "--sign=cosign", "--cosign-key="+pri, testImageRef+":two") + + data.Labels().Set("public_key", pub) + data.Labels().Set("image_ref", testImageRef) }, + Cleanup: func(data test.Data, helpers test.Helpers) { - if keyPair != nil { - keyPair.Cleanup() - } - if registry != nil { - registry.Cleanup(nil) - testImageRef := fmt.Sprintf("%s:%d/%s", "127.0.0.1", registry.Port, data.Identifier()) + if reg != nil { + reg.Cleanup(data, helpers) + testImageRef := data.Labels().Get("image_ref") helpers.Anyhow("rmi", "-f", testImageRef+":one") helpers.Anyhow("rmi", "-f", testImageRef+":two") } }, + SubTests: []*test.Case{ { Description: "Pull with the correct key", Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - return helpers.Command("pull", "--quiet", "--verify=cosign", "--cosign-key="+keyPair.PublicKey, data.Labels().Get("imageref")+":one") + return helpers.Command( + "pull", "--quiet", "--verify=cosign", + "--cosign-key="+data.Labels().Get("public_key"), + data.Labels().Get("image_ref")+":one") }, Expected: test.Expects(0, nil, nil), }, @@ -96,8 +98,8 @@ CMD ["echo", "nerdctl-build-test-string"] "COSIGN_PASSWORD": "2", }, Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - newKeyPair := testhelpers.NewCosignKeyPair(t, "cosign-key-pair-test", "2") - return helpers.Command("pull", "--quiet", "--verify=cosign", "--cosign-key="+newKeyPair.PublicKey, data.Labels().Get("imageref")+":two") + _, pub := nerdtest.GenerateCosignKeyPair(data, helpers, "2") + return helpers.Command("pull", "--quiet", "--verify=cosign", "--cosign-key="+pub, data.Labels().Get("image_ref")+":two") }, Expected: test.Expects(12, nil, nil), }, @@ -110,42 +112,44 @@ CMD ["echo", "nerdctl-build-test-string"] func TestImagePullPlainHttpWithDefaultPort(t *testing.T) { nerdtest.Setup() - var registry *testregistry.RegistryServer + var reg *registry.Server + dockerfile := fmt.Sprintf(`FROM %s +CMD ["echo", "nerdctl-build-test-string"] + `, testutil.CommonImage) testCase := &test.Case{ Require: require.All( require.Linux, require.Not(nerdtest.Docker), nerdtest.Build, + nerdtest.Registry, ), + Setup: func(data test.Data, helpers test.Helpers) { - base := testutil.NewBase(t) - registry = testregistry.NewWithNoAuth(base, 80, false) + data.Temp().Save(dockerfile, "Dockerfile") + reg = nerdtest.RegistryWithNoAuth(data, helpers, 80, false) + reg.Setup(data, helpers) testImageRef := fmt.Sprintf("%s/%s:%s", - registry.IP.String(), data.Identifier(), strings.Split(testutil.CommonImage, ":")[1]) - dockerfile := fmt.Sprintf(`FROM %s -CMD ["echo", "nerdctl-build-test-string"] - `, testutil.CommonImage) - + reg.IP.String(), data.Identifier(), strings.Split(testutil.CommonImage, ":")[1]) buildCtx := data.Temp().Path() - err := os.WriteFile(filepath.Join(buildCtx, "Dockerfile"), []byte(dockerfile), 0o600) - assert.NilError(helpers.T(), err) + helpers.Ensure("build", "-t", testImageRef, buildCtx) helpers.Ensure("--insecure-registry", "push", testImageRef) helpers.Ensure("rmi", "-f", testImageRef) + + data.Labels().Set("image_ref", testImageRef) }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { - testImageRef := fmt.Sprintf("%s/%s:%s", - registry.IP.String(), data.Identifier(), strings.Split(testutil.CommonImage, ":")[1]) - return helpers.Command("--insecure-registry", "pull", testImageRef) + return helpers.Command("--insecure-registry", "pull", data.Labels().Get("image_ref")) }, + Expected: test.Expects(0, nil, nil), + Cleanup: func(data test.Data, helpers test.Helpers) { - if registry != nil { - registry.Cleanup(nil) - testImageRef := fmt.Sprintf("%s/%s:%s", - registry.IP.String(), data.Identifier(), strings.Split(testutil.CommonImage, ":")[1]) - helpers.Anyhow("rmi", "-f", testImageRef) + if reg != nil { + reg.Cleanup(data, helpers) + helpers.Anyhow("rmi", "-f", data.Labels().Get("image_ref")) } }, } @@ -165,6 +169,8 @@ func TestImagePullSoci(t *testing.T) { // NOTE: these tests cannot be run in parallel, as they depend on the output of host `mount` // They also feel prone to raciness... + NoParallel: true, + SubTests: []*test.Case{ { Description: "Run without specifying SOCI index", @@ -190,13 +196,14 @@ func TestImagePullSoci(t *testing.T) { }, Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - Output: func(stdout string, info string, t *testing.T) { + Output: func(stdout string, _ string, t *testing.T) { remoteSnapshotsInitialCount, _ := strconv.Atoi(data.Labels().Get("remoteSnapshotsInitialCount")) remoteSnapshotsActualCount := strings.Count(stdout, "fuse.rawBridge") assert.Equal(t, data.Labels().Get("remoteSnapshotsExpectedCount"), strconv.Itoa(remoteSnapshotsActualCount-remoteSnapshotsInitialCount), - info) + "expected remote snapshot count to match", + ) }, } }, @@ -231,7 +238,7 @@ func TestImagePullSoci(t *testing.T) { assert.Equal(t, data.Labels().Get("remoteSnapshotsExpectedCount"), strconv.Itoa(remoteSnapshotsActualCount-remoteSnapshotsInitialCount), - info) + "expected remote snapshot count to match") }, } }, diff --git a/cmd/nerdctl/ipfs/ipfs_simple_linux_test.go b/cmd/nerdctl/ipfs/ipfs_simple_linux_test.go index eb1262cc696..9edd24f5af1 100644 --- a/cmd/nerdctl/ipfs/ipfs_simple_linux_test.go +++ b/cmd/nerdctl/ipfs/ipfs_simple_linux_test.go @@ -24,7 +24,6 @@ import ( "github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/test" - testhelpers "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) @@ -168,14 +167,12 @@ func TestIPFSSimple(t *testing.T) { helpers.Ensure("pull", "--quiet", "ipfs://"+data.Labels().Get(mainImageCIDKey)) // Prep a key pair - keyPair := testhelpers.NewJWEKeyPair(t) - // FIXME: this will only cleanup when the group is done, not right, but it works - t.Cleanup(keyPair.Cleanup) - data.Labels().Set("pub", keyPair.Pub) - data.Labels().Set("prv", keyPair.Prv) + pri, pub := nerdtest.GenerateJWEKeyPair(data, helpers) + data.Labels().Set("prv", pri) + data.Labels().Set("pub", pub) // Encrypt the image, and verify it is encrypted - helpers.Ensure("image", "encrypt", "--recipient=jwe:"+keyPair.Pub, data.Labels().Get(mainImageCIDKey), data.Identifier("encrypted")) + helpers.Ensure("image", "encrypt", "--recipient=jwe:"+pub, data.Labels().Get(mainImageCIDKey), data.Identifier("encrypted")) cmd := helpers.Command("image", "inspect", "--mode=native", "--format={{len .Index.Manifests}}", data.Identifier("encrypted")) cmd.Run(&test.Expected{ Output: expect.Equals("1\n"), diff --git a/cmd/nerdctl/network/network_create_linux_test.go b/cmd/nerdctl/network/network_create_linux_test.go index 5dd82655390..01ed943467f 100644 --- a/cmd/nerdctl/network/network_create_linux_test.go +++ b/cmd/nerdctl/network/network_create_linux_test.go @@ -17,6 +17,7 @@ package network import ( + "fmt" "net" "strings" "testing" @@ -26,7 +27,6 @@ import ( "github.com/containerd/nerdctl/mod/tigron/expect" "github.com/containerd/nerdctl/mod/tigron/test" - ipv6helper "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) @@ -100,8 +100,8 @@ func TestNetworkCreate(t *testing.T) { ExitCode: 0, Output: func(stdout string, info string, t *testing.T) { _, subnet, _ := net.ParseCIDR(data.Labels().Get("subnetStr")) - ip := ipv6helper.FindIPv6(stdout) - assert.Assert(t, subnet.Contains(ip), info) + ip := nerdtest.FindIPv6(stdout) + assert.Assert(t, subnet.Contains(ip), fmt.Sprintf("subnet %s contains ip %s", subnet, ip)) }, } }, diff --git a/pkg/testutil/nerdtest/utilities.go b/pkg/testutil/nerdtest/utilities.go index 71b2d662f5c..a5c15eede0c 100644 --- a/pkg/testutil/nerdtest/utilities.go +++ b/pkg/testutil/nerdtest/utilities.go @@ -18,12 +18,15 @@ package nerdtest import ( "encoding/json" + "net" + "strings" "testing" "time" "gotest.tools/v3/assert" "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/require" "github.com/containerd/nerdctl/mod/tigron/test" "github.com/containerd/nerdctl/mod/tigron/tig" @@ -132,3 +135,59 @@ func EnsureContainerStarted(helpers test.Helpers, con string) { helpers.T().Fatalf("container %s still not running after %d retries", con, maxRetry) } } + +func GenerateJWEKeyPair(data test.Data, helpers test.Helpers) (string, string) { + helpers.T().Helper() + + path := "jwe-key-pair" + data.Temp().Dir(path) + + pass, message := require.Binary("openssl").Check(data, helpers) + if !pass { + helpers.T().Skip(message) + } + + pri := data.Temp().Path(path, "mykey.pem") + pub := data.Temp().Path(path, "mypubkey.pem") + + // Exec openssl commands to ensure that nerdctl is compatible with the output of openssl commands. + // Do NOT refactor this function to use "crypto/rsa" stdlib. + helpers.Custom("openssl", "genrsa", "-out", pri).Run(&test.Expected{}) + helpers.Custom("openssl", "rsa", "-in", pri, "-pubout", "-out", pub).Run(&test.Expected{}) + + return pri, pub +} + +func GenerateCosignKeyPair(data test.Data, helpers test.Helpers, password string) (pri string, pub string) { + helpers.T().Helper() + + path := "cosign-key-pair" + data.Temp().Dir(path) + + pass, message := require.Binary("cosign").Check(data, helpers) + if !pass { + helpers.T().Skip(message) + } + + cmd := helpers.Custom("cosign", "generate-key-pair") + cmd.WithCwd(data.Temp().Path(path)) + cmd.Setenv("COSIGN_PASSWORD", password) + cmd.Run(&test.Expected{}) + + return data.Temp().Path(path, "cosign.key"), data.Temp().Path(path, "cosign.pub") +} + +func FindIPv6(output string) net.IP { + var ipv6 string + lines := strings.Split(output, "\n") + for _, line := range lines { + if strings.Contains(line, "inet6") { + fields := strings.Fields(line) + if len(fields) > 1 { + ipv6 = strings.Split(fields[1], "/")[0] + break + } + } + } + return net.ParseIP(ipv6) +} diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index 525225fb996..f12c704e8bb 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -413,14 +413,6 @@ func (c *Cmd) AssertOutContains(s string) { c.Assert(expected) } -func (c *Cmd) AssertErrContains(s string) { - c.Base.T.Helper() - expected := icmd.Expected{ - Err: s, - } - c.Assert(expected) -} - func (c *Cmd) AssertCombinedOutContains(s string) { c.Base.T.Helper() res := c.runIfNecessary() @@ -465,16 +457,6 @@ func (c *Cmd) AssertOutNotContains(s string) { }) } -func (c *Cmd) AssertErrNotContains(s string) { - c.Base.T.Helper() - c.AssertOutWithFunc(func(stderr string) error { - if strings.Contains(stderr, s) { - return fmt.Errorf("expected stdout to not contain %q", s) - } - return nil - }) -} - func (c *Cmd) AssertOutExactly(s string) { c.Base.T.Helper() fn := func(stdout string) error { @@ -486,24 +468,6 @@ func (c *Cmd) AssertOutExactly(s string) { c.AssertOutWithFunc(fn) } -func (c *Cmd) AssertOutStreamsExactly(stdout, stderr string) { - c.Base.T.Helper() - fn := func(sout, serr string) error { - msg := "" - if sout != stdout { - msg += fmt.Sprintf("stdout mismatch, expected %q, got %q\n", stdout, sout) - } - if serr != stderr { - msg += fmt.Sprintf("stderr mismatch, expected %q, got %q\n", stderr, serr) - } - if msg != "" { - return errors.New(msg) - } - return nil - } - c.AssertOutStreamsWithFunc(fn) -} - func (c *Cmd) AssertOutWithFunc(fn func(stdout string) error) { c.Base.T.Helper() res := c.runIfNecessary() @@ -511,13 +475,6 @@ func (c *Cmd) AssertOutWithFunc(fn func(stdout string) error) { assert.NilError(c.Base.T, fn(res.Stdout()), res.Combined()) } -func (c *Cmd) AssertOutStreamsWithFunc(fn func(stdout, stderr string) error) { - c.Base.T.Helper() - res := c.runIfNecessary() - assert.Equal(c.Base.T, 0, res.ExitCode, res) - assert.NilError(c.Base.T, fn(res.Stdout(), res.Stderr()), res.Combined()) -} - func (c *Cmd) Out() string { c.Base.T.Helper() res := c.runIfNecessary() @@ -525,13 +482,6 @@ func (c *Cmd) Out() string { return res.Stdout() } -func (c *Cmd) OutLines() []string { - c.Base.T.Helper() - out := c.Out() - // FIXME: improve memory efficiency - return strings.Split(out, "\n") -} - type Target = string const ( @@ -544,7 +494,6 @@ var ( flagTestKillDaemon bool flagTestIPv6 bool flagTestKube bool - flagVerbose bool flagTestFlaky bool ) @@ -558,9 +507,6 @@ func M(m *testing.M) { flag.BoolVar(&flagTestIPv6, "test.only-ipv6", false, "enable tests on IPv6") flag.BoolVar(&flagTestKube, "test.only-kubernetes", false, "enable tests on Kubernetes") flag.BoolVar(&flagTestFlaky, "test.only-flaky", false, "enable testing of flaky tests only (if false, flaky tests are ignored)") - if flag.Lookup("test.v") != nil { - flagVerbose = true - } flag.Parse() os.Exit(func() int { @@ -638,8 +584,6 @@ func IsDocker() bool { return GetTarget() == Docker } -func GetVerbose() bool { return flagVerbose } - func DockerIncompatible(t testing.TB) { if IsDocker() { t.Skip("test is incompatible with Docker") @@ -667,22 +611,6 @@ func RequireExecPlatform(t testing.TB, ss ...string) { } } -func RequireDaemonVersion(b *Base, constraint string) { - b.T.Helper() - c, err := semver.NewConstraint(constraint) - if err != nil { - b.T.Fatal(err) - } - info := b.Info() - sv, err := semver.NewVersion(info.ServerVersion) - if err != nil { - b.T.Skip(err) - } - if !c.Check(sv) { - b.T.Skipf("version %v does not satisfy constraints %v", sv, c) - } -} - func RequireKernelVersion(t testing.TB, constraint string) { t.Helper() c, err := semver.NewConstraint(constraint) @@ -839,3 +767,8 @@ func RegisterBuildCacheCleanup(t *testing.T) { NewBase(t).Cmd("builder", "prune", "--all", "--force").Run() }) } + +func mirrorOf(s string) string { + // plain mirror, NOT stargz-converted images + return fmt.Sprintf("ghcr.io/stargz-containers/%s-org", s) +} diff --git a/pkg/testutil/testutil_freebsd.go b/pkg/testutil/testutil_freebsd.go index 63a79f07a02..9761008585f 100644 --- a/pkg/testutil/testutil_freebsd.go +++ b/pkg/testutil/testutil_freebsd.go @@ -16,8 +16,6 @@ package testutil -import "fmt" - const ( CommonImage = "docker.io/knast/freebsd:13-STABLE" @@ -35,8 +33,3 @@ var ( NginxAlpineImage = mirrorOf("nginx:1.19-alpine") GolangImage = mirrorOf("golang:1.18") ) - -func mirrorOf(s string) string { - // plain mirror, NOT stargz-converted images - return fmt.Sprintf("ghcr.io/stargz-containers/%s-org", s) -} diff --git a/pkg/testutil/testutil_linux.go b/pkg/testutil/testutil_linux.go index 1224bf121ee..f7ab0688d42 100644 --- a/pkg/testutil/testutil_linux.go +++ b/pkg/testutil/testutil_linux.go @@ -16,15 +16,6 @@ package testutil -import ( - "fmt" -) - -func mirrorOf(s string) string { - // plain mirror, NOT stargz-converted images - return fmt.Sprintf("ghcr.io/stargz-containers/%s-org", s) -} - var ( BusyboxImage = "ghcr.io/containerd/busybox:1.36" AlpineImage = mirrorOf("alpine:3.13") diff --git a/pkg/testutil/testutil_windows.go b/pkg/testutil/testutil_windows.go index 868e5df1cd8..d1b830da6bf 100644 --- a/pkg/testutil/testutil_windows.go +++ b/pkg/testutil/testutil_windows.go @@ -43,9 +43,6 @@ 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" - 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. // (e.g. attempting to connect using http to an https endpoint). @@ -56,6 +53,9 @@ const ( ) var ( + GolangImage = mirrorOf("fixme-test-using-this-image-is-disabled-on-windows") + AlpineImage = mirrorOf("fixme-test-using-this-image-is-disabled-on-windows") + hypervContainer bool hypervSupported bool hypervSupportedOnce sync.Once