From 2df542a23eb2590cbb39ebc98f8bc9c2f2333063 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 30 Mar 2021 15:37:40 +0800 Subject: [PATCH 1/4] Rewrites trickiest cmd tests to help clarify what's happening Before, the cmd unit tests were difficult to understand. They are still complicated and difficult, but far better documented now. Moreover, by using standard testing libs, individual tests and even table cells can be driven by the IDE, and failures point to a relevant location in a table also. Signed-off-by: Adrian Cole --- pkg/cmd/extension/build/build_suite_test.go | 27 - pkg/cmd/extension/build/cmd_test.go | 314 +++-- pkg/cmd/extension/clean/cmd_test.go | 314 +++-- pkg/cmd/extension/clean/test_suite_test.go | 27 - pkg/cmd/extension/push/cmd_test.go | 199 ++- pkg/cmd/extension/push/push_suite_test.go | 27 - pkg/cmd/extension/run/cmd_test.go | 1154 ++++++----------- pkg/cmd/extension/run/run_suite_test.go | 27 - pkg/cmd/extension/test/cmd_test.go | 6 +- .../runtime/getenvoy/getenvoy_suite_test.go | 27 - .../example/runtime/getenvoy/runtime_test.go | 257 ++-- .../runtime/getenvoy/testdata/.licenserignore | 2 - .../runtime/getenvoy/testdata/envoy/bin/envoy | 51 - .../builtin/testdata/toolchain/docker | 15 +- .../toolchain/builtin/toolchain_test.go | 18 +- pkg/test/cmd/extension/command.go | 234 ++++ 16 files changed, 1165 insertions(+), 1534 deletions(-) delete mode 100644 pkg/cmd/extension/build/build_suite_test.go delete mode 100644 pkg/cmd/extension/clean/test_suite_test.go delete mode 100644 pkg/cmd/extension/push/push_suite_test.go delete mode 100644 pkg/cmd/extension/run/run_suite_test.go delete mode 100644 pkg/extension/workspace/example/runtime/getenvoy/getenvoy_suite_test.go delete mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/.licenserignore delete mode 100755 pkg/extension/workspace/example/runtime/getenvoy/testdata/envoy/bin/envoy create mode 100644 pkg/test/cmd/extension/command.go diff --git a/pkg/cmd/extension/build/build_suite_test.go b/pkg/cmd/extension/build/build_suite_test.go deleted file mode 100644 index d128c254..00000000 --- a/pkg/cmd/extension/build/build_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 build_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestBuild(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Build Suite") -} diff --git a/pkg/cmd/extension/build/cmd_test.go b/pkg/cmd/extension/build/cmd_test.go index 01a5ede4..ccfec69b 100644 --- a/pkg/cmd/extension/build/cmd_test.go +++ b/pkg/cmd/extension/build/cmd_test.go @@ -15,186 +15,162 @@ package build_test import ( - "bytes" "fmt" - "os" - "path/filepath" - "strings" + "os/user" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/spf13/cobra" + "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/cmd" - testcontext "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" + "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -var _ = Describe("getenvoy extension build", func() { +// relativeWorkspaceDir points to a usable pre-initialized workspace +const relativeWorkspaceDir = "testdata/workspace" - var dockerDir string - - BeforeEach(func() { - dir, err := filepath.Abs("../../../extension/workspace/toolchain/builtin/testdata/toolchain") - Expect(err).ToNot(HaveOccurred()) - dockerDir = dir - }) - - var pathBackup string - - BeforeEach(func() { - pathBackup = os.Getenv("PATH") - - // override PATH to overshadow `docker` executable during the test - path := strings.Join([]string{dockerDir, pathBackup}, string(filepath.ListSeparator)) - os.Setenv("PATH", path) - }) - - AfterEach(func() { - os.Setenv("PATH", pathBackup) - }) - - 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()) - } - }) - - testcontext.SetDefaultUser() // UID:GID == 1001:1002 - - 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 validate value of --toolchain-container-image flag", func() { - By("running command") - c.SetArgs([]string{"extension", "build", "--toolchain-container-image", "?invalid value?"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "?invalid value?" is not a valid image name: invalid reference format - -Run 'getenvoy extension build --help' for usage. -`)) - }) - - It("should validate value of --toolchain-container-options flag", func() { - By("running command") - c.SetArgs([]string{"extension", "build", "--toolchain-container-options", "imbalanced ' quotes"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "imbalanced ' quotes" is not a valid command line string - -Run 'getenvoy extension build --help' for usage. -`)) - }) - - chdir := func(path string) string { - dir, err := filepath.Abs(path) - Expect(err).ToNot(HaveOccurred()) - - err = os.Chdir(dir) - Expect(err).ToNot(HaveOccurred()) - - return dir +func TestGetEnvoyExtensionBuildValidateFlag(t *testing.T) { + type TestCase struct { + flag string + flagValue string + expectedErr string } - //nolint:lll - Context("inside a workspace directory", func() { - It("should succeed", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "build"}) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal("docker stderr\n")) - }) - - It("should allow to override build image and add Docker cli options", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "build", - "--toolchain-container-image", "build/image", - "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, - }) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e VAR=VALUE -v /host:/container build/image build --output-file target/getenvoy/extension.wasm\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal("docker stderr\n")) - }) - - It("should properly handle Docker build failing", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "build", - "--toolchain-container-image", "build/image", - "--toolchain-container-options", `-e EXIT_CODE=3`, - }) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 build/image build --output-file target/getenvoy/extension.wasm\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal(fmt.Sprintf(`docker stderr -Error: failed to build Envoy extension using "default" toolchain: failed to execute an external command "%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 build/image build --output-file target/getenvoy/extension.wasm": exit status 3 - -Run 'getenvoy extension build --help' for usage. -`, dockerDir, workspaceDir))) - }) - }) - - Context("outside of a workspace directory", func() { - It("should fail", func() { - By("changing to a non-workspace dir") - dir := chdir("testdata") + tests := []TestCase{ + { + flag: "--toolchain-container-image", + flagValue: "?invalid value?", + expectedErr: `"?invalid value?" is not a valid image name: invalid reference format`, + }, + { + flag: "--toolchain-container-options", + flagValue: "imbalanced ' quotes", + expectedErr: `"imbalanced ' quotes" is not a valid command line string`, + }, + } - By("running command") - c.SetArgs([]string{"extension", "build"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - 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 + t.Run(test.flag, func(t *testing.T) { + // Run "getenvoy extension build" with the flags we are testing + cmd, stdout, stderr := extension.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) -Run 'getenvoy extension build --help' for usage. -`, dir))) + // Verify the command failed with the expected error + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + 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) }) + } +} + +func TestGetEnvoyExtensionBuildFailsOutsideWorkspaceDirectory(t *testing.T) { + // Change to a non-workspace dir + dir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir+"/..") + defer revertWd() + + // Run "getenvoy extension build" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "build"}) + err := cmdutil.Execute(cmd) + + // 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) + 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) +} + +func TestGetEnvoyExtensionBuild(t *testing.T) { + // We use a fake docker command to capture the commandline that would be invoked + dockerDir, revertPath := extension.RequireOverridePath(t, extension.FakeDockerDir) + defer revertPath() + + // "getenvoy extension build" must be in a valid workspace directory + workspaceDir, revertWd := extension.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 := extension.OverrideGetCurrentUser(&expectedUser) + defer revertGetCurrentUser() + + // Run "getenvoy extension build" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "build"}) + err := cmdutil.Execute(cmd) + + // 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) +} + +// 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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + defer revertPath() + + // "getenvoy extension build" must be in a valid workspace directory + _, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + defer revertWd() + + // Run "getenvoy extension build" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "build", + "--toolchain-container-image", "build/image", + "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, }) -}) + err := cmdutil.Execute(cmd) + + // 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) +} + +// 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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + defer revertPath() + + // "getenvoy extension build" must be in a valid workspace directory + workspaceDir, revertWd := extension.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 := extension.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 := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "build", "--toolchain-container-options", toolchainOptions}) + err := cmdutil.Execute(cmd) + + // 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", + dockerDir, expectedUser.Uid, expectedUser.Gid, workspaceDir, toolchainOptions) + + // 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) + + // We should see stdout because the docker script was invoked. + require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, cmd) + + // 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) +} diff --git a/pkg/cmd/extension/clean/cmd_test.go b/pkg/cmd/extension/clean/cmd_test.go index 50389ab4..8a0b196e 100644 --- a/pkg/cmd/extension/clean/cmd_test.go +++ b/pkg/cmd/extension/clean/cmd_test.go @@ -15,186 +15,162 @@ package clean_test import ( - "bytes" "fmt" - "os" - "path/filepath" - "strings" + "os/user" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/spf13/cobra" + "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/cmd" - testcontext "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" + "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -var _ = Describe("getenvoy extension clean", func() { +// relativeWorkspaceDir points to a usable pre-initialized workspace +const relativeWorkspaceDir = "../build/testdata/workspace" - var dockerDir string - - BeforeEach(func() { - dir, err := filepath.Abs("../../../extension/workspace/toolchain/builtin/testdata/toolchain") - Expect(err).ToNot(HaveOccurred()) - dockerDir = dir - }) - - var pathBackup string - - BeforeEach(func() { - pathBackup = os.Getenv("PATH") - - // override PATH to overshadow `docker` executable during the test - path := strings.Join([]string{dockerDir, pathBackup}, string(filepath.ListSeparator)) - os.Setenv("PATH", path) - }) - - AfterEach(func() { - os.Setenv("PATH", pathBackup) - }) - - 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()) - } - }) - - testcontext.SetDefaultUser() // UID:GID == 1001:1002 - - 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 validate value of --toolchain-container-image flag", func() { - By("running command") - c.SetArgs([]string{"extension", "clean", "--toolchain-container-image", "?invalid value?"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "?invalid value?" is not a valid image name: invalid reference format - -Run 'getenvoy extension clean --help' for usage. -`)) - }) - - It("should validate value of --toolchain-container-options flag", func() { - By("running command") - c.SetArgs([]string{"extension", "clean", "--toolchain-container-options", "imbalanced ' quotes"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "imbalanced ' quotes" is not a valid command line string - -Run 'getenvoy extension clean --help' for usage. -`)) - }) - - chdir := func(path string) string { - dir, err := filepath.Abs(path) - Expect(err).ToNot(HaveOccurred()) - - err = os.Chdir(dir) - Expect(err).ToNot(HaveOccurred()) - - return dir +func TestGetEnvoyExtensionCleanValidateFlag(t *testing.T) { + type TestCase struct { + flag string + flagValue string + expectedErr string } - //nolint:lll - Context("inside a workspace directory", func() { - It("should succeed", func() { - By("changing to a workspace dir") - workspaceDir := chdir("../build/testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "clean"}) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest clean\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal("docker stderr\n")) - }) - - It("should allow to override build image and add Docker cli options", func() { - By("changing to a workspace dir") - workspaceDir := chdir("../build/testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "clean", - "--toolchain-container-image", "clean/image", - "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, - }) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e VAR=VALUE -v /host:/container clean/image clean\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal("docker stderr\n")) - }) - - It("should properly handle Docker build failing", func() { - By("changing to a workspace dir") - workspaceDir := chdir("../build/testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "clean", - "--toolchain-container-image", "clean/image", - "--toolchain-container-options", `-e EXIT_CODE=3`, - }) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 clean/image clean\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal(fmt.Sprintf(`docker stderr -Error: failed to clean build directory of Envoy extension using "default" toolchain: failed to execute an external command "%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 clean/image clean": exit status 3 - -Run 'getenvoy extension clean --help' for usage. -`, dockerDir, workspaceDir))) - }) - }) - - Context("outside of a workspace directory", func() { - It("should fail", func() { - By("changing to a non-workspace dir") - dir := chdir("../build/testdata") + tests := []TestCase{ + { + flag: "--toolchain-container-image", + flagValue: "?invalid value?", + expectedErr: `"?invalid value?" is not a valid image name: invalid reference format`, + }, + { + flag: "--toolchain-container-options", + flagValue: "imbalanced ' quotes", + expectedErr: `"imbalanced ' quotes" is not a valid command line string`, + }, + } - By("running command") - c.SetArgs([]string{"extension", "clean"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - 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 + t.Run(test.flag, func(t *testing.T) { + // Run "getenvoy extension clean" with the flags we are testing + cmd, stdout, stderr := extension.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) -Run 'getenvoy extension clean --help' for usage. -`, dir))) + // Verify the command failed with the expected error + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + 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) }) + } +} + +func TestGetEnvoyExtensionCleanFailsOutsideWorkspaceDirectory(t *testing.T) { + // Change to a non-workspace dir + dir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir+"/..") + defer revertWd() + + // Run "getenvoy extension clean" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "clean"}) + err := cmdutil.Execute(cmd) + + // 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) + 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) +} + +func TestGetEnvoyExtensionClean(t *testing.T) { + // We use a fake docker command to capture the commandline that would be invoked + dockerDir, revertPath := extension.RequireOverridePath(t, extension.FakeDockerDir) + defer revertPath() + + // "getenvoy extension clean" must be in a valid workspace directory + workspaceDir, revertWd := extension.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 := extension.OverrideGetCurrentUser(&expectedUser) + defer revertGetCurrentUser() + + // Run "getenvoy extension clean" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "clean"}) + err := cmdutil.Execute(cmd) + + // 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) +} + +// 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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + defer revertPath() + + // "getenvoy extension clean" must be in a valid workspace directory + _, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + defer revertWd() + + // Run "getenvoy extension clean" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "clean", + "--toolchain-container-image", "clean/image", + "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, }) -}) + err := cmdutil.Execute(cmd) + + // 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) +} + +// 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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + defer revertPath() + + // "getenvoy extension clean" must be in a valid workspace directory + workspaceDir, revertWd := extension.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 := extension.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 := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "clean", "--toolchain-container-options", toolchainOptions}) + err := cmdutil.Execute(cmd) + + // 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", + dockerDir, expectedUser.Uid, expectedUser.Gid, workspaceDir, toolchainOptions) + + // 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) + + // We should see stdout because the docker script was invoked. + require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, cmd) + + // 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) +} diff --git a/pkg/cmd/extension/clean/test_suite_test.go b/pkg/cmd/extension/clean/test_suite_test.go deleted file mode 100644 index b72c07c0..00000000 --- a/pkg/cmd/extension/clean/test_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 clean_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestTest(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Clean Suite") -} diff --git a/pkg/cmd/extension/push/cmd_test.go b/pkg/cmd/extension/push/cmd_test.go index 929215fa..9654310b 100644 --- a/pkg/cmd/extension/push/cmd_test.go +++ b/pkg/cmd/extension/push/cmd_test.go @@ -11,126 +11,101 @@ // 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. +// 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 push_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" - testcontext "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" + "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -const ( - localRegistryWasmImageRef = "localhost:5000/getenvoy/sample" -) - -var _ = Describe("getenvoy extension push", 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()) - } - }) - - testcontext.SetDefaultUser() // UID:GID == 1001:1002 - - 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()) - - err = os.Chdir(dir) - Expect(err).ToNot(HaveOccurred()) - - return dir - } - - // TODO(musaprg): write teardown process for local registries if it's needed - - //nolint:lll - Context("inside a workspace directory", func() { - When("if the image ref is valid", func() { - It("should succeed", func() { - By("changing to a workspace dir") - _ = chdir("testdata/workspace") - - By("push to local registry") - c.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).NotTo(BeEmpty()) - Expect(stderr.String()).To(BeEmpty()) - }) - }) - }) - - Context("outside of a workspace directory", func() { - When("if the target wasm binary is specified", func() { - It("should succeed", func() { - By("changing to a non-workspace dir") - dir := chdir("testdata") - - By("running command") - c.SetArgs([]string{"extension", "push", localRegistryWasmImageRef, "--extension-file", filepath.Join(dir, "workspace", "extension.wasm")}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).NotTo(BeEmpty()) - Expect(stderr.String()).To(BeEmpty()) - }) - }) - When("if no wasm binary specified", func() { - It("should fail", func() { - By("changing to a non-workspace dir") - dir := chdir("testdata") - - By("running command") - c.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) - 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 push --help' for usage. -`, dir))) - }) - }) - }) -}) +// relativeWorkspaceDir points to a usable pre-initialized workspace +const relativeWorkspaceDir = "testdata/workspace" + +// localRegistryWasmImageRef corresponds to a Docker container running the image "registry:2" +// As this is not intended to be an end-to-end test, this could be improved to use a mock/fake HTTP registry instead. +const localRegistryWasmImageRef = "localhost:5000/getenvoy/sample" + +// When unspecified, we default the tag to Docker's default "latest". Note: recent tools enforce qualifying this! +const defaultTag = "latest" + +// TestGetEnvoyExtensionPush shows current directory is usable, provided it is a valid workspace. +func TestGetEnvoyExtensionPush(t *testing.T) { + _, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + defer revertWd() + + // Run "getenvoy extension push localhost:5000/getenvoy/sample" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) + err := cmdutil.Execute(cmd) + + // 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.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) +} + +func TestGetEnvoyExtensionPushFailsOutsideWorkspaceDirectory(t *testing.T) { + // Change to a non-workspace dir + dir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir+"/..") + defer revertWd() + + // Run "getenvoy extension push localhost:5000/getenvoy/sample" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) + err := cmdutil.Execute(cmd) + + // 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) + 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) +} + +// TestGetEnvoyExtensionPushWithExplicitFileOption shows +func TestGetEnvoyExtensionPushWithExplicitFileOption(t *testing.T) { + // Change to a non-workspace dir + dir, revertWd := extension.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 := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "push", localRegistryWasmImageRef, "--extension-file", wasm}) + err := cmdutil.Execute(cmd) + + // Verify the pushed a latest tag to the correct registry + require.NoError(t, err, `expected no error running [%v]`, cmd) + 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) +} diff --git a/pkg/cmd/extension/push/push_suite_test.go b/pkg/cmd/extension/push/push_suite_test.go deleted file mode 100644 index 8233ff5c..00000000 --- a/pkg/cmd/extension/push/push_suite_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// 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 push_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestPush(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Push Suite") -} diff --git a/pkg/cmd/extension/run/cmd_test.go b/pkg/cmd/extension/run/cmd_test.go index bf89769d..8e4b5721 100644 --- a/pkg/cmd/extension/run/cmd_test.go +++ b/pkg/cmd/extension/run/cmd_test.go @@ -15,684 +15,365 @@ package run_test import ( - "bytes" - "encoding/json" "fmt" "io/ioutil" "os" + "os/user" "path/filepath" - "strings" + "testing" - "github.com/Masterminds/semver" - "github.com/ghodss/yaml" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" "github.com/otiai10/copy" - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/tetratelabs/getenvoy/pkg/cmd" - "github.com/tetratelabs/getenvoy/pkg/manifest" - testcontext "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" - manifesttest "github.com/tetratelabs/getenvoy/pkg/test/manifest" - "github.com/tetratelabs/getenvoy/pkg/types" + "github.com/stretchr/testify/require" + + "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -//nolint:lll -var _ = Describe("getenvoy extension run", 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 dockerDir string - - BeforeEach(func() { - dir, err := filepath.Abs("../../../extension/workspace/toolchain/builtin/testdata/toolchain") - Expect(err).ToNot(HaveOccurred()) - dockerDir = dir - }) - - var pathBackup string - - BeforeEach(func() { - pathBackup = os.Getenv("PATH") - }) - - AfterEach(func() { - os.Setenv("PATH", pathBackup) - }) - - BeforeEach(func() { - // override PATH to overshadow `docker` executable during the test - path := strings.Join([]string{dockerDir, pathBackup}, string(filepath.ListSeparator)) - os.Setenv("PATH", path) - }) - - var getenvoyHomeBackup string - - BeforeEach(func() { - getenvoyHomeBackup = os.Getenv("GETENVOY_HOME") - }) - - AfterEach(func() { - os.Setenv("GETENVOY_HOME", getenvoyHomeBackup) - }) - - var getenvoyHomeDir string - - BeforeEach(func() { - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - getenvoyHomeDir = tempDir - - // override GETENVOY_HOME directory during the test - os.Setenv("GETENVOY_HOME", getenvoyHomeDir) - }) - - AfterEach(func() { - if getenvoyHomeDir != "" { - Expect(os.RemoveAll(getenvoyHomeDir)).To(Succeed()) - } - }) - - var envoySubstituteArchiveDir string - - BeforeEach(func() { - envoySubstituteArchiveDir = filepath.Join(cwdBackup, "../../../extension/workspace/example/runtime/getenvoy/testdata/envoy") - }) - - var manifestURLBackup string - - BeforeEach(func() { - manifestURLBackup = manifest.GetURL() - }) - - AfterEach(func() { - Expect(manifest.SetURL(manifestURLBackup)).To(Succeed()) - }) - - var manifestServer manifesttest.Server - - BeforeEach(func() { - testManifest, err := manifesttest.NewSimpleManifest("standard:1.17.0", "wasm:1.15", "wasm:stable") - Expect(err).NotTo(HaveOccurred()) - - 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) { - Expect(err).NotTo(HaveOccurred()) - }, - }) - - // override location of the GetEnvoy manifest - err = manifest.SetURL(manifestServer.GetManifestURL()) - Expect(err).NotTo(HaveOccurred()) - }) +// relativeWorkspaceDir points to a usable pre-initialized workspace +const relativeWorkspaceDir = "testdata/workspace" - AfterEach(func() { - if manifestServer != nil { - manifestServer.Close() - } - }) - - var platform string - - BeforeEach(func() { - key, err := manifest.NewKey("standard:1.17.0") - Expect(err).NotTo(HaveOccurred()) - platform = strings.ToLower(key.Platform) - }) +func TestGetEnvoyExtensionRunValidateFlag(t *testing.T) { + type TestCase struct { + name string + flags []string + flagValues []string + expectedErr string + } - // envoyCaptureDir represents a directory used by the Envoy substitute script - // to store captured info. - var envoyCaptureDir string + tempDir, closer := extension.RequireNewTempDir(t) + defer closer() - BeforeEach(func() { - dir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - envoyCaptureDir = dir - }) + // Create a fake envoy script so that we can verify execute bit is required. + notExecutable := filepath.Join(tempDir, "envoy") + err := ioutil.WriteFile(notExecutable, []byte(`#!/bin/sh`), 0600) + require.NoError(t, err, `couldn't create fake envoy script'`) - AfterEach(func() { - if envoyCaptureDir != "" { - Expect(os.RemoveAll(envoyCaptureDir)).To(Succeed()) - } - }) - - BeforeEach(func() { - // set environment variables to give `envoy` substitute script a hint - // where to put captured info - os.Setenv("TEST_ENVOY_CAPTURE_CMDLINE_FILE", filepath.Join(envoyCaptureDir, "cmdline")) - os.Setenv("TEST_ENVOY_CAPTURE_CWD_FILE", filepath.Join(envoyCaptureDir, "cwd")) - os.Setenv("TEST_ENVOY_CAPTURE_CWD_DIR", filepath.Join(envoyCaptureDir, "cwd.d")) - }) - - envoyCaptured := struct { - cmdline func() string - cwd func() string - readFile func(string) []byte - readFileToJSON func(string) map[string]interface{} - }{ - cmdline: func() string { - data, err := ioutil.ReadFile(os.Getenv("TEST_ENVOY_CAPTURE_CMDLINE_FILE")) - Expect(err).NotTo(HaveOccurred()) - return string(data) + tests := []TestCase{ + { + name: "--envoy-options with imbalanced quotes", + flags: []string{"--envoy-options"}, + flagValues: []string{"imbalanced ' quotes"}, + expectedErr: `"imbalanced ' quotes" is not a valid command line string`, }, - cwd: func() string { - data, err := ioutil.ReadFile(os.Getenv("TEST_ENVOY_CAPTURE_CWD_FILE")) - Expect(err).NotTo(HaveOccurred()) - return strings.TrimSpace(string(data)) + { + name: "--envoy-path file doesn't exist", + flags: []string{"--envoy-path"}, + flagValues: []string{"non-existing-file"}, + expectedErr: `unable to find custom Envoy binary at "non-existing-file": stat non-existing-file: no such file or directory`, }, - readFile: func(name string) []byte { - data, err := ioutil.ReadFile(filepath.Join(os.Getenv("TEST_ENVOY_CAPTURE_CWD_DIR"), name)) - Expect(err).NotTo(HaveOccurred()) - return data + { + name: "--envoy-path is a directory", + flags: []string{"--envoy-path"}, + flagValues: []string{"."}, + expectedErr: `unable to find custom Envoy binary at ".": there is a directory at a given path instead of a regular file`, }, - readFileToJSON: func(name string) map[string]interface{} { - data, err := ioutil.ReadFile(filepath.Join(os.Getenv("TEST_ENVOY_CAPTURE_CWD_DIR"), name)) - Expect(err).NotTo(HaveOccurred()) - data, err = yaml.YAMLToJSON(data) - Expect(err).NotTo(HaveOccurred()) - obj := make(map[string]interface{}) - err = json.Unmarshal(data, &obj) - Expect(err).ToNot(HaveOccurred()) - return obj + { + name: "--envoy-path not executable", + flags: []string{"--envoy-path"}, + flagValues: []string{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{"???"}, + 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"}, + 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"}, + 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{"."}, + 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"}, + 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{"."}, + 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?"}, + 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"}, + expectedErr: `"imbalanced ' quotes" is not a valid command line string`, }, } - testcontext.SetDefaultUser() // UID:GID == 1001:1002 - - 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 validate value of --toolchain-container-image flag", func() { - By("running command") - c.SetArgs([]string{"extension", "run", "--toolchain-container-image", "?invalid value?"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "?invalid value?" is not a valid image name: invalid reference format - -Run 'getenvoy extension run --help' for usage. -`)) - }) - - It("should validate value of --toolchain-container-options flag", func() { - By("running command") - c.SetArgs([]string{"extension", "run", "--toolchain-container-options", "imbalanced ' quotes"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "imbalanced ' quotes" is not a valid command line string - -Run 'getenvoy extension run --help' for usage. -`)) - }) - - It("should validate value of --envoy-version flag", func() { - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-version", "???"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: Envoy version is not valid: "???" is not a valid GetEnvoy reference. Expected format: :[/] - -Run 'getenvoy extension run --help' for usage. -`)) - }) - - It("should not allow --envoy-version and --envoy-path flags at the same time", func() { - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-version", "standard:1.17.0", "--envoy-path", "envoy"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: only one of flags '--envoy-version' and '--envoy-path' can be used at a time - -Run 'getenvoy extension run --help' for usage. -`)) - }) - - It("should validate value of --envoy-path flag (path doesn't exist)", func() { - By("creating a path for test") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - filePath := filepath.Join(tempDir, "non-existing-dir", "non-existing-file") - - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-path", filePath}) - 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: unable to find custom Envoy binary at %[1]q: stat %[1]s: no such file or directory - -Run 'getenvoy extension run --help' for usage. -`, filePath))) - }) - - It("should validate value of --envoy-path flag (path is a dir)", func() { - By("creating a path for test") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - dirPath := tempDir - - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-path", dirPath}) - 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: unable to find custom Envoy binary at %q: there is a directory at a given path instead of a regular file - -Run 'getenvoy extension run --help' for usage. -`, dirPath))) - }) - - It("should validate value of --envoy-path flag (file is not executable)", func() { - By("creating a path for test") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - - By("creating a non-executable file") - filePath := filepath.Join(tempDir, "envoy") - err = ioutil.WriteFile(filePath, []byte(`#!/bin/sh`), 0600) - Expect(err).NotTo(HaveOccurred()) - - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-path", filePath}) - 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: unable to find custom Envoy binary at %q: file is not executable - -Run 'getenvoy extension run --help' for usage. -`, filePath))) - }) - - It("should validate value of --envoy-options flag", func() { - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-options", "imbalanced ' quotes"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "imbalanced ' quotes" is not a valid command line string - -Run 'getenvoy extension run --help' for usage. -`)) - }) - - It("should validate value of --extension-file flag (path doesn't exist)", func() { - By("creating a path for test") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - filePath := filepath.Join(tempDir, "non-existing-dir", "non-existing-file") - - By("running command") - c.SetArgs([]string{"extension", "run", "--extension-file", filePath}) - 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: unable to find a pre-built *.wasm file at %[1]q: stat %[1]s: no such file or directory - -Run 'getenvoy extension run --help' for usage. -`, filePath))) - }) - - It("should validate value of --extension-file flag (path is a dir)", func() { - By("creating a path for test") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - dirPath := tempDir - - By("running command") - c.SetArgs([]string{"extension", "run", "--extension-file", dirPath}) - 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: unable to find a pre-built *.wasm file at %q: there is a directory at a given path instead of a regular file - -Run 'getenvoy extension run --help' for usage. -`, dirPath))) - }) - - It("should validate value of --extension-config-file flag (path doesn't exist)", func() { - By("creating a path for test") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - filePath := filepath.Join(tempDir, "non-existing-dir", "non-existing-file") - - By("running command") - c.SetArgs([]string{"extension", "run", "--extension-config-file", filePath}) - 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: failed to read custom extension config from file %[1]q: open %[1]s: no such file or directory - -Run 'getenvoy extension run --help' for usage. -`, filePath))) - }) - - 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 succeed", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "run"}) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf(`%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm -%s/builds/standard/1.17.0/%s/bin/envoy -c %s/envoy.tmpl.yaml -`, dockerDir, workspaceDir, getenvoyHomeDir, platform, envoyCaptured.cwd()))) - Expect(stderr.String()).To(Equal("docker stderr\nenvoy stderr\n")) - - By("verifying Envoy config") - placeholders := envoyCaptured.readFileToJSON("placeholders.tmpl.yaml") - Expect(placeholders["extension.name"]).To(Equal(`mycompany.filters.http.custom_metrics`)) - Expect(placeholders["extension.code"]).To(Equal(map[string]interface{}{ - "local": map[string]interface{}{ - "filename": filepath.Join(workspaceDir, "target/getenvoy/extension.wasm"), - }, - })) - Expect(placeholders["extension.config"]).To(Equal(map[string]interface{}{ - "@type": "type.googleapis.com/google.protobuf.StringValue", - "value": `{"key":"value"}`, - })) - }) - - It("should allow to override build image and add Docker cli options", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "run", - "--toolchain-container-image", "build/image", - "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, - }) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf(`%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e VAR=VALUE -v /host:/container build/image build --output-file target/getenvoy/extension.wasm -%s/builds/standard/1.17.0/%s/bin/envoy -c %s/envoy.tmpl.yaml -`, dockerDir, workspaceDir, getenvoyHomeDir, platform, envoyCaptured.cwd()))) - Expect(stderr.String()).To(Equal("docker stderr\nenvoy stderr\n")) - }) - - It("should properly handle Docker build failing", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "run", - "--toolchain-container-image", "build/image", - "--toolchain-container-options", `-e EXIT_CODE=3`, - }) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 build/image build --output-file target/getenvoy/extension.wasm\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal(fmt.Sprintf(`docker stderr -Error: failed to build Envoy extension using "default" toolchain: failed to execute an external command "%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 build/image build --output-file target/getenvoy/extension.wasm": exit status 3 - -Run 'getenvoy extension run --help' for usage. -`, dockerDir, workspaceDir))) - }) - - It("should allow to override Envoy version via --envoy-version flag", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-version", "wasm:stable"}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf(`%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm -%s/builds/wasm/stable/%s/bin/envoy -c %s/envoy.tmpl.yaml -`, dockerDir, workspaceDir, getenvoyHomeDir, platform, envoyCaptured.cwd()))) - Expect(stderr.String()).To(Equal("docker stderr\nenvoy stderr\n")) - }) - - It("should properly handle unknown Envoy version", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-version", "wasm:unknown"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - key, err := manifest.NewKey("wasm:unknown") - Expect(err).NotTo(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal(fmt.Sprintf(`docker stderr -Error: failed to run "default" example: unable to find matching GetEnvoy build for reference %q - -Run 'getenvoy extension run --help' for usage. -`, key))) - }) - - It("should allow to provide a custom Envoy binary via --envoy-path flag", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-path", filepath.Join(envoySubstituteArchiveDir, "bin/envoy")}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf(`%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm -%s -c %s/envoy.tmpl.yaml -`, dockerDir, workspaceDir, filepath.Join(envoySubstituteArchiveDir, "bin/envoy"), envoyCaptured.cwd()))) - Expect(stderr.String()).To(Equal("docker stderr\nenvoy stderr\n")) - }) - - It("should allow to provide extra options for Envoy via --envoy-options flag", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "run", "--envoy-options", "'--concurrency 2 --component-log-level wasm:debug,config:trace'"}) - err := cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf(`%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm -%s/builds/standard/1.17.0/%s/bin/envoy -c %s/envoy.tmpl.yaml --concurrency 2 --component-log-level wasm:debug,config:trace -`, dockerDir, workspaceDir, getenvoyHomeDir, platform, envoyCaptured.cwd()))) - Expect(stderr.String()).To(Equal("docker stderr\nenvoy stderr\n")) - }) - - It("should allow to provide a pre-build *.wasm files via --extension-file flag", func() { - By("changing to a workspace dir") - _ = chdir("testdata/workspace") - - By("simulating a pre-built *.wasm file") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - wasmFile := filepath.Join(tempDir, "extension.wasm") - err = ioutil.WriteFile(wasmFile, []byte{}, 0600) - Expect(err).NotTo(HaveOccurred()) - - By("running command") - c.SetArgs([]string{"extension", "run", "--extension-file", wasmFile}) - err = cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/builds/standard/1.17.0/%s/bin/envoy -c %s/envoy.tmpl.yaml\n", getenvoyHomeDir, platform, envoyCaptured.cwd()))) - Expect(stderr.String()).To(Equal("envoy stderr\n")) - - By("verifying Envoy config") - placeholders := envoyCaptured.readFileToJSON("placeholders.tmpl.yaml") - Expect(placeholders["extension.code"]).To(Equal(map[string]interface{}{ - "local": map[string]interface{}{ - "filename": wasmFile, - }, - })) - }) - - It("should allow to provide a custom extension config via --extension-config-file flag", func() { - By("changing to a workspace dir") - workspaceDir := chdir("testdata/workspace") - - By("simulating a custom extension config") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - configFile := filepath.Join(tempDir, "config.json") - err = ioutil.WriteFile(configFile, []byte(`{"key2":"value2"}`), 0600) - Expect(err).NotTo(HaveOccurred()) - - By("running command") - c.SetArgs([]string{"extension", "run", "--extension-config-file", configFile}) - err = cmdutil.Execute(c) - Expect(err).NotTo(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf(`%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm -%s/builds/standard/1.17.0/%s/bin/envoy -c %s/envoy.tmpl.yaml -`, dockerDir, workspaceDir, getenvoyHomeDir, platform, envoyCaptured.cwd()))) - Expect(stderr.String()).To(Equal("docker stderr\nenvoy stderr\n")) - - By("verifying Envoy config") - placeholders := envoyCaptured.readFileToJSON("placeholders.tmpl.yaml") - Expect(placeholders["extension.config"]).To(Equal(map[string]interface{}{ - "@type": "type.googleapis.com/google.protobuf.StringValue", - "value": `{"key2":"value2"}`, - })) + 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 run" with the flags we are testing + cmd, stdout, stderr := extension.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) + + // Verify the command failed with the expected error + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + 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) }) - - It("should create default example if missing", func() { - By("simulating a workspace without 'default' example") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - err = copy.Copy("../build/testdata/workspace", tempDir) - Expect(err).NotTo(HaveOccurred()) - - By("changing to a workspace dir") - workspaceDir := chdir(tempDir) - - By("running command") - c.SetArgs([]string{"extension", "run"}) - err = cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf(`%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest build --output-file target/getenvoy/extension.wasm -%s/builds/standard/1.17.0/%s/bin/envoy -c %s/envoy.tmpl.yaml -`, dockerDir, workspaceDir, getenvoyHomeDir, platform, envoyCaptured.cwd()))) - Expect(stderr.String()).To(Equal(`Scaffolding a new example setup: + } +} + +func TestGetEnvoyExtensionRunFailsOutsideWorkspaceDirectory(t *testing.T) { + // Change to a non-workspace dir + config, cleanup := setupTest(t, relativeWorkspaceDir+"/..") + defer cleanup() + + // Run "getenvoy extension run" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run"}) + err := cmdutil.Execute(cmd) + + // 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) + 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) +} + +func TestGetEnvoyExtensionRun(t *testing.T) { + config, cleanup := setupTest(t, relativeWorkspaceDir) + defer cleanup() + + // Run "getenvoy extension run" + cmd, stdout, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) + err := cmdutil.Execute(cmd) + + // Verify the command invoked, passing the correct default commandline + require.NoError(t, err, `expected no error running [%v]`, cmd) + + 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 := extension.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 +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) + + // Verify the placeholders envoy would have ran substituted, notably including the generated extension.wasm + expectedYaml := fmt.Sprintf(`'extension.name': "mycompany.filters.http.custom_metrics" +'extension.code': {"local":{"filename":"%s/target/getenvoy/extension.wasm"}} +'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) +} + +// TestGetEnvoyExtensionRunDockerFail ensures docker failures show useful information in stderr +func TestGetEnvoyExtensionRunDockerFail(t *testing.T) { + config, cleanup := setupTest(t, relativeWorkspaceDir) + defer cleanup() + + // "-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 := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--toolchain-container-options", toolchainOptions}) + err := cmdutil.Execute(cmd) + + // 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", + config.dockerDir, config.expectedUidGid, config.workspaceDir, toolchainOptions) + + // 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) + + // 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) +} + +// TestGetEnvoyExtensionRunWithExplicitVersion only tests that a version override is used. It doesn't test things that +// aren't different between here and TestGetEnvoyExtensionRun. +func TestGetEnvoyExtensionRunWithExplicitVersion(t *testing.T) { + config, cleanup := setupTest(t, relativeWorkspaceDir) + defer cleanup() + + // Run "getenvoy extension run --envoy-version wasm:stable" + cmd, stdout, _ := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-version", "wasm:stable"}) + err := cmdutil.Execute(cmd) + + // Verify the command invoked, passing the correct default commandline + require.NoError(t, err, `expected no error running [%v]`, cmd) + + // 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) +} + +func TestGetEnvoyExtensionRunFailWithUnknownVersion(t *testing.T) { + config, cleanup := setupTest(t, relativeWorkspaceDir) + defer cleanup() + + version := "wasm:unknown" + // Run "getenvoy extension run --envoy-version wasm:unknown" + cmd, _, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-version", version}) + err := cmdutil.Execute(cmd) + + // 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) + + // 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) +} + +func TestGetEnvoyExtensionRunWithCustomBinary(t *testing.T) { + config, cleanup := setupTest(t, relativeWorkspaceDir) + defer cleanup() + + // Run "getenvoy extension run --envoy-path $ENVOY_HOME/bin/envoy" + envoyBin := filepath.Join(config.envoyHome, "bin/envoy") + cmd, stdout, _ := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--envoy-path", envoyBin}) + err := cmdutil.Execute(cmd) + + // Verify the command invoked, passing the correct default commandline + require.NoError(t, err, `expected no error running [%v]`, cmd) + + // 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) +} + +func TestGetEnvoyExtensionRunWithOptions(t *testing.T) { + config, cleanup := setupTest(t, relativeWorkspaceDir) + defer cleanup() + + // Run "getenvoy extension run ..." + cmd, stdout, _ := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, + "--envoy-options", "'--concurrency 2 --component-log-level wasm:debug,config:trace'"}) + err := cmdutil.Execute(cmd) + + // Verify the command invoked, passing the correct default commandline + require.NoError(t, err, `expected no error running [%v]`, cmd) + + // The working directory of envoy is a temp directory not controlled by this test, so we have to parse it. + envoyWd := extension.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) +} + +// TestGetEnvoyExtensionRunWithWasm shows docker isn't run when the user supplies an "--extension-file" +func TestGetEnvoyExtensionRunWithWasm(t *testing.T) { + config, cleanup := setupTest(t, relativeWorkspaceDir) + defer cleanup() + + // As all scripts invoked are fakes, we only need to touch a file as it isn't read + wasmFile := filepath.Join(config.tempDir, "extension.wasm") + err := ioutil.WriteFile(wasmFile, []byte{}, 0600) + 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 := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--extension-file", wasmFile}) + err = cmdutil.Execute(cmd) + + // Verify the command invoked, passing the correct default commandline + require.NoError(t, err, `expected no error running [%v]`, cmd) + + 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 := extension.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) + + // 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) +} + +// TestGetEnvoyExtensionRunWithConfig shows extension config passed as an argument ends up readable by envoy. +func TestGetEnvoyExtensionRunWithConfig(t *testing.T) { + config, cleanup := setupTest(t, relativeWorkspaceDir) + defer cleanup() + + // As all scripts invoked are fakes, we only need to touch a file as it isn't read + configFile := filepath.Join(config.tempDir, "config.json") + err := ioutil.WriteFile(configFile, []byte(`{"key2":"value2"}`), 0600) + require.NoError(t, err, `expected no error creating extension.wasm: %s`, configFile) + + // Run "getenvoy extension run --extension-config-file /path/to/config.json" + cmd, _, _ := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--extension-config-file", configFile}) + err = cmdutil.Execute(cmd) + + // Verify the command invoked, passing the correct default commandline + require.NoError(t, err, `expected no error running [%v]`, cmd) + + // 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) +} + +func TestGetEnvoyExtensionRunCreatesExampleWhenMissing(t *testing.T) { + // Use the workspace from the "extension build" test as it doesn't include examples. + config, cleanup := setupTest(t, "../build/testdata/workspace") + defer cleanup() + + // Run "getenvoy extension run" + cmd, _, stderr := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) + err := cmdutil.Execute(cmd) + + // Verify the command invoked, passing the correct default commandline + require.NoError(t, err, `expected no error running [%v]`, cmd) + + // Verify a new example was scaffolded prior to running docker and envoy + require.Equal(t, `Scaffolding a new example setup: * .getenvoy/extension/examples/default/README.md * .getenvoy/extension/examples/default/envoy.tmpl.yaml * .getenvoy/extension/examples/default/example.yaml @@ -700,67 +381,96 @@ Run 'getenvoy extension run --help' for usage. Done! docker stderr envoy stderr -`)) - - By("verifying Envoy config") - bootstrap := envoyCaptured.readFileToJSON("envoy.tmpl.yaml") - Expect(bootstrap).NotTo(BeEmpty()) - }) - - It("should create default example if missing for TinyGo", func() { - By("simulating a workspace without 'default' example") - tempDir, err := ioutil.TempDir("", "") - Expect(err).NotTo(HaveOccurred()) - defer func() { - Expect(os.RemoveAll(tempDir)).To(Succeed()) - }() - err = copy.Copy("testdata/workspace_tinygo", tempDir) - Expect(err).NotTo(HaveOccurred()) - - By("changing to a workspace dir") - workspaceDir := chdir(tempDir) - - By("running command") - c.SetArgs([]string{"extension", "run"}) - err = cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf(`%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-tinygo-builder:latest build --output-file build/extension.wasm -%s/builds/standard/1.17.0/%s/bin/envoy -c %s/envoy.tmpl.yaml -`, dockerDir, workspaceDir, getenvoyHomeDir, platform, envoyCaptured.cwd()))) - 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! -docker stderr -envoy stderr -`)) - - By("verifying Envoy config") - bootstrap := envoyCaptured.readFileToJSON("envoy.tmpl.yaml") - Expect(bootstrap).NotTo(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", "run"}) - 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 run --help' for usage. -`, dir))) - }) - }) -}) +`, stderr.String(), `expected stderr running [%v]`, cmd) +} + +// TestGetEnvoyExtensionRunTinyGo ensures the docker command isn't pinned to rust projects +func TestGetEnvoyExtensionRunTinyGo(t *testing.T) { + config, cleanup := setupTest(t, "testdata/workspace_tinygo") + defer cleanup() + + // Run "getenvoy extension run" + cmd, stdout, _ := extension.NewRootCommand() + cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) + err := cmdutil.Execute(cmd) + + // Verify the command invoked, passing the correct default commandline + require.NoError(t, err, `expected no error running [%v]`, cmd) + + // 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) +} + +type testEnvoyExtensionConfig struct { + // tempDir is deleted on exit and contains many of the other directories + tempDir string + // dockerDir is the absolute location of extension.FakeDockerDir + dockerDir string + // workspaceDir will be the CWD of "getenvoy" + workspaceDir string + // envoyHome is a fake $tempDir/envoy_home, initialized with initFakeEnvoyHome + envoyHome string + // platform is the types.Reference.Platform used in manifest commands + platform string + // expectedUidGid corresponds to a fake user.User ex 1001:1002 the builtin toolchain will see. + expectedUidGid string //nolint +} + +// setupTest returns testEnvoyExtensionConfig and a tear-down function. +// The tear-down functions reverts side-effects such as temp directories and a fake manifest server. +// relativeWorkspaceTemplate is relative to the test file and will be copied into the resulting config.workspaceDir. +func setupTest(t *testing.T, relativeWorkspaceTemplate string) (*testEnvoyExtensionConfig, func()) { + result := testEnvoyExtensionConfig{} + var tearDown []func() + + tempDir, deleteTempDir := extension.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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + tearDown = append(tearDown, revertPath) + result.dockerDir = dockerDir + + envoyHome := filepath.Join(tempDir, "envoy_home") + extension.InitFakeEnvoyHome(t, envoyHome) + result.envoyHome = envoyHome + + // create a new workspaceDir under tempDir + workspaceDir := filepath.Join(tempDir, "workspace") + err := os.Mkdir(workspaceDir, 0700) + 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(extension.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 := extension.RequireChDir(t, workspaceDir) + tearDown = append(tearDown, revertWd) + + platform := extension.RequireManifestPlatform(t) + shutdownTestServer := extension.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 := extension.OverrideGetCurrentUser(&expectedUser) + tearDown = append(tearDown, revertGetCurrentUser) + result.expectedUidGid = expectedUser.Uid + ":" + expectedUser.Gid + + return &result, func() { + for i := len(tearDown) - 1; i >= 0; i-- { + tearDown[i]() + } + } +} + +func requirePlaceholdersYaml(t *testing.T, envoyHome string) string { + placeholders := filepath.Join(envoyHome, "capture", "placeholders.tmpl.yaml") + b, err := ioutil.ReadFile(placeholders) + require.NoError(t, err, `expected no error reading placeholders: %s`, placeholders) + return string(b) +} diff --git a/pkg/cmd/extension/run/run_suite_test.go b/pkg/cmd/extension/run/run_suite_test.go deleted file mode 100644 index 894b6418..00000000 --- a/pkg/cmd/extension/run/run_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 run_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestRun(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Run Suite") -} diff --git a/pkg/cmd/extension/test/cmd_test.go b/pkg/cmd/extension/test/cmd_test.go index f4204edb..54e0dad6 100644 --- a/pkg/cmd/extension/test/cmd_test.go +++ b/pkg/cmd/extension/test/cmd_test.go @@ -164,15 +164,15 @@ Run 'getenvoy extension test --help' for usage. By("running command") c.SetArgs([]string{"extension", "test", "--toolchain-container-image", "build/image", - "--toolchain-container-options", `-e EXIT_CODE=3`, + "--toolchain-container-options", `-e DOCKER_EXIT_CODE=3`, }) err := cmdutil.Execute(c) Expect(err).To(HaveOccurred()) By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 build/image test\n", dockerDir, workspaceDir))) + Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 build/image test\n", dockerDir, workspaceDir))) Expect(stderr.String()).To(Equal(fmt.Sprintf(`docker stderr -Error: failed to unit test Envoy extension using "default" toolchain: failed to execute an external command "%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 build/image test": exit status 3 +Error: failed to unit test Envoy extension using "default" toolchain: failed to execute an external command "%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 build/image test": exit status 3 Run 'getenvoy extension test --help' for usage. `, dockerDir, workspaceDir))) diff --git a/pkg/extension/workspace/example/runtime/getenvoy/getenvoy_suite_test.go b/pkg/extension/workspace/example/runtime/getenvoy/getenvoy_suite_test.go deleted file mode 100644 index 1e5993f3..00000000 --- a/pkg/extension/workspace/example/runtime/getenvoy/getenvoy_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 getenvoy_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestGetenvoy(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "GetEnvoy Suite") -} diff --git a/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go b/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go index 61cae555..c5de53cb 100644 --- a/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go +++ b/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go @@ -17,166 +17,121 @@ package getenvoy_test import ( "bytes" "fmt" - stdioutil "io/ioutil" - "os" "path/filepath" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/common" workspaces "github.com/tetratelabs/getenvoy/pkg/extension/workspace" "github.com/tetratelabs/getenvoy/pkg/extension/workspace/example/runtime" . "github.com/tetratelabs/getenvoy/pkg/extension/workspace/example/runtime/getenvoy" "github.com/tetratelabs/getenvoy/pkg/extension/workspace/model" - argutil "github.com/tetratelabs/getenvoy/pkg/util/args" + "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" ioutil "github.com/tetratelabs/getenvoy/pkg/util/io" ) -var _ = Describe("runtime", func() { - Describe("Run()", func() { - - var backupHomeDir string - - BeforeEach(func() { - backupHomeDir = common.HomeDir - }) - - AfterEach(func() { - common.HomeDir = backupHomeDir - }) - - var tempHomeDir string - - BeforeEach(func() { - dir, err := stdioutil.TempDir("", "getenvoy-home") - Expect(err).NotTo(HaveOccurred()) - tempHomeDir = dir - }) - - AfterEach(func() { - if tempHomeDir != "" { - Expect(os.RemoveAll(tempHomeDir)).To(Succeed()) - } - }) - - BeforeEach(func() { - common.HomeDir = tempHomeDir - }) - - envoyPath := func() string { - path, err := filepath.Abs("testdata/envoy/bin/envoy") - Expect(err).ToNot(HaveOccurred()) - return path - } - - var stdout *bytes.Buffer - var stderr *bytes.Buffer - - BeforeEach(func() { - stdout = new(bytes.Buffer) - stderr = new(bytes.Buffer) - }) - - runContext := func(workspace model.Workspace, example model.Example) *runtime.RunContext { - return &runtime.RunContext{ - Opts: runtime.RunOpts{ - Workspace: workspace, - Example: runtime.ExampleOpts{ - Name: "default", - Example: example, - }, - Extension: runtime.ExtensionOpts{ - WasmFile: `/path/to/extension.wasm`, - Config: model.File{ - Source: "/path/to/config", - Content: []byte(`{"key2":"value2"}`), - }, - }, - Envoy: runtime.EnvoyOpts{ - Path: envoyPath(), - }, - }, - IO: ioutil.StdStreams{ - Out: stdout, - Err: stderr, +// relativeWorkspaceDir points to a usable pre-initialized workspace +const relativeWorkspaceDir = "../configdir/testdata/workspace1" + +func TestRuntimeRun(t *testing.T) { + workspace, err := workspaces.GetWorkspaceAt(relativeWorkspaceDir) + require.NoError(t, err, `expected no error getting workspace from directory %s`, relativeWorkspaceDir) + + example, err := workspace.GetExample("default") + require.NoError(t, err, `expected no error getting example from workspace %s`, workspace) + + fakeEnvoyPath, tearDown := setupFakeEnvoy(t) + defer tearDown() + + // Create and run a new context that will invoke a fake envoy script + ctx, stdout, stderr := runContext(workspace, example, fakeEnvoyPath) + err = NewRuntime().Run(ctx) + require.NoError(t, err, `expected no error running running [%v]`, ctx) + + // The working directory of envoy is a temp directory not controlled by this test, so we have to parse it. + envoyWd := extension.ParseEnvoyWorkDirectory(stdout) + + // Verify we executed the indicated envoy binary, and it captured the arguments we expected + expectedStdout := fmt.Sprintf(`envoy pwd: %s +envoy bin: %s +envoy args: -c %s/envoy.tmpl.yaml +`, envoyWd, ctx.Opts.Envoy.Path, envoyWd) + require.Equal(t, expectedStdout, stdout.String(), `expected stdout running [%v]`, ctx) + + // Verify we didn't accidentally combine the stderr of envoy into stdout, or otherwise dropped it. + require.Equal(t, "envoy stderr\n", stderr.String(), `expected stderr running [%v]`, ctx) +} + +func TestRuntimeRunFailsOnInvalidWorkspace(t *testing.T) { + invalidWorkspaceDir := extension.RequireAbsDir(t, "../configdir/testdata/workspace5") + workspace, err := workspaces.GetWorkspaceAt(invalidWorkspaceDir) + require.NoError(t, err, `expected no error getting workspace from directory %s`, invalidWorkspaceDir) + + example, err := workspace.GetExample("default") + require.NoError(t, err, `expected no error getting example from workspace %s`, workspace) + + fakeEnvoyPath, tearDown := setupFakeEnvoy(t) + defer tearDown() + + // Create and run a new context that will invoke a fake envoy script + ctx, stdout, stderr := runContext(workspace, example, fakeEnvoyPath) + err = NewRuntime().Run(ctx) + + // Verify the error raised parsing the template from the input directory, before running envoy. + invalidTemplate := invalidWorkspaceDir + "/.getenvoy/extension/examples/default/envoy.tmpl.yaml" + expectedErr := fmt.Sprintf(`failed to process Envoy config template coming from "%s": failed to render Envoy config template: template: :4:19: executing "" at <.GetEnvoy.DefaultValue>: error calling DefaultValue: unknown property "???"`, invalidTemplate) + require.EqualError(t, err, expectedErr, `expected an error running [%v]`, ctx) + + // Verify there was no stdout or stderr because envoy shouldn't have run, yet. + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, ctx) + require.Empty(t, stderr.String(), `expected no stderr running [%v]`, ctx) +} + +func runContext(workspace model.Workspace, example model.Example, envoyPath string) (ctx *runtime.RunContext, stdout, stderr *bytes.Buffer) { + stdout = new(bytes.Buffer) + stderr = new(bytes.Buffer) + ctx = &runtime.RunContext{ + Opts: runtime.RunOpts{ + Workspace: workspace, + Example: runtime.ExampleOpts{ + Name: "default", + Example: example, + }, + Extension: runtime.ExtensionOpts{ + WasmFile: `/path/to/extension.wasm`, + Config: model.File{ + Source: "/path/to/config", + Content: []byte(`{"key2":"value2"}`), }, - } + }, + Envoy: runtime.EnvoyOpts{ + Path: envoyPath, + }, + }, + IO: ioutil.StdStreams{ + Out: stdout, + Err: stderr, + }, + } + return +} + +// setupFakeEnvoy creates a fake envoy home and returns the path to the binary. +// Side effects are reversed in the returned tear-down function. +func setupFakeEnvoy(t *testing.T) (string, func()) { + var tearDown []func() + + tempDir, deleteTempDir := extension.RequireNewTempDir(t) + tearDown = append(tearDown, deleteTempDir) + + envoyHome := filepath.Join(tempDir, "envoy_home") + fakeEnvoyPath := extension.InitFakeEnvoyHome(t, envoyHome) + revertHomeDir := extension.OverrideHomeDir(envoyHome) + tearDown = append(tearDown, revertHomeDir) + + return fakeEnvoyPath, func() { + for i := len(tearDown) - 1; i >= 0; i-- { + tearDown[i]() } - - Describe("in case of valid input", func() { - type testCase struct { - workspaceDir string - isEnvoyTemplate func(string) bool - } - DescribeTable("should run Envoy with a proper config", - func(given testCase) { - workspace, err := workspaces.GetWorkspaceAt(given.workspaceDir) - Expect(err).ToNot(HaveOccurred()) - - example, err := workspace.GetExample("default") - Expect(err).ToNot(HaveOccurred()) - - ctx := runContext(workspace, example) - - By("running Envoy") - err = NewRuntime().Run(ctx) - Expect(err).ToNot(HaveOccurred()) - - By("verifying Envoy output") - Expect(stdout.String()).NotTo(BeEmpty()) - Expect(stderr.String()).To(Equal("envoy stderr\n")) - - By("verifying Envoy arguments") - args, err := argutil.SplitCommandLine(stdout.String()) - Expect(err).ToNot(HaveOccurred()) - Expect(args).To(HaveLen(3)) - Expect(args[0]).To(Equal(ctx.Opts.Envoy.Path)) - Expect(args[1]).To(Equal("-c")) - }, - Entry("envoy.tmpl.yaml", testCase{ - workspaceDir: "../configdir/testdata/workspace1", - isEnvoyTemplate: func(name string) bool { - return name == "envoy.tmpl.yaml" - }, - }), - ) - }) - - Describe("in case of invalid input", func() { - abs := func(path string) string { - path, err := filepath.Abs(path) - if err != nil { - panic(err) - } - return path - } - - type testCase struct { - workspaceDir string - expectedErr string - } - //nolint:lll - DescribeTable("should fail with a proper error", - func(given testCase) { - workspace, err := workspaces.GetWorkspaceAt(given.workspaceDir) - Expect(err).ToNot(HaveOccurred()) - - example, err := workspace.GetExample("default") - Expect(err).ToNot(HaveOccurred()) - - ctx := runContext(workspace, example) - - By("running Envoy") - err = NewRuntime().Run(ctx) - Expect(err).To(MatchError(given.expectedErr)) - }, - Entry("envoy.tmpl.yaml: invalid placeholder", testCase{ - workspaceDir: "../configdir/testdata/workspace5", - expectedErr: fmt.Sprintf(`failed to process Envoy config template coming from %q: failed to render Envoy config template: template: :4:19: executing "" at <.GetEnvoy.DefaultValue>: error calling DefaultValue: unknown property "???"`, abs("../configdir/testdata/workspace5/.getenvoy/extension/examples/default/envoy.tmpl.yaml")), - }), - ) - }) - }) -}) + } +} diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/.licenserignore b/pkg/extension/workspace/example/runtime/getenvoy/testdata/.licenserignore deleted file mode 100644 index 0f2cfc01..00000000 --- a/pkg/extension/workspace/example/runtime/getenvoy/testdata/.licenserignore +++ /dev/null @@ -1,2 +0,0 @@ -# shell script -/envoy/bin/envoy diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/envoy/bin/envoy b/pkg/extension/workspace/example/runtime/getenvoy/testdata/envoy/bin/envoy deleted file mode 100755 index 2aae9144..00000000 --- a/pkg/extension/workspace/example/runtime/getenvoy/testdata/envoy/bin/envoy +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash - -# 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. - -set -e - -echo "$0" "$@" - -if [[ -n "${TEST_ENVOY_CAPTURE_CMDLINE_FILE}" ]]; then - mkdir -p "$(dirname "${TEST_ENVOY_CAPTURE_CMDLINE_FILE}")" - echo "$0" "$@" >"${TEST_ENVOY_CAPTURE_CMDLINE_FILE}" -fi - -if [[ -n "${TEST_ENVOY_CAPTURE_CWD_FILE}" ]]; then - mkdir -p "$(dirname "${TEST_ENVOY_CAPTURE_CWD_FILE}")" - echo "$PWD" >"${TEST_ENVOY_CAPTURE_CWD_FILE}" -fi - -if [[ -n "${TEST_ENVOY_CAPTURE_CWD_DIR}" ]]; then - mkdir -p "${TEST_ENVOY_CAPTURE_CWD_DIR}" - cp -R "$PWD"/* "${TEST_ENVOY_CAPTURE_CWD_DIR}" -fi - -printf >&2 '%s\n' "envoy stderr" - -# -# To simulate exit with an error, pass an argument in the form EXIT_CODE=NN, -# e.g. EXIT_CODE=3 -# -exit_code="0" -while test $# -gt 0; do - case "$1" in - EXIT_CODE=*) - exit_code="${1:10}" - ;; - esac - shift -done -exit "$exit_code" diff --git a/pkg/extension/workspace/toolchain/builtin/testdata/toolchain/docker b/pkg/extension/workspace/toolchain/builtin/testdata/toolchain/docker index 09e3412b..ab1894c5 100755 --- a/pkg/extension/workspace/toolchain/builtin/testdata/toolchain/docker +++ b/pkg/extension/workspace/toolchain/builtin/testdata/toolchain/docker @@ -15,20 +15,13 @@ # limitations under the License. echo "$0" "$@" +echo >&2 docker stderr -printf >&2 '%s\n' "docker stderr" - -# -# To simulate exit with an error, pass an argument in the form EXIT_CODE=NN, -# e.g. EXIT_CODE=3 -# -exit_code="0" +# An arg DOCKER_EXIT_CODE=N exits with that code number while test $# -gt 0; do case "$1" in - EXIT_CODE=*) - exit_code="${1:10}" - ;; + DOCKER_EXIT_CODE=*) exit "${1:17}" ;; esac shift done -exit "$exit_code" +exit 0 diff --git a/pkg/extension/workspace/toolchain/builtin/toolchain_test.go b/pkg/extension/workspace/toolchain/builtin/toolchain_test.go index 78cf78b4..95438108 100644 --- a/pkg/extension/workspace/toolchain/builtin/toolchain_test.go +++ b/pkg/extension/workspace/toolchain/builtin/toolchain_test.go @@ -234,13 +234,13 @@ var _ = Describe("built-in toolchain", func() { image: build/image options: - -e - - EXIT_CODE=3 + - DOCKER_EXIT_CODE=3 output: wasmFile: output/file.wasm `, tool: build, - expectedStdOut: fmt.Sprintf("testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 build/image build --output-file output/file.wasm\n", workspace.GetDir().GetRootDir()), - expectedErr: fmt.Sprintf("failed to execute an external command \"testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 build/image build --output-file output/file.wasm\": exit status 3", workspace.GetDir().GetRootDir()), + expectedStdOut: fmt.Sprintf("testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 build/image build --output-file output/file.wasm\n", workspace.GetDir().GetRootDir()), + expectedErr: fmt.Sprintf("failed to execute an external command \"testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 build/image build --output-file output/file.wasm\": exit status 3", workspace.GetDir().GetRootDir()), } }), Entry("test using given container image", func() testCase { @@ -300,11 +300,11 @@ var _ = Describe("built-in toolchain", func() { image: test/image options: - -e - - EXIT_CODE=3 + - DOCKER_EXIT_CODE=3 `, tool: test, - expectedStdOut: fmt.Sprintf("testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 test/image test\n", workspace.GetDir().GetRootDir()), - expectedErr: fmt.Sprintf("failed to execute an external command \"testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 test/image test\": exit status 3", workspace.GetDir().GetRootDir()), + expectedStdOut: fmt.Sprintf("testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 test/image test\n", workspace.GetDir().GetRootDir()), + expectedErr: fmt.Sprintf("failed to execute an external command \"testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 test/image test\": exit status 3", workspace.GetDir().GetRootDir()), } }), Entry("clean using given container image", func() testCase { @@ -364,11 +364,11 @@ var _ = Describe("built-in toolchain", func() { image: clean/image options: - -e - - EXIT_CODE=3 + - DOCKER_EXIT_CODE=3 `, tool: clean, - expectedStdOut: fmt.Sprintf("testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 clean/image clean\n", workspace.GetDir().GetRootDir()), - expectedErr: fmt.Sprintf("failed to execute an external command \"testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e EXIT_CODE=3 clean/image clean\": exit status 3", workspace.GetDir().GetRootDir()), + expectedStdOut: fmt.Sprintf("testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 clean/image clean\n", workspace.GetDir().GetRootDir()), + expectedErr: fmt.Sprintf("failed to execute an external command \"testdata/toolchain/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 clean/image clean\": exit status 3", workspace.GetDir().GetRootDir()), } }), ) diff --git a/pkg/test/cmd/extension/command.go b/pkg/test/cmd/extension/command.go new file mode 100644 index 00000000..8051afbb --- /dev/null +++ b/pkg/test/cmd/extension/command.go @@ -0,0 +1,234 @@ +// 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 extension + +import ( + "bytes" + "fmt" + "io/fs" + "io/ioutil" + "os" + "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. +// +// TODO: fake via exec.Run in unit tests because it is less complicated and error-prone than faking via shell scripts. +const FakeDockerDir = "../../../extension/workspace/toolchain/builtin/testdata/toolchain" + +// NewRootCommand initializes a command with buffers for stdout and stderr. +func NewRootCommand() (c *cobra.Command, stdout, stderr *bytes.Buffer) { + stdout = new(bytes.Buffer) + stderr = new(bytes.Buffer) + c = cmd.NewRoot() + c.SetOut(stdout) + c.SetErr(stderr) + 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() { + previous := builtintoolchain.GetCurrentUser + builtintoolchain.GetCurrentUser = func() (*user.User, error) { + return u, nil + } + return func() { + builtintoolchain.GetCurrentUser = previous + } +} + +// OverrideHomeDir sets common.HomeDir to return the indicated path. The function returned reverts to the original. +func OverrideHomeDir(homeDir string) func() { + previous := common.HomeDir + common.HomeDir = homeDir + return func() { + common.HomeDir = previous + } +} + +// 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. +// +// "$envoyHome/bin/envoy" also copies any contents in current working directory to "$envoyHome/capture" when invoked. +// +// The capture is necessary because "$envoyHome/bin/envoy" is executed from a getenvoy-managed temp directory, deleted +// on exit. This directory defines how envoy would have run, so we need to save off contents in order to verify them. +// +// TODO: fake via exec.Run in unit tests because it is less complicated and error-prone than faking via shell scripts. +func InitFakeEnvoyHome(t *testing.T, envoyHome string) string { + // Setup $envoyHome/bin and $envoyHome/capture + _ = os.Mkdir(envoyHome, fs.ModePerm) + envoyBin := filepath.Join(envoyHome, "bin") + envoyCapture := filepath.Join(envoyHome, "capture") + for _, dir := range []string{envoyBin, envoyCapture} { + err := os.Mkdir(dir, fs.ModePerm) + require.NoError(t, err, `couldn't create directory: %s`, dir) + } + + // Create script literal of $envoyHome/bin/envoy which copies the current directory to $envoyCapture when invoked. + // stdout and stderr are prefixed "envoy " to differentiate them from other command output, namely docker. + fakeEnvoyScript := fmt.Sprintf(`#!/bin/sh +set -ue +# Copy all files in the cwd to the capture directory. +cp -r . "%s" + +# Echo invocation context to stdout and fake stderr to ensure it is not combined into stdout. +echo envoy pwd: $PWD +echo envoy bin: $0 +echo envoy args: $@ +echo >&2 envoy stderr +`, envoyCapture) + + // Write $envoyHome/bin/envoy and ensure it is executable + fakeEnvoyPath := filepath.Join(envoyBin, "envoy") + err := ioutil.WriteFile(fakeEnvoyPath, []byte(fakeEnvoyScript), 0700) // nolint:gosec + require.NoError(t, err, `couldn't create fake envoy script: %s`, fakeEnvoyPath) + return fakeEnvoyPath +} + +// ParseEnvoyWorkDirectory returns the CWD captured by the script generated by InitFakeEnvoyHome. +func ParseEnvoyWorkDirectory(stdout *bytes.Buffer) string { + re := regexp.MustCompile(`.*envoy pwd: (.*)\n.*`) + envoyWd := re.FindStringSubmatch(stdout.String())[1] + return envoyWd +} From 2eb4666728156d1fa4c5d84d82cceb8d4d182ad7 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 31 Mar 2021 09:57:05 +0800 Subject: [PATCH 2/4] don't export inline type Signed-off-by: Adrian Cole --- pkg/cmd/extension/build/cmd_test.go | 4 ++-- pkg/cmd/extension/clean/cmd_test.go | 4 ++-- pkg/cmd/extension/run/cmd_test.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/extension/build/cmd_test.go b/pkg/cmd/extension/build/cmd_test.go index ccfec69b..bf2d07ec 100644 --- a/pkg/cmd/extension/build/cmd_test.go +++ b/pkg/cmd/extension/build/cmd_test.go @@ -29,13 +29,13 @@ import ( const relativeWorkspaceDir = "testdata/workspace" func TestGetEnvoyExtensionBuildValidateFlag(t *testing.T) { - type TestCase struct { + type testCase struct { flag string flagValue string expectedErr string } - tests := []TestCase{ + tests := []testCase{ { flag: "--toolchain-container-image", flagValue: "?invalid value?", diff --git a/pkg/cmd/extension/clean/cmd_test.go b/pkg/cmd/extension/clean/cmd_test.go index 8a0b196e..4cb7a16d 100644 --- a/pkg/cmd/extension/clean/cmd_test.go +++ b/pkg/cmd/extension/clean/cmd_test.go @@ -29,13 +29,13 @@ import ( const relativeWorkspaceDir = "../build/testdata/workspace" func TestGetEnvoyExtensionCleanValidateFlag(t *testing.T) { - type TestCase struct { + type testCase struct { flag string flagValue string expectedErr string } - tests := []TestCase{ + tests := []testCase{ { flag: "--toolchain-container-image", flagValue: "?invalid value?", diff --git a/pkg/cmd/extension/run/cmd_test.go b/pkg/cmd/extension/run/cmd_test.go index 8e4b5721..ed90e733 100644 --- a/pkg/cmd/extension/run/cmd_test.go +++ b/pkg/cmd/extension/run/cmd_test.go @@ -33,7 +33,7 @@ import ( const relativeWorkspaceDir = "testdata/workspace" func TestGetEnvoyExtensionRunValidateFlag(t *testing.T) { - type TestCase struct { + type testCase struct { name string flags []string flagValues []string @@ -48,7 +48,7 @@ func TestGetEnvoyExtensionRunValidateFlag(t *testing.T) { err := ioutil.WriteFile(notExecutable, []byte(`#!/bin/sh`), 0600) require.NoError(t, err, `couldn't create fake envoy script'`) - tests := []TestCase{ + tests := []testCase{ { name: "--envoy-options with imbalanced quotes", flags: []string{"--envoy-options"}, From f360cc84e6d51c2390658a1a58076d9cfef2e567 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 31 Mar 2021 10:07:02 +0800 Subject: [PATCH 3/4] redundant header Signed-off-by: Adrian Cole --- pkg/cmd/extension/push/cmd_test.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pkg/cmd/extension/push/cmd_test.go b/pkg/cmd/extension/push/cmd_test.go index 9654310b..36ef4294 100644 --- a/pkg/cmd/extension/push/cmd_test.go +++ b/pkg/cmd/extension/push/cmd_test.go @@ -11,19 +11,6 @@ // 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. -// 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 push_test From 512a87f7a91d73121510347b470210c7e33d0dbc Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Thu, 1 Apr 2021 15:32:37 +0800 Subject: [PATCH 4/4] feedback --- pkg/cmd/extension/build/cmd_test.go | 32 +- pkg/cmd/extension/clean/cmd_test.go | 34 +- .../extension/clean/testdata/.licenserignore | 1 + .../.getenvoy/extension/extension.yaml | 14 + .../extension/toolchains/default.yaml | 1 + pkg/cmd/extension/push/cmd_test.go | 16 +- pkg/cmd/extension/run/cmd_test.go | 50 +-- pkg/cmd/extension/test/cmd_test.go | 314 ++++++++---------- pkg/cmd/extension/test/test_suite_test.go | 27 -- .../.getenvoy/extension/extension.yaml | 28 ++ .../extension/toolchains/default.yaml | 15 + .../example/runtime/getenvoy/runtime_test.go | 20 +- .../runtime/getenvoy/testdata/.licenserignore | 2 + .../examples/default/envoy.tmpl.yaml | 86 +++++ .../extension/examples/default/example.yaml | 18 + .../extension/examples/default/extension.json | 1 + .../.getenvoy/extension/extension.yaml | 28 ++ .../examples/default/envoy.tmpl.yaml | 72 ++++ .../extension/examples/default/example.yaml | 4 + .../extension/examples/default/extension.json | 1 + .../.getenvoy/extension/extension.yaml | 14 + .../getenvoy_extension_run/envoy.tmpl.yaml | 75 +++++ .../getenvoy_extension_run/example.yaml | 4 + .../getenvoy_extension_run/extension.json | 1 + pkg/test/cmd/{extension => }/command.go | 2 +- pkg/test/cmd/{extension => }/context.go | 2 +- 26 files changed, 589 insertions(+), 273 deletions(-) create mode 100644 pkg/cmd/extension/clean/testdata/.licenserignore create mode 100644 pkg/cmd/extension/clean/testdata/workspace/.getenvoy/extension/extension.yaml create mode 100644 pkg/cmd/extension/clean/testdata/workspace/.getenvoy/extension/toolchains/default.yaml delete mode 100644 pkg/cmd/extension/test/test_suite_test.go create mode 100644 pkg/cmd/extension/test/testdata/workspace/.getenvoy/extension/extension.yaml create mode 100644 pkg/cmd/extension/test/testdata/workspace/.getenvoy/extension/toolchains/default.yaml create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/.licenserignore create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/envoy.tmpl.yaml create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/example.yaml create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/extension.json create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/extension.yaml create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/envoy.tmpl.yaml create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/example.yaml create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/extension.json create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/extension.yaml create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/envoy.tmpl.yaml create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/example.yaml create mode 100644 pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/extension.json rename pkg/test/cmd/{extension => }/command.go (99%) rename pkg/test/cmd/{extension => }/context.go (98%) diff --git a/pkg/cmd/extension/build/cmd_test.go b/pkg/cmd/extension/build/cmd_test.go index bf2d07ec..30cc6fd3 100644 --- a/pkg/cmd/extension/build/cmd_test.go +++ b/pkg/cmd/extension/build/cmd_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" + cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) @@ -51,9 +51,9 @@ func TestGetEnvoyExtensionBuildValidateFlag(t *testing.T) { for _, test := range tests { test := test // pin! see https://github.com/kyoh86/scopelint for why - t.Run(test.flag, func(t *testing.T) { + t.Run(test.flag+"="+test.flagValue, func(t *testing.T) { // Run "getenvoy extension build" with the flags we are testing - cmd, stdout, stderr := extension.NewRootCommand() + 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) @@ -68,11 +68,11 @@ func TestGetEnvoyExtensionBuildValidateFlag(t *testing.T) { func TestGetEnvoyExtensionBuildFailsOutsideWorkspaceDirectory(t *testing.T) { // Change to a non-workspace dir - dir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir+"/..") + dir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..") defer revertWd() // Run "getenvoy extension build" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "build"}) err := cmdutil.Execute(cmd) @@ -86,20 +86,20 @@ func TestGetEnvoyExtensionBuildFailsOutsideWorkspaceDirectory(t *testing.T) { func TestGetEnvoyExtensionBuild(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked - dockerDir, revertPath := extension.RequireOverridePath(t, extension.FakeDockerDir) + dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) defer revertPath() // "getenvoy extension build" must be in a valid workspace directory - workspaceDir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := cmd2.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 := extension.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) defer revertGetCurrentUser() // Run "getenvoy extension build" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "build"}) err := cmdutil.Execute(cmd) @@ -116,15 +116,15 @@ func TestGetEnvoyExtensionBuild(t *testing.T) { // 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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + _, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) defer revertPath() // "getenvoy extension build" must be in a valid workspace directory - _, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + _, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Run "getenvoy extension build" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "build", "--toolchain-container-image", "build/image", "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, @@ -140,22 +140,22 @@ func TestGetEnvoyExtensionBuildWithDockerOptions(t *testing.T) { // 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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) defer revertPath() // "getenvoy extension build" must be in a valid workspace directory - workspaceDir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := cmd2.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 := extension.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd2.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 := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "build", "--toolchain-container-options", toolchainOptions}) err := cmdutil.Execute(cmd) diff --git a/pkg/cmd/extension/clean/cmd_test.go b/pkg/cmd/extension/clean/cmd_test.go index 4cb7a16d..d6683674 100644 --- a/pkg/cmd/extension/clean/cmd_test.go +++ b/pkg/cmd/extension/clean/cmd_test.go @@ -21,12 +21,12 @@ import ( "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" + cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) // relativeWorkspaceDir points to a usable pre-initialized workspace -const relativeWorkspaceDir = "../build/testdata/workspace" +const relativeWorkspaceDir = "testdata/workspace" func TestGetEnvoyExtensionCleanValidateFlag(t *testing.T) { type testCase struct { @@ -51,9 +51,9 @@ func TestGetEnvoyExtensionCleanValidateFlag(t *testing.T) { for _, test := range tests { test := test // pin! see https://github.com/kyoh86/scopelint for why - t.Run(test.flag, func(t *testing.T) { + t.Run(test.flag+"="+test.flagValue, func(t *testing.T) { // Run "getenvoy extension clean" with the flags we are testing - cmd, stdout, stderr := extension.NewRootCommand() + 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) @@ -68,11 +68,11 @@ func TestGetEnvoyExtensionCleanValidateFlag(t *testing.T) { func TestGetEnvoyExtensionCleanFailsOutsideWorkspaceDirectory(t *testing.T) { // Change to a non-workspace dir - dir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir+"/..") + dir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..") defer revertWd() // Run "getenvoy extension clean" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "clean"}) err := cmdutil.Execute(cmd) @@ -86,20 +86,20 @@ func TestGetEnvoyExtensionCleanFailsOutsideWorkspaceDirectory(t *testing.T) { func TestGetEnvoyExtensionClean(t *testing.T) { // We use a fake docker command to capture the commandline that would be invoked - dockerDir, revertPath := extension.RequireOverridePath(t, extension.FakeDockerDir) + dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) defer revertPath() // "getenvoy extension clean" must be in a valid workspace directory - workspaceDir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := cmd2.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 := extension.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) defer revertGetCurrentUser() // Run "getenvoy extension clean" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "clean"}) err := cmdutil.Execute(cmd) @@ -116,15 +116,15 @@ func TestGetEnvoyExtensionClean(t *testing.T) { // 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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + _, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) defer revertPath() // "getenvoy extension clean" must be in a valid workspace directory - _, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + _, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Run "getenvoy extension clean" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "clean", "--toolchain-container-image", "clean/image", "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, @@ -140,22 +140,22 @@ func TestGetEnvoyExtensionCleanWithDockerOptions(t *testing.T) { // 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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) defer revertPath() // "getenvoy extension clean" must be in a valid workspace directory - workspaceDir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + workspaceDir, revertWd := cmd2.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 := extension.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd2.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 := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "clean", "--toolchain-container-options", toolchainOptions}) err := cmdutil.Execute(cmd) diff --git a/pkg/cmd/extension/clean/testdata/.licenserignore b/pkg/cmd/extension/clean/testdata/.licenserignore new file mode 100644 index 00000000..9e440c00 --- /dev/null +++ b/pkg/cmd/extension/clean/testdata/.licenserignore @@ -0,0 +1 @@ +/workspace/ diff --git a/pkg/cmd/extension/clean/testdata/workspace/.getenvoy/extension/extension.yaml b/pkg/cmd/extension/clean/testdata/workspace/.getenvoy/extension/extension.yaml new file mode 100644 index 00000000..c03cb2ad --- /dev/null +++ b/pkg/cmd/extension/clean/testdata/workspace/.getenvoy/extension/extension.yaml @@ -0,0 +1,14 @@ +# +# Envoy Wasm extension created with getenvoy toolkit. +# +kind: Extension + +name: mycompany.filters.http.custom_metrics + +category: envoy.filters.http +language: rust + +# Runtime the extension is being developed against. +runtime: + envoy: + version: standard:1.17.0 diff --git a/pkg/cmd/extension/clean/testdata/workspace/.getenvoy/extension/toolchains/default.yaml b/pkg/cmd/extension/clean/testdata/workspace/.getenvoy/extension/toolchains/default.yaml new file mode 100644 index 00000000..df16b59a --- /dev/null +++ b/pkg/cmd/extension/clean/testdata/workspace/.getenvoy/extension/toolchains/default.yaml @@ -0,0 +1 @@ +kind: BuiltinToolchain diff --git a/pkg/cmd/extension/push/cmd_test.go b/pkg/cmd/extension/push/cmd_test.go index 36ef4294..d2f21c1b 100644 --- a/pkg/cmd/extension/push/cmd_test.go +++ b/pkg/cmd/extension/push/cmd_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" + cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) @@ -37,11 +37,11 @@ const defaultTag = "latest" // TestGetEnvoyExtensionPush shows current directory is usable, provided it is a valid workspace. func TestGetEnvoyExtensionPush(t *testing.T) { - _, revertWd := extension.RequireChDir(t, relativeWorkspaceDir) + _, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) defer revertWd() // Run "getenvoy extension push localhost:5000/getenvoy/sample" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) err := cmdutil.Execute(cmd) @@ -59,11 +59,11 @@ digest: sha256`, defaultTag, imageRef), `unexpected stderr after running [%v]`, func TestGetEnvoyExtensionPushFailsOutsideWorkspaceDirectory(t *testing.T) { // Change to a non-workspace dir - dir, revertWd := extension.RequireChDir(t, relativeWorkspaceDir+"/..") + dir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..") defer revertWd() // Run "getenvoy extension push localhost:5000/getenvoy/sample" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "push", localRegistryWasmImageRef}) err := cmdutil.Execute(cmd) @@ -75,17 +75,17 @@ func TestGetEnvoyExtensionPushFailsOutsideWorkspaceDirectory(t *testing.T) { require.Equal(t, expectedStderr, stderr.String(), `expected stderr running [%v]`, cmd) } -// TestGetEnvoyExtensionPushWithExplicitFileOption shows +// 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 := extension.RequireChDir(t, relativeWorkspaceDir+"/..") + dir, revertWd := cmd2.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 := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "push", localRegistryWasmImageRef, "--extension-file", wasm}) err := cmdutil.Execute(cmd) diff --git a/pkg/cmd/extension/run/cmd_test.go b/pkg/cmd/extension/run/cmd_test.go index ed90e733..c99c363a 100644 --- a/pkg/cmd/extension/run/cmd_test.go +++ b/pkg/cmd/extension/run/cmd_test.go @@ -25,7 +25,7 @@ import ( "github.com/otiai10/copy" "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" + cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) @@ -40,7 +40,7 @@ func TestGetEnvoyExtensionRunValidateFlag(t *testing.T) { expectedErr string } - tempDir, closer := extension.RequireNewTempDir(t) + tempDir, closer := cmd2.RequireNewTempDir(t) defer closer() // Create a fake envoy script so that we can verify execute bit is required. @@ -128,7 +128,7 @@ 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 := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() args := []string{"extension", "run"} for i := range test.flags { args = append(args, test.flags[i], test.flagValues[i]) @@ -151,7 +151,7 @@ func TestGetEnvoyExtensionRunFailsOutsideWorkspaceDirectory(t *testing.T) { defer cleanup() // Run "getenvoy extension run" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run"}) err := cmdutil.Execute(cmd) @@ -168,7 +168,7 @@ func TestGetEnvoyExtensionRun(t *testing.T) { defer cleanup() // Run "getenvoy extension run" - cmd, stdout, stderr := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) err := cmdutil.Execute(cmd) @@ -177,7 +177,7 @@ func TestGetEnvoyExtensionRun(t *testing.T) { 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 := extension.ParseEnvoyWorkDirectory(stdout) + envoyWd := cmd2.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 @@ -205,7 +205,7 @@ 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 := extension.NewRootCommand() + cmd, _, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--toolchain-container-options", toolchainOptions}) err := cmdutil.Execute(cmd) @@ -229,7 +229,7 @@ func TestGetEnvoyExtensionRunWithExplicitVersion(t *testing.T) { defer cleanup() // Run "getenvoy extension run --envoy-version wasm:stable" - cmd, stdout, _ := extension.NewRootCommand() + cmd, stdout, _ := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-version", "wasm:stable"}) err := cmdutil.Execute(cmd) @@ -247,7 +247,7 @@ func TestGetEnvoyExtensionRunFailWithUnknownVersion(t *testing.T) { version := "wasm:unknown" // Run "getenvoy extension run --envoy-version wasm:unknown" - cmd, _, stderr := extension.NewRootCommand() + cmd, _, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-version", version}) err := cmdutil.Execute(cmd) @@ -267,7 +267,7 @@ func TestGetEnvoyExtensionRunWithCustomBinary(t *testing.T) { // Run "getenvoy extension run --envoy-path $ENVOY_HOME/bin/envoy" envoyBin := filepath.Join(config.envoyHome, "bin/envoy") - cmd, stdout, _ := extension.NewRootCommand() + cmd, stdout, _ := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--envoy-path", envoyBin}) err := cmdutil.Execute(cmd) @@ -283,7 +283,7 @@ func TestGetEnvoyExtensionRunWithOptions(t *testing.T) { defer cleanup() // Run "getenvoy extension run ..." - cmd, stdout, _ := extension.NewRootCommand() + cmd, stdout, _ := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--envoy-options", "'--concurrency 2 --component-log-level wasm:debug,config:trace'"}) err := cmdutil.Execute(cmd) @@ -292,7 +292,7 @@ func TestGetEnvoyExtensionRunWithOptions(t *testing.T) { require.NoError(t, err, `expected no error running [%v]`, cmd) // The working directory of envoy is a temp directory not controlled by this test, so we have to parse it. - envoyWd := extension.ParseEnvoyWorkDirectory(stdout) + envoyWd := cmd2.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) @@ -309,7 +309,7 @@ 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 := extension.NewRootCommand() + cmd, stdout, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--extension-file", wasmFile}) err = cmdutil.Execute(cmd) @@ -319,7 +319,7 @@ func TestGetEnvoyExtensionRunWithWasm(t *testing.T) { 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 := extension.ParseEnvoyWorkDirectory(stdout) + envoyWd := cmd2.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 @@ -346,7 +346,7 @@ 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, _, _ := extension.NewRootCommand() + cmd, _, _ := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome, "--extension-config-file", configFile}) err = cmdutil.Execute(cmd) @@ -365,7 +365,7 @@ func TestGetEnvoyExtensionRunCreatesExampleWhenMissing(t *testing.T) { defer cleanup() // Run "getenvoy extension run" - cmd, _, stderr := extension.NewRootCommand() + cmd, _, stderr := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) err := cmdutil.Execute(cmd) @@ -390,7 +390,7 @@ func TestGetEnvoyExtensionRunTinyGo(t *testing.T) { defer cleanup() // Run "getenvoy extension run" - cmd, stdout, _ := extension.NewRootCommand() + cmd, stdout, _ := cmd2.NewRootCommand() cmd.SetArgs([]string{"extension", "run", "--home-dir", config.envoyHome}) err := cmdutil.Execute(cmd) @@ -423,17 +423,17 @@ func setupTest(t *testing.T, relativeWorkspaceTemplate string) (*testEnvoyExtens result := testEnvoyExtensionConfig{} var tearDown []func() - tempDir, deleteTempDir := extension.RequireNewTempDir(t) + tempDir, deleteTempDir := cmd2.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 := extension.RequireOverridePath(t, extension.FakeDockerDir) + dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir) tearDown = append(tearDown, revertPath) result.dockerDir = dockerDir envoyHome := filepath.Join(tempDir, "envoy_home") - extension.InitFakeEnvoyHome(t, envoyHome) + cmd2.InitFakeEnvoyHome(t, envoyHome) result.envoyHome = envoyHome // create a new workspaceDir under tempDir @@ -442,22 +442,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(extension.RequireAbsDir(t, relativeWorkspaceTemplate), workspaceDir) + err = copy.Copy(cmd2.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 := extension.RequireChDir(t, workspaceDir) + _, revertWd := cmd2.RequireChDir(t, workspaceDir) tearDown = append(tearDown, revertWd) - platform := extension.RequireManifestPlatform(t) - shutdownTestServer := extension.RequireManifestTestServer(t, envoyHome) + platform := cmd2.RequireManifestPlatform(t) + shutdownTestServer := cmd2.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 := extension.OverrideGetCurrentUser(&expectedUser) + revertGetCurrentUser := cmd2.OverrideGetCurrentUser(&expectedUser) tearDown = append(tearDown, revertGetCurrentUser) result.expectedUidGid = expectedUser.Uid + ":" + expectedUser.Gid diff --git a/pkg/cmd/extension/test/cmd_test.go b/pkg/cmd/extension/test/cmd_test.go index 54e0dad6..092e82c3 100644 --- a/pkg/cmd/extension/test/cmd_test.go +++ b/pkg/cmd/extension/test/cmd_test.go @@ -15,186 +15,162 @@ package test_test import ( - "bytes" "fmt" - "os" - "path/filepath" - "strings" + "os/user" + "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/spf13/cobra" + "github.com/stretchr/testify/require" - "github.com/tetratelabs/getenvoy/pkg/cmd" - testcontext "github.com/tetratelabs/getenvoy/pkg/test/cmd/extension" + cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd" cmdutil "github.com/tetratelabs/getenvoy/pkg/util/cmd" ) -var _ = Describe("getenvoy extension test", func() { +// relativeWorkspaceDir points to a usable pre-initialized workspace +const relativeWorkspaceDir = "testdata/workspace" - var dockerDir string - - BeforeEach(func() { - dir, err := filepath.Abs("../../../extension/workspace/toolchain/builtin/testdata/toolchain") - Expect(err).ToNot(HaveOccurred()) - dockerDir = dir - }) - - var pathBackup string - - BeforeEach(func() { - pathBackup = os.Getenv("PATH") - - // override PATH to overshadow `docker` executable during the test - path := strings.Join([]string{dockerDir, pathBackup}, string(filepath.ListSeparator)) - os.Setenv("PATH", path) - }) - - AfterEach(func() { - os.Setenv("PATH", pathBackup) - }) - - 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()) - } - }) - - testcontext.SetDefaultUser() // UID:GID == 1001:1002 - - 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 validate value of --toolchain-container-image flag", func() { - By("running command") - c.SetArgs([]string{"extension", "test", "--toolchain-container-image", "?invalid value?"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "?invalid value?" is not a valid image name: invalid reference format - -Run 'getenvoy extension test --help' for usage. -`)) - }) - - It("should validate value of --toolchain-container-options flag", func() { - By("running command") - c.SetArgs([]string{"extension", "test", "--toolchain-container-options", "imbalanced ' quotes"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(BeEmpty()) - Expect(stderr.String()).To(Equal(`Error: "imbalanced ' quotes" is not a valid command line string - -Run 'getenvoy extension test --help' for usage. -`)) - }) - - chdir := func(path string) string { - dir, err := filepath.Abs(path) - Expect(err).ToNot(HaveOccurred()) - - err = os.Chdir(dir) - Expect(err).ToNot(HaveOccurred()) - - return dir +func TestGetEnvoyExtensionTestValidateFlag(t *testing.T) { + type testCase struct { + flag string + flagValue string + expectedErr string } - //nolint:lll - Context("inside a workspace directory", func() { - It("should succeed", func() { - By("changing to a workspace dir") - workspaceDir := chdir("../build/testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "test"}) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init getenvoy/extension-rust-builder:latest test\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal("docker stderr\n")) - }) - - It("should allow to override build image and add Docker cli options", func() { - By("changing to a workspace dir") - workspaceDir := chdir("../build/testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "test", - "--toolchain-container-image", "build/image", - "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, - }) - err := cmdutil.Execute(c) - Expect(err).ToNot(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e VAR=VALUE -v /host:/container build/image test\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal("docker stderr\n")) - }) - - It("should properly handle Docker build failing", func() { - By("changing to a workspace dir") - workspaceDir := chdir("../build/testdata/workspace") - - By("running command") - c.SetArgs([]string{"extension", "test", - "--toolchain-container-image", "build/image", - "--toolchain-container-options", `-e DOCKER_EXIT_CODE=3`, - }) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) - - By("verifying command output") - Expect(stdout.String()).To(Equal(fmt.Sprintf("%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 build/image test\n", dockerDir, workspaceDir))) - Expect(stderr.String()).To(Equal(fmt.Sprintf(`docker stderr -Error: failed to unit test Envoy extension using "default" toolchain: failed to execute an external command "%s/docker run -u 1001:1002 --rm -t -v %s:/source -w /source --init -e DOCKER_EXIT_CODE=3 build/image test": exit status 3 - -Run 'getenvoy extension test --help' for usage. -`, dockerDir, workspaceDir))) - }) - }) - - Context("outside of a workspace directory", func() { - It("should fail", func() { - By("changing to a non-workspace dir") - dir := chdir("../build/testdata") + tests := []testCase{ + { + flag: "--toolchain-container-image", + flagValue: "?invalid value?", + expectedErr: `"?invalid value?" is not a valid image name: invalid reference format`, + }, + { + flag: "--toolchain-container-options", + flagValue: "imbalanced ' quotes", + expectedErr: `"imbalanced ' quotes" is not a valid command line string`, + }, + } - By("running command") - c.SetArgs([]string{"extension", "test"}) - err := cmdutil.Execute(c) - Expect(err).To(HaveOccurred()) + for _, test := range tests { + test := test // pin! see https://github.com/kyoh86/scopelint for why - 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 + t.Run(test.flag+"="+test.flagValue, 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) -Run 'getenvoy extension test --help' for usage. -`, dir))) + // Verify the command failed with the expected error + require.Empty(t, stdout.String(), `expected no stdout running [%v]`, cmd) + 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) }) + } +} + +func TestGetEnvoyExtensionTestFailsOutsideWorkspaceDirectory(t *testing.T) { + // Change to a non-workspace dir + dir, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..") + defer revertWd() + + // Run "getenvoy extension test" + cmd, stdout, stderr := cmd2.NewRootCommand() + cmd.SetArgs([]string{"extension", "test"}) + err := cmdutil.Execute(cmd) + + // 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) + 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) +} + +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) + defer revertPath() + + // "getenvoy extension test" must be in a valid workspace directory + workspaceDir, revertWd := cmd2.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) + defer revertGetCurrentUser() + + // Run "getenvoy extension test" + cmd, stdout, stderr := cmd2.NewRootCommand() + cmd.SetArgs([]string{"extension", "build"}) + err := cmdutil.Execute(cmd) + + // 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) +} + +// 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) + defer revertPath() + + // "getenvoy extension build" must be in a valid workspace directory + _, revertWd := cmd2.RequireChDir(t, relativeWorkspaceDir) + defer revertWd() + + // Run "getenvoy extension build" + cmd, stdout, stderr := cmd2.NewRootCommand() + cmd.SetArgs([]string{"extension", "build", + "--toolchain-container-image", "build/image", + "--toolchain-container-options", `-e 'VAR=VALUE' -v "/host:/container"`, }) -}) + err := cmdutil.Execute(cmd) + + // 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) +} + +// 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) + defer revertPath() + + // "getenvoy extension test" must be in a valid workspace directory + workspaceDir, revertWd := cmd2.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) + 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) + + // 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", + dockerDir, expectedUser.Uid, expectedUser.Gid, workspaceDir, toolchainOptions) + + // 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) + + // We should see stdout because the docker script was invoked. + require.Equal(t, expectedDockerExec+"\n", stdout.String(), `expected stdout running [%v]`, cmd) + + // 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) +} diff --git a/pkg/cmd/extension/test/test_suite_test.go b/pkg/cmd/extension/test/test_suite_test.go deleted file mode 100644 index 13147a14..00000000 --- a/pkg/cmd/extension/test/test_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 test_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestTest(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Test Suite") -} diff --git a/pkg/cmd/extension/test/testdata/workspace/.getenvoy/extension/extension.yaml b/pkg/cmd/extension/test/testdata/workspace/.getenvoy/extension/extension.yaml new file mode 100644 index 00000000..dae80a2d --- /dev/null +++ b/pkg/cmd/extension/test/testdata/workspace/.getenvoy/extension/extension.yaml @@ -0,0 +1,28 @@ +# 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. + +# +# Envoy Wasm extension created with getenvoy toolkit. +# +kind: Extension + +name: mycompany.filters.http.custom_metrics + +category: envoy.filters.http +language: rust + +# Runtime the extension is being developed against. +runtime: + envoy: + version: standard:1.17.0 diff --git a/pkg/cmd/extension/test/testdata/workspace/.getenvoy/extension/toolchains/default.yaml b/pkg/cmd/extension/test/testdata/workspace/.getenvoy/extension/toolchains/default.yaml new file mode 100644 index 00000000..4b9d971b --- /dev/null +++ b/pkg/cmd/extension/test/testdata/workspace/.getenvoy/extension/toolchains/default.yaml @@ -0,0 +1,15 @@ +# 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. + +kind: BuiltinToolchain diff --git a/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go b/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go index c5de53cb..90e4122b 100644 --- a/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go +++ b/pkg/extension/workspace/example/runtime/getenvoy/runtime_test.go @@ -26,12 +26,14 @@ import ( "github.com/tetratelabs/getenvoy/pkg/extension/workspace/example/runtime" . "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/extension" + "github.com/tetratelabs/getenvoy/pkg/test/cmd" ioutil "github.com/tetratelabs/getenvoy/pkg/util/io" ) -// relativeWorkspaceDir points to a usable pre-initialized workspace -const relativeWorkspaceDir = "../configdir/testdata/workspace1" +const ( + relativeWorkspaceDir = "testdata/workspace" + invalidWorkspaceDir = "testdata/invalidWorkspace" +) func TestRuntimeRun(t *testing.T) { workspace, err := workspaces.GetWorkspaceAt(relativeWorkspaceDir) @@ -49,7 +51,7 @@ func TestRuntimeRun(t *testing.T) { require.NoError(t, err, `expected no error running running [%v]`, ctx) // The working directory of envoy is a temp directory not controlled by this test, so we have to parse it. - envoyWd := extension.ParseEnvoyWorkDirectory(stdout) + envoyWd := cmd.ParseEnvoyWorkDirectory(stdout) // Verify we executed the indicated envoy binary, and it captured the arguments we expected expectedStdout := fmt.Sprintf(`envoy pwd: %s @@ -63,7 +65,7 @@ envoy args: -c %s/envoy.tmpl.yaml } func TestRuntimeRunFailsOnInvalidWorkspace(t *testing.T) { - invalidWorkspaceDir := extension.RequireAbsDir(t, "../configdir/testdata/workspace5") + invalidWorkspaceDir := cmd.RequireAbsDir(t, invalidWorkspaceDir) workspace, err := workspaces.GetWorkspaceAt(invalidWorkspaceDir) require.NoError(t, err, `expected no error getting workspace from directory %s`, invalidWorkspaceDir) @@ -79,7 +81,7 @@ func TestRuntimeRunFailsOnInvalidWorkspace(t *testing.T) { // Verify the error raised parsing the template from the input directory, before running envoy. invalidTemplate := invalidWorkspaceDir + "/.getenvoy/extension/examples/default/envoy.tmpl.yaml" - expectedErr := fmt.Sprintf(`failed to process Envoy config template coming from "%s": failed to render Envoy config template: template: :4:19: executing "" at <.GetEnvoy.DefaultValue>: error calling DefaultValue: unknown property "???"`, invalidTemplate) + expectedErr := fmt.Sprintf(`failed to process Envoy config template coming from "%s": failed to render Envoy config template: template: :18:19: executing "" at <.GetEnvoy.DefaultValue>: error calling DefaultValue: unknown property "???"`, invalidTemplate) require.EqualError(t, err, expectedErr, `expected an error running [%v]`, ctx) // Verify there was no stdout or stderr because envoy shouldn't have run, yet. @@ -121,12 +123,12 @@ func runContext(workspace model.Workspace, example model.Example, envoyPath stri func setupFakeEnvoy(t *testing.T) (string, func()) { var tearDown []func() - tempDir, deleteTempDir := extension.RequireNewTempDir(t) + tempDir, deleteTempDir := cmd.RequireNewTempDir(t) tearDown = append(tearDown, deleteTempDir) envoyHome := filepath.Join(tempDir, "envoy_home") - fakeEnvoyPath := extension.InitFakeEnvoyHome(t, envoyHome) - revertHomeDir := extension.OverrideHomeDir(envoyHome) + fakeEnvoyPath := cmd.InitFakeEnvoyHome(t, envoyHome) + revertHomeDir := cmd.OverrideHomeDir(envoyHome) tearDown = append(tearDown, revertHomeDir) return fakeEnvoyPath, func() { diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/.licenserignore b/pkg/extension/workspace/example/runtime/getenvoy/testdata/.licenserignore new file mode 100644 index 00000000..1db62e1a --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/.licenserignore @@ -0,0 +1,2 @@ +/workspace/ +/invalidWorkspace/ diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/envoy.tmpl.yaml b/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/envoy.tmpl.yaml new file mode 100644 index 00000000..bec22c96 --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/envoy.tmpl.yaml @@ -0,0 +1,86 @@ +# 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. + +# +# Example Envoy configuration. +# +admin: {{ .GetEnvoy.DefaultValue "???" }} + +static_resources: + listeners: + - name: ingress + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.wasm + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.wasm.v3.Wasm + config: + configuration: {{ .GetEnvoy.Extension.Config }} + name: {{ .GetEnvoy.Extension.Name }} + root_id: {{ .GetEnvoy.Extension.Name }} + vm_config: + vm_id: {{ .GetEnvoy.Extension.Name }} + runtime: envoy.wasm.runtime.v8 + code: {{ .GetEnvoy.Extension.Code }} + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: ingress + cluster: mock_service + + - name: mock + address: + socket_address: + address: 127.0.0.1 + port_value: 10001 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: mock + route_config: + name: local_route + virtual_hosts: + - name: mock + domains: + - "*" + routes: + - match: + prefix: "/" + direct_response: + status: 200 + body: + inline_string: "Hi from mock service!\n" + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: mock_service + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: mock_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 10001 diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/example.yaml b/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/example.yaml new file mode 100644 index 00000000..c2f49598 --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/example.yaml @@ -0,0 +1,18 @@ +# 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. + +# +# Example of a Wasm Http Filter. +# +kind: Example diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/extension.json b/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/extension.json new file mode 100644 index 00000000..29f05f6b --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/examples/default/extension.json @@ -0,0 +1 @@ +{"key":"value"} diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/extension.yaml b/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/extension.yaml new file mode 100644 index 00000000..dae80a2d --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/invalidWorkspace/.getenvoy/extension/extension.yaml @@ -0,0 +1,28 @@ +# 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. + +# +# Envoy Wasm extension created with getenvoy toolkit. +# +kind: Extension + +name: mycompany.filters.http.custom_metrics + +category: envoy.filters.http +language: rust + +# Runtime the extension is being developed against. +runtime: + envoy: + version: standard:1.17.0 diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/envoy.tmpl.yaml b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/envoy.tmpl.yaml new file mode 100644 index 00000000..9f531e69 --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/envoy.tmpl.yaml @@ -0,0 +1,72 @@ +# +# Example Envoy configuration. +# +admin: {{ .GetEnvoy.DefaultValue "admin" }} + +static_resources: + listeners: + - name: ingress + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.wasm + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.wasm.v3.Wasm + config: + configuration: {{ .GetEnvoy.Extension.Config }} + name: {{ .GetEnvoy.Extension.Name }} + root_id: {{ .GetEnvoy.Extension.Name }} + vm_config: + vm_id: {{ .GetEnvoy.Extension.Name }} + runtime: envoy.wasm.runtime.v8 + code: {{ .GetEnvoy.Extension.Code }} + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: ingress + cluster: mock_service + + - name: mock + address: + socket_address: + address: 127.0.0.1 + port_value: 10001 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: mock + route_config: + name: local_route + virtual_hosts: + - name: mock + domains: + - "*" + routes: + - match: + prefix: "/" + direct_response: + status: 200 + body: + inline_string: "Hi from mock service!\n" + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: mock_service + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: mock_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 10001 diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/example.yaml b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/example.yaml new file mode 100644 index 00000000..c45b1262 --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/example.yaml @@ -0,0 +1,4 @@ +# +# Example of a Wasm Http Filter. +# +kind: Example diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/extension.json b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/extension.json new file mode 100644 index 00000000..29f05f6b --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/examples/default/extension.json @@ -0,0 +1 @@ +{"key":"value"} diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/extension.yaml b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/extension.yaml new file mode 100644 index 00000000..c03cb2ad --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/.getenvoy/extension/extension.yaml @@ -0,0 +1,14 @@ +# +# Envoy Wasm extension created with getenvoy toolkit. +# +kind: Extension + +name: mycompany.filters.http.custom_metrics + +category: envoy.filters.http +language: rust + +# Runtime the extension is being developed against. +runtime: + envoy: + version: standard:1.17.0 diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/envoy.tmpl.yaml b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/envoy.tmpl.yaml new file mode 100644 index 00000000..5ad3c038 --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/envoy.tmpl.yaml @@ -0,0 +1,75 @@ +# +# Example Envoy configuration. +# +admin: {"accessLogPath":"/dev/null","address":{"socketAddress":{"address":"127.0.0.1","portValue":9901}}} + +static_resources: + listeners: + - name: ingress + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.wasm + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.wasm.v3.Wasm + config: + configuration: + '@type': type.googleapis.com/google.protobuf.StringValue + value: | + {"key":"value"} + name: mycompany.filters.http.custom_metrics + root_id: mycompany.filters.http.custom_metrics + vm_config: + vm_id: mycompany.filters.http.custom_metrics + runtime: envoy.wasm.runtime.v8 + code: {"local":{"filename":"/path/to/extension.wasm"}} + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: ingress + cluster: mock_service + + - name: mock + address: + socket_address: + address: 127.0.0.1 + port_value: 10001 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: mock + route_config: + name: local_route + virtual_hosts: + - name: mock + domains: + - "*" + routes: + - match: + prefix: "/" + direct_response: + status: 200 + body: + inline_string: "Hi from mock service!\n" + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: mock_service + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: mock_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 10001 diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/example.yaml b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/example.yaml new file mode 100644 index 00000000..c45b1262 --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/example.yaml @@ -0,0 +1,4 @@ +# +# Example of a Wasm Http Filter. +# +kind: Example diff --git a/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/extension.json b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/extension.json new file mode 100644 index 00000000..29f05f6b --- /dev/null +++ b/pkg/extension/workspace/example/runtime/getenvoy/testdata/workspace/expected/getenvoy_extension_run/extension.json @@ -0,0 +1 @@ +{"key":"value"} diff --git a/pkg/test/cmd/extension/command.go b/pkg/test/cmd/command.go similarity index 99% rename from pkg/test/cmd/extension/command.go rename to pkg/test/cmd/command.go index 8051afbb..2202716f 100644 --- a/pkg/test/cmd/extension/command.go +++ b/pkg/test/cmd/command.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package extension +package cmd import ( "bytes" diff --git a/pkg/test/cmd/extension/context.go b/pkg/test/cmd/context.go similarity index 98% rename from pkg/test/cmd/extension/context.go rename to pkg/test/cmd/context.go index d882c05f..57b7fe02 100644 --- a/pkg/test/cmd/extension/context.go +++ b/pkg/test/cmd/context.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package extension +package cmd import ( "os/user"