diff --git a/pkg/cmd/extension/build/cmd_test.go b/pkg/cmd/extension/build/cmd_test.go index 30cc6fd3..10043be2 100644 --- a/pkg/cmd/extension/build/cmd_test.go +++ b/pkg/cmd/extension/build/cmd_test.go @@ -21,7 +21,8 @@ import ( "github.com/stretchr/testify/require" - cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) @@ -30,20 +31,20 @@ const relativeWorkspaceDir = "testdata/workspace" func TestGetEnvoyExtensionBuildValidateFlag(t *testing.T) { type testCase struct { - flag string - flagValue string + name string + args []string expectedErr string } tests := []testCase{ { - flag: "--toolchain-container-image", - flagValue: "?invalid value?", + name: "--toolchain-container-image with invalid value", + args: []string{"--toolchain-container-image", "?invalid value?"}, expectedErr: `"?invalid value?" is not a valid image name: invalid reference format`, }, { - flag: "--toolchain-container-options", - flagValue: "imbalanced ' quotes", + name: "--toolchain-container-options has imbalanced quotes", + args: []string{"--toolchain-container-options", "imbalanced ' quotes"}, expectedErr: `"imbalanced ' quotes" is not a valid command line string`, }, } @@ -51,113 +52,113 @@ func TestGetEnvoyExtensionBuildValidateFlag(t *testing.T) { for _, test := range tests { test := test // pin! see https://github.com/kyoh86/scopelint for why - t.Run(test.flag+"="+test.flagValue, func(t *testing.T) { - // Run "getenvoy extension build" with the flags we are testing - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "build", test.flag, test.flagValue}) - err := cmdutil.Execute(cmd) - require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, cmd) + t.Run(test.name, func(t *testing.T) { + // Run "getenvoy extension build" with the args we are testing + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs(append([]string{"extension", "build"}, test.args...)) + err := cmdutil.Execute(c) + require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, c) // Verify the command failed with the expected error - require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + require.Empty(t, stdout, `expected no stdout running [%v]`, c) expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension build --help' for usage.\n", test.expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) }) } } func TestGetEnvoyExtensionBuildFailsOutsideWorkspaceDirectory(t *testing.T) { // Change to a non-workspace dir - dir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..") + dir, revertWd := RequireChDir(t, relativeWorkspaceDir+"/..") defer revertWd() // Run "getenvoy extension build" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "build"}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "build"}) + err := cmdutil.Execute(c) // Verify the command failed with the expected error expectedErr := "there is no extension directory at or above: " + dir - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) - require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) + require.Empty(t, stdout, `expected no stdout running [%v]`, c) expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension build --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } func TestGetEnvoyExtensionBuild(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked - dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + dockerDir, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) defer revertPath() // "getenvoy extension build" must be in a valid workspace directory - workspaceDir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Fake the current user so we can test it is used in the docker args expectedUser := user.User{Uid: "1001", Gid: "1002"} - revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd.OverrideGetCurrentUser(&expectedUser) defer revertGetCurrentUser() // Run "getenvoy extension build" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "build"}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "build"}) + err := cmdutil.Execute(c) // We expect docker to run from the correct path, as the current user and mount a volume for the correct workspace. expectedDockerExec := fmt.Sprintf("%s/docker run -u %s:%s --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm", dockerDir, expectedUser.Uid, expectedUser.Gid, workspaceDir) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) - require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, cmd) - require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) + require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, c) + require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, c) } // This tests --toolchain-container flags become docker command options func TestGetEnvoyExtensionBuildWithDockerOptions(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked - _, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + _, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) defer revertPath() // "getenvoy extension build" must be in a valid workspace directory - _, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + _, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Run "getenvoy extension build" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "build", + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "build", "--toolchain-container-image", "build/image", "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, }) - err := cmdutil.Execute(cmd) + err := cmdutil.Execute(c) // Verify the command's stdout includes the init args. TestGetEnvoyExtensionBuild tests the rest of stdout. - require.NoError(t, err, `expected no error running [%v]`, cmd) - require.Regexp(t, ".*--init -e VAR=VALUE -v /host:/container build/image build.*", stdout.String(), `expected stdout running [%v]`, cmd) - require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) + require.Regexp(t, ".*--init -e VAR=VALUE -v /host:/container build/image build.*", stdout, `expected stdout running [%v]`, c) + require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, c) } // TestGetEnvoyExtensionBuildFail ensures build failures show useful information in stderr func TestGetEnvoyExtensionBuildFail(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked, and force a failure. - dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + dockerDir, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) defer revertPath() // "getenvoy extension build" must be in a valid workspace directory - workspaceDir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Fake the current user so we can test it is used in the docker args expectedUser := user.User{Uid: "1001", Gid: "1002"} - revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd.OverrideGetCurrentUser(&expectedUser) defer revertGetCurrentUser() // "-e DOCKER_EXIT_CODE=3" is a special instruction handled in the fake docker script toolchainOptions := "-e DOCKER_EXIT_CODE=3" // Run "getenvoy extension build" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "build", "--toolchain-container-options", toolchainOptions}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "build", "--toolchain-container-options", toolchainOptions}) + err := cmdutil.Execute(c) // We expect the exit instruction to have gotten to the fake docker script, along with the default options. expectedDockerExec := fmt.Sprintf("%s/docker run -u %s:%s --rm -t -v %s:/source -w /source --init %s getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm", @@ -165,12 +166,12 @@ func TestGetEnvoyExtensionBuildFail(t *testing.T) { // Verify the command failed with the expected error. expectedErr := fmt.Sprintf(`failed to build Envoy extension using "default" toolchain: failed to execute an external command "%s": exit status 3`, expectedDockerExec) - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) // We should see stdout because the docker script was invoked. - require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, cmd) + require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, c) // We also expect "docker stderr" in the output for the same reason. expectedStderr := fmt.Sprintf("docker stderr\nError: %s\n\nRun 'getenvoy extension build --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } diff --git a/pkg/cmd/extension/clean/cmd_test.go b/pkg/cmd/extension/clean/cmd_test.go index d6683674..9e2288c4 100644 --- a/pkg/cmd/extension/clean/cmd_test.go +++ b/pkg/cmd/extension/clean/cmd_test.go @@ -21,7 +21,8 @@ import ( "github.com/stretchr/testify/require" - cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) @@ -30,20 +31,20 @@ const relativeWorkspaceDir = "testdata/workspace" func TestGetEnvoyExtensionCleanValidateFlag(t *testing.T) { type testCase struct { - flag string - flagValue string + name string + args []string expectedErr string } tests := []testCase{ { - flag: "--toolchain-container-image", - flagValue: "?invalid value?", + name: "--toolchain-container-image with invalid value", + args: []string{"--toolchain-container-image", "?invalid value?"}, expectedErr: `"?invalid value?" is not a valid image name: invalid reference format`, }, { - flag: "--toolchain-container-options", - flagValue: "imbalanced ' quotes", + name: "--toolchain-container-options has imbalanced quotes", + args: []string{"--toolchain-container-options", "imbalanced ' quotes"}, expectedErr: `"imbalanced ' quotes" is not a valid command line string`, }, } @@ -51,113 +52,113 @@ func TestGetEnvoyExtensionCleanValidateFlag(t *testing.T) { for _, test := range tests { test := test // pin! see https://github.com/kyoh86/scopelint for why - t.Run(test.flag+"="+test.flagValue, func(t *testing.T) { - // Run "getenvoy extension clean" with the flags we are testing - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "clean", test.flag, test.flagValue}) - err := cmdutil.Execute(cmd) - require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, cmd) + t.Run(test.name, func(t *testing.T) { + // Run "getenvoy extension clean" with the args we are testing + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs(append([]string{"extension", "clean"}, test.args...)) + err := cmdutil.Execute(c) + require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, c) // Verify the command failed with the expected error - require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension clean --help' for usage.\n", test.expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) }) } } func TestGetEnvoyExtensionCleanFailsOutsideWorkspaceDirectory(t *testing.T) { // Change to a non-workspace dir - dir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..") + dir, revertWd := RequireChDir(t, relativeWorkspaceDir+"/..") defer revertWd() // Run "getenvoy extension clean" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "clean"}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "clean"}) + err := cmdutil.Execute(c) // Verify the command failed with the expected error expectedErr := "there is no extension directory at or above: " + dir - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) - require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension clean --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } func TestGetEnvoyExtensionClean(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked - dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + dockerDir, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) defer revertPath() // "getenvoy extension clean" must be in a valid workspace directory - workspaceDir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Fake the current user so we can test it is used in the docker args expectedUser := user.User{Uid: "1001", Gid: "1002"} - revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd.OverrideGetCurrentUser(&expectedUser) defer revertGetCurrentUser() // Run "getenvoy extension clean" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "clean"}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "clean"}) + err := cmdutil.Execute(c) // We expect docker to run from the correct path, as the current user and mount a volume for the correct workspace. expectedDockerExec := fmt.Sprintf("%s/docker run -u %s:%s --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest clean", dockerDir, expectedUser.Uid, expectedUser.Gid, workspaceDir) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) - require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, cmd) - require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) + require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, c) + require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, c) } // This tests --toolchain-container flags become docker command options func TestGetEnvoyExtensionCleanWithDockerOptions(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked - _, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + _, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) defer revertPath() // "getenvoy extension clean" must be in a valid workspace directory - _, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + _, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Run "getenvoy extension clean" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "clean", + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "clean", "--toolchain-container-image", "clean/image", "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, }) - err := cmdutil.Execute(cmd) + err := cmdutil.Execute(c) // Verify the command's stdout includes the init args. TestGetEnvoyExtensionClean tests the rest of stdout. - require.NoError(t, err, `expected no error running [%v]`, cmd) - require.Regexp(t, ".*--init -e VAR=VALUE -v /host:/container clean/image clean.*", stdout.String(), `expected stdout running [%v]`, cmd) - require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) + require.Regexp(t, ".*--init -e VAR=VALUE -v /host:/container clean/image clean.*", stdout.String(), `expected stdout running [%v]`, c) + require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, c) } // TestGetEnvoyExtensionCleanFail ensures clean failures show useful information in stderr func TestGetEnvoyExtensionCleanFail(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked, and force a failure. - dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + dockerDir, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) defer revertPath() // "getenvoy extension clean" must be in a valid workspace directory - workspaceDir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Fake the current user so we can test it is used in the docker args expectedUser := user.User{Uid: "1001", Gid: "1002"} - revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd.OverrideGetCurrentUser(&expectedUser) defer revertGetCurrentUser() // "-e DOCKER_EXIT_CODE=3" is a special instruction handled in the fake docker script toolchainOptions := "-e DOCKER_EXIT_CODE=3" // Run "getenvoy extension clean" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "clean", "--toolchain-container-options", toolchainOptions}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "clean", "--toolchain-container-options", toolchainOptions}) + err := cmdutil.Execute(c) // We expect the exit instruction to have gotten to the fake docker script, along with the default options. expectedDockerExec := fmt.Sprintf("%s/docker run -u %s:%s --rm -t -v %s:/source -w /source --init %s getenvoy/extension-rust-builder:latest clean", @@ -165,12 +166,12 @@ func TestGetEnvoyExtensionCleanFail(t *testing.T) { // Verify the command failed with the expected error. expectedErr := fmt.Sprintf(`failed to clean build directory of Envoy extension using "default" toolchain: failed to execute an external command "%s": exit status 3`, expectedDockerExec) - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) // We should see stdout because the docker script was invoked. - require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, cmd) + require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, c) // We also expect "docker stderr" in the output for the same reason. expectedStderr := fmt.Sprintf("docker stderr\nError: %s\n\nRun 'getenvoy extension clean --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } diff --git a/pkg/cmd/extension/example/cmd_add_test.go b/pkg/cmd/extension/example/cmd_add_test.go index 285021ea..c23610cf 100644 --- a/pkg/cmd/extension/example/cmd_add_test.go +++ b/pkg/cmd/extension/example/cmd_add_test.go @@ -15,243 +15,176 @@ package example_test import ( - "bytes" "fmt" "io/ioutil" - "os" "path/filepath" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/otiai10/copy" - "github.com/spf13/cobra" + "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/cmd" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -var _ = Describe("getenvoy extension examples add", func() { - - var cwdBackup string - - BeforeEach(func() { - cwd, err := os.Getwd() - Expect(err).ToNot(HaveOccurred()) - cwdBackup = cwd - }) - - AfterEach(func() { - if cwdBackup != "" { - Expect(os.Chdir(cwdBackup)).To(Succeed()) - } - }) - - var tempDir string - - BeforeEach(func() { - dir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - tempDir = dir - }) - - AfterEach(func() { - if tempDir != "" { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - } - }) - - var stdout *bytes.Buffer - var stderr *bytes.Buffer - - BeforeEach(func() { - stdout = new(bytes.Buffer) - stderr = new(bytes.Buffer) - }) - - var c *cobra.Command - - BeforeEach(func() { - c = cmd.NewRoot() - c.SetOut(stdout) - c.SetErr(stderr) - }) - - //nolint:lll - It("should validate --name flag", func() { - By("running command") - c.SetArgs([]string{"extension", "examples", "add", "--name", "my:example"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "my:example" is not a valid example name. Example name must match the format "^[a-z0-9._-]+$". E.g., 'my.example', 'my-example' or 'my_example' - -Run 'getenvoy extension examples add --help' for usage. -`)) - }) +func TestGetEnvoyExtensionExamplesAddValidateFlag(t *testing.T) { + type testCase struct { + name string + args []string + expectedErr string + } - chdir := func(path string) string { - dir, err := filepath.Abs(path) - Expect(err).ToNot(HaveOccurred()) + tests := []testCase{ + { + name: "--name with invalid value", + args: []string{"--name", "my:example"}, + expectedErr: `"my:example" is not a valid example name. Example name must match the format "^[a-z0-9._-]+$". E.g., 'my.example', 'my-example' or 'my_example'`, + }, + } - dir, err = filepath.EvalSymlinks(dir) - Expect(err).ToNot(HaveOccurred()) + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - err = os.Chdir(dir) - Expect(err).ToNot(HaveOccurred()) + t.Run(test.name, func(t *testing.T) { + // Run "getenvoy extension examples add" with the args we are testing + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs(append([]string{"extension", "examples", "add"}, test.args...)) + err := cmdutil.Execute(c) + require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, c) - return dir + // Verify the command failed with the expected error + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension examples add --help' for usage.\n", test.expectedErr) + require.Equal(t, expectedStderr, stderr.String(), `unexpected stderr running [%v]`, c) + }) + } +} + +func TestGetEnvoyExtensionExamplesAddFailsOutsideWorkspaceDirectory(t *testing.T) { + // Change to a non-workspace dir + dir, revertWd := RequireChDir(t, relativeRustWorkspaceDirWithOneExample+"/..") + defer revertWd() + + // Run "getenvoy extension examples add" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "add"}) + err := cmdutil.Execute(c) + + // Verify the command failed with the expected error + expectedErr := "there is no extension directory at or above: " + dir + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension examples add --help' for usage.\n", expectedErr) + require.Equal(t, expectedStderr, stderr.String(), `unexpected stderr running [%v]`, c) +} + +func TestGetEnvoyExtensionExamplesAddFailsWhenExampleExists(t *testing.T) { + _, revertWd := RequireChDir(t, relativeRustWorkspaceDirWithOneExample) + defer revertWd() + + // Run "getenvoy extension examples add" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "add"}) + err := cmdutil.Execute(c) + + // Verify the command failed with the expected error + expectedErr := `example setup "default" already exists` + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension examples add --help' for usage.\n", expectedErr) + require.Equal(t, expectedStderr, stderr.String(), `unexpected stderr running [%v]`, c) +} + +func TestGetEnvoyExtensionExamplesAdd(t *testing.T) { + type testCase struct { + name string + templateWorkspace string + flags []string + flagValues []string + expectedName string + expectedConfigFileName string } - //nolint:lll - Context("inside a workspace directory", func() { - It("should create 'default' example setup when no --name is omitted", func() { - By("simulating a workspace without any examples") - err := copy.Copy("testdata/workspace1", tempDir) - Expect(err).NotTo(HaveOccurred()) + tests := []testCase{ + { + name: `rust workspace`, + templateWorkspace: relativeRustWorkspaceDirWithNoExample, + flags: []string{"--name"}, + flagValues: []string{"test-example"}, + expectedName: `test-example`, + expectedConfigFileName: `extension.json`, + }, + { + name: `tinygo workspace`, + templateWorkspace: relativeTinyGoWorkspaceDirWithNoExample, + flags: []string{"--name"}, + flagValues: []string{"test-example"}, + expectedName: `test-example`, + expectedConfigFileName: `extension.txt`, + }, + { + name: `--name defaults to "default"`, + templateWorkspace: relativeTinyGoWorkspaceDirWithNoExample, + flags: []string{}, + flagValues: []string{}, + expectedName: `default`, + expectedConfigFileName: `extension.txt`, + }, + } - By("changing to a workspace dir") - chdir(tempDir) + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why + + t.Run(test.name, func(t *testing.T) { + // Copy the workspace as this test will delete it, and we don't want to mutate our test data! + workspaceDir, revertWorkspaceDir := RequireCopyOfDir(t, test.templateWorkspace) + defer revertWorkspaceDir() + + // "getenvoy extension examples add" must be in a valid workspace directory + _, revertWd := RequireChDir(t, workspaceDir) + defer revertWd() + + // Run "getenvoy extension examples add" + c, stdout, stderr := cmd.NewRootCommand() + args := []string{"extension", "examples", "add"} + for i := range test.flags { + args = append(args, test.flags[i], test.flagValues[i]) + } + c.SetArgs(args) + err := cmdutil.Execute(c) - By("running command") - c.SetArgs([]string{"extension", "examples", "add"}) - err = cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) + exampleDir := filepath.Join(`.getenvoy`, `extension`, `examples`, test.expectedName) - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Scaffolding a new example setup: -* .getenvoy/extension/examples/default/README.md -* .getenvoy/extension/examples/default/envoy.tmpl.yaml -* .getenvoy/extension/examples/default/example.yaml -* .getenvoy/extension/examples/default/extension.json + // Verify the files created end up in stderr + require.NoError(t, err, `expected no error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stderr running [%v]`, c) + require.Equal(t, fmt.Sprintf(`Scaffolding a new example setup: +* %[1]s/README.md +* %[1]s/envoy.tmpl.yaml +* %[1]s/example.yaml +* %[1]s/%[2]s Done! -`)) - By("verifying file system") - readmePath := filepath.Join(tempDir, ".getenvoy/extension/examples/default/README.md") - Expect(readmePath).To(BeAnExistingFile()) - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/envoy.tmpl.yaml")).To(BeAnExistingFile()) - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/example.yaml")).To(BeAnExistingFile()) - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/extension.json")).To(BeAnExistingFile()) - // Check README substitution: ${EXTENSION_CONFIG_FILE_NAME} must be replaced with "extension.json". - data, err := ioutil.ReadFile(readmePath) - Expect(err).ToNot(HaveOccurred()) - readme := string(data) - Expect(readme).To(ContainSubstring("extension.json")) - Expect(readme).NotTo(ContainSubstring("EXTENSION_CONFIG_FILE_NAME")) - }) - - It("should create example setup with a given --name", func() { - By("simulating a workspace without any examples") - err := copy.Copy("testdata/workspace1", tempDir) - Expect(err).NotTo(HaveOccurred()) +`, exampleDir, test.expectedConfigFileName), stderr.String(), `unexpected stderr running [%v]`, c) - By("changing to a workspace dir") - chdir(tempDir) + // Get the absolute path + exampleDir = filepath.Join(workspaceDir, exampleDir) - By("running command") - c.SetArgs([]string{"extension", "examples", "add", "--name", "advanced"}) - err = cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) + // Verify the files actually exist. + for _, p := range []string{ + `README.md`, `envoy.tmpl.yaml`, `example.yaml`, test.expectedConfigFileName, + } { + require.FileExists(t, filepath.Join(exampleDir, p), `expected to find %s in %s`, p, exampleDir) + } - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Scaffolding a new example setup: -* .getenvoy/extension/examples/advanced/README.md -* .getenvoy/extension/examples/advanced/envoy.tmpl.yaml -* .getenvoy/extension/examples/advanced/example.yaml -* .getenvoy/extension/examples/advanced/extension.json -Done! -`)) - By("verifying file system") - readmePath := filepath.Join(tempDir, ".getenvoy/extension/examples/advanced/README.md") - Expect(readmePath).To(BeAnExistingFile()) - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/advanced/envoy.tmpl.yaml")).To(BeAnExistingFile()) - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/advanced/example.yaml")).To(BeAnExistingFile()) - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/advanced/extension.json")).To(BeAnExistingFile()) // Check README substitution: ${EXTENSION_CONFIG_FILE_NAME} must be replaced with "extension.json". + readmePath := filepath.Join(exampleDir, `README.md`) data, err := ioutil.ReadFile(readmePath) - Expect(err).ToNot(HaveOccurred()) - readme := string(data) - Expect(readme).To(ContainSubstring("extension.json")) - Expect(readme).NotTo(ContainSubstring("${EXTENSION_CONFIG_FILE_NAME}")) - }) - - It("should fail if such example already exists", func() { - By("simulating a workspace with the 'default' example") - err := copy.Copy("testdata/workspace2", tempDir) - Expect(err).NotTo(HaveOccurred()) - - By("changing to a workspace dir") - chdir(tempDir) - - By("running command") - c.SetArgs([]string{"extension", "examples", "add"}) - err = cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: example setup "default" already exists - -Run 'getenvoy extension examples add --help' for usage. -`)) - }) - - It("should create 'default' example setup when no --name is omitted for TinyGo", func() { - By("simulating a workspace without any examples") - err := copy.Copy("testdata/workspace4", tempDir) - Expect(err).NotTo(HaveOccurred()) + require.NoError(t, err, `expected no error reading README.md file: %s`, readmePath) - By("changing to a workspace dir") - chdir(tempDir) - - By("running command") - c.SetArgs([]string{"extension", "examples", "add"}) - err = cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Scaffolding a new example setup: -* .getenvoy/extension/examples/default/README.md -* .getenvoy/extension/examples/default/envoy.tmpl.yaml -* .getenvoy/extension/examples/default/example.yaml -* .getenvoy/extension/examples/default/extension.txt -Done! -`)) - By("verifying file system") - readmePath := filepath.Join(tempDir, ".getenvoy/extension/examples/default/README.md") - Expect(readmePath).To(BeAnExistingFile()) - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/envoy.tmpl.yaml")).To(BeAnExistingFile()) - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/example.yaml")).To(BeAnExistingFile()) - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default/extension.txt")).To(BeAnExistingFile()) - // Check README substitution: ${EXTENSION_CONFIG_FILE_NAME} must be replaced with "extension.txt". - data, err := ioutil.ReadFile(readmePath) - Expect(err).ToNot(HaveOccurred()) + // Check one variable, noting that EXTENSION_CONFIG_FILE_NAME is language specific. readme := string(data) - Expect(readme).To(ContainSubstring("extension.txt")) - Expect(readme).NotTo(ContainSubstring("${EXTENSION_CONFIG_FILE_NAME}")) + require.NotContains(t, readme, `EXTENSION_CONFIG_FILE_NAME`, `expected variables to be replaced in %s`, readmePath) + require.Contains(t, readme, test.expectedConfigFileName, `expected to see config file %s in %s`, test.expectedConfigFileName, readmePath) }) - }) - - Context("outside of a workspace directory", func() { - It("should fail", func() { - By("changing to a non-workspace dir") - dir := chdir("testdata") - - By("running command") - c.SetArgs([]string{"extension", "examples", "add"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(fmt.Sprintf(`Error: there is no extension directory at or above: %s - -Run 'getenvoy extension examples add --help' for usage. -`, dir))) - }) - }) -}) + } +} diff --git a/pkg/cmd/extension/example/cmd_list_test.go b/pkg/cmd/extension/example/cmd_list_test.go index 173c6f32..61cddb00 100644 --- a/pkg/cmd/extension/example/cmd_list_test.go +++ b/pkg/cmd/extension/example/cmd_list_test.go @@ -15,133 +15,86 @@ package example_test import ( - "bytes" "fmt" - "os" - "path/filepath" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/spf13/cobra" + "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/cmd" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -var _ = Describe("getenvoy extension examples list", func() { - - var cwdBackup string - - BeforeEach(func() { - cwd, err := os.Getwd() - Expect(err).ToNot(HaveOccurred()) - cwdBackup = cwd - }) - - AfterEach(func() { - if cwdBackup != "" { - Expect(os.Chdir(cwdBackup)).To(Succeed()) - } - }) - - var stdout *bytes.Buffer - var stderr *bytes.Buffer - - BeforeEach(func() { - stdout = new(bytes.Buffer) - stderr = new(bytes.Buffer) - }) - - var c *cobra.Command - - BeforeEach(func() { - c = cmd.NewRoot() - c.SetOut(stdout) - c.SetErr(stderr) - }) - - chdir := func(path string) string { - dir, err := filepath.Abs(path) - Expect(err).ToNot(HaveOccurred()) - - dir, err = filepath.EvalSymlinks(dir) - Expect(err).ToNot(HaveOccurred()) - - err = os.Chdir(dir) - Expect(err).ToNot(HaveOccurred()) - - return dir - } - - //nolint:lll - Context("inside a workspace directory", func() { - It("should support a case with no examples", func() { - By("changing to a workspace dir") - chdir("testdata/workspace1") - - By("running command") - c.SetArgs([]string{"extension", "examples", "list"}) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Extension has no example setups. +func TestGetEnvoyExtensionExamplesListFailsOutsideWorkspaceDirectory(t *testing.T) { + // Change to a non-workspace dir + dir, revertWd := RequireChDir(t, relativeRustWorkspaceDirWithOneExample+"/..") + defer revertWd() + + // Run "getenvoy extension examples list" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "list"}) + err := cmdutil.Execute(c) + + // Verify the command failed with the expected error + expectedErr := "there is no extension directory at or above: " + dir + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension examples list --help' for usage.\n", expectedErr) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) +} + +func TestGetEnvoyExtensionExamplesListNone(t *testing.T) { + // "getenvoy extension examples list" must be in a valid workspace directory + _, revertWd := RequireChDir(t, relativeTinyGoWorkspaceDirWithNoExample) + defer revertWd() + + // Run "getenvoy extension examples list" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "list"}) + err := cmdutil.Execute(c) + + // Verify lack of examples on list is a warning. not an error. + require.NoError(t, err, `expected no error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + require.Equal(t, `Extension has no example setups. Use "getenvoy extension examples add --help" for more information on how to add one. -`)) - }) - - It("should support a case with 1 example", func() { - By("changing to a workspace dir") - chdir("testdata/workspace2") - - By("running command") - c.SetArgs([]string{"extension", "examples", "list"}) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(`EXAMPLE +`, stderr.String(), `unexpected stderr running [%v]`, c) +} + +func TestGetEnvoyExtensionExamplesListOne(t *testing.T) { + // "getenvoy extension examples list" must be in a valid workspace directory + _, revertWd := RequireChDir(t, relativeRustWorkspaceDirWithOneExample) + defer revertWd() + + // Run "getenvoy extension examples list" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "list"}) + err := cmdutil.Execute(c) + + // Verify the simple name of each example ended up in stdout + require.NoError(t, err, `expected no error running [%v]`, c) + require.Equal(t, `EXAMPLE default -`)) - Expect(stderr.String()).To(BeEmpty()) - }) - - It("should support a case with multiple examples", func() { - By("changing to a workspace dir") - chdir("testdata/workspace3") - - By("running command") - c.SetArgs([]string{"extension", "examples", "list"}) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(`EXAMPLE +`, stdout.String(), `unexpected stdout running [%v]`, c) + require.Empty(t, stderr, `expected no stderr running [%v]`, c) +} + +func TestGetEnvoyExtensionExamplesListTwo(t *testing.T) { + // "getenvoy extension examples list" must be in a valid workspace directory + _, revertWd := RequireChDir(t, relativeWorkspaceDirWithTwoExamples) + defer revertWd() + + // Run "getenvoy extension examples list" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "list"}) + err := cmdutil.Execute(c) + + // Verify the simple name of each example ended up in stdout + require.NoError(t, err, `expected no error running [%v]`, c) + require.Equal(t, `EXAMPLE another default -`)) - Expect(stderr.String()).To(BeEmpty()) - }) - }) - - Context("outside of a workspace directory", func() { - It("should fail", func() { - By("changing to a non-workspace dir") - dir := chdir("testdata") - - By("running command") - c.SetArgs([]string{"extension", "examples", "list"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(fmt.Sprintf(`Error: there is no extension directory at or above: %s - -Run 'getenvoy extension examples list --help' for usage. -`, dir))) - }) - }) -}) +`, stdout.String(), `unexpected stdout running [%v]`, c) + require.Empty(t, stderr, `expected no stderr running [%v]`, c) +} diff --git a/pkg/cmd/extension/example/cmd_remove_test.go b/pkg/cmd/extension/example/cmd_remove_test.go index 944c5a3d..3b06c5c2 100644 --- a/pkg/cmd/extension/example/cmd_remove_test.go +++ b/pkg/cmd/extension/example/cmd_remove_test.go @@ -15,214 +15,145 @@ package example_test import ( - "bytes" "fmt" - "io/ioutil" - "os" "path/filepath" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/otiai10/copy" - "github.com/spf13/cobra" + "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/cmd" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -var _ = Describe("getenvoy extension examples remove", func() { - - var cwdBackup string - - BeforeEach(func() { - cwd, err := os.Getwd() - Expect(err).ToNot(HaveOccurred()) - cwdBackup = cwd - }) - - AfterEach(func() { - if cwdBackup != "" { - Expect(os.Chdir(cwdBackup)).To(Succeed()) - } - }) - - var tempDir string - - BeforeEach(func() { - dir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - tempDir = dir - }) - - AfterEach(func() { - if tempDir != "" { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - } - }) - - var stdout *bytes.Buffer - var stderr *bytes.Buffer - - BeforeEach(func() { - stdout = new(bytes.Buffer) - stderr = new(bytes.Buffer) - }) - - var c *cobra.Command - - BeforeEach(func() { - c = cmd.NewRoot() - c.SetOut(stdout) - c.SetErr(stderr) - }) - - It("should require --name flag", func() { - By("running command") - c.SetArgs([]string{"extension", "examples", "remove"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: example name cannot be empty - -Run 'getenvoy extension examples remove --help' for usage. -`)) - }) - - //nolint:lll - It("should validate --name flag", func() { - By("running command") - c.SetArgs([]string{"extension", "examples", "remove", "--name", "my:example"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "my:example" is not a valid example name. Example name must match the format "^[a-z0-9._-]+$". E.g., 'my.example', 'my-example' or 'my_example' - -Run 'getenvoy extension examples remove --help' for usage. -`)) - }) - - chdir := func(path string) string { - dir, err := filepath.Abs(path) - Expect(err).ToNot(HaveOccurred()) - - dir, err = filepath.EvalSymlinks(dir) - Expect(err).ToNot(HaveOccurred()) - - err = os.Chdir(dir) - Expect(err).ToNot(HaveOccurred()) - - return dir +func TestGetEnvoyExtensionExamplesRemoveValidateFlag(t *testing.T) { + type testCase struct { + name string + args []string + expectedErr string } - //nolint:lll - Context("inside a workspace directory", func() { + tests := []testCase{ + { + name: "--name is missing", + args: []string{}, + expectedErr: `example name cannot be empty`, + }, + { + name: "--name with invalid value", + args: []string{"--name", "my:example"}, + expectedErr: `"my:example" is not a valid example name. Example name must match the format "^[a-z0-9._-]+$". E.g., 'my.example', 'my-example' or 'my_example'`, + }, + } - var tempDir string + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - BeforeEach(func() { - dir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - tempDir = dir - }) + t.Run(test.name, func(t *testing.T) { + // Run "getenvoy extension examples remove" with the flags we are testing + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs(append([]string{"extension", "examples", "remove"}, test.args...)) + err := cmdutil.Execute(c) + require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, c) - AfterEach(func() { - if tempDir != "" { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - } + // Verify the command failed with the expected error + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension examples remove --help' for usage.\n", test.expectedErr) + require.Equal(t, expectedStderr, stderr.String(), `unexpected stderr running [%v]`, c) }) + } +} + +func TestGetEnvoyExtensionExamplesRemoveFailsOutsideWorkspaceDirectory(t *testing.T) { + // Change to a non-workspace dir + dir, revertWd := RequireChDir(t, relativeRustWorkspaceDirWithOneExample+"/..") + defer revertWd() + + // Run "getenvoy extension examples remove" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "remove", "--name", "default"}) + err := cmdutil.Execute(c) + + // Verify the command failed with the expected error + expectedErr := "there is no extension directory at or above: " + dir + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension examples remove --help' for usage.\n", expectedErr) + require.Equal(t, expectedStderr, stderr.String(), `unexpected stderr running [%v]`, c) +} + +func TestGetEnvoyExtensionExamplesRemoveWarnsOnMissingExample(t *testing.T) { + _, revertWd := RequireChDir(t, relativeRustWorkspaceDirWithOneExample) + defer revertWd() + + name := "doesnt-exist" + // Run "getenvoy extension examples remove" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "remove", "--name", name}) + err := cmdutil.Execute(c) + + // The command shouldn't err, but it should warn to stderr + require.NoError(t, err, `expected no error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + require.Equal(t, fmt.Sprintf(`There is no example setup named "%s". - It("should be able to remove the 'default' example setup", func() { - By("simulating a workspace with the 'default' example setup") - err := copy.Copy("testdata/workspace2", tempDir) - Expect(err).NotTo(HaveOccurred()) - - By("changing to a workspace dir") - chdir(tempDir) - - By("running command") - c.SetArgs([]string{"extension", "examples", "remove", "--name", "default"}) - err = cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Removing example setup: +Use "getenvoy extension examples list" to list existing example setups. +`, name), stderr.String(), `unexpected stderr running [%v]`, c) +} + +func TestGetEnvoyExtensionExamplesRemoveDefault(t *testing.T) { + // Copy the workspace as this test will delete it, and we don't want to mutate our test data! + workspaceDir, revertWorkspaceDir := RequireCopyOfDir(t, relativeRustWorkspaceDirWithOneExample) + defer revertWorkspaceDir() + + // "getenvoy extension examples remove" must be in a valid workspace directory + _, revertWd := RequireChDir(t, workspaceDir) + defer revertWd() + + // Run "getenvoy extension examples remove" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "remove", "--name", "default"}) + err := cmdutil.Execute(c) + + // Verify the files deleted end up in stderr + require.NoError(t, err, `expected no error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + require.Equal(t, `Removing example setup: * .getenvoy/extension/examples/default/envoy.tmpl.yaml * .getenvoy/extension/examples/default/example.yaml * .getenvoy/extension/examples/default/extension.json Done! -`)) - By("verifying file system") - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/default")).NotTo(BeAnExistingFile()) - }) - - It("should be able to remove a non-default example setup", func() { - By("simulating a workspace with a non-default example setup") - err := copy.Copy("testdata/workspace3", tempDir) - Expect(err).NotTo(HaveOccurred()) - - By("changing to a workspace dir") - chdir(tempDir) - - By("running command") - c.SetArgs([]string{"extension", "examples", "remove", "--name", "another"}) - err = cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Removing example setup: +`, stderr.String(), `unexpected stderr running [%v]`, c) + + // Verify the directory actually deleted + require.NoDirExists(t, filepath.Join(workspaceDir, ".getenvoy/extension/examples/default"), `expected to not delete example "default"`) +} + +func TestGetEnvoyExtensionExamplesRemoveDoesntEffectOtherExample(t *testing.T) { + // Copy the workspace as this test will delete it, and we don't want to mutate our test data! + workspaceDir, revertWorkspaceDir := RequireCopyOfDir(t, relativeWorkspaceDirWithTwoExamples) + defer revertWorkspaceDir() + + // "getenvoy extension examples remove" must be in a valid workspace directory + _, revertWd := RequireChDir(t, workspaceDir) + defer revertWd() + + // Run "getenvoy extension examples remove" + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "examples", "remove", "--name", "another"}) + err := cmdutil.Execute(c) + + // Verify the files deleted end up in stderr + require.NoError(t, err, `expected no error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + require.Equal(t, `Removing example setup: * .getenvoy/extension/examples/another/envoy.tmpl.yaml * .getenvoy/extension/examples/another/example.yaml * .getenvoy/extension/examples/another/extension.json Done! -`)) - By("verifying file system") - Expect(filepath.Join(tempDir, ".getenvoy/extension/examples/another")).NotTo(BeAnExistingFile()) - }) - - It("should not fail if such example doesn't exist", func() { - By("simulating a workspace with the 'default' example") - err := copy.Copy("testdata/workspace2", tempDir) - Expect(err).NotTo(HaveOccurred()) +`, stderr.String(), `unexpected stderr running [%v]`, c) - By("changing to a workspace dir") - chdir(tempDir) - - By("running command") - c.SetArgs([]string{"extension", "examples", "remove", "--name", "non-existing-setup"}) - err = cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`There is no example setup named "non-existing-setup". - -Use "getenvoy extension examples list" to list existing example setups. -`)) - }) - }) - - Context("outside of a workspace directory", func() { - It("should fail", func() { - By("changing to a non-workspace dir") - dir := chdir("testdata") - - By("running command") - c.SetArgs([]string{"extension", "examples", "remove", "--name", "default"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(fmt.Sprintf(`Error: there is no extension directory at or above: %s - -Run 'getenvoy extension examples remove --help' for usage. -`, dir))) - }) - }) -}) + // Verify the other example still exists + require.NoDirExists(t, filepath.Join(workspaceDir, ".getenvoy/extension/examples/another"), `expected to delete example "another"`) + require.DirExists(t, filepath.Join(workspaceDir, ".getenvoy/extension/examples/default"), `expected to not delete example "default"`) +} diff --git a/pkg/cmd/extension/example/example_suite_test.go b/pkg/cmd/extension/example/cmd_test.go similarity index 53% rename from pkg/cmd/extension/example/example_suite_test.go rename to pkg/cmd/extension/example/cmd_test.go index 68c0f002..b882d39d 100644 --- a/pkg/cmd/extension/example/example_suite_test.go +++ b/pkg/cmd/extension/example/cmd_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 Tetrate +// Copyright 2021 Tetrate // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,14 +14,11 @@ package example_test -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" +const ( + relativeRustWorkspaceDirWithNoExample = "testdata/workspaceRustNoExample" + relativeTinyGoWorkspaceDirWithNoExample = "testdata/workspaceTinyGoNoExample" + // relativeRustWorkspaceDirWithOneExample is a workspace with example "default" + relativeRustWorkspaceDirWithOneExample = "testdata/workspaceRustOneExample" + // relativeWorkspaceDirWithTwoExamples is a workspace with examples "default" and "another" + relativeWorkspaceDirWithTwoExamples = "testdata/workspaceRustTwoExamples" ) - -func TestRun(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Run Suite") -} diff --git a/pkg/cmd/extension/example/testdata/workspace1/.getenvoy/extension/extension.yaml b/pkg/cmd/extension/example/testdata/workspaceRustNoExample/.getenvoy/extension/extension.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace1/.getenvoy/extension/extension.yaml rename to pkg/cmd/extension/example/testdata/workspaceRustNoExample/.getenvoy/extension/extension.yaml diff --git a/pkg/cmd/extension/example/testdata/workspace2/.getenvoy/extension/examples/default/envoy.tmpl.yaml b/pkg/cmd/extension/example/testdata/workspaceRustOneExample/.getenvoy/extension/examples/default/envoy.tmpl.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace2/.getenvoy/extension/examples/default/envoy.tmpl.yaml rename to pkg/cmd/extension/example/testdata/workspaceRustOneExample/.getenvoy/extension/examples/default/envoy.tmpl.yaml diff --git a/pkg/cmd/extension/example/testdata/workspace2/.getenvoy/extension/examples/default/example.yaml b/pkg/cmd/extension/example/testdata/workspaceRustOneExample/.getenvoy/extension/examples/default/example.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace2/.getenvoy/extension/examples/default/example.yaml rename to pkg/cmd/extension/example/testdata/workspaceRustOneExample/.getenvoy/extension/examples/default/example.yaml diff --git a/pkg/cmd/extension/example/testdata/workspace2/.getenvoy/extension/examples/default/extension.json b/pkg/cmd/extension/example/testdata/workspaceRustOneExample/.getenvoy/extension/examples/default/extension.json similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace2/.getenvoy/extension/examples/default/extension.json rename to pkg/cmd/extension/example/testdata/workspaceRustOneExample/.getenvoy/extension/examples/default/extension.json diff --git a/pkg/cmd/extension/example/testdata/workspace2/.getenvoy/extension/extension.yaml b/pkg/cmd/extension/example/testdata/workspaceRustOneExample/.getenvoy/extension/extension.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace2/.getenvoy/extension/extension.yaml rename to pkg/cmd/extension/example/testdata/workspaceRustOneExample/.getenvoy/extension/extension.yaml diff --git a/pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/another/envoy.tmpl.yaml b/pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/another/envoy.tmpl.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/another/envoy.tmpl.yaml rename to pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/another/envoy.tmpl.yaml diff --git a/pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/another/example.yaml b/pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/another/example.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/another/example.yaml rename to pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/another/example.yaml diff --git a/pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/another/extension.json b/pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/another/extension.json similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/another/extension.json rename to pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/another/extension.json diff --git a/pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/default/envoy.tmpl.yaml b/pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/default/envoy.tmpl.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/default/envoy.tmpl.yaml rename to pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/default/envoy.tmpl.yaml diff --git a/pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/default/example.yaml b/pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/default/example.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/default/example.yaml rename to pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/default/example.yaml diff --git a/pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/default/extension.json b/pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/default/extension.json similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/examples/default/extension.json rename to pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/examples/default/extension.json diff --git a/pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/extension.yaml b/pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/extension.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace3/.getenvoy/extension/extension.yaml rename to pkg/cmd/extension/example/testdata/workspaceRustTwoExamples/.getenvoy/extension/extension.yaml diff --git a/pkg/cmd/extension/example/testdata/workspace4/.getenvoy/extension/extension.yaml b/pkg/cmd/extension/example/testdata/workspaceTinyGoNoExample/.getenvoy/extension/extension.yaml similarity index 100% rename from pkg/cmd/extension/example/testdata/workspace4/.getenvoy/extension/extension.yaml rename to pkg/cmd/extension/example/testdata/workspaceTinyGoNoExample/.getenvoy/extension/extension.yaml diff --git a/pkg/cmd/extension/extension_suite_test.go b/pkg/cmd/extension/extension_suite_test.go deleted file mode 100644 index a55ca0a7..00000000 --- a/pkg/cmd/extension/extension_suite_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 Tetrate -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package extension_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestExtension(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Extension Suite") -} diff --git a/pkg/cmd/extension/init/cmd_test.go b/pkg/cmd/extension/init/cmd_test.go index 8d5a769f..d2813568 100644 --- a/pkg/cmd/extension/init/cmd_test.go +++ b/pkg/cmd/extension/init/cmd_test.go @@ -15,191 +15,201 @@ package init_test import ( - "bytes" "fmt" - "io/ioutil" "os" + "path/filepath" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - "github.com/spf13/cobra" + "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/cmd" workspaces "github.com/tetratelabs/getenvoy/pkg/extension/workspace" + "github.com/tetratelabs/getenvoy/pkg/extension/workspace/config/extension" + toolchains "github.com/tetratelabs/getenvoy/pkg/extension/workspace/toolchain" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -var _ = Describe("getenvoy extension init", func() { +func TestGetEnvoyExtensionInitValidateFlag(t *testing.T) { + type testCase struct { + name string + args []string + expectedErr string + } + + cwd, err := os.Getwd() + require.NoError(t, err, "error getting current working directory") + + outputDir, revertOutputDir := RequireNewTempDir(t) + defer revertOutputDir() + + tests := []testCase{ + { + name: "extension category is missing", + args: []string{}, + expectedErr: `extension category cannot be empty`, + }, + { + name: "extension category with invalid value", + args: []string{"--category", "invalid.category"}, + expectedErr: `"invalid.category" is not a supported extension category`, + }, + { + name: "programming language is missing", + args: []string{"--category", "envoy.filters.http"}, + expectedErr: `programming language cannot be empty`, + }, + { + name: "programming language with invalid value", + args: []string{"--category", "envoy.filters.http", "--language", "invalid.language"}, + expectedErr: `"invalid.language" is not a supported programming language`, + }, + { + name: "output directory exists but is not empty", + args: []string{"--category", "envoy.filters.http", "--language", "tinygo"}, + expectedErr: fmt.Sprintf(`output directory must be empty or new: %s`, cwd), + }, + { + name: "extension name is missing", + args: []string{"--category", "envoy.filters.http", "--language", "tinygo", outputDir}, + expectedErr: `extension name cannot be empty`, + }, + { + name: "extension name with invalid value", + args: []string{"--category", "envoy.filters.http", "--language", "tinygo", "--name", "?!", outputDir}, + expectedErr: `"?!" is not a valid extension name. Extension name must match the format "^[a-z0-9_]+(\\.[a-z0-9_]+)*$". E.g., 'mycompany.filters.http.custom_metrics'`, + }, + } + + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why + + t.Run(test.name, func(t *testing.T) { + // Run "getenvoy extension init" with the flags we are testing + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs(append([]string{"extension", "init", "--no-prompt", "--no-colors"}, test.args...)) + err := cmdutil.Execute(c) + require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, c) + + // Verify the command failed with the expected error + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension init --help' for usage.\n", test.expectedErr) + require.Equal(t, expectedStderr, stderr.String(), `unexpected stderr running [%v]`, c) + }) + } +} + +func TestGetEnvoyExtensionInit(t *testing.T) { + const extensionName = "getenvoy_extension_init" + const envoyVersion = "standard:1.17.0" + + type testCase struct { + name string + extension.Category + extension.Language + currentDirectory bool + } + + var tests []testCase + for _, c := range extension.Categories { + for _, l := range extension.Languages { + name := fmt.Sprintf(`category=%s language=%s`, c, l) + tests = append(tests, + testCase{name + "-currentDirectory", c, l, true}, + testCase{name + "-newDirectory", c, l, false}, + ) + } + } - var stdout *bytes.Buffer - var stderr *bytes.Buffer + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - BeforeEach(func() { - stdout = new(bytes.Buffer) - stderr = new(bytes.Buffer) - }) + t.Run(test.name, func(t *testing.T) { + outputDir, revertOutputDir := RequireNewTempDir(t) + defer revertOutputDir() - var c *cobra.Command + // Run "getenvoy extension init" + c, stdout, stderr := cmd.NewRootCommand() + args := []string{"extension", "init", "--no-prompt", "--no-colors", + "--category", test.Category.String(), + "--language", test.Language.String(), + "--name", extensionName, + } - BeforeEach(func() { - c = cmd.NewRoot() - c.SetOut(stdout) - c.SetErr(stderr) - }) + if test.currentDirectory { + _, revertChDir := RequireChDir(t, outputDir) + defer revertChDir() + } else { + args = append(args, outputDir) + } - Describe("should validate parameters", func() { - type testCase struct { - args []string - expectedStdErr string - } - type testCaseFn func() testCase - give := func(given testCase) testCaseFn { - return func() testCase { - return given + c.SetArgs(args) + err := cmdutil.Execute(c) + + require.NoError(t, err, `expected no error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + // Check that the contents look valid for the inputs. + for _, regex := range []string{ + `^\QScaffolding a new extension:\E\n`, + fmt.Sprintf(`\QGenerating files in %s:\E\n`, outputDir), + `\Q* .getenvoy/extension/extension.yaml\E\n`, + `\QDone!\E\n$`, + } { + require.Regexp(t, regex, stderr, `invalid stderr running [%v]`, c) } - } - //nolint:lll - DescribeTable("should fail if a parameter is missing or has an invalid value", - func(givenFn testCaseFn) { - given := givenFn() - - By("running command") - c.SetArgs(append([]string{"extension", "init", "--no-prompt"}, given.args...)) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(given.expectedStdErr)) - }, - Entry("extension category is missing", give(testCase{ - args: []string{}, - expectedStdErr: `Error: extension category cannot be empty - -Run 'getenvoy extension init --help' for usage. -`, - })), - Entry("extension category is not valid", give(testCase{ - args: []string{"--category", "invalid.category"}, - expectedStdErr: `Error: "invalid.category" is not a supported extension category - -Run 'getenvoy extension init --help' for usage. -`, - })), - Entry("programming language is missing", give(testCase{ - args: []string{"--category", "envoy.filters.http"}, - expectedStdErr: `Error: programming language cannot be empty - -Run 'getenvoy extension init --help' for usage. -`, - })), - Entry("programming language is not valid", give(testCase{ - args: []string{"--category", "envoy.filters.http", "--language", "invalid.language"}, - expectedStdErr: `Error: "invalid.language" is not a supported programming language - -Run 'getenvoy extension init --help' for usage. -`, - })), - Entry("output directory exists but is not empty", func() testCase { - cwd, err := os.Getwd() - Expect(err).ToNot(HaveOccurred()) - return testCase{ - args: []string{"--category", "envoy.filters.http", "--language", "rust"}, - expectedStdErr: fmt.Sprintf(`Error: output directory must be empty or new: %s - -Run 'getenvoy extension init --help' for usage. -`, cwd), - } - }), - Entry("extension name is missing", func() testCase { - outputDirName, err := ioutil.TempDir("", "test") - Expect(err).ToNot(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(outputDirName)).To(Succeed()) - }() - - return testCase{ - args: []string{"--category", "envoy.filters.http", "--language", "rust", outputDirName}, - expectedStdErr: `Error: extension name cannot be empty - -Run 'getenvoy extension init --help' for usage. -`, - } - }), - Entry("extension name is not valid", func() testCase { - outputDirName, err := ioutil.TempDir("", "test") - Expect(err).ToNot(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(outputDirName)).To(Succeed()) - }() - - return testCase{ - args: []string{"--category", "envoy.filters.http", "--language", "rust", "--name", "?!", outputDirName}, - expectedStdErr: `Error: "?!" is not a valid extension name. Extension name must match the format "^[a-z0-9_]+(\\.[a-z0-9_]+)*$". E.g., 'mycompany.filters.http.custom_metrics' - -Run 'getenvoy extension init --help' for usage. -`, + + // Check to see that the extension.yaml mentioned in stderr exists. + // Note: we don't check all files as extensions are language-specific. + require.FileExists(t, filepath.Join(outputDir, ".getenvoy/extension/extension.yaml"), `extension.yaml missing after running [%v]`, c) + + // Check the generated extension.yaml includes values we passed and includes the default toolchain. + workspace, err := workspaces.GetWorkspaceAt(outputDir) + require.NoError(t, err, `error getting workspace after running [%v]`, c) + require.NotNil(t, workspace, `nil workspace running [%v]`, c) + descriptor := workspace.GetExtensionDescriptor() + require.Equal(t, extensionName, descriptor.Name, `wrong extension name running [%v]: %s`, c, descriptor) + require.Equal(t, test.Category, descriptor.Category, `wrong extension category running [%v]: %s`, c, descriptor) + require.Equal(t, test.Language, descriptor.Language, `wrong extension language running [%v]: %s`, c, descriptor) + require.Equal(t, envoyVersion, descriptor.Runtime.Envoy.Version, `wrong extension envoy version running [%v]: %s`, c, descriptor) + + // Check the default toolchain is loadable + toolchain, err := toolchains.LoadToolchain(toolchains.Default, workspace) + require.NoError(t, err, `error loading toolchain running [%v]`, c) + require.NotNil(t, toolchain, `nil toolchain running [%v]`, c) + + // Verify ignore files didn't end up in the output directory + for _, ignore := range []string{".gitignore", ".licenserignore"} { + require.NotContains(t, stderr.String(), fmt.Sprintf("* %s\n", ignore), `ignore file %s found in stderr running [%v]`, ignore, c) + } + + // Verify language-specific files + var languageSpecificPaths []string + switch test.Language { + case extension.LanguageRust: + languageSpecificPaths = []string{ + ".cargo/config", + "Cargo.toml", + "README.md", + "src/config.rs", + "src/lib.rs", + "wasm/module/Cargo.toml", + "wasm/module/src/lib.rs", } - }), - ) - }) - - Describe("should generate source code when all parameters are valid", func() { - type testCase struct { - category string - language string - } - DescribeTable("should generate source code in the output directory", - func(given testCase) { - outputDirName, err := ioutil.TempDir("", "test") - Expect(err).ToNot(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(outputDirName)).To(Succeed()) - }() - - name := fmt.Sprintf("mycompany.%s.example", given.category) - - By("running command") - c.SetArgs([]string{"extension", "init", "--no-colors", "--category", given.category, "--language", given.language, "--name", name, outputDirName}) - err = cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying that extension files have been generated") - outputDir, err := os.Open(outputDirName) - Expect(err).ToNot(HaveOccurred()) - defer func() { Expect(outputDir.Close()).To(Succeed()) }() - names, err := outputDir.Readdirnames(-1) - Expect(err).ToNot(HaveOccurred()) - Expect(names).NotTo(BeEmpty()) - - By("verifying that a workspace has been created") - workspace, err := workspaces.GetWorkspaceAt(outputDirName) - Expect(err).ToNot(HaveOccurred()) - - By("verifying contents of the extension descriptor file") - descriptor := workspace.GetExtensionDescriptor() - Expect(descriptor.Name).To(Equal(name)) - Expect(descriptor.Category.String()).To(Equal(given.category)) - Expect(descriptor.Language.String()).To(Equal(given.language)) - Expect(descriptor.Runtime.Envoy.Version).To(Equal("standard:1.17.0")) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).NotTo(BeEmpty()) - }, - func() []TableEntry { - entries := []TableEntry{} - for _, category := range []string{"envoy.filters.http", "envoy.filters.network", "envoy.access_loggers"} { - for _, language := range []string{"rust"} { - entries = append(entries, Entry(fmt.Sprintf("category=%s language=%s", category, language), testCase{ - category: category, - language: language, - })) - } + + case extension.LanguageTinyGo: + languageSpecificPaths = []string{ + "go.mod", + "go.sum", + "main.go", + "main_test.go", } - return entries - }()..., - ) - }) -}) + } + + // Verify the paths were in stderr and actually exist. + for _, f := range languageSpecificPaths { + require.Regexp(t, fmt.Sprintf(`\Q* %s\E\n`, f), stderr, `expected stderr to include %s running [%v]`, f, c) + require.FileExists(t, filepath.Join(outputDir, f), `%s missing after running [%v]`, f, c) + } + }) + } +} diff --git a/pkg/cmd/extension/init/feedback_test.go b/pkg/cmd/extension/init/feedback_test.go index 53333812..2cd137c9 100644 --- a/pkg/cmd/extension/init/feedback_test.go +++ b/pkg/cmd/extension/init/feedback_test.go @@ -16,72 +16,40 @@ package init import ( "bytes" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" "github.com/spf13/cobra" + "github.com/stretchr/testify/require" scaffold "github.com/tetratelabs/getenvoy/pkg/extension/init" "github.com/tetratelabs/getenvoy/pkg/extension/workspace/config/extension" uiutil "github.com/tetratelabs/getenvoy/pkg/util/ui" ) -var _ = Describe("feedback", func() { - Describe("console output", func() { - type testCase struct { - noColors bool - usedWizard bool - expected string - } - DescribeTable("should depend on whether colors are enabled", - func(given testCase) { - uiutil.StylesEnabled = !given.noColors - - c := &cobra.Command{ - Use: "init", - } - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - c.SetOut(stdout) - c.SetErr(stderr) - - f := &feedback{ - cmd: c, - opts: &scaffold.ScaffoldOpts{ - Extension: &extension.Descriptor{ - Category: extension.EnvoyHTTPFilter, - Language: extension.LanguageRust, - Name: "my_company.my_http_filter", - }, - OutputDir: "/path/to/dir", - }, - usedWizard: given.usedWizard, - w: c.ErrOrStderr(), - } - - f.OnStart() - f.OnFile("Cargo.toml") - f.OnFile("src/lib.rs") - f.OnComplete() - - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(given.expected)) - }, - Entry("--no-colors", testCase{ - noColors: true, - usedWizard: false, - expected: `Scaffolding a new extension: +func TestFeedbackArgs(t *testing.T) { + type testCase struct { + name string + noColors bool + usedWizard bool + expected string + } + tests := []testCase{ + { + name: "--no-colors", + noColors: true, + usedWizard: false, + expected: `Scaffolding a new extension: Generating files in /path/to/dir: * Cargo.toml * src/lib.rs Done! `, - }), - Entry("--no-colors + wizard", testCase{ - noColors: true, - usedWizard: true, - expected: `Scaffolding a new extension: + }, + { + name: "--no-colors + wizard", + noColors: true, + usedWizard: true, + expected: `Scaffolding a new extension: Generating files in /path/to/dir: * Cargo.toml * src/lib.rs @@ -91,29 +59,65 @@ Hint: Next time you can skip the wizard by running init --category envoy.filters.http --language rust --name my_company.my_http_filter /path/to/dir `, - }), - Entry("--no-colors=false", testCase{ - noColors: false, - usedWizard: false, - expected: "\x1b[4mScaffolding a new extension:\x1b[0m\n" + - "Generating files in \x1b[2m/path/to/dir\x1b[0m:\n" + - "\x1b[32m✔\x1b[0m Cargo.toml\n" + - "\x1b[32m✔\x1b[0m src/lib.rs\n" + - "Done!\n", - }), - Entry("--no-colors=false + wizard", testCase{ - noColors: false, - usedWizard: true, - expected: "\x1b[4mScaffolding a new extension:\x1b[0m\n" + - "Generating files in \x1b[2m/path/to/dir\x1b[0m:\n" + - "\x1b[32m✔\x1b[0m Cargo.toml\n" + - "\x1b[32m✔\x1b[0m src/lib.rs\n" + - "Done!\n" + - "\n" + - "\x1b[2m\x1b[4mHint:\x1b[0m\n" + - "\x1b[2mNext time you can skip the wizard by running\x1b[0m\n" + - "\x1b[2m init --category envoy.filters.http --language rust --name my_company.my_http_filter /path/to/dir\x1b[0m\n", - }), - ) - }) -}) + }, { + name: "--no-colors=false", + noColors: false, + usedWizard: false, + expected: "\x1b[4mScaffolding a new extension:\x1b[0m\n" + + "Generating files in \x1b[2m/path/to/dir\x1b[0m:\n" + + "\x1b[32m✔\x1b[0m Cargo.toml\n" + + "\x1b[32m✔\x1b[0m src/lib.rs\n" + + "Done!\n", + }, { + name: "--no-colors=false + wizard", + noColors: false, + usedWizard: true, + expected: "\x1b[4mScaffolding a new extension:\x1b[0m\n" + + "Generating files in \x1b[2m/path/to/dir\x1b[0m:\n" + + "\x1b[32m✔\x1b[0m Cargo.toml\n" + + "\x1b[32m✔\x1b[0m src/lib.rs\n" + + "Done!\n" + + "\n" + + "\x1b[2m\x1b[4mHint:\x1b[0m\n" + + "\x1b[2mNext time you can skip the wizard by running\x1b[0m\n" + + "\x1b[2m init --category envoy.filters.http --language rust --name my_company.my_http_filter /path/to/dir\x1b[0m\n", + }, + } + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why + + t.Run(test.name, func(t *testing.T) { + uiutil.StylesEnabled = !test.noColors + + c := &cobra.Command{ + Use: "init", + } + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + c.SetOut(stdout) + c.SetErr(stderr) + + f := &feedback{ + cmd: c, + opts: &scaffold.ScaffoldOpts{ + Extension: &extension.Descriptor{ + Category: extension.EnvoyHTTPFilter, + Language: extension.LanguageRust, + Name: "my_company.my_http_filter", + }, + OutputDir: "/path/to/dir", + }, + usedWizard: test.usedWizard, + w: c.ErrOrStderr(), + } + + f.OnStart() + f.OnFile("Cargo.toml") + f.OnFile("src/lib.rs") + f.OnComplete() + + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + require.Equal(t, test.expected, stderr.String(), `unexpected stderr running [%v]`, c) + }) + } +} diff --git a/pkg/cmd/extension/init/init_suite_test.go b/pkg/cmd/extension/init/init_suite_test.go deleted file mode 100644 index dfcc9e9c..00000000 --- a/pkg/cmd/extension/init/init_suite_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 Tetrate -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package init_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestInit(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Init Suite") -} diff --git a/pkg/cmd/extension/init/params_test.go b/pkg/cmd/extension/init/params_test.go index 28a6b083..d927500b 100644 --- a/pkg/cmd/extension/init/params_test.go +++ b/pkg/cmd/extension/init/params_test.go @@ -16,89 +16,81 @@ package init import ( "fmt" - "io/ioutil" "os" "path/filepath" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) + "github.com/stretchr/testify/require" -var _ = Describe("Params", func() { - Describe("OutputDir", func() { - It("should reject output path that is a file", func() { - By("creating a file") - tmpFile, err := ioutil.TempFile("", "file") - Expect(err).ToNot(HaveOccurred()) - defer func() { - Expect(tmpFile.Close()).To(Succeed()) - Expect(os.Remove(tmpFile.Name())).To(Succeed()) - }() + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" +) - By("verifying file path") - err = newParams().OutputDir.Validator(tmpFile.Name()) - Expect(err).To(MatchError(fmt.Sprintf(`output path is not a directory: %s`, tmpFile.Name()))) - }) +func TestOutputDirValidatorReject(t *testing.T) { + type testCase struct { + name string + path string + expectedErr string + } - It("should reject output path that is under a file", func() { - By("creating a file") - tmpFile, err := ioutil.TempFile("", "file") - Expect(err).ToNot(HaveOccurred()) - defer func() { - Expect(tmpFile.Close()).To(Succeed()) - Expect(os.Remove(tmpFile.Name())).To(Succeed()) - }() + cwd, err := os.Getwd() + require.NoError(t, err, "error getting current working directory") - By("verifying path under a file") - invalidPath := filepath.Join(tmpFile.Name(), "new_dir") - err = newParams().OutputDir.Validator(invalidPath) - Expect(err).To(MatchError(fmt.Sprintf(`stat %s: not a directory`, invalidPath))) - }) + file := os.Args[0] + pathUnderFile := filepath.Join(file, "subdir") + tests := []testCase{ + { + name: "output path that is a file", + path: file, + expectedErr: fmt.Sprintf(`output path is not a directory: %s`, file), + }, + { + name: "output path under a file", + path: pathUnderFile, + expectedErr: fmt.Sprintf(`stat %s: not a directory`, pathUnderFile), + }, + { + name: "output path not empty", + path: cwd, + expectedErr: fmt.Sprintf(`output directory must be empty or new: %s`, cwd), + }, + } - It("should reject output path that is a non-empty existing dir", func() { - By("creating a dir") - tmpDir, err := ioutil.TempDir("", "dir") - Expect(err).ToNot(HaveOccurred()) - defer func() { - Expect(os.Remove(tmpDir)).To(Succeed()) - }() - By("creating another dir inside") - innerDir, err := ioutil.TempDir(tmpDir, "dir") - Expect(err).ToNot(HaveOccurred()) - defer func() { - Expect(os.Remove(innerDir)).To(Succeed()) - }() + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - By("verifying non-empty existing dir") - err = newParams().OutputDir.Validator(tmpDir) - Expect(err).To(MatchError(fmt.Sprintf(`output directory must be empty or new: %s`, tmpDir))) + t.Run(test.name, func(t *testing.T) { + err = newParams().OutputDir.Validator(test.path) + require.EqualError(t, err, test.expectedErr) }) + } +} - It("should accept output path that is a non-existing dir", func() { - By("creating a dir") - tmpDir, err := ioutil.TempDir("", "dir") - Expect(err).ToNot(HaveOccurred()) - defer func() { - Expect(os.Remove(tmpDir)).To(Succeed()) - }() +func TestOutputDirValidatorAccept(t *testing.T) { + tempDir, revertTempDir := RequireNewTempDir(t) + defer revertTempDir() - By("verifying non-existing dir") - validPath := filepath.Join(tmpDir, "child_dir", "grand_child_dir") - err = newParams().OutputDir.Validator(validPath) - Expect(err).NotTo(HaveOccurred()) - }) + type testCase struct { + name string + path string + } + + tests := []testCase{ + { + name: "output path that is an empty directory", + path: tempDir, + }, + { + name: "output path that doesn't exist", + path: filepath.Join(tempDir, "subdir"), + }, + } - It("should accept output path that is an empty existing dir", func() { - By("creating a dir") - tmpDir, err := ioutil.TempDir("", "dir") - Expect(err).ToNot(HaveOccurred()) - defer func() { - Expect(os.Remove(tmpDir)).To(Succeed()) - }() + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - By("verifying an empty existing dir") - err = newParams().OutputDir.Validator(tmpDir) - Expect(err).NotTo(HaveOccurred()) + t.Run(test.name, func(t *testing.T) { + err := newParams().OutputDir.Validator(test.path) + require.NoError(t, err) }) - }) -}) + } +} diff --git a/pkg/cmd/extension/init/wizard_test.go b/pkg/cmd/extension/init/wizard_test.go index 0292ded8..3638de60 100644 --- a/pkg/cmd/extension/init/wizard_test.go +++ b/pkg/cmd/extension/init/wizard_test.go @@ -17,85 +17,70 @@ package init import ( "bytes" "fmt" - "io/ioutil" - "os" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" uiutil "github.com/tetratelabs/getenvoy/pkg/util/ui" ) -var _ = Describe("interactive mode", func() { - Describe("wizard", func() { - Describe("all parameters are valid", func() { - var tmpDir string +func TestWizardArgs(t *testing.T) { + tempDir, revertTempDir := RequireNewTempDir(t) + defer revertTempDir() - BeforeEach(func() { - dir, err := ioutil.TempDir("", "test") - Expect(err).ToNot(HaveOccurred()) - tmpDir = dir - }) - - AfterEach(func() { - if tmpDir != "" { - Expect(os.RemoveAll(tmpDir)).To(Succeed()) - } - }) + type testCase struct { + name string + noColors bool + expected string + } + tests := []testCase{ + { + name: "colors disabled", + noColors: true, + expected: fmt.Sprintf(`What kind of extension would you like to create? +* Category HTTP Filter +* Language Rust +* Output directory %s +* Extension name mycompany.filters.http.custom_metrics +Great! Let me help you with that! - type testCase struct { - noColors bool - expected string - } - DescribeTable("should not require user interaction if all parameters are valid", - func(givenFn func() testCase) { - given := givenFn() +`, tempDir), + }, + { + name: "colors enabled", + noColors: false, + expected: "\x1b[4mWhat kind of extension would you like to create?\x1b[0m\n" + + "\x1b[32m✔\x1b[0m \x1b[3mCategory\x1b[0m \x1b[2mHTTP Filter\x1b[0m\n" + + "\x1b[32m✔\x1b[0m \x1b[3mLanguage\x1b[0m \x1b[2mRust\x1b[0m\n" + + fmt.Sprintf("\x1b[32m✔\x1b[0m \x1b[3mOutput directory\x1b[0m \x1b[2m%s\x1b[0m\n", tempDir) + + "\x1b[32m✔\x1b[0m \x1b[3mExtension name\x1b[0m \x1b[2mmycompany.filters.http.custom_metrics\x1b[0m\n" + + "Great! Let me help you with that!\n" + + "\n", + }, + } - uiutil.StylesEnabled = !given.noColors + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - cmd := new(cobra.Command) - out := new(bytes.Buffer) - cmd.SetOut(out) + t.Run(test.name, func(t *testing.T) { + uiutil.StylesEnabled = !test.noColors - params := newParams() - params.Category.Value = "envoy.filters.http" - params.Language.Value = "rust" - params.OutputDir.Value = tmpDir - params.Name.Value = "mycompany.filters.http.custom_metrics" + c := new(cobra.Command) + out := new(bytes.Buffer) + c.SetOut(out) - err := newWizard(cmd).Fill(params) - Expect(err).ToNot(HaveOccurred()) + params := newParams() + params.Category.Value = "envoy.filters.http" + params.Language.Value = "rust" + params.OutputDir.Value = tempDir + params.Name.Value = "mycompany.filters.http.custom_metrics" - Expect(out.String()).To(Equal(given.expected)) - }, - Entry("colors disabled", func() testCase { - return testCase{ - noColors: true, - expected: fmt.Sprintf(`What kind of extension would you like to create? -* Category HTTP Filter -* Language Rust -* Output directory %s -* Extension name mycompany.filters.http.custom_metrics -Great! Let me help you with that! + err := newWizard(c).Fill(params) -`, tmpDir), - } - }), - Entry("colors enabled", func() testCase { - return testCase{ - noColors: false, - expected: "\x1b[4mWhat kind of extension would you like to create?\x1b[0m\n" + - "\x1b[32m✔\x1b[0m \x1b[3mCategory\x1b[0m \x1b[2mHTTP Filter\x1b[0m\n" + - "\x1b[32m✔\x1b[0m \x1b[3mLanguage\x1b[0m \x1b[2mRust\x1b[0m\n" + - fmt.Sprintf("\x1b[32m✔\x1b[0m \x1b[3mOutput directory\x1b[0m \x1b[2m%s\x1b[0m\n", tmpDir) + - "\x1b[32m✔\x1b[0m \x1b[3mExtension name\x1b[0m \x1b[2mmycompany.filters.http.custom_metrics\x1b[0m\n" + - "Great! Let me help you with that!\n" + - "\n", - } - }), - ) + require.NoError(t, err) + require.Equal(t, test.expected, out.String(), `unexpected output running [%v]`, c) }) - }) -}) + } +} diff --git a/pkg/cmd/extension/push/cmd_test.go b/pkg/cmd/extension/push/cmd_test.go index d2f21c1b..12c12c69 100644 --- a/pkg/cmd/extension/push/cmd_test.go +++ b/pkg/cmd/extension/push/cmd_test.go @@ -21,7 +21,8 @@ import ( "github.com/stretchr/testify/require" - cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) @@ -37,62 +38,62 @@ const defaultTag = "latest" // TestGetEnvoyExtensionPush shows current directory is usable, provided it is a valid workspace. func TestGetEnvoyExtensionPush(t *testing.T) { - _, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + _, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Run "getenvoy extension push localhost:5000/getenvoy/sample" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) + err := cmdutil.Execute(c) // A fully qualified image ref includes the tag imageRef := localRegistryWasmImageRef + ":" + defaultTag // Verify stdout shows the latest tag and the correct image ref - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) require.Contains(t, stdout.String(), fmt.Sprintf(`Using default tag: %s Pushed %s -digest: sha256`, defaultTag, imageRef), `unexpected stderr after running [%v]`, cmd) - require.Empty(t, stderr.String(), `expected no stderr running [%v]`, cmd) +digest: sha256`, defaultTag, imageRef), `unexpected stderr after running [%v]`, c) + require.Empty(t, stderr, `expected no stderr running [%v]`, c) } func TestGetEnvoyExtensionPushFailsOutsideWorkspaceDirectory(t *testing.T) { // Change to a non-workspace dir - dir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..") + dir, revertWd := RequireChDir(t, relativeWorkspaceDir+"/..") defer revertWd() // Run "getenvoy extension push localhost:5000/getenvoy/sample" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) + err := cmdutil.Execute(c) // Verify the command failed with the expected error expectedErr := "there is no extension directory at or above: " + dir - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) - require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension push --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } // TestGetEnvoyExtensionPushWithExplicitFileOption shows we don't need to be in a workspace directory to push a wasm. func TestGetEnvoyExtensionPushWithExplicitFileOption(t *testing.T) { // Change to a non-workspace dir - dir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..") + dir, revertWd := RequireChDir(t, relativeWorkspaceDir+"/..") defer revertWd() // Point to a wasm file explicitly wasm := filepath.Join(dir, "workspace", "extension.wasm") // Run "getenvoy extension push localhost:5000/getenvoy/sample --extension-file testdata/workspace/extension.wasm" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "push", localRegistryWasmImageRef, "--extension-file", wasm}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "push", localRegistryWasmImageRef, "--extension-file", wasm}) + err := cmdutil.Execute(c) // Verify the pushed a latest tag to the correct registry - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) require.Contains(t, stdout.String(), fmt.Sprintf(`Using default tag: latest Pushed %s:latest digest: sha256`, localRegistryWasmImageRef)) - require.Empty(t, stderr.String(), `expected no stderr running [%v]`, cmd) + require.Empty(t, stderr, `expected no stderr running [%v]`, c) } diff --git a/pkg/cmd/extension/root_test.go b/pkg/cmd/extension/root_test.go index 81f59dbb..61a27555 100644 --- a/pkg/cmd/extension/root_test.go +++ b/pkg/cmd/extension/root_test.go @@ -15,92 +15,68 @@ package extension_test import ( - "bytes" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - "github.com/spf13/cobra" + "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/cmd" "github.com/tetratelabs/getenvoy/pkg/cmd/extension/globals" + cmdtest "github.com/tetratelabs/getenvoy/pkg/test/cmd" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -var _ = Describe("getenvoy extension", func() { +func TestGetEnvoyExtensionGlobalFlags(t *testing.T) { + type testCase struct { + flag string + value *bool + expected bool + } + tests := []testCase{ // we don't test default as that depends on the runtime env + { + flag: "--no-prompt", + value: &globals.NoPrompt, + expected: true, + }, + { + flag: "--no-prompt=true", + value: &globals.NoPrompt, + expected: true, + }, + { + flag: "--no-prompt=false", + value: &globals.NoPrompt, + expected: false, + }, + { + flag: "--no-colors", + value: &globals.NoColors, + expected: true, + }, + { + flag: "--no-colors=true", + value: &globals.NoColors, + expected: true, + }, + { + flag: "--no-colors=false", + value: &globals.NoColors, + expected: false, + }, + } - var stdout *bytes.Buffer - var stderr *bytes.Buffer + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - BeforeEach(func() { - stdout = new(bytes.Buffer) - stderr = new(bytes.Buffer) - }) + t.Run(test.flag, func(t *testing.T) { + // Run "getenvoy extension" + c, stdout, stderr := cmdtest.NewRootCommand() + c.SetArgs(append([]string{"extension"}, test.flag)) + err := cmdutil.Execute(c) - var c *cobra.Command + require.NoError(t, err, `expected no error running [%v]`, c) + require.NotEmpty(t, stdout.String(), `expected stdout running [%v]`, c) + require.Empty(t, stderr.String(), `expected no stderr running [%v]`, c) - BeforeEach(func() { - c = cmd.NewRoot() - c.SetOut(stdout) - c.SetErr(stderr) - }) - - Describe("--no-prompt", func() { - type testCase struct { - args []string - expected bool - } - DescribeTable("should be available as `globals.NoPrompt` variable", - func(given testCase) { - By("running command") - c.SetArgs(append([]string{"extension"}, given.args...)) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying side effects") - Expect(globals.NoPrompt).To(Equal(given.expected)) - - By("verifying command output") - Expect(stdout.String()).ToNot(BeEmpty()) - Expect(stderr.String()).To(BeEmpty()) - }, - Entry("--no-prompt", testCase{ - args: []string{"--no-prompt"}, - expected: true, - }), - Entry("--no-prompt=false", testCase{ - args: []string{"--no-prompt=false"}, - expected: false, - }), - ) - }) - Describe("--no-colors", func() { - type testCase struct { - args []string - expected bool - } - DescribeTable("should be available as `globals.NoColors` variable", - func(given testCase) { - By("running command") - c.SetArgs(append([]string{"extension"}, given.args...)) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying side effects") - Expect(globals.NoColors).To(Equal(given.expected)) - - By("verifying command output") - Expect(stdout.String()).ToNot(BeEmpty()) - Expect(stderr.String()).To(BeEmpty()) - }, - Entry("--no-colors", testCase{ - args: []string{"--no-colors"}, - expected: true, - }), - Entry("--no-colors=false", testCase{ - args: []string{"--no-colors=false"}, - expected: false, - }), - ) - }) -}) + require.Equal(t, test.expected, *test.value) + }) + } +} diff --git a/pkg/cmd/extension/run/cmd_test.go b/pkg/cmd/extension/run/cmd_test.go index c99c363a..87bb8df0 100644 --- a/pkg/cmd/extension/run/cmd_test.go +++ b/pkg/cmd/extension/run/cmd_test.go @@ -22,10 +22,16 @@ import ( "path/filepath" "testing" + "github.com/Masterminds/semver" "github.com/otiai10/copy" + "github.com/pkg/errors" "github.com/stretchr/testify/require" - cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" + "github.com/tetratelabs/getenvoy/pkg/manifest" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" + manifesttest "github.com/tetratelabs/getenvoy/pkg/test/manifest" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" + "github.com/tetratelabs/getenvoy/pkg/types" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) @@ -35,12 +41,11 @@ const relativeWorkspaceDir = "testdata/workspace" func TestGetEnvoyExtensionRunValidateFlag(t *testing.T) { type testCase struct { name string - flags []string - flagValues []string + args []string expectedErr string } - tempDir, closer := cmd2.RequireNewTempDir(t) + tempDir, closer := RequireNewTempDir(t) defer closer() // Create a fake envoy script so that we can verify execute bit is required. @@ -51,74 +56,62 @@ func TestGetEnvoyExtensionRunValidateFlag(t *testing.T) { tests := []testCase{ { name: "--envoy-options with imbalanced quotes", - flags: []string{"--envoy-options"}, - flagValues: []string{"imbalanced ' quotes"}, + args: []string{"--envoy-options", "imbalanced ' quotes"}, expectedErr: `"imbalanced ' quotes" is not a valid command line string`, }, { name: "--envoy-path file doesn't exist", - flags: []string{"--envoy-path"}, - flagValues: []string{"non-existing-file"}, + args: []string{"--envoy-path", "non-existing-file"}, expectedErr: `unable to find custom Envoy binary at "non-existing-file": stat non-existing-file: no such file or directory`, }, { name: "--envoy-path is a directory", - flags: []string{"--envoy-path"}, - flagValues: []string{"."}, + args: []string{"--envoy-path", "."}, expectedErr: `unable to find custom Envoy binary at ".": there is a directory at a given path instead of a regular file`, }, { name: "--envoy-path not executable", - flags: []string{"--envoy-path"}, - flagValues: []string{notExecutable}, + args: []string{"--envoy-path", notExecutable}, expectedErr: fmt.Sprintf(`unable to find custom Envoy binary at "%s": file is not executable`, notExecutable), }, { name: "--envoy-version with invalid value", - flags: []string{"--envoy-version"}, - flagValues: []string{"???"}, + args: []string{"--envoy-version", "???"}, expectedErr: `Envoy version is not valid: "???" is not a valid GetEnvoy reference. Expected format: :[/]`, }, { name: "--envoy-version and --envoy-path flags at the same time", - flags: []string{"--envoy-version", "--envoy-path"}, - flagValues: []string{"standard:1.17.0", "envoy"}, + args: []string{"--envoy-version", "standard:1.17.0", "--envoy-path", "envoy"}, expectedErr: `only one of flags '--envoy-version' and '--envoy-path' can be used at a time`, }, { name: "--extension-config-file file doesn't exist", - flags: []string{"--extension-config-file"}, - flagValues: []string{"non-existing-file"}, + args: []string{"--extension-config-file", "non-existing-file"}, expectedErr: `failed to read custom extension config from file "non-existing-file": open non-existing-file: no such file or directory`, }, { name: "--extension-config-file is a directory", - flags: []string{"--extension-config-file"}, - flagValues: []string{"."}, + args: []string{"--extension-config-file", "."}, expectedErr: `failed to read custom extension config from file ".": read .: is a directory`, }, { name: "--extension-file file doesn't exist", - flags: []string{"--extension-file"}, - flagValues: []string{"non-existing-file"}, + args: []string{"--extension-file", "non-existing-file"}, expectedErr: `unable to find a pre-built *.wasm file at "non-existing-file": stat non-existing-file: no such file or directory`, }, { name: "--extension-file is a directory", - flags: []string{"--extension-file"}, - flagValues: []string{"."}, + args: []string{"--extension-file", "."}, expectedErr: `unable to find a pre-built *.wasm file at ".": there is a directory at a given path instead of a regular file`, }, { name: "--toolchain-container-options with invalid value", - flags: []string{"--toolchain-container-image"}, - flagValues: []string{"?invalid value?"}, + args: []string{"--toolchain-container-image", "?invalid value?"}, expectedErr: `"?invalid value?" is not a valid image name: invalid reference format`, }, { name: "--toolchain-container-options with imbalanced quotes", - flags: []string{"--toolchain-container-options"}, - flagValues: []string{"imbalanced ' quotes"}, + args: []string{"--toolchain-container-options", "imbalanced ' quotes"}, expectedErr: `"imbalanced ' quotes" is not a valid command line string`, }, } @@ -128,19 +121,15 @@ func TestGetEnvoyExtensionRunValidateFlag(t *testing.T) { t.Run(test.name, func(t *testing.T) { // Run "getenvoy extension run" with the flags we are testing - cmd, stdout, stderr := cmd2.NewRootCommand() - args := []string{"extension", "run"} - for i := range test.flags { - args = append(args, test.flags[i], test.flagValues[i]) - } - cmd.SetArgs(args) - err := cmdutil.Execute(cmd) - require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs(append([]string{"extension", "run"}, test.args...)) + err := cmdutil.Execute(c) + require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, c) // Verify the command failed with the expected error - require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension run --help' for usage.\n", test.expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) }) } } @@ -151,16 +140,16 @@ func TestGetEnvoyExtensionRunFailsOutsideWorkspaceDirectory(t *testing.T) { defer cleanup() // Run "getenvoy extension run" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run"}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run"}) + err := cmdutil.Execute(c) // Verify the command failed with the expected error expectedErr := "there is no extension directory at or above: " + config.workspaceDir - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) - require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension run --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } func TestGetEnvoyExtensionRun(t *testing.T) { @@ -168,16 +157,16 @@ func TestGetEnvoyExtensionRun(t *testing.T) { defer cleanup() // Run "getenvoy extension run" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) + err := cmdutil.Execute(c) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) envoyBin := filepath.Join(config.envoyHome, "builds/standard/1.17.0", config.platform, "/bin/envoy") // The working directory of envoy isn't the same as docker or the workspace - envoyWd := cmd2.ParseEnvoyWorkDirectory(stdout) + envoyWd := cmd.ParseEnvoyWorkDirectory(stdout) // We expect docker to build from the correct path, as the current user and mount a volume for the correct workspace. expectedStdout := fmt.Sprintf(`%s/docker run -u %s --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm @@ -185,8 +174,8 @@ envoy pwd: %s envoy bin: %s envoy args: -c %s/envoy.tmpl.yaml`, config.dockerDir, config.expectedUidGid, config.workspaceDir, envoyWd, envoyBin, envoyWd) - require.Equal(t, expectedStdout+"\n", stdout.String(), `expected stdout running [%v]`, cmd) - require.Equal(t, "docker stderr\nenvoy stderr\n", stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStdout+"\n", stdout.String(), `expected stdout running [%v]`, c) + require.Equal(t, "docker stderr\nenvoy stderr\n", stderr.String(), `expected stderr running [%v]`, c) // Verify the placeholders envoy would have ran substituted, notably including the generated extension.wasm expectedYaml := fmt.Sprintf(`'extension.name': "mycompany.filters.http.custom_metrics" @@ -194,7 +183,7 @@ envoy args: -c %s/envoy.tmpl.yaml`, 'extension.config': {"@type":"type.googleapis.com/google.protobuf.StringValue","value":"{\"key\":\"value\"}"} `, config.workspaceDir) yaml := requirePlaceholdersYaml(t, config.envoyHome) - require.Equal(t, expectedYaml, yaml, `unexpected placeholders yaml after running [%v]`, cmd) + require.Equal(t, expectedYaml, yaml, `unexpected placeholders yaml after running [%v]`, c) } // TestGetEnvoyExtensionRunDockerFail ensures docker failures show useful information in stderr @@ -205,9 +194,9 @@ func TestGetEnvoyExtensionRunDockerFail(t *testing.T) { // "-e DOCKER_EXIT_CODE=3" is a special instruction handled in the fake docker script toolchainOptions := "-e DOCKER_EXIT_CODE=3" // Run "getenvoy extension run" - cmd, _, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--toolchain-container-options", toolchainOptions}) - err := cmdutil.Execute(cmd) + c, _, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--toolchain-container-options", toolchainOptions}) + err := cmdutil.Execute(c) // We expect the exit instruction to have gotten to the fake docker script, along with the default options. expectedDockerExec := fmt.Sprintf("%s/docker run -u %s --rm -t -v %s:/source -w /source --init %s getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm", @@ -215,11 +204,11 @@ func TestGetEnvoyExtensionRunDockerFail(t *testing.T) { // Verify the command failed with the expected error. expectedErr := fmt.Sprintf(`failed to build Envoy extension using "default" toolchain: failed to execute an external command "%s": exit status 3`, expectedDockerExec) - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) // We also expect "docker stderr" in the output for the same reason. expectedStderr := fmt.Sprintf("docker stderr\nError: %s\n\nRun 'getenvoy extension run --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } // TestGetEnvoyExtensionRunWithExplicitVersion only tests that a version override is used. It doesn't test things that @@ -229,16 +218,16 @@ func TestGetEnvoyExtensionRunWithExplicitVersion(t *testing.T) { defer cleanup() // Run "getenvoy extension run --envoy-version wasm:stable" - cmd, stdout, _ := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-version", "wasm:stable"}) - err := cmdutil.Execute(cmd) + c, stdout, _ := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-version", "wasm:stable"}) + err := cmdutil.Execute(c) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) // verify the expected binary is in the command output envoyBin := filepath.Join(config.envoyHome, "builds/wasm/stable", config.platform, "/bin/envoy") - require.Contains(t, stdout.String(), "envoy bin: "+envoyBin, `expected stdout running [%v]`, cmd) + require.Contains(t, stdout.String(), "envoy bin: "+envoyBin, `expected stdout running [%v]`, c) } func TestGetEnvoyExtensionRunFailWithUnknownVersion(t *testing.T) { @@ -247,18 +236,18 @@ func TestGetEnvoyExtensionRunFailWithUnknownVersion(t *testing.T) { version := "wasm:unknown" // Run "getenvoy extension run --envoy-version wasm:unknown" - cmd, _, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-version", version}) - err := cmdutil.Execute(cmd) + c, _, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-version", version}) + err := cmdutil.Execute(c) // Verify the command failed with the expected error. reference := version + "/" + config.platform expectedErr := fmt.Sprintf(`failed to run "default" example: unable to find matching GetEnvoy build for reference "%s"`, reference) - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) // We also expect "docker stderr" in the output for the same reason. expectedStderr := fmt.Sprintf("docker stderr\nError: %s\n\nRun 'getenvoy extension run --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } func TestGetEnvoyExtensionRunWithCustomBinary(t *testing.T) { @@ -267,15 +256,15 @@ func TestGetEnvoyExtensionRunWithCustomBinary(t *testing.T) { // Run "getenvoy extension run --envoy-path $ENVOY_HOME/bin/envoy" envoyBin := filepath.Join(config.envoyHome, "bin/envoy") - cmd, stdout, _ := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--envoy-path", envoyBin}) - err := cmdutil.Execute(cmd) + c, stdout, _ := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--envoy-path", envoyBin}) + err := cmdutil.Execute(c) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) // The only way we can see "envoy bin: ..." in stdout, is if our fake envoy script was read - require.Contains(t, stdout.String(), "envoy bin: "+envoyBin, `expected stdout running [%v]`, cmd) + require.Contains(t, stdout.String(), "envoy bin: "+envoyBin, `expected stdout running [%v]`, c) } func TestGetEnvoyExtensionRunWithOptions(t *testing.T) { @@ -283,19 +272,19 @@ func TestGetEnvoyExtensionRunWithOptions(t *testing.T) { defer cleanup() // Run "getenvoy extension run ..." - cmd, stdout, _ := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, + c, stdout, _ := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-options", "'--concurrency 2 --component-log-level wasm:debug,config:trace'"}) - err := cmdutil.Execute(cmd) + err := cmdutil.Execute(c) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) // The working directory of envoy is a temp directory not controlled by this test, so we have to parse it. - envoyWd := cmd2.ParseEnvoyWorkDirectory(stdout) + envoyWd := cmd.ParseEnvoyWorkDirectory(stdout) envoyArgs := fmt.Sprintf(`-c %s/envoy.tmpl.yaml --concurrency 2 --component-log-level wasm:debug,config:trace`, envoyWd) - require.Contains(t, stdout.String(), "envoy args: "+envoyArgs, `expected stdout running [%v]`, cmd) + require.Contains(t, stdout.String(), "envoy args: "+envoyArgs, `expected stdout running [%v]`, c) } // TestGetEnvoyExtensionRunWithWasm shows docker isn't run when the user supplies an "--extension-file" @@ -309,30 +298,30 @@ func TestGetEnvoyExtensionRunWithWasm(t *testing.T) { require.NoError(t, err, `expected no error creating extension.wasm: %s`, wasmFile) // Run "getenvoy extension run --extension-file /path/to/extension.wasm" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--extension-file", wasmFile}) - err = cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--extension-file", wasmFile}) + err = cmdutil.Execute(c) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) envoyBin := filepath.Join(config.envoyHome, "builds/standard/1.17.0", config.platform, "/bin/envoy") // The working directory of envoy is a temp directory not controlled by this test, so we have to parse it. - envoyWd := cmd2.ParseEnvoyWorkDirectory(stdout) + envoyWd := cmd.ParseEnvoyWorkDirectory(stdout) // We expect docker to not have ran, since we supplied a pre-existing wasm. However, envoy should have. expectedStdout := fmt.Sprintf(`envoy pwd: %s envoy bin: %s envoy args: -c %s/envoy.tmpl.yaml`, envoyWd, envoyBin, envoyWd) - require.Equal(t, expectedStdout+"\n", stdout.String(), `expected stdout running [%v]`, cmd) - require.Equal(t, "envoy stderr\n", stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStdout+"\n", stdout.String(), `expected stdout running [%v]`, c) + require.Equal(t, "envoy stderr\n", stderr.String(), `expected stderr running [%v]`, c) // Verify the placeholders envoy would have ran substituted, notably including the specified extension.wasm yamlExtensionCode := fmt.Sprintf(`'extension.code': {"local":{"filename":"%s"}}`, wasmFile) yaml := requirePlaceholdersYaml(t, config.envoyHome) - require.Contains(t, yaml, yamlExtensionCode, `unexpected placeholders yaml after running [%v]`, cmd) + require.Contains(t, yaml, yamlExtensionCode, `unexpected placeholders yaml after running [%v]`, c) } // TestGetEnvoyExtensionRunWithConfig shows extension config passed as an argument ends up readable by envoy. @@ -346,17 +335,17 @@ func TestGetEnvoyExtensionRunWithConfig(t *testing.T) { require.NoError(t, err, `expected no error creating extension.wasm: %s`, configFile) // Run "getenvoy extension run --extension-config-file /path/to/config.json" - cmd, _, _ := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--extension-config-file", configFile}) - err = cmdutil.Execute(cmd) + c, _, _ := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--extension-config-file", configFile}) + err = cmdutil.Execute(c) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) // Verify the placeholders envoy would have ran substituted, notably including the escaped config yamlExtensionConfig := `'extension.config': {"@type":"type.googleapis.com/google.protobuf.StringValue","value":"{\"key2\":\"value2\"}"}` yaml := requirePlaceholdersYaml(t, config.envoyHome) - require.Contains(t, yaml, yamlExtensionConfig, `unexpected placeholders yaml after running [%v]`, cmd) + require.Contains(t, yaml, yamlExtensionConfig, `unexpected placeholders yaml after running [%v]`, c) } func TestGetEnvoyExtensionRunCreatesExampleWhenMissing(t *testing.T) { @@ -365,12 +354,12 @@ func TestGetEnvoyExtensionRunCreatesExampleWhenMissing(t *testing.T) { defer cleanup() // Run "getenvoy extension run" - cmd, _, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) - err := cmdutil.Execute(cmd) + c, _, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) + err := cmdutil.Execute(c) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) // Verify a new example was scaffolded prior to running docker and envoy require.Equal(t, `Scaffolding a new example setup: @@ -381,7 +370,7 @@ func TestGetEnvoyExtensionRunCreatesExampleWhenMissing(t *testing.T) { Done! docker stderr envoy stderr -`, stderr.String(), `expected stderr running [%v]`, cmd) +`, stderr.String(), `expected stderr running [%v]`, c) } // TestGetEnvoyExtensionRunTinyGo ensures the docker command isn't pinned to rust projects @@ -390,15 +379,15 @@ func TestGetEnvoyExtensionRunTinyGo(t *testing.T) { defer cleanup() // Run "getenvoy extension run" - cmd, stdout, _ := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) - err := cmdutil.Execute(cmd) + c, stdout, _ := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) + err := cmdutil.Execute(c) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) // Verify the docker command used the tinygo instead of the rust builder image - require.Contains(t, stdout.String(), `--init getenvoy/extension-tinygo-builder:latest build`, `expected stdout running [%v]`, cmd) + require.Contains(t, stdout.String(), `--init getenvoy/extension-tinygo-builder:latest build`, `expected stdout running [%v]`, c) } type testEnvoyExtensionConfig struct { @@ -423,17 +412,17 @@ func setupTest(t *testing.T, relativeWorkspaceTemplate string) (*testEnvoyExtens result := testEnvoyExtensionConfig{} var tearDown []func() - tempDir, deleteTempDir := cmd2.RequireNewTempDir(t) + tempDir, deleteTempDir := RequireNewTempDir(t) tearDown = append(tearDown, deleteTempDir) result.tempDir = tempDir // We use a fake docker command to capture the commandline that would be invoked - dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + dockerDir, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) tearDown = append(tearDown, revertPath) result.dockerDir = dockerDir envoyHome := filepath.Join(tempDir, "envoy_home") - cmd2.InitFakeEnvoyHome(t, envoyHome) + cmd.InitFakeEnvoyHome(t, envoyHome) result.envoyHome = envoyHome // create a new workspaceDir under tempDir @@ -442,22 +431,22 @@ func setupTest(t *testing.T, relativeWorkspaceTemplate string) (*testEnvoyExtens require.NoError(t, err, `error creating directory: %s`, workspaceDir) // Copy the template into the new workspaceDir to avoid tainting the source tree - err = copy.Copy(cmd2.RequireAbsDir(t, relativeWorkspaceTemplate), workspaceDir) + err = copy.Copy(RequireAbsDir(t, relativeWorkspaceTemplate), workspaceDir) require.NoError(t, err, `expected no error copying the directory: %s`, relativeWorkspaceTemplate) result.workspaceDir = workspaceDir // "getenvoy extension run" must be executed inside a valid workspace directory - _, revertWd := cmd2.RequireChDir(t, workspaceDir) + _, revertWd := RequireChDir(t, workspaceDir) tearDown = append(tearDown, revertWd) - platform := cmd2.RequireManifestPlatform(t) - shutdownTestServer := cmd2.RequireManifestTestServer(t, envoyHome) + platform := requireManifestPlatform(t) + shutdownTestServer := requireManifestTestServer(t, envoyHome) tearDown = append(tearDown, shutdownTestServer) result.platform = platform // Fake the current user so we can test it is used in the docker args expectedUser := user.User{Uid: "1001", Gid: "1002"} - revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd.OverrideGetCurrentUser(&expectedUser) tearDown = append(tearDown, revertGetCurrentUser) result.expectedUidGid = expectedUser.Uid + ":" + expectedUser.Gid @@ -474,3 +463,53 @@ func requirePlaceholdersYaml(t *testing.T, envoyHome string) string { require.NoError(t, err, `expected no error reading placeholders: %s`, placeholders) return string(b) } + +// requireManifestPlatform returns the current platform as used in manifests. +func requireManifestPlatform(t *testing.T) string { + key, err := manifest.NewKey("standard:1.17.0") + require.NoError(t, err, `error resolving manifest for key: %s`, key) + return key.Platform +} + +// requireManifestTestServer calls manifest.SetURL to a test new tests server. +// The function returned stops that server and calls manifest.SetURL with the original URL. +func requireManifestTestServer(t *testing.T, envoySubstituteArchiveDir string) func() { + testManifest, err := manifesttest.NewSimpleManifest("standard:1.17.0", "wasm:1.15", "wasm:stable") + + require.NoError(t, err, `error creating test manifest`) + + manifestServer := manifesttest.NewServer(&manifesttest.ServerOpts{ + Manifest: testManifest, + GetArtifactDir: func(uri string) (string, error) { + ref, e := types.ParseReference(uri) + if e != nil { + return "", e + } + if ref.Flavor == "wasm" { + return envoySubstituteArchiveDir, nil + } + if ref.Flavor == "standard" { + ver, e := semver.NewVersion(ref.Version) + if e == nil && ver.Major() >= 1 && ver.Minor() >= 17 { + return envoySubstituteArchiveDir, nil + } + } + return "", errors.Errorf("unexpected version of Envoy %q", uri) + }, + OnError: func(err error) { + require.NoError(t, err, `unexpected error from test manifest server`) + }, + }) + + // override location of the GetEnvoy manifest + previous := manifest.GetURL() + u := manifestServer.GetManifestURL() + err = manifest.SetURL(u) + require.NoError(t, err, `error manifest URL to: %s`, u) + + return func() { + e := manifest.SetURL(previous) + manifestServer.Close() // before require to ensure this occurs + require.NoError(t, e, `error reverting manifest URL to: %s`, previous) + } +} diff --git a/pkg/cmd/extension/test/cmd_test.go b/pkg/cmd/extension/test/cmd_test.go index 092e82c3..1e7cba07 100644 --- a/pkg/cmd/extension/test/cmd_test.go +++ b/pkg/cmd/extension/test/cmd_test.go @@ -21,7 +21,8 @@ import ( "github.com/stretchr/testify/require" - cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) @@ -30,20 +31,20 @@ const relativeWorkspaceDir = "testdata/workspace" func TestGetEnvoyExtensionTestValidateFlag(t *testing.T) { type testCase struct { - flag string - flagValue string + name string + args []string expectedErr string } tests := []testCase{ { - flag: "--toolchain-container-image", - flagValue: "?invalid value?", + name: "--toolchain-container-options with invalid value", + args: []string{"--toolchain-container-image", "?invalid value?"}, expectedErr: `"?invalid value?" is not a valid image name: invalid reference format`, }, { - flag: "--toolchain-container-options", - flagValue: "imbalanced ' quotes", + name: "--toolchain-container-options with imbalanced quotes", + args: []string{"--toolchain-container-options", "imbalanced ' quotes"}, expectedErr: `"imbalanced ' quotes" is not a valid command line string`, }, } @@ -51,113 +52,113 @@ func TestGetEnvoyExtensionTestValidateFlag(t *testing.T) { for _, test := range tests { test := test // pin! see https://github.com/kyoh86/scopelint for why - t.Run(test.flag+"="+test.flagValue, func(t *testing.T) { + t.Run(test.name, func(t *testing.T) { // Run "getenvoy extension test" with the flags we are testing - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "test", test.flag, test.flagValue}) - err := cmdutil.Execute(cmd) - require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs(append([]string{"extension", "test"}, test.args...)) + err := cmdutil.Execute(c) + require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, c) // Verify the command failed with the expected error - require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension test --help' for usage.\n", test.expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) }) } } func TestGetEnvoyExtensionTestFailsOutsideWorkspaceDirectory(t *testing.T) { // Change to a non-workspace dir - dir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..") + dir, revertWd := RequireChDir(t, relativeWorkspaceDir+"/..") defer revertWd() // Run "getenvoy extension test" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "test"}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "test"}) + err := cmdutil.Execute(c) // Verify the command failed with the expected error expectedErr := "there is no extension directory at or above: " + dir - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) - require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy extension test --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } func TestGetEnvoyExtensionTest(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked - dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + dockerDir, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) defer revertPath() // "getenvoy extension test" must be in a valid workspace directory - workspaceDir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Fake the current user so we can test it is used in the docker args expectedUser := user.User{Uid: "1001", Gid: "1002"} - revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd.OverrideGetCurrentUser(&expectedUser) defer revertGetCurrentUser() // Run "getenvoy extension test" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "build"}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "build"}) + err := cmdutil.Execute(c) // We expect docker to run from the correct path, as the current user and mount a volume for the correct workspace. expectedDockerExec := fmt.Sprintf("%s/docker run -u %s:%s --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm", dockerDir, expectedUser.Uid, expectedUser.Gid, workspaceDir) // Verify the command invoked, passing the correct default commandline - require.NoError(t, err, `expected no error running [%v]`, cmd) - require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, cmd) - require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) + require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, c) + require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, c) } // This tests --toolchain-container flags become docker command options func TestGetEnvoyExtensionBuildWithDockerOptions(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked - _, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + _, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) defer revertPath() // "getenvoy extension build" must be in a valid workspace directory - _, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + _, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Run "getenvoy extension build" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "build", + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "build", "--toolchain-container-image", "build/image", "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, }) - err := cmdutil.Execute(cmd) + err := cmdutil.Execute(c) // Verify the command's stdout includes the init args. TestGetEnvoyExtensionBuild tests the rest of stdout. - require.NoError(t, err, `expected no error running [%v]`, cmd) - require.Regexp(t, ".*--init -e VAR=VALUE -v /host:/container build/image build.*", stdout.String(), `expected stdout running [%v]`, cmd) - require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, cmd) + require.NoError(t, err, `expected no error running [%v]`, c) + require.Regexp(t, ".*--init -e VAR=VALUE -v /host:/container build/image build.*", stdout.String(), `expected stdout running [%v]`, c) + require.Equal(t, "docker stderr\n", stderr.String(), `expected stderr running [%v]`, c) } // TestGetEnvoyExtensionTestFail ensures test failures show useful information in stderr func TestGetEnvoyExtensionTestFail(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked, and force a failure. - dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) + dockerDir, revertPath := RequireOverridePath(t, cmd.FakeDockerDir) defer revertPath() // "getenvoy extension test" must be in a valid workspace directory - workspaceDir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Fake the current user so we can test it is used in the docker args expectedUser := user.User{Uid: "1001", Gid: "1002"} - revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd.OverrideGetCurrentUser(&expectedUser) defer revertGetCurrentUser() // "-e DOCKER_EXIT_CODE=3" is a special instruction handled in the fake docker script toolchainOptions := "-e DOCKER_EXIT_CODE=3" // Run "getenvoy extension test" - cmd, stdout, stderr := cmd2.NewRootCommand() - cmd.SetArgs([]string{"extension", "test", "--toolchain-container-options", toolchainOptions}) - err := cmdutil.Execute(cmd) + c, stdout, stderr := cmd.NewRootCommand() + c.SetArgs([]string{"extension", "test", "--toolchain-container-options", toolchainOptions}) + err := cmdutil.Execute(c) // We expect the exit instruction to have gotten to the fake docker script, along with the default options. expectedDockerExec := fmt.Sprintf("%s/docker run -u %s:%s --rm -t -v %s:/source -w /source --init %s getenvoy/extension-rust-builder:latest test", @@ -165,12 +166,12 @@ func TestGetEnvoyExtensionTestFail(t *testing.T) { // Verify the command failed with the expected error. expectedErr := fmt.Sprintf(`failed to unit test Envoy extension using "default" toolchain: failed to execute an external command "%s": exit status 3`, expectedDockerExec) - require.EqualError(t, err, expectedErr, `expected an error running [%v]`, cmd) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c) // We should see stdout because the docker script was invoked. - require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, cmd) + require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, c) // We also expect "docker stderr" in the output for the same reason. expectedStderr := fmt.Sprintf("docker stderr\nError: %s\n\nRun 'getenvoy extension test --help' for usage.\n", expectedErr) - require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) + require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, c) } diff --git a/pkg/cmd/root_test.go b/pkg/cmd/root_test.go index 3bb1df50..7c2d99e4 100644 --- a/pkg/cmd/root_test.go +++ b/pkg/cmd/root_test.go @@ -15,220 +15,187 @@ package cmd_test import ( - "bytes" - "io" - "os" + "fmt" "path/filepath" - "strings" + "testing" "github.com/mitchellh/go-homedir" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/spf13/cobra" + "github.com/stretchr/testify/require" - . "github.com/tetratelabs/getenvoy/pkg/cmd" "github.com/tetratelabs/getenvoy/pkg/common" "github.com/tetratelabs/getenvoy/pkg/manifest" + cmdtest "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -var _ = Describe("getenvoy", func() { +func TestGetEnvoyValidateArgs(t *testing.T) { + type testCase struct { + name string + args []string + expectedErr string + } + + tests := []testCase{ + { + name: "--home-dir empty", + args: []string{"--home-dir", ""}, + expectedErr: `GetEnvoy home directory cannot be empty`, + }, + { + name: "--manifest empty", + args: []string{"--manifest", ""}, + expectedErr: `GetEnvoy manifest URL cannot be empty`, + }, + { + name: "--manifest not a URL", + args: []string{"--manifest", "/not/url"}, + expectedErr: `"/not/url" is not a valid manifest URL`, + }, + } + + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - var backupEnviron []string + t.Run(test.name, func(t *testing.T) { + c, stdout, stderr := cmdtest.NewRootCommand() + c.SetArgs(append(test.args, "help")) + err := cmdutil.Execute(c) + require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, c) - BeforeEach(func() { - backupEnviron = os.Environ() - }) + // Verify the command failed with the expected error + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, c) + expectedStderr := fmt.Sprintf("Error: %s\n\nRun 'getenvoy help --help' for usage.\n", test.expectedErr) + require.Equal(t, expectedStderr, stderr.String(), `unexpected stderr running [%v]`, c) + }) + } +} + +func TestGetEnvoyHomeDir(t *testing.T) { + type testCase struct { + name string + args []string + // setup returns a tear-down function + setup func() func() + expected string + } - AfterEach(func() { - for _, pair := range backupEnviron { - parts := strings.SplitN(pair, "=", 2) - key, value := parts[0], parts[1] - os.Setenv(key, value) + emptySetup := func() func() { + return func() { } - }) - - var stdout *bytes.Buffer - var stderr *bytes.Buffer - - BeforeEach(func() { - stdout = new(bytes.Buffer) - stderr = new(bytes.Buffer) - }) - - newRootCmd := func(stdout, stderr io.Writer) *cobra.Command { - c := NewRoot() - c.SetOut(stdout) - c.SetErr(stderr) - - // add a fake sub-command for unit test - c.AddCommand(&cobra.Command{ - Use: "fake-command", - RunE: func(_ *cobra.Command, _ []string) error { - return nil + } + + home, err := homedir.Dir() + require.NoError(t, err, `error getting current user's home dir'`) + defaultHomeDir, err := filepath.Abs(filepath.Join(home, ".getenvoy")) + require.NoError(t, err, `error resolving absolute path to default GETENVOY_HOME'`) + + tests := []testCase{ // we don't test default as that depends on the runtime env + { + name: "default is ~/.getenvoy", + setup: emptySetup, + expected: defaultHomeDir, + }, + { + name: "GETENVOY_HOME env", + setup: func() func() { + return RequireSetenv(t, "GETENVOY_HOME", "/from/GETENVOY_HOME/env") + }, + expected: "/from/GETENVOY_HOME/env", + }, + { + name: "--home-dir arg", + args: []string{"--home-dir", "/from/home-dir/arg"}, + setup: emptySetup, + expected: "/from/home-dir/arg", + }, + { + name: "prioritizes --home-dir arg over GETENVOY_HOME env", + args: []string{"--home-dir", "/from/home-dir/arg"}, + setup: func() func() { + return RequireSetenv(t, "GETENVOY_HOME", "/from/GETENVOY_HOME/env") }, + expected: "/from/home-dir/arg", + }, + } + + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why + + t.Run(test.name, func(t *testing.T) { + tearDown := test.setup() + defer tearDown() + c, stdout, stderr := cmdtest.NewRootCommand() + c.SetArgs(append(test.args, "help")) + err := cmdutil.Execute(c) + + require.NoError(t, err, `expected no error running [%v]`, c) + require.NotEmpty(t, stdout.String(), `expected stdout running [%v]`, c) + require.Empty(t, stderr.String(), `expected no stderr running [%v]`, c) + + require.Equal(t, test.expected, common.HomeDir) }) - return c + } +} + +func TestGetEnvoyManifest(t *testing.T) { + type testCase struct { + name string + args []string + // setup returns a tear-down function + setup func() func() + expected string } - defaultHomeDir := func() string { - home, err := homedir.Dir() - Expect(err).NotTo(HaveOccurred()) - return filepath.Join(home, ".getenvoy") + emptySetup := func() func() { + return func() { + } } - It("should not have any required arguments", func() { - By("running command") - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"fake-command"}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(BeEmpty()) - - By("verifying global state") - Expect(common.HomeDir).To(Equal(defaultHomeDir())) - Expect(manifest.GetURL()).To(Equal(`https://tetrate.bintray.com/getenvoy/manifest.json`)) - }) - - It("should support 'GETENVOY_HOME' environment variable", func() { - expected := "/path/to/getenvoy/home" //nolint:goconst - - By("running command") - os.Setenv("GETENVOY_HOME", expected) - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"fake-command"}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying global state") - Expect(common.HomeDir).To(Equal(expected)) - }) - - It("should support '--home-dir' command line option", func() { - expected := "/path/to/getenvoy/home" //nolint:goconst - - By("running command") - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"--home-dir", expected, "fake-command"}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying global state") - Expect(common.HomeDir).To(Equal(expected)) - }) - - It("should prioritize '--home-dir' command line option over 'GETENVOY_HOME' environment variable", func() { - unexpected := "/path/that/should/be/ignored" - expected := "/path/to/getenvoy/home" //nolint:goconst - - By("running command") - os.Setenv("GETENVOY_HOME", unexpected) - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"--home-dir", expected, "fake-command"}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying global state") - Expect(common.HomeDir).To(Equal(expected)) - }) - - It("should reject empty '--home-dir'", func() { - unexpected := "/path/that/should/be/ignored" - - By("running command") - os.Setenv("GETENVOY_HOME", unexpected) - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"--home-dir=", "fake-command"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: GetEnvoy home directory cannot be empty - -Run 'getenvoy fake-command --help' for usage. -`)) - }) - - It("should support 'GETENVOY_MANIFEST_URL' environment variable", func() { - expected := "http://host/path/to/manifest" - - By("running command") - os.Setenv("GETENVOY_MANIFEST_URL", expected) - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"fake-command"}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying global state") - Expect(manifest.GetURL()).To(Equal(expected)) - }) - - It("should support '--manifest' command line option", func() { - expected := "http://host/path/to/manifest" - - By("running command") - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"--manifest", expected, "fake-command"}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying global state") - Expect(manifest.GetURL()).To(Equal(expected)) - }) - - It("should prioritize '--manifest' command line option over 'GETENVOY_MANIFEST_URL' environment variable", func() { - unexpected := "https://host/path/that/should/be/ignored" //nolint:goconst - expected := "https://host/path/to/manifest" - - By("running command") - os.Setenv("GETENVOY_MANIFEST_URL", unexpected) - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"--manifest", expected, "fake-command"}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying global state") - Expect(manifest.GetURL()).To(Equal(expected)) - }) - - It("should reject empty '--manifest' command line option", func() { - unexpected := "https://host/path/that/should/be/ignored" //nolint:goconst - - By("running command") - os.Setenv("GETENVOY_MANIFEST_URL", unexpected) - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"--manifest=", "fake-command"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: GetEnvoy manifest URL cannot be empty - -Run 'getenvoy fake-command --help' for usage. -`)) - }) - - It("should reject invalid '--manifest' command line option", func() { - unexpected := "https://host/path/that/should/be/ignored" //nolint:goconst - invalid := "/not/a/url" - - By("running command") - os.Setenv("GETENVOY_MANIFEST_URL", unexpected) - c := newRootCmd(stdout, stderr) - c.SetArgs([]string{"--manifest", invalid, "fake-command"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "/not/a/url" is not a valid manifest URL - -Run 'getenvoy fake-command --help' for usage. -`)) - }) -}) + tests := []testCase{ // we don't test default as that depends on the runtime env + { + name: "default is https://tetrate.bintray.com/getenvoy/manifest.json", + setup: emptySetup, + expected: "https://tetrate.bintray.com/getenvoy/manifest.json", + }, + { + name: "GETENVOY_MANIFEST_URL env", + setup: func() func() { + return RequireSetenv(t, "GETENVOY_MANIFEST_URL", "http://GETENVOY_MANIFEST_URL/env") + }, + expected: "http://GETENVOY_MANIFEST_URL/env", + }, + { + name: "--manifest arg", + args: []string{"--manifest", "http://manifest/arg"}, + setup: emptySetup, + expected: "http://manifest/arg", + }, + { + name: "prioritizes --manifest arg over GETENVOY_MANIFEST_URL env", + args: []string{"--manifest", "http://manifest/arg"}, + setup: func() func() { + return RequireSetenv(t, "GETENVOY_MANIFEST_URL", "http://GETENVOY_MANIFEST_URL/env") + }, + expected: "http://manifest/arg", + }, + } + + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why + + t.Run(test.name, func(t *testing.T) { + tearDown := test.setup() + defer tearDown() + c, stdout, stderr := cmdtest.NewRootCommand() + c.SetArgs(append(test.args, "help")) + err := cmdutil.Execute(c) + + require.NoError(t, err, `expected no error running [%v]`, c) + require.NotEmpty(t, stdout.String(), `expected stdout running [%v]`, c) + require.Empty(t, stderr.String(), `expected no stderr running [%v]`, c) + + require.Equal(t, test.expected, manifest.GetURL()) + }) + } +} diff --git a/pkg/extension/init/init.go b/pkg/extension/init/init.go index f2750287..05e123b2 100644 --- a/pkg/extension/init/init.go +++ b/pkg/extension/init/init.go @@ -86,6 +86,7 @@ func (s *scaffolder) walk(sourceDirName, destinationDirName string) (errs error) if err != nil && err != io.EOF { return err } + for _, sourceFile := range sourceFiles { if sourceFile.IsDir() { if err := s.walk(path.Join(sourceDirName, sourceFile.Name()), filepath.Join(destinationDirName, sourceFile.Name())); err != nil { @@ -102,10 +103,13 @@ func (s *scaffolder) walk(sourceDirName, destinationDirName string) (errs error) func (s *scaffolder) visit(sourceDirName, destinationDirName string, sourceFileInfo os.FileInfo) (errs error) { baseOutputFileName := sourceFileInfo.Name() - // We rename go.mod to go.mod_ to workaround https://github.com/golang/go/issues/45197 - if baseOutputFileName == "go.mod_" { - baseOutputFileName = "go.mod" + switch baseOutputFileName { + case ".gitignore", ".licenserignore": + return nil // Ignore files shouldn't end up in the output directory + case "go.mod_": + baseOutputFileName = "go.mod" // rename workaround for https://github.com/golang/go/issues/45197 } + relOutputFileName := filepath.Join(destinationDirName, baseOutputFileName) outputFileName := filepath.Join(s.opts.OutputDir, relOutputFileName) if err := osutil.EnsureDirExists(filepath.Dir(outputFileName)); err != nil { diff --git a/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go b/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go index 90e4122b..98154473 100644 --- a/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go +++ b/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go @@ -27,6 +27,7 @@ import ( . "github.com/tetratelabs/getenvoy/pkg/extension/workspace/example/runtime/getenvoy" "github.com/tetratelabs/getenvoy/pkg/extension/workspace/model" "github.com/tetratelabs/getenvoy/pkg/test/cmd" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" ioutil "github.com/tetratelabs/getenvoy/pkg/util/io" ) @@ -65,7 +66,7 @@ envoy args: -c %s/envoy.tmpl.yaml } func TestRuntimeRunFailsOnInvalidWorkspace(t *testing.T) { - invalidWorkspaceDir := cmd.RequireAbsDir(t, invalidWorkspaceDir) + invalidWorkspaceDir := RequireAbsDir(t, invalidWorkspaceDir) workspace, err := workspaces.GetWorkspaceAt(invalidWorkspaceDir) require.NoError(t, err, `expected no error getting workspace from directory %s`, invalidWorkspaceDir) @@ -123,7 +124,7 @@ func runContext(workspace model.Workspace, example model.Example, envoyPath stri func setupFakeEnvoy(t *testing.T) (string, func()) { var tearDown []func() - tempDir, deleteTempDir := cmd.RequireNewTempDir(t) + tempDir, deleteTempDir := RequireNewTempDir(t) tearDown = append(tearDown, deleteTempDir) envoyHome := filepath.Join(tempDir, "envoy_home") diff --git a/pkg/test/cmd/command.go b/pkg/test/cmd/command.go index 2202716f..d462ebfc 100644 --- a/pkg/test/cmd/command.go +++ b/pkg/test/cmd/command.go @@ -23,20 +23,14 @@ import ( "os/user" "path/filepath" "regexp" - "strings" "testing" - "github.com/Masterminds/semver" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/stretchr/testify/require" "github.com/tetratelabs/getenvoy/pkg/cmd" "github.com/tetratelabs/getenvoy/pkg/common" builtintoolchain "github.com/tetratelabs/getenvoy/pkg/extension/workspace/toolchain/builtin" - "github.com/tetratelabs/getenvoy/pkg/manifest" - manifesttest "github.com/tetratelabs/getenvoy/pkg/test/manifest" - "github.com/tetratelabs/getenvoy/pkg/types" ) // FakeDockerDir includes "docker" which only executes the output. This means it doesn't really invoke docker. @@ -54,67 +48,6 @@ func NewRootCommand() (c *cobra.Command, stdout, stderr *bytes.Buffer) { return c, stdout, stderr } -// RequireNewTempDir creates a new directory. The function returned cleans it up. -func RequireNewTempDir(t *testing.T) (string, func()) { - d, err := ioutil.TempDir("", "") - if err != nil { - require.NoError(t, err, `ioutil.TempDir("", "") erred`) - } - d, err = filepath.EvalSymlinks(d) - require.NoError(t, err, `filepath.EvalSymlinks(%s) erred`, d) - require.NotEmpty(t, d, `filepath.EvalSymlinks(%s) returned ""`) - return d, func() { - e := os.RemoveAll(d) - require.NoError(t, e, `error removing directory: %v`, d) - } -} - -// RequireChDir will os.Chdir into the indicated dir, panicing on any problem. -// The string returned is the absolute path corresponding to the input. The function returned reverts to the original. -func RequireChDir(t *testing.T, d string) (string, func()) { - dir := RequireAbsDir(t, d) - - // Save previous working directory to that it can be reverted later. - previous, err := os.Getwd() - require.NoError(t, err, `error determining current directory`) - - // Now, actually change to the directory. - err = os.Chdir(d) - require.NoError(t, err, `error changing to directory: %v`, d) - return dir, func() { - e := os.Chdir(previous) - require.NoError(t, e, `error changing to directory: %v`, previous) - } -} - -// RequireAbsDir runs filepath.Abs and ensures there are no errors and the input is a directory. -func RequireAbsDir(t *testing.T, d string) string { - dir, err := filepath.Abs(d) - require.NoError(t, err, `error determining absolute directory: %v`, d) - require.DirExists(t, dir, `directory doesn't exist': %v`, dir) - return dir -} - -// RequireOverridePath will prefix os.Setenv with the indicated dir, panicing on any problem. -// The string returned is the absolute path corresponding to the input. The function returned reverts to the original. -func RequireOverridePath(t *testing.T, d string) (string, func()) { - dir := RequireAbsDir(t, d) - - // Save previous path to that it can be reverted later. - previous := os.Getenv("PATH") - - // Place the resolved directory in from of the previous path - path := strings.Join([]string{dir, previous}, string(filepath.ListSeparator)) - - // Now, actually change the PATH env - err := os.Setenv("PATH", path) - require.NoError(t, err, `error setting PATH to: %v`, path) - return dir, func() { - e := os.Setenv("PATH", previous) - require.NoError(t, e, `error reverting to PATH: %v`, previous) - } -} - // OverrideGetCurrentUser sets builtin.GetCurrentUser to return the indicated user. // The function returned reverts to the original. func OverrideGetCurrentUser(u *user.User) func() { @@ -136,56 +69,6 @@ func OverrideHomeDir(homeDir string) func() { } } -// RequireManifestPlatform returns the current platform as used in manifests. -func RequireManifestPlatform(t *testing.T) string { - key, err := manifest.NewKey("standard:1.17.0") - require.NoError(t, err, `error resolving manifest for key: %s`, key) - return key.Platform -} - -// RequireManifestTestServer calls manifest.SetURL to a test new tests server. -// The function returned stops that server and calls manifest.SetURL with the original URL. -func RequireManifestTestServer(t *testing.T, envoySubstituteArchiveDir string) func() { - testManifest, err := manifesttest.NewSimpleManifest("standard:1.17.0", "wasm:1.15", "wasm:stable") - - require.NoError(t, err, `error creating test manifest`) - - manifestServer := manifesttest.NewServer(&manifesttest.ServerOpts{ - Manifest: testManifest, - GetArtifactDir: func(uri string) (string, error) { - ref, e := types.ParseReference(uri) - if e != nil { - return "", e - } - if ref.Flavor == "wasm" { - return envoySubstituteArchiveDir, nil - } - if ref.Flavor == "standard" { - ver, e := semver.NewVersion(ref.Version) - if e == nil && ver.Major() >= 1 && ver.Minor() >= 17 { - return envoySubstituteArchiveDir, nil - } - } - return "", errors.Errorf("unexpected version of Envoy %q", uri) - }, - OnError: func(err error) { - require.NoError(t, err, `unexpected error from test manifest server`) - }, - }) - - // override location of the GetEnvoy manifest - previous := manifest.GetURL() - u := manifestServer.GetManifestURL() - err = manifest.SetURL(u) - require.NoError(t, err, `error manifest URL to: %s`, u) - - return func() { - e := manifest.SetURL(previous) - manifestServer.Close() // before require to ensure this occurs - require.NoError(t, e, `error reverting manifest URL to: %s`, previous) - } -} - // InitFakeEnvoyHome creates "$envoyHome/bin/envoy", which echos the commandline, output and stderr. It returns the // path to the fake envoy script. // diff --git a/pkg/test/morerequire/morerequire.go b/pkg/test/morerequire/morerequire.go new file mode 100644 index 00000000..48c77b8d --- /dev/null +++ b/pkg/test/morerequire/morerequire.go @@ -0,0 +1,108 @@ +// Copyright 2021 Tetrate +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package morerequire includes more require functions than "github.com/stretchr/testify/require" +// Do not add dependencies on any main code as it will cause cycles. +package morerequire + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/otiai10/copy" + "github.com/stretchr/testify/require" +) + +// RequireNewTempDir creates a new directory. The function returned cleans it up. +func RequireNewTempDir(t *testing.T) (string, func()) { + d, err := ioutil.TempDir("", "") + if err != nil { + require.NoError(t, err, `ioutil.TempDir("", "") erred`) + } + d, err = filepath.EvalSymlinks(d) + require.NoError(t, err, `filepath.EvalSymlinks(%s) erred`, d) + require.NotEmpty(t, d, `filepath.EvalSymlinks(%s) returned ""`) + return d, func() { + e := os.RemoveAll(d) + require.NoError(t, e, `error removing directory: %v`, d) + } +} + +// RequireSetenv will os.Setenv the given key and value. The function returned reverts to the original. +func RequireSetenv(t *testing.T, key, value string) func() { + previous := os.Getenv(key) + err := os.Setenv(key, value) + require.NoError(t, err, `error setting env variable %s=%s`, key, value) + return func() { + e := os.Setenv(key, previous) + require.NoError(t, e, `error reverting env variable %s=%s`, key, previous) + } +} + +// RequireChDir will os.Chdir into the indicated dir, panicing on any problem. +// The string returned is the absolute path corresponding to the input. The function returned reverts to the original. +func RequireChDir(t *testing.T, d string) (string, func()) { + dir := RequireAbsDir(t, d) + + // Save previous working directory to that it can be reverted later. + previous, err := os.Getwd() + require.NoError(t, err, `error determining current directory`) + + // Now, actually change to the directory. + err = os.Chdir(d) + require.NoError(t, err, `error changing to directory: %v`, d) + return dir, func() { + e := os.Chdir(previous) + require.NoError(t, e, `error changing to directory: %v`, previous) + } +} + +// RequireCopyOfDir creates a new directory which is a copy of the template. The function returned cleans it up. +func RequireCopyOfDir(t *testing.T, templateDir string) (string, func()) { + d, cleanup := RequireNewTempDir(t) + err := copy.Copy(templateDir, d) + require.NoError(t, err, `expected no error copying %s to %s`, templateDir, d) + return d, cleanup +} + +// RequireAbsDir runs filepath.Abs and ensures there are no errors and the input is a directory. +func RequireAbsDir(t *testing.T, d string) string { + dir, err := filepath.Abs(d) + require.NoError(t, err, `error determining absolute directory: %v`, d) + require.DirExists(t, dir, `directory doesn't exist': %v`, dir) + return dir +} + +// RequireOverridePath will prefix os.Setenv with the indicated dir, panicing on any problem. +// The string returned is the absolute path corresponding to the input. The function returned reverts to the original. +func RequireOverridePath(t *testing.T, d string) (string, func()) { + dir := RequireAbsDir(t, d) + + // Save previous path to that it can be reverted later. + previous := os.Getenv("PATH") + + // Place the resolved directory in from of the previous path + path := strings.Join([]string{dir, previous}, string(filepath.ListSeparator)) + + // Now, actually change the PATH env + err := os.Setenv("PATH", path) + require.NoError(t, err, `error setting PATH to: %v`, path) + return dir, func() { + e := os.Setenv("PATH", previous) + require.NoError(t, e, `error reverting to PATH: %v`, previous) + } +} diff --git a/pkg/util/exec/exec_test.go b/pkg/util/exec/exec_test.go index 44cd3817..053f136a 100644 --- a/pkg/util/exec/exec_test.go +++ b/pkg/util/exec/exec_test.go @@ -70,10 +70,10 @@ var _ = Describe("Run()", func() { }) It("should properly pipe standard I/O", func() { - cmd := exec.Command("testdata/test_stdio.sh", "0", "stderr") + c := exec.Command("testdata/test_stdio.sh", "0", "stderr") stdin.WriteString("stdin\n") - err := Run(cmd, stdio) + err := Run(c, stdio) Expect(err).ToNot(HaveOccurred()) Expect(stdout.String()).To(Equal(`stdin`)) @@ -81,10 +81,10 @@ var _ = Describe("Run()", func() { }) It("should return a meaningful error when a command cannot start", func() { - cmd := exec.Command("testdata/test_stdio.sh", "123") - cmd.Dir = "testdata" + c := exec.Command("testdata/test_stdio.sh", "123") + c.Dir = "testdata" - err := Run(cmd, stdio) + err := Run(c, stdio) Expect(err).To(HaveOccurred()) Expect(err).To(MatchError(`failed to execute an external command "testdata/test_stdio.sh 123": ` + @@ -99,9 +99,9 @@ var _ = Describe("Run()", func() { }) It("should return a meaningful error when a command exits with a non-0 code", func() { - cmd := exec.Command("testdata/test_stdio.sh", "123") + c := exec.Command("testdata/test_stdio.sh", "123") - err := Run(cmd, stdio) + err := Run(c, stdio) Expect(err).To(HaveOccurred()) Expect(err).To(MatchError(`failed to execute an external command "testdata/test_stdio.sh 123": exit status 123`)) diff --git a/test/e2e/getenvoy_extension_build_test.go b/test/e2e/getenvoy_extension_build_test.go index ba106d22..82b3efcd 100644 --- a/test/e2e/getenvoy_extension_build_test.go +++ b/test/e2e/getenvoy_extension_build_test.go @@ -19,6 +19,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" ) // TestGetEnvoyExtensionBuild runs the equivalent of "getenvoy extension build" for a matrix of extension.Categories and @@ -32,10 +34,10 @@ func TestGetEnvoyExtensionBuild(t *testing.T) { test := test // pin! see https://github.com/kyoh86/scopelint for why t.Run(test.String(), func(t *testing.T) { - workDir, removeWorkDir := requireNewTempDir(t) + workDir, removeWorkDir := RequireNewTempDir(t) defer removeWorkDir() - revertChDir := requireChDir(t, workDir) + _, revertChDir := RequireChDir(t, workDir) defer revertChDir() // test requires "get envoy extension init" to have succeeded @@ -44,12 +46,12 @@ func TestGetEnvoyExtensionBuild(t *testing.T) { // "getenvoy extension build" only returns stdout because `docker run -t` redirects stderr to stdout. // We don't verify stdout because it is low signal vs looking at files created. - cmd := getEnvoy("extension build").Args(getToolchainContainerOptions()...) - _ = requireExecNoStderr(t, cmd) + c := getEnvoy("extension build").Args(getToolchainContainerOptions()...) + _ = requireExecNoStderr(t, c) // Verify the extension built extensionWasmFile := filepath.Join(workDir, extensionWasmPath(test.Language)) - require.FileExists(t, extensionWasmFile, `extension wasm file %s missing after running [%v]`, extensionWasmFile, cmd) + require.FileExists(t, extensionWasmFile, `extension wasm file %s missing after running [%v]`, extensionWasmFile, c) }) } } diff --git a/test/e2e/getenvoy_extension_examples_test.go b/test/e2e/getenvoy_extension_examples_test.go index 5b09e807..d7207620 100644 --- a/test/e2e/getenvoy_extension_examples_test.go +++ b/test/e2e/getenvoy_extension_examples_test.go @@ -20,6 +20,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" ) // TestGetEnvoyExtensionExampleAdd runs the equivalent of "getenvoy extension example XXX" commands for a matrix of @@ -35,10 +37,10 @@ func TestGetEnvoyExtensionExample(t *testing.T) { t.Run(test.String(), func(t *testing.T) { extensionConfigFileName := extensionConfigFileName(test.Language) - workDir, removeWorkDir := requireNewTempDir(t) + workDir, removeWorkDir := RequireNewTempDir(t) defer removeWorkDir() - revertChDir := requireChDir(t, workDir) + _, revertChDir := RequireChDir(t, workDir) defer revertChDir() // "getenvoy extension example XXX" commands require an extension init to succeed @@ -46,16 +48,16 @@ func TestGetEnvoyExtensionExample(t *testing.T) { defer requireExtensionClean(t, workDir) // "getenvoy extension examples list" should start empty - cmd := getEnvoy("extension examples list") - stderr := requireExecNoStdout(t, cmd) + c := getEnvoy("extension examples list") + stderr := requireExecNoStdout(t, c) require.Equal(t, `Extension has no example setups. Use "getenvoy extension examples add --help" for more information on how to add one. -`, stderr, `invalid stderr running [%v]`, cmd) +`, stderr, `invalid stderr running [%v]`, c) // "getenvoy extension examples add" should result in stderr describing files created. - cmd = getEnvoy("extension examples add") - stderr = requireExecNoStdout(t, cmd) + c = getEnvoy("extension examples add") + stderr = requireExecNoStdout(t, c) exampleFiles := []string{ filepath.Join(workDir, ".getenvoy/extension/examples/default/README.md"), @@ -73,29 +75,29 @@ Use "getenvoy extension examples add --help" for more information on how to add // Check stderr mentions the files created require.Equal(t, fmt.Sprintf("Scaffolding a new example setup:%sDone!\n", exampleFileText), - stderr, `invalid stderr running [%v]`, cmd) + stderr, `invalid stderr running [%v]`, c) // Check the files mentioned actually exist for _, path := range exampleFiles { - require.FileExists(t, path, `example file %s missing after running [%v]`, path, cmd) + require.FileExists(t, path, `example file %s missing after running [%v]`, path, c) } // "getenvoy extension examples list" should now include an example - cmd = getEnvoy("extension examples list") - stdout := requireExecNoStderr(t, cmd) - require.Equal(t, "EXAMPLE\ndefault\n", stdout, `invalid stdout running [%v]`, cmd) + c = getEnvoy("extension examples list") + stdout := requireExecNoStderr(t, c) + require.Equal(t, "EXAMPLE\ndefault\n", stdout, `invalid stdout running [%v]`, c) // "getenvoy extension examples add" should result in stderr describing files created. - cmd = getEnvoy("extension examples remove --name default") - stderr = requireExecNoStdout(t, cmd) + c = getEnvoy("extension examples remove --name default") + stderr = requireExecNoStdout(t, c) // Check stderr mentions the files removed require.Equal(t, fmt.Sprintf("Removing example setup:%sDone!\n", exampleFileText), - stderr, `invalid stderr running [%v]`, cmd) + stderr, `invalid stderr running [%v]`, c) // Check the files mentioned actually were removed for _, path := range exampleFiles { - require.NoFileExists(t, path, `example file %s still exists after running [%v]`, path, cmd) + require.NoFileExists(t, path, `example file %s still exists after running [%v]`, path, c) } }) } diff --git a/test/e2e/getenvoy_extension_init_test.go b/test/e2e/getenvoy_extension_init_test.go index 1afe52c3..479c17d9 100644 --- a/test/e2e/getenvoy_extension_init_test.go +++ b/test/e2e/getenvoy_extension_init_test.go @@ -24,6 +24,7 @@ import ( workspaces "github.com/tetratelabs/getenvoy/pkg/extension/workspace" "github.com/tetratelabs/getenvoy/pkg/extension/workspace/config/extension" toolchains "github.com/tetratelabs/getenvoy/pkg/extension/workspace/toolchain" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" ) // TestGetEnvoyExtensionInit runs the equivalent of "getenvoy extension init" for a matrix of extension.Categories and @@ -32,70 +33,107 @@ import ( // "getenvoy extension init" does not use Docker. See TestMain for general notes on about the test runtime. func TestGetEnvoyExtensionInit(t *testing.T) { const extensionName = "getenvoy_extension_init" + const envoyVersion = "standard:1.17.0" - type testTuple struct { - testName string + type testCase struct { + name string extension.Category extension.Language currentDirectory bool } - tests := make([]testTuple, 0) - for _, c := range getExtensionTestMatrix() { + tests := make([]testCase, 0) + for _, cell := range getExtensionTestMatrix() { tests = append(tests, - testTuple{c.String() + "-currentDirectory", c.Category, c.Language, true}, - testTuple{c.String() + "-newDirectory", c.Category, c.Language, false}, + testCase{cell.String() + "-currentDirectory", cell.Category, cell.Language, true}, + testCase{cell.String() + "-newDirectory", cell.Category, cell.Language, false}, ) } for _, test := range tests { test := test // pin! see https://github.com/kyoh86/scopelint for why - t.Run(test.testName, func(t *testing.T) { - workDir, removeWorkDir := requireNewTempDir(t) - defer removeWorkDir() - - revertChDir := requireChDir(t, workDir) - defer revertChDir() - - if !test.currentDirectory { - workDir = filepath.Join(workDir, "newDirectory") - } + t.Run(test.name, func(t *testing.T) { + outputDir, removeOutputDir := RequireNewTempDir(t) + defer removeOutputDir() // "getenvoy extension init" should result in stderr describing files created. - cmd := getEnvoy("extension init"). - Arg(workDir). + c := getEnvoy("extension init"). Arg("--category").Arg(test.Category.String()). Arg("--language").Arg(test.Language.String()). Arg("--name").Arg(extensionName) - stderr := requireExecNoStdout(t, cmd) + + if test.currentDirectory { + _, revertChDir := RequireChDir(t, outputDir) + defer revertChDir() + } else { + c = c.Arg(outputDir) + } + + stderr := requireExecNoStdout(t, c) // Check that the contents look valid for the inputs. for _, regex := range []string{ `^\QScaffolding a new extension:\E\n`, - fmt.Sprintf(`\QGenerating files in %s:\E\n`, workDir), + fmt.Sprintf(`\QGenerating files in %s:\E\n`, outputDir), `\Q* .getenvoy/extension/extension.yaml\E\n`, `\QDone!\E\n$`, } { - require.Regexp(t, regex, stderr, `invalid stderr running [%v]`, cmd) + require.Regexp(t, regex, stderr, `invalid stderr running [%v]`, c) } // Check to see that the extension.yaml mentioned in stderr exists. // Note: we don't check all files as extensions are language-specific. - require.FileExists(t, filepath.Join(workDir, ".getenvoy/extension/extension.yaml"), `extension.yaml missing after running [%v]`, cmd) + require.FileExists(t, filepath.Join(outputDir, ".getenvoy/extension/extension.yaml"), `extension.yaml missing after running [%v]`, c) // Check the generated extension.yaml includes values we passed and includes the default toolchain. - workspace, err := workspaces.GetWorkspaceAt(workDir) - require.NoError(t, err, `error getting workspace after running [%v]`, cmd) - require.NotNil(t, workspace, `nil workspace running [%v]`, cmd) - require.Equal(t, extensionName, workspace.GetExtensionDescriptor().Name, `wrong extension name running [%v]`, cmd) - require.Equal(t, test.Category, workspace.GetExtensionDescriptor().Category, `wrong extension category running [%v]`, cmd) - require.Equal(t, test.Language, workspace.GetExtensionDescriptor().Language, `wrong extension language running [%v]`, cmd) + workspace, err := workspaces.GetWorkspaceAt(outputDir) + require.NoError(t, err, `error getting workspace after running [%v]`, c) + require.NotNil(t, workspace, `nil workspace running [%v]`, c) + descriptor := workspace.GetExtensionDescriptor() + require.Equal(t, extensionName, descriptor.Name, `wrong extension name running [%v]: %s`, c, descriptor) + require.Equal(t, test.Category, descriptor.Category, `wrong extension category running [%v]: %s`, c, descriptor) + require.Equal(t, test.Language, descriptor.Language, `wrong extension language running [%v]: %s`, c, descriptor) + require.Equal(t, envoyVersion, descriptor.Runtime.Envoy.Version, `wrong extension envoy version running [%v]: %s`, c, descriptor) // Check the default toolchain is loadable toolchain, err := toolchains.LoadToolchain(toolchains.Default, workspace) - require.NoError(t, err, `error loading toolchain running [%v]`, cmd) - require.NotNil(t, toolchain, `nil toolchain running [%v]`, cmd) + require.NoError(t, err, `error loading toolchain running [%v]`, c) + require.NotNil(t, toolchain, `nil toolchain running [%v]`, c) + + // Verify ignore files didn't end up in the output directory + for _, ignore := range []string{".gitignore", ".licenserignore"} { + require.NotContains(t, stderr, fmt.Sprintf("* %s\n", ignore), `ignore file %s found in stderr running [%v]`, ignore, c) + } + + // Verify language-specific files + var languageSpecificPaths []string + switch test.Language { + case extension.LanguageRust: + languageSpecificPaths = []string{ + ".cargo/config", + "Cargo.toml", + "README.md", + "src/config.rs", + "src/lib.rs", + "wasm/module/Cargo.toml", + "wasm/module/src/lib.rs", + } + + case extension.LanguageTinyGo: + languageSpecificPaths = []string{ + "go.mod", + "go.sum", + "main.go", + "main_test.go", + } + } + + // Verify the paths were in stderr and actually exist. + for _, f := range languageSpecificPaths { + require.Regexp(t, fmt.Sprintf(`\Q* %s\E\n`, f), stderr, `expected stderr to include %s running [%v]`, f, c) + require.FileExists(t, filepath.Join(outputDir, f), `%s missing after running [%v]`, f, c) + } }) } } diff --git a/test/e2e/getenvoy_extension_push_test.go b/test/e2e/getenvoy_extension_push_test.go index 6f0a1a18..52919fbb 100644 --- a/test/e2e/getenvoy_extension_push_test.go +++ b/test/e2e/getenvoy_extension_push_test.go @@ -24,6 +24,7 @@ import ( "github.com/tetratelabs/getenvoy/pkg/extension/wasmimage" "github.com/tetratelabs/getenvoy/pkg/extension/workspace/config/extension" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" ) // TestGetEnvoyExtensionPush runs the equivalent of "getenvoy extension push". "getenvoy extension init" and @@ -41,7 +42,7 @@ func TestGetEnvoyExtensionPush(t *testing.T) { // When unspecified, we default the tag to Docker's default "latest". Note: recent tools enforce qualifying this! const defaultTag = "latest" - type testTuple struct { + type testCase struct { name string extension.Category extension.Language @@ -49,7 +50,7 @@ func TestGetEnvoyExtensionPush(t *testing.T) { // Push is not language-specific, so we don't need to test a large matrix, and doing so would slow down e2e runtime. // Instead, we choose something that executes "getenvoy extension build" quickly. - tests := []testTuple{ + tests := []testCase{ {"tinygo HTTP filter", extension.EnvoyHTTPFilter, extension.LanguageTinyGo}, } @@ -57,10 +58,10 @@ func TestGetEnvoyExtensionPush(t *testing.T) { test := test // pin! see https://github.com/kyoh86/scopelint for why t.Run(test.name, func(t *testing.T) { - workDir, removeWorkDir := requireNewTempDir(t) + workDir, removeWorkDir := RequireNewTempDir(t) defer removeWorkDir() - revertChDir := requireChDir(t, workDir) + _, revertChDir := RequireChDir(t, workDir) defer revertChDir() // push requires "get envoy extension init" and "get envoy extension build" to have succeeded @@ -69,8 +70,8 @@ func TestGetEnvoyExtensionPush(t *testing.T) { wasmBytes := requireExtensionBuild(t, test.Language, workDir) // After pushing, stderr should include the registry URL and the image tag. - cmd := getEnvoy("extension push").Arg(localRegistryWasmImageRef) - stderr := requireExecNoStdout(t, cmd) + c := getEnvoy("extension push").Arg(localRegistryWasmImageRef) + stderr := requireExecNoStdout(t, c) // Assemble a fully-qualified image ref as we'll pull this later imageRef := localRegistryWasmImageRef + ":" + defaultTag @@ -78,28 +79,28 @@ func TestGetEnvoyExtensionPush(t *testing.T) { // Verify stderr shows the latest tag and the correct image ref require.Contains(t, stderr, fmt.Sprintf(`Using default tag: %s Pushed %s -digest: sha256`, defaultTag, imageRef), `unexpected stderr after running [%v]`, cmd) +digest: sha256`, defaultTag, imageRef), `unexpected stderr after running [%v]`, c) // Get a puller we can use to pull what we just pushed. puller, err := wasmimage.NewPuller(false, false) - require.NoError(t, err, `error getting puller instance after running [%v]`, cmd) - require.NotNil(t, puller, `nil puller instance after running [%v]`, cmd) + require.NoError(t, err, `error getting puller instance after running [%v]`, c) + require.NotNil(t, puller, `nil puller instance after running [%v]`, c) // Pull the wasm we just pushed, writing it to a local file. dstPath := filepath.Join(workDir, "pulled_extension.wasm") desc, err := puller.Pull(imageRef, dstPath) - require.NoError(t, err, `error pulling wasm after running [%v]: %s`, cmd) + require.NoError(t, err, `error pulling wasm after running [%v]: %s`, c) // Verify the pulled image descriptor is valid and the image file exists/ - require.Equal(t, "application/vnd.module.wasm.content.layer.v1+wasm", desc.MediaType, `invalid media type after running [%v]`, cmd) - require.Equal(t, "extension.wasm", desc.Annotations["org.opencontainers.image.title"], `invalid image title after running [%v]`, cmd) - require.FileExists(t, dstPath, `image not written after running [%v]`, cmd) + require.Equal(t, "application/vnd.module.wasm.content.layer.v1+wasm", desc.MediaType, `invalid media type after running [%v]`, c) + require.Equal(t, "extension.wasm", desc.Annotations["org.opencontainers.image.title"], `invalid image title after running [%v]`, c) + require.FileExists(t, dstPath, `image not written after running [%v]`, c) // Verify the bytes pulled are exactly the same as what we pushed. pulledBytes, err := ioutil.ReadFile(dstPath) - require.NoError(t, err, `error reading file wasm %s after running [%v]`, dstPath, cmd) - require.NotEmpty(t, wasmBytes, `%s empty after running [%v]`, dstPath, cmd) - require.Equal(t, wasmBytes, pulledBytes, `pulled bytes don't match source after running [%v]`, cmd) + require.NoError(t, err, `error reading file wasm %s after running [%v]`, dstPath, c) + require.NotEmpty(t, wasmBytes, `%s empty after running [%v]`, dstPath, c) + require.Equal(t, wasmBytes, pulledBytes, `pulled bytes don't match source after running [%v]`, c) }) } } diff --git a/test/e2e/getenvoy_extension_run_test.go b/test/e2e/getenvoy_extension_run_test.go index 4bb639cb..e290c904 100644 --- a/test/e2e/getenvoy_extension_run_test.go +++ b/test/e2e/getenvoy_extension_run_test.go @@ -27,6 +27,7 @@ import ( "github.com/tetratelabs/log" "github.com/tetratelabs/getenvoy/pkg/common" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" e2e "github.com/tetratelabs/getenvoy/test/e2e/util" utilenvoy "github.com/tetratelabs/getenvoy/test/e2e/util/envoy" ) @@ -46,10 +47,10 @@ func TestGetEnvoyExtensionRun(t *testing.T) { test := test // pin! see https://github.com/kyoh86/scopelint for why t.Run(test.String(), func(t *testing.T) { - workDir, removeWorkDir := requireNewTempDir(t) + workDir, removeWorkDir := RequireNewTempDir(t) defer removeWorkDir() - revertChDir := requireChDir(t, workDir) + _, revertChDir := RequireChDir(t, workDir) defer revertChDir() // run requires "get envoy extension init" to have succeeded @@ -57,8 +58,8 @@ func TestGetEnvoyExtensionRun(t *testing.T) { defer requireExtensionClean(t, workDir) // "getenvoy extension run" only returns stdout because `docker run -t` redirects stderr to stdout. - cmd := getEnvoy("extension run --envoy-options '-l trace'").Args(getToolchainContainerOptions()...) - _, stderr, terminate := cmd.Start(t, terminateTimeout) + c := getEnvoy("extension run --envoy-options '-l trace'").Args(getToolchainContainerOptions()...) + _, stderr, terminate := c.Start(t, terminateTimeout) // The underlying call is conditional to ensure errors that raise before we stop the server, stop it. deferredTerminate := terminate @@ -68,25 +69,25 @@ func TestGetEnvoyExtensionRun(t *testing.T) { stderrLines := e2e.StreamLines(stderr).Named("stderr") - log.Infof(`waiting for Envoy Admin address to get logged after running [%v]`, cmd) + log.Infof(`waiting for Envoy Admin address to get logged after running [%v]`, c) adminAddressPattern := regexp.MustCompile(`admin address: ([^:]+:[0-9]+)`) line, err := stderrLines.FirstMatch(adminAddressPattern).Wait(10 * time.Minute) // give time to compile the extension - require.NoError(t, err, `error parsing admin address from stderr of [%v]`, cmd) + require.NoError(t, err, `error parsing admin address from stderr of [%v]`, c) adminAddress := adminAddressPattern.FindStringSubmatch(line)[1] - log.Infof(`waiting for Envoy start-up to complete after running [%v]`, cmd) + log.Infof(`waiting for Envoy start-up to complete after running [%v]`, c) _, err = stderrLines.FirstMatch(regexp.MustCompile(`starting main dispatch loop`)).Wait(1 * time.Minute) - require.NoError(t, err, `error parsing startup from stderr of [%v]`, cmd) + require.NoError(t, err, `error parsing startup from stderr of [%v]`, c) - log.Infof(`waiting for Envoy client to connect after running [%v]`, cmd) + log.Infof(`waiting for Envoy client to connect after running [%v]`, c) envoyClient, err := utilenvoy.NewClient(adminAddress) - require.NoError(t, err, `error from envoy client %s after running [%v]`, adminAddress, cmd) + require.NoError(t, err, `error from envoy client %s after running [%v]`, adminAddress, c) require.Eventually(t, func() bool { ready, e := envoyClient.IsReady() return e == nil && ready - }, 1*time.Minute, 100*time.Millisecond, `envoy client %s never ready after running [%v]`, adminAddress, cmd) + }, 1*time.Minute, 100*time.Millisecond, `envoy client %s never ready after running [%v]`, adminAddress, c) - log.Infof(`waiting for Wasm extensions after running [%v]`, cmd) + log.Infof(`waiting for Wasm extensions after running [%v]`, c) require.Eventually(t, func() bool { stats, e := envoyClient.GetStats() if e != nil { @@ -96,9 +97,9 @@ func TestGetEnvoyExtensionRun(t *testing.T) { concurrency := stats.GetMetric("server.concurrency") activeWasmVms := stats.GetMetric("wasm.envoy.wasm.runtime.v8.active") return concurrency != nil && activeWasmVms != nil && activeWasmVms.Value == concurrency.Value+2 - }, 1*time.Minute, 100*time.Millisecond, `wasm stats never found after running [%v]`, adminAddress, cmd) + }, 1*time.Minute, 100*time.Millisecond, `wasm stats never found after running [%v]`, adminAddress, c) - log.Infof(`stopping Envoy after running [%v]`, cmd) + log.Infof(`stopping Envoy after running [%v]`, c) terminate() deferredTerminate = func() { // no-op as we already terminated @@ -106,11 +107,11 @@ func TestGetEnvoyExtensionRun(t *testing.T) { // verify the debug dump of Envoy state has been taken files, err := ioutil.ReadDir(debugDir) - require.NoError(t, err, `error reading %s after stopping [%v]`, debugDir, cmd) - require.Equal(t, 1, len(files), `expected 1 file in %s after stopping [%v]`, debugDir, cmd) + require.NoError(t, err, `error reading %s after stopping [%v]`, debugDir, c) + require.Equal(t, 1, len(files), `expected 1 file in %s after stopping [%v]`, debugDir, c) defer func() { e := os.RemoveAll(debugDir) - require.NoError(t, e, `error removing debug dir %s after stopping [%v]`, debugDir, cmd) + require.NoError(t, e, `error removing debug dir %s after stopping [%v]`, debugDir, c) }() // get a listing of the debug archive @@ -120,11 +121,11 @@ func TestGetEnvoyExtensionRun(t *testing.T) { dumpFiles = append(dumpFiles, f.Name()) return nil }) - require.NoError(t, err, `error reading debug archive %s after stopping [%v]`, debugArchive, cmd) + require.NoError(t, err, `error reading debug archive %s after stopping [%v]`, debugArchive, c) // ensure the minimum contents exist for _, file := range []string{"config_dump.json", "stats.json"} { - require.Contains(t, dumpFiles, file, `debug archive %s doesn't contain %s after stopping [%v]`, debugArchive, file, cmd) + require.Contains(t, dumpFiles, file, `debug archive %s doesn't contain %s after stopping [%v]`, debugArchive, file, c) } }) } @@ -146,7 +147,7 @@ func backupDebugDir(t *testing.T) (string, func()) { } // get a name of a new temp directory, which we'll rename the existing debug to - backupDir, _ := requireNewTempDir(t) + backupDir, _ := RequireNewTempDir(t) err := os.RemoveAll(backupDir) require.NoError(t, err, `error removing temp directory: %s`, backupDir) diff --git a/test/e2e/getenvoy_extension_test_test.go b/test/e2e/getenvoy_extension_test_test.go index 8ea62a78..65adfea5 100644 --- a/test/e2e/getenvoy_extension_test_test.go +++ b/test/e2e/getenvoy_extension_test_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tetratelabs/getenvoy/pkg/extension/workspace/config/extension" + . "github.com/tetratelabs/getenvoy/pkg/test/morerequire" ) // TestGetEnvoyExtensionTest runs the equivalent of "getenvoy extension test" for a matrix of extension.Categories and @@ -34,31 +35,31 @@ func TestGetEnvoyExtensionTest(t *testing.T) { test := test // pin! see https://github.com/kyoh86/scopelint for why t.Run(test.String(), func(t *testing.T) { - workDir, removeWorkDir := requireNewTempDir(t) + workDir, removeWorkDir := RequireNewTempDir(t) defer removeWorkDir() - revertChDir := requireChDir(t, workDir) + _, revertChDir := RequireChDir(t, workDir) defer revertChDir() // test requires "get envoy extension init" to have succeeded requireExtensionInit(t, workDir, test.Category, test.Language, extensionName) defer requireExtensionClean(t, workDir) - cmd := getEnvoy("extension test").Args(getToolchainContainerOptions()...) + c := getEnvoy("extension test").Args(getToolchainContainerOptions()...) // "getenvoy extension test" only returns stdout because `docker run -t` redirects stderr to stdout. - stdout := requireExecNoStderr(t, cmd) + stdout := requireExecNoStderr(t, c) // Verify the tests ran switch test.Language { case extension.LanguageRust: // `cargo` colorizes output. After stripping ANSI codes, ensure the output is successful. stdout = stripAnsiEscapeRegexp.ReplaceAllString(stdout, "") - require.Regexp(t, `(?s)^.*test result: ok.*$`, stdout, `invalid stdout running [%v]`, cmd) + require.Regexp(t, `(?s)^.*test result: ok.*$`, stdout, `invalid stdout running [%v]`, c) case extension.LanguageTinyGo: // We expect the test output to include the extension name. stdoutRegexp := fmt.Sprintf(`(?s)^.*ok %s.*$`, extensionName) - require.Regexp(t, stdoutRegexp, stdout, `invalid stdout running [%v]`, cmd) + require.Regexp(t, stdoutRegexp, stdout, `invalid stdout running [%v]`, c) } }) } diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go index f567251d..53e5fdf4 100644 --- a/test/e2e/main_test.go +++ b/test/e2e/main_test.go @@ -48,22 +48,22 @@ const ( E2E_TOOLCHAIN_CONTAINER_OPTIONS = "E2E_TOOLCHAIN_CONTAINER_OPTIONS" ) -// ExtensionTestTuple represents a combination of extension category and programming language. -type extensionTestTuple struct { +// ExtensionTestCase represents a combination of extension category and programming language. +type extensionTestCase struct { extension.Category extension.Language } -func (t extensionTestTuple) String() string { +func (t extensionTestCase) String() string { return fmt.Sprintf("category=%s, language=%s", t.Category, t.Language) } // getExtensionTestMatrix returns the base matrix of category and language "getenvoy extension" tests run. -func getExtensionTestMatrix() []extensionTestTuple { - tuples := make([]extensionTestTuple, 0) +func getExtensionTestMatrix() []extensionTestCase { + tuples := make([]extensionTestCase, 0) for _, category := range extension.Categories { for _, language := range extensionLanguages { - tuples = append(tuples, extensionTestTuple{category, language}) + tuples = append(tuples, extensionTestCase{category, language}) } } return tuples @@ -147,100 +147,60 @@ func getToolchainContainerOptions() []string { return []string{"--toolchain-container-options", value} } -// requireNewTempDir creates a new directory. The function returned cleans it up. -func requireNewTempDir(t *testing.T) (string, func()) { - d, err := ioutil.TempDir("", "") - if err != nil { - require.NoError(t, err, `ioutil.TempDir("", "") erred`) - } - dir := requireAbsDir(t, d) - return dir, func() { - e := os.RemoveAll(dir) - require.NoError(t, e, `error removing directory: %v`, dir) - } -} - -// RequireChDir will os.Chdir into the indicated dir, panicing on any problem. -// The function returned reverts to the original. -func requireChDir(t *testing.T, d string) func() { - // Save previous working directory to that it can be reverted later. - previous, err := os.Getwd() - require.NoError(t, err, `error determining current directory`) - - // Now, actually change to the directory. - err = os.Chdir(d) - require.NoError(t, err, `error changing to directory: %v`, d) - return func() { - e := os.Chdir(previous) - require.NoError(t, e, `error changing to directory: %v`, previous) - } -} - -// requireAbsDir runs filepath.Abs and ensures there are no errors and the input is a directory. -func requireAbsDir(t *testing.T, d string) string { - dir, err := filepath.Abs(d) - require.NoError(t, err, `error determining absolute directory: %v`, d) - require.DirExists(t, dir, `directory doesn't exist': %v`, dir) - dir, err = filepath.EvalSymlinks(dir) - require.NoError(t, err, `filepath.EvalSymlinks(%s) erred`, dir) - require.NotEmpty(t, dir, `filepath.EvalSymlinks(%s) returned ""`) - return dir -} - // Command gives us an interface needed for testing getEnvoy type Command interface { Exec() (string, string, error) } // requireExecNoStdout invokes the command and returns its stderr if successful and stdout is empty. -func requireExecNoStdout(t *testing.T, cmd Command) string { - stdout, stderr := requireExec(t, cmd) - require.Empty(t, stdout, `expected no stdout running [%v]`, cmd) - require.NotEmpty(t, stderr, `expected stderr running [%v]`, cmd) +func requireExecNoStdout(t *testing.T, c Command) string { + stdout, stderr := requireExec(t, c) + require.Empty(t, stdout, `expected no stdout running [%v]`, c) + require.NotEmpty(t, stderr, `expected stderr running [%v]`, c) return stderr } // requireExecNoStderr invokes the command and returns its stdout if successful and stderr is empty. -func requireExecNoStderr(t *testing.T, cmd Command) string { - stdout, stderr := requireExec(t, cmd) - require.NotEmpty(t, stdout, `expected stdout running [%v]`, cmd) - require.Empty(t, stderr, `expected no stderr running [%v]`, cmd) +func requireExecNoStderr(t *testing.T, c Command) string { + stdout, stderr := requireExec(t, c) + require.NotEmpty(t, stdout, `expected stdout running [%v]`, c) + require.Empty(t, stderr, `expected no stderr running [%v]`, c) return stdout } // requireExec invokes the command and returns its (stdout, stderr) if successful. -func requireExec(t *testing.T, cmd Command) (string, string) { - log.Infof(`running [%v]`, cmd) - stdout, stderr, err := cmd.Exec() +func requireExec(t *testing.T, c Command) (string, string) { + log.Infof(`running [%v]`, c) + stdout, stderr, err := c.Exec() - require.NoError(t, err, `error running [%v]`, cmd) + require.NoError(t, err, `error running [%v]`, c) return stdout, stderr } // requireExtensionInit is useful for tests that depend on "getenvoy extension init" as a prerequisite. func requireExtensionInit(t *testing.T, workDir string, category extension.Category, language extension.Language, name string) { - cmd := getEnvoy("extension init"). + c := getEnvoy("extension init"). Arg(workDir). Arg("--category").Arg(string(category)). Arg("--language").Arg(string(language)). Arg("--name").Arg(name) // stderr returned is not tested because doing so is redundant to TestGetEnvoyExtensionInit. - _ = requireExecNoStdout(t, cmd) + _ = requireExecNoStdout(t, c) } // requireExtensionInit is useful for tests that depend on "getenvoy extension build" as a prerequisite. // The result of calling this is the bytes representing the built wasm func requireExtensionBuild(t *testing.T, language extension.Language, workDir string) []byte { - cmd := getEnvoy("extension build").Args(getToolchainContainerOptions()...) + c := getEnvoy("extension build").Args(getToolchainContainerOptions()...) // stderr returned is not tested because doing so is redundant to TestGetEnvoyExtensionInit. - _ = requireExecNoStderr(t, cmd) + _ = requireExecNoStderr(t, c) extensionWasmFile := filepath.Join(workDir, extensionWasmPath(language)) - require.FileExists(t, extensionWasmFile, `extension wasm file %s missing after running [%v]`, extensionWasmFile, cmd) + require.FileExists(t, extensionWasmFile, `extension wasm file %s missing after running [%v]`, extensionWasmFile, c) wasmBytes, err := ioutil.ReadFile(extensionWasmFile) - require.NoError(t, err, `error reading %s after running [%v]: %s`, extensionWasmFile, cmd) - require.NotEmpty(t, wasmBytes, `%s empty after running [%v]`, extensionWasmFile, cmd) + require.NoError(t, err, `error reading %s after running [%v]: %s`, extensionWasmFile, c) + require.NotEmpty(t, wasmBytes, `%s empty after running [%v]`, extensionWasmFile, c) return wasmBytes } @@ -250,8 +210,8 @@ func requireExtensionClean(t *testing.T, workDir string) { err := os.Chdir(workDir) require.NoError(t, err, `error changing to directory: %v`, workDir) - cmd := getEnvoy("extension clean") - _, _ = requireExec(t, cmd) + c := getEnvoy("extension clean") + _, _ = requireExec(t, c) } // extensionWasmPath returns the language-specific location of the extension.wasm.