Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrites trickiest cmd tests to help clarify what's happening #151

Merged
merged 4 commits into from
Apr 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions pkg/cmd/extension/build/build_suite_test.go

This file was deleted.

314 changes: 145 additions & 169 deletions pkg/cmd/extension/build/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
cmd2 "github.com/tetratelabs/getenvoy/pkg/test/cmd"
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+"="+test.flagValue, func(t *testing.T) {
// Run "getenvoy extension build" with the flags we are testing
cmd, stdout, stderr := cmd2.NewRootCommand()
cmd.SetArgs([]string{"extension", "build", test.flag, test.flagValue})
err := cmdutil.Execute(cmd)
require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, cmd)

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 := cmd2.RequireChDir(t, relativeWorkspaceDir+"/..")
defer revertWd()

// Run "getenvoy extension build"
cmd, stdout, stderr := cmd2.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 := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir)
defer revertPath()

// "getenvoy extension build" 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 build"
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)
}

// TestGetEnvoyExtensionBuildFail ensures build failures show useful information in stderr
func TestGetEnvoyExtensionBuildFail(t *testing.T) {
// We use a fake docker command to capture the commandline that would be invoked, and force a failure.
dockerDir, revertPath := cmd2.RequireOverridePath(t, cmd2.FakeDockerDir)
defer revertPath()

// "getenvoy extension build" 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 build"
cmd, stdout, stderr := cmd2.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)
}
Loading