From 1b825e53216743391d641d104cba57e2655e5016 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Tue, 21 Dec 2021 15:39:38 -0800 Subject: [PATCH] Unit and integration test enhancements --- .github/workflows/pull_request.yaml | 10 +- CONTRIBUTING.md | 2 +- Makefile | 30 +++-- .../configmap}/configmap_test.go | 3 +- integration/builders/configmap/doc.go | 1 + .../default}/default_test.go | 9 +- integration/builders/default/doc.go | 1 + .../default}/parallel_default_test.go | 8 +- integration/builders/localregistry/doc.go | 1 + .../localregistry}/localregistry_test.go | 40 +++++- integration/builders/randomlb/doc.go | 1 + .../randomlb}/randomlb_test.go | 3 +- integration/builders/readme.md | 3 + integration/builders/rootless/doc.go | 1 + .../rootless}/rootless_test.go | 3 +- integration/common/basesuites.go | 12 +- integration/common/buildcontext.go | 5 +- integration/common/kubeclient.go | 19 ++- integration/common/runners.go | 24 ++-- integration/common/skipper.go | 15 --- integration/suites/doc.go | 1 - pkg/cmd/build_test.go | 117 ++++++++++++++++++ pkg/driver/driver_test.go | 105 ++++++++++++++++ pkg/platformutil/parse_test.go | 29 ++++- 24 files changed, 375 insertions(+), 68 deletions(-) rename integration/{suites => builders/configmap}/configmap_test.go (99%) create mode 100644 integration/builders/configmap/doc.go rename integration/{suites => builders/default}/default_test.go (87%) create mode 100644 integration/builders/default/doc.go rename integration/{suites => builders/default}/parallel_default_test.go (94%) create mode 100644 integration/builders/localregistry/doc.go rename integration/{suites => builders/localregistry}/localregistry_test.go (92%) create mode 100644 integration/builders/randomlb/doc.go rename integration/{suites => builders/randomlb}/randomlb_test.go (94%) create mode 100644 integration/builders/readme.md create mode 100644 integration/builders/rootless/doc.go rename integration/{suites => builders/rootless}/rootless_test.go (97%) delete mode 100644 integration/common/skipper.go delete mode 100644 integration/suites/doc.go create mode 100644 pkg/cmd/build_test.go create mode 100644 pkg/driver/driver_test.go diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index 153777e6..bda70da9 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -24,7 +24,7 @@ jobs: - name: Unit Tests run: make test - name: Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: file: cover-unit.out flags: unit-tests @@ -66,9 +66,9 @@ jobs: kubectl wait --for=condition=ready --timeout=30s node --all kubectl get nodes -o wide - name: Run integration tests - run: make integration EXTRA_GO_TEST_FLAGS=-v + run: make integration TEST_FLAGS=-v - name: Gather integration coverage results - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: file: cover-int.out flags: integration-tests @@ -110,9 +110,9 @@ jobs: kubectl get nodes -o wide - name: Run integration tests - run: make integration EXTRA_GO_TEST_FLAGS=-v + run: make integration TEST_FLAGS=-v - name: Gather integration coverage results - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: file: cover-int.out flags: integration-tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04a9553f..d1be2cfc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,7 +48,7 @@ make integration If you want to run a single suite of tests while working on a specific area of the tests or main code, use something like this: ``` -make integration EXTRA_GO_TEST_FLAGS="-run TestConfigMapSuite -v" +make integration TEST_FLAGS="-run TestConfigMapSuite -v" ``` Hint: find the current test suites with `grep "func Test" integration/suites/*.go` diff --git a/Makefile b/Makefile index 41143ade..31092637 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,7 @@ # Copyright (C) 2020 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 -VERSION?=$(shell git describe --tags --first-parent --abbrev=7 --long --dirty --always) -TEST_KUBECONFIG?=$(HOME)/.kube/config +VERSION?=dev TEST_TIMEOUT=600s TEST_PARALLELISM=3 @@ -45,6 +44,8 @@ GO_DEPS=$(foreach dir,$(shell go list -deps -f '{{.Dir}}' ./cmd/kubectl-buildkit REVISION=$(shell git describe --match 'v[0-9]*' --always --dirty --tags) GO_FLAGS=-ldflags "-X $(GO_MOD_NAME)/version.Version=${VERSION} -X $(GO_MOD_NAME)/version.DefaultHelperImage=$(BUILDKIT_PROXY_IMAGE)" -mod=vendor GO_COVER_FLAGS=-cover -coverpkg=./... -covermode=count +UNIT_TEST_PACKAGES=$(shell go list ./... | grep -v "/integration/") +COVERAGE_FILTERS=grep -v "\.pb\.go" | grep -v "/integration/" .PHONY: help help: @@ -105,28 +106,37 @@ $(BIN_DIR)/%.tgz: $(BIN_DIR)/%/* generate: go generate ./... + .PHONY: test test: - go test $(GO_FLAGS) $(GO_COVER_FLAGS) -coverprofile=./cover-unit.out ./... + go test $(GO_FLAGS) \ + -parallel $(TEST_PARALLELISM) \ + $(TEST_FLAGS) \ + $(GO_COVER_FLAGS) -coverprofile=./cover-unit-full.out \ + $(UNIT_TEST_PACKAGES) + cat ./cover-unit-full.out | $(COVERAGE_FILTERS) > ./cover-unit.out + rm -f ./cover-unit-full.out + .PHONY: integration integration: - @echo "Running integration tests with $(TEST_KUBECONFIG)" + @echo "Running integration tests" @kubectl config get-contexts - TEST_KUBECONFIG=$(TEST_KUBECONFIG) go test -timeout $(TEST_TIMEOUT) $(GO_FLAGS) \ + go test -timeout $(TEST_TIMEOUT) $(GO_FLAGS) \ -parallel $(TEST_PARALLELISM) \ - $(EXTRA_GO_TEST_FLAGS) \ - $(GO_COVER_FLAGS) -coverprofile=./cover-int.out \ + $(TEST_FLAGS) \ + $(GO_COVER_FLAGS) -coverprofile=./cover-int-full.out \ ./integration/... - + cat ./cover-int-full.out | $(COVERAGE_FILTERS) > ./cover-int.out + rm -f ./cover-int-full.out go tool cover -html=./cover-int.out -o ./cover-int.html .PHONY: coverage coverage: cover.html cover.html: cover-int.out cover-unit.out - cp cover-int.out cover.out - tail +2 cover-unit.out >> cover.out + cat cover-int.out > cover.out + tail +2 cover-unit.out | $(COVERAGE_FILTERS) >> cover.out go tool cover -html=./cover.out -o ./cover.html go tool cover -func cover.out | grep total: open ./cover.html diff --git a/integration/suites/configmap_test.go b/integration/builders/configmap/configmap_test.go similarity index 99% rename from integration/suites/configmap_test.go rename to integration/builders/configmap/configmap_test.go index 12c17579..4b87d09e 100644 --- a/integration/suites/configmap_test.go +++ b/integration/builders/configmap/configmap_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2020 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -package suites +package configmap import ( "context" @@ -206,7 +206,6 @@ func (s *configMapSuite) TestPreExistingWithCustomCreate() { } func TestConfigMapSuite(t *testing.T) { - common.Skipper(t) t.Parallel() suite.Run(t, &configMapSuite{ Name: "configmaptest", diff --git a/integration/builders/configmap/doc.go b/integration/builders/configmap/doc.go new file mode 100644 index 00000000..5e170fe4 --- /dev/null +++ b/integration/builders/configmap/doc.go @@ -0,0 +1 @@ +package configmap diff --git a/integration/suites/default_test.go b/integration/builders/default/default_test.go similarity index 87% rename from integration/suites/default_test.go rename to integration/builders/default/default_test.go index 95f1ffaa..5ee3edfb 100644 --- a/integration/suites/default_test.go +++ b/integration/builders/default/default_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2020 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -package suites +package dflt import ( "bytes" @@ -26,8 +26,11 @@ func (s *DefaultSuite) TestVersion() { } func TestDefaultSuite(t *testing.T) { - common.Skipper(t) - t.Parallel() + // Clean up any pre-existing default builder before proceeding + _ = common.RunBuildkit("rm", []string{ + "buildkit", + }, common.RunBuildStreams{}) + suite.Run(t, &DefaultSuite{ BaseSuite: common.BaseSuite{ Name: "buildkit", // TODO pull this from the actual default name diff --git a/integration/builders/default/doc.go b/integration/builders/default/doc.go new file mode 100644 index 00000000..0d067854 --- /dev/null +++ b/integration/builders/default/doc.go @@ -0,0 +1 @@ +package dflt diff --git a/integration/suites/parallel_default_test.go b/integration/builders/default/parallel_default_test.go similarity index 94% rename from integration/suites/parallel_default_test.go rename to integration/builders/default/parallel_default_test.go index 227c2831..a36ab5f0 100644 --- a/integration/suites/parallel_default_test.go +++ b/integration/builders/default/parallel_default_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2020 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -package suites +package dflt import ( "context" @@ -98,8 +98,12 @@ func (s *parallelDefaultSuite) TestParallelDefaultBuilds() { } func TestParallelDefaultBuildSuite(t *testing.T) { - common.Skipper(t) // We don't parallelize with other tests, since we use the default builder name + // Clean up any pre-existing default builder before proceeding + _ = common.RunBuildkit("rm", []string{ + "buildkit", + }, common.RunBuildStreams{}) + suite.Run(t, ¶llelDefaultSuite{ Name: "buildkit", CreateFlags: []string{"--buildkitd-flags=--debug"}, diff --git a/integration/builders/localregistry/doc.go b/integration/builders/localregistry/doc.go new file mode 100644 index 00000000..66aaf78f --- /dev/null +++ b/integration/builders/localregistry/doc.go @@ -0,0 +1 @@ +package localregistry diff --git a/integration/suites/localregistry_test.go b/integration/builders/localregistry/localregistry_test.go similarity index 92% rename from integration/suites/localregistry_test.go rename to integration/builders/localregistry/localregistry_test.go index b4d9183f..eb6375a1 100644 --- a/integration/suites/localregistry_test.go +++ b/integration/builders/localregistry/localregistry_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2020 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -package suites +package localregistry import ( "bytes" @@ -29,6 +29,13 @@ const ( RegistryImageName = "docker.io/registry:2.7" ) +var ( + FastPlatforms = []string{"linux/386", "linux/amd64", "linux/arm/v6", "linux/arm/v7", "linux/arm64"} + + // Run with -short to skip these slower platforms (larger base images) + SlowPlatforms = []string{"windows/amd64"} +) + type localRegistrySuite struct { suite.Suite Name string @@ -49,7 +56,6 @@ type localRegistrySuite struct { } func (s *localRegistrySuite) SetupSuite() { - // s.skipTeardown = true var err error ctx := context.Background() s.ClientSet, s.Namespace, err = common.GetKubeClientset() @@ -260,8 +266,18 @@ func (s *localRegistrySuite) TearDownSuite() { ctx := context.Background() + // Grab logs from the registry in case there are any interesting failures + pod, err := s.podClient.Get(ctx, s.registryName, metav1.GetOptions{}) + if err != nil { + logrus.Warnf("failed to get registry pod for logs %s: %s", s.registryName, err) + } else { + logrus.Infof("--- BEGIN REGISTRY LOGS ---") + common.LogPodLogs(ctx, []corev1.Pod{*pod}, s.Namespace, s.ClientSet) + logrus.Infof("--- END REGISTRY LOGS ---") + } + // Clean everything up... - err := s.podClient.Delete(ctx, s.registryName, metav1.DeleteOptions{}) + err = s.podClient.Delete(ctx, s.registryName, metav1.DeleteOptions{}) if err != nil { logrus.Warnf("failed to clean up pod %s: %s", s.registryName, err) } @@ -429,11 +445,18 @@ func main() { require.NoError(s.T(), err, "%s: config file creation", s.Name) defer cleanup() imageName := s.registryFQDN + "/" + s.Name + "multiarchcrosscompile:latest" + var platforms []string + if testing.Short() { + platforms = FastPlatforms[0:2] + } else { + platforms = append(FastPlatforms, SlowPlatforms...) + } + args := []string{"--progress=plain", "--builder", s.Name, "--push", "--tag", imageName, - "--platform", "linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,windows/amd64", + "--platform", strings.Join(platforms, ","), dir, } err = common.RunBuild(args, common.RunBuildStreams{}) @@ -455,12 +478,17 @@ func (s *localRegistrySuite) TestVersion() { } func TestLocalRegistrySuite(t *testing.T) { - common.Skipper(t) // TODO this testcase should be safe to run parallel, but I'm seeing failures in CI that look // like containerd runtime concurrency problems. They don't seem related to this particular change though //t.Parallel() + skipTeardown := false + skipTeardownStr := os.Getenv("SKIP_TEARDOWN") + if skipTeardownStr != "" { + skipTeardown = true + } suite.Run(t, &localRegistrySuite{ - Name: "regtest", + Name: "regtest", + skipTeardown: skipTeardown, // Debug set in the config file }) } diff --git a/integration/builders/randomlb/doc.go b/integration/builders/randomlb/doc.go new file mode 100644 index 00000000..4368661e --- /dev/null +++ b/integration/builders/randomlb/doc.go @@ -0,0 +1 @@ +package randomlb diff --git a/integration/suites/randomlb_test.go b/integration/builders/randomlb/randomlb_test.go similarity index 94% rename from integration/suites/randomlb_test.go rename to integration/builders/randomlb/randomlb_test.go index 1bc09a3e..b74e4349 100644 --- a/integration/suites/randomlb_test.go +++ b/integration/builders/randomlb/randomlb_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2020 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -package suites +package randomlb import ( "testing" @@ -15,7 +15,6 @@ type RandomLBSuite struct{ common.BaseSuite } // TODO - add some conditional test cases to varify random scheduling is actually working func TestRandomLBSuite(t *testing.T) { - common.Skipper(t) t.Parallel() suite.Run(t, &RandomLBSuite{ BaseSuite: common.BaseSuite{ diff --git a/integration/builders/readme.md b/integration/builders/readme.md new file mode 100644 index 00000000..3ea504b7 --- /dev/null +++ b/integration/builders/readme.md @@ -0,0 +1,3 @@ +# End to End tests + +Each directory contains tests based on a builder configuration pattern. \ No newline at end of file diff --git a/integration/builders/rootless/doc.go b/integration/builders/rootless/doc.go new file mode 100644 index 00000000..081519e7 --- /dev/null +++ b/integration/builders/rootless/doc.go @@ -0,0 +1 @@ +package rootless diff --git a/integration/suites/rootless_test.go b/integration/builders/rootless/rootless_test.go similarity index 97% rename from integration/suites/rootless_test.go rename to integration/builders/rootless/rootless_test.go index 2e6b2471..99a99ae4 100644 --- a/integration/suites/rootless_test.go +++ b/integration/builders/rootless/rootless_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2020 VMware, Inc. // SPDX-License-Identifier: Apache-2.0 -package suites +package rootless import ( "testing" @@ -13,7 +13,6 @@ import ( type rootlessSuite struct{ common.BaseSuite } func TestRootlessSuite(t *testing.T) { - common.Skipper(t) //t.Parallel() // TODO - tests fail if run in parallel, may be actual race bug suite.Run(t, &rootlessSuite{ BaseSuite: common.BaseSuite{ diff --git a/integration/common/basesuites.go b/integration/common/basesuites.go index f6b2a8aa..f3b04183 100644 --- a/integration/common/basesuites.go +++ b/integration/common/basesuites.go @@ -156,7 +156,7 @@ func (s *BaseSuite) TestLs() { func (s *BaseSuite) TestBuildWithSecret() { logrus.Infof("%s: Build with Secret", s.Name) - proxyImage := os.Getenv("TEST_IMAGE_BASE") + proxyImage := GetTestImageBase() dir, cleanup, err := NewBuildContext(map[string]string{ "Dockerfile": fmt.Sprintf(`# syntax=docker/dockerfile:experimental @@ -205,7 +205,7 @@ func (s *BaseSuite) TestBuildWithSSHKey() { s.T().Skip("Skipping SSH test as it's too flaky") logrus.Infof("%s: Build with SSH key", s.Name) - proxyImage := os.Getenv("TEST_IMAGE_BASE") + proxyImage := GetTestImageBase() // Note: if the key is not valid, the socket will not be created, so simply checking for // its existence is sufficient to verify the ssh plumbing is working. @@ -261,3 +261,11 @@ RUN --mount=type=ssh,id=myssh echo "SSH_AUTH_SOCK=${SSH_AUTH_SOCK}" && ls -l ${S require.NoError(s.T(), err, "build failed") } + +func GetTestImageBase() string { + base := os.Getenv("TEST_IMAGE_BASE") + if base == "" { + base = "busybox" + } + return base +} diff --git a/integration/common/buildcontext.go b/integration/common/buildcontext.go index a39d90bb..2cad8c04 100644 --- a/integration/common/buildcontext.go +++ b/integration/common/buildcontext.go @@ -38,10 +38,7 @@ func NewBuildContext(payloads map[string]string) (string, func(), error) { // NewSimpleBuildContext creates a very simple Dockerfile for exercising the builder func NewSimpleBuildContext() (string, func(), error) { - proxyImage := os.Getenv("TEST_IMAGE_BASE") - if proxyImage == "" { - return "", nil, fmt.Errorf("TEST_IMAGE_BASE env var unset") - } + proxyImage := GetTestImageBase() return NewBuildContext(map[string]string{ "Dockerfile": fmt.Sprintf(`FROM %s RUN echo "#!/bin/sh" > /run diff --git a/integration/common/kubeclient.go b/integration/common/kubeclient.go index ab6c34fb..0b6955df 100644 --- a/integration/common/kubeclient.go +++ b/integration/common/kubeclient.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/kubernetes" @@ -30,6 +31,18 @@ func GetKubeClientset() (*kubernetes.Clientset, string, error) { return nil, "", err } clientset, err := kubernetes.NewForConfig(restClientConfig) + if err != nil { + return nil, "", err + } + + // Verify the cluster is accessible so we can fail fast + content, err := clientset.Discovery().RESTClient().Get().AbsPath("/livez").DoRaw(context.Background()) + if err != nil { + return nil, "", errors.Wrap(err, "kubernetes cluster is unhealthy or inaccessible") + } + if !strings.Contains(string(content), "ok") { + return nil, "", fmt.Errorf("kubernetes cluster is unhealthy or inaccessible: %s", string(content)) + } return clientset, ns, err } @@ -196,6 +209,11 @@ func LogBuilderLogs(ctx context.Context, name, namespace string, clientset *kube } logrus.Infof("Detected %d pods for builder %s - gathering logs", len(pods), name) logrus.Infof("--- BEGIN BUILDER LOGS ---") + LogPodLogs(ctx, pods, namespace, clientset) + logrus.Infof("--- END BUILDER LOGS ---") +} + +func LogPodLogs(ctx context.Context, pods []v1.Pod, namespace string, clientset *kubernetes.Clientset) { podClient := clientset.CoreV1().Pods(namespace) for _, pod := range pods { for _, ctr := range pod.Spec.Containers { @@ -211,6 +229,5 @@ func LogBuilderLogs(ctx context.Context, name, namespace string, clientset *kube } } } - logrus.Infof("--- END BUILDER LOGS ---") } diff --git a/integration/common/runners.go b/integration/common/runners.go index cf89f6ea..b67978fa 100644 --- a/integration/common/runners.go +++ b/integration/common/runners.go @@ -28,10 +28,6 @@ type RunBuildStreams struct { func RunBuild(args []string, streams RunBuildStreams) error { flags := pflag.NewFlagSet("kubectl-build", pflag.ExitOnError) pflag.CommandLine = flags - finalArgs := append( - []string{"--kubeconfig", os.Getenv("TEST_KUBECONFIG")}, - args..., - ) if streams.In == nil { streams.In = os.Stdin } @@ -44,17 +40,29 @@ func RunBuild(args []string, streams RunBuildStreams) error { // TODO do we want to capture the output someplace else? root := commands.NewRootBuildCmd(genericclioptions.IOStreams{In: streams.In, Out: streams.Out, ErrOut: streams.Err}) - root.SetArgs(finalArgs) - logrus.Infof("Build: %v", finalArgs) + root.SetArgs(args) + logrus.Infof("Build: %v", args) - return root.Execute() + // BUG! - there's some flakiness in the layers beneath us - we'll retry up to 3 times for lease failures inside buildkit + // example: "Error: failed to solve: failed to compute cache key: lease does not exist: not found" + limit := 3 + var err error + for i := 0; i < limit; i++ { + err = root.Execute() + if err != nil && strings.Contains(err.Error(), "failed to compute cache key") { + logrus.Warnf("Hit flaky error in buildkit: %d of %d - %s", i+1, limit, err) + continue + } + break + } + return err } func RunBuildkit(command string, args []string, streams RunBuildStreams) error { flags := pflag.NewFlagSet("kubectl-buildkit", pflag.ExitOnError) pflag.CommandLine = flags finalArgs := append( - []string{command, "--kubeconfig", os.Getenv("TEST_KUBECONFIG")}, + []string{command}, args..., ) diff --git a/integration/common/skipper.go b/integration/common/skipper.go deleted file mode 100644 index 5d260608..00000000 --- a/integration/common/skipper.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (C) 2020 VMware, Inc. -// SPDX-License-Identifier: Apache-2.0 -package common - -import ( - "os" - "testing" -) - -// Skipper will skip this test if we're not in integration mode -func Skipper(t *testing.T) { - if _, found := os.LookupEnv("TEST_KUBECONFIG"); !found { - t.Skip("Skipping integration tests without TEST_KUBECONFIG set") - } -} diff --git a/integration/suites/doc.go b/integration/suites/doc.go deleted file mode 100644 index 1c56f4e8..00000000 --- a/integration/suites/doc.go +++ /dev/null @@ -1 +0,0 @@ -package suites diff --git a/pkg/cmd/build_test.go b/pkg/cmd/build_test.go new file mode 100644 index 00000000..7314d4ee --- /dev/null +++ b/pkg/cmd/build_test.go @@ -0,0 +1,117 @@ +package commands + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +func Test_runBuild(t *testing.T) { + t.Parallel() + + err := runBuild(genericclioptions.IOStreams{}, buildOptions{ + squash: true, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "squash currently") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + quiet: true, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "quiet currently") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + platforms: []string{"acme"}, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unknown operating system") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + secrets: []string{"foo=baddata"}, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unexpected key") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + ssh: []string{"someid=bogus-file-does-not-exist"}, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no such file or directory") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + outputs: []string{"type=local"}, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "dest is required for local output") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + commonOptions: commonOptions{ + exportPush: true, + exportLoad: true, + }, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "push and load") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + outputs: []string{"type=local,dest=out"}, + commonOptions: commonOptions{ + exportPush: true, + }, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "push and ") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + outputs: []string{"type=local,dest=out"}, + commonOptions: commonOptions{ + exportLoad: true, + }, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "load and ") + + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + cacheFrom: []string{"type="}, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "type required") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + cacheTo: []string{"type="}, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "type required") + err = runBuild(genericclioptions.IOStreams{}, buildOptions{ + allow: []string{"garbage"}, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid entitlement") +} + +func Test_listToMap(t *testing.T) { + res := listToMap([]string{"HOME=bogus"}, false) + assert.Len(t, res, 1) + assert.Contains(t, res, "HOME") + assert.Equal(t, res["HOME"], "bogus") + res = listToMap([]string{"HOME"}, true) + assert.Len(t, res, 1) + assert.Contains(t, res, "HOME") +} + +func Test_Complete(t *testing.T) { + cmd := buildCmd(genericclioptions.IOStreams{}, &rootOptions{}) + err := cmd.ParseFlags([]string{ + "--namespace=somenamespace", + "--context=foo", + "--cluster=bar", + "--kubeconfig=baz", + }) + assert.NoError(t, err) + + opts := commonKubeOptions{ + configFlags: &genericclioptions.ConfigFlags{}, + } + err = opts.Complete(cmd, []string{}) + // If no valid contetx is set up, we'll allow this unit-test to pass + if err != nil { + if strings.Contains(err.Error(), "no context is currently set") { + return + } + } + assert.NoError(t, err) +} diff --git a/pkg/driver/driver_test.go b/pkg/driver/driver_test.go new file mode 100644 index 00000000..f23cdcfe --- /dev/null +++ b/pkg/driver/driver_test.go @@ -0,0 +1,105 @@ +package driver + +import ( + "context" + "fmt" + "io" + "net" + "testing" + + "github.com/moby/buildkit/session" + "github.com/stretchr/testify/assert" + "github.com/vmware-tanzu/buildkit-cli-for-kubectl/pkg/imagetools" + "github.com/vmware-tanzu/buildkit-cli-for-kubectl/pkg/progress" +) + +func TestFailFast(t *testing.T) { + t.Parallel() + assert.True(t, failFast(fmt.Errorf("Failed to pull image"))) + assert.True(t, failFast(fmt.Errorf("Error: ErrImagePull"))) + assert.False(t, failFast(fmt.Errorf("retry me"))) + assert.False(t, failFast(nil)) +} + +func TestStatus(t *testing.T) { + t.Parallel() + assert.Equal(t, Inactive.String(), "inactive") + assert.Equal(t, Starting.String(), "starting") + assert.Equal(t, Running.String(), "running") + assert.Equal(t, Stopping.String(), "stopping") + assert.Equal(t, Stopped.String(), "stopped") + assert.Equal(t, (Stopped + Starting).String(), "unknown") +} + +func TestBoot(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + cancel() + _, err := Boot(ctx, &testDriver{}, nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "timed out") + + _, err = Boot(context.Background(), &testDriver{ + infoErr: fmt.Errorf("Info not implemented"), + }, nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Info not implemented") + + _, err = Boot(context.Background(), &testDriver{ + infoRes: &Info{ + Status: Inactive, + }, + infoErr: nil, + bootstrapErr: fmt.Errorf("Failed to pull image"), + }, nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Failed to pull image") + +} + +type testDriver struct { + infoRes *Info + infoErr error + bootstrapErr error +} + +func (d *testDriver) Factory() Factory { + return nil +} +func (d *testDriver) Bootstrap(context.Context, progress.Logger) error { + return d.bootstrapErr +} +func (d *testDriver) Info(context.Context) (*Info, error) { + return d.infoRes, d.infoErr +} +func (d *testDriver) Stop(ctx context.Context, force bool) error { + return fmt.Errorf("not implemented") +} +func (d *testDriver) Rm(ctx context.Context, force bool) error { + return fmt.Errorf("not implemented") +} +func (d *testDriver) Clients(ctx context.Context) (*BuilderClients, error) { + return nil, fmt.Errorf("not implemented") +} +func (d *testDriver) Features() map[Feature]bool { + return nil +} +func (d *testDriver) List(ctx context.Context) ([]Builder, error) { + return nil, fmt.Errorf("not implemented") +} +func (d *testDriver) RuntimeSockProxy(ctx context.Context, name string) (net.Conn, error) { + return nil, fmt.Errorf("not implemented") +} +func (d *testDriver) GetVersion(ctx context.Context) (string, error) { + return "", fmt.Errorf("not implemented") +} + +func (d *testDriver) GetAuthWrapper(string) imagetools.Auth { + return nil +} +func (d *testDriver) GetAuthProvider(secretName string, stderr io.Writer) session.Attachable { + return nil +} +func (d *testDriver) GetAuthHintMessage() string { + return "not implemented" +} diff --git a/pkg/platformutil/parse_test.go b/pkg/platformutil/parse_test.go index 25477589..b16bd71b 100644 --- a/pkg/platformutil/parse_test.go +++ b/pkg/platformutil/parse_test.go @@ -5,7 +5,7 @@ package platformutil import ( "testing" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) func Test_Parse(t *testing.T) { @@ -14,7 +14,28 @@ func Test_Parse(t *testing.T) { "linux/armv7", "linux/amd64,linux/arm64", }) - require.NoError(t, err) - require.NotNil(t, platforms) - require.Len(t, platforms, 3) + assert.NoError(t, err) + assert.NotNil(t, platforms) + assert.Len(t, platforms, 3) + + res := FormatInGroups(platforms) + assert.Len(t, res, 3) + res = Format(platforms) + assert.Len(t, res, 3) + res = Format(nil) + assert.Len(t, res, 0) + + platforms, err = Parse([]string{ + "local", + "local,local", + }) + assert.NoError(t, err) + assert.Len(t, platforms, 3) + deduped := Dedupe(platforms) + assert.Len(t, deduped, 1) + + _, err = Parse([]string{ + "linux/amd64,acme", + }) + assert.Error(t, err) }