From 39d994794bdd550ac0a4a3d3083838e22082aa7f Mon Sep 17 00:00:00 2001 From: "dr.max" Date: Tue, 11 Jun 2019 19:49:43 -0700 Subject: [PATCH] Adds a Golang version of e2e Basic workflow that can execute locally (#121) 1. creates a namespace 'kne2etests' (default name, change with env KN_E2E_NAMESPACE) 2. executes kn commands as per Basic workflow doc in said namespace 3. verifies each command's output 4. deletes the 'kne2etests' namespace --- test/e2e-tests-local.sh | 29 ++++++++ test/e2e-tests.sh | 20 ++--- test/e2e/basic_workflow_test.go | 126 ++++++++++++++++++++++++++++++++ test/e2e/common.go | 116 +++++++++++++++++++++++++++++ test/e2e/e2e.go | 40 ++++++++++ test/e2e/env.go | 47 ++++++++++++ test/e2e/kn.go | 40 ++++++++++ test/e2e/kubectl.go | 35 +++++++++ test/e2e/version_test.go | 33 +++++++++ 9 files changed, 471 insertions(+), 15 deletions(-) create mode 100755 test/e2e-tests-local.sh create mode 100644 test/e2e/basic_workflow_test.go create mode 100644 test/e2e/common.go create mode 100644 test/e2e/e2e.go create mode 100644 test/e2e/env.go create mode 100644 test/e2e/kn.go create mode 100644 test/e2e/kubectl.go create mode 100644 test/e2e/version_test.go diff --git a/test/e2e-tests-local.sh b/test/e2e-tests-local.sh new file mode 100755 index 0000000000..b44ed8a485 --- /dev/null +++ b/test/e2e-tests-local.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Copyright 2019 The Knative Authors +# +# 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. + +export PATH=$PWD:$PATH + +dir=$(dirname "${BASH_SOURCE[0]}") +base=$(cd "$dir/.." && pwd) + +# Will create and delete this namespace (used for all tests, modify if you want a different one used) +export KN_E2E_NAMESPACE=kne2etests + +echo "๐Ÿ“‹ Formatting" +go fmt ${base}/test/e2e/... + +echo "๐Ÿงช Testing" +go test ${base}/test/e2e/ -test.v --tags 'e2e' \ No newline at end of file diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh index 659be3f444..dd80f5c464 100755 --- a/test/e2e-tests.sh +++ b/test/e2e-tests.sh @@ -38,25 +38,15 @@ function knative_setup() { start_latest_knative_serving } +# Add local dir to have access to built kn +export PATH=$PATH:${REPO_ROOT_DIR} + # Script entry point. initialize $@ header "Running tests" -./kn service create hello --image gcr.io/knative-samples/helloworld-go -e TARGET=Knative || fail_test -sleep 5 -./kn service get || fail_test -./kn service update hello --env TARGET=kn || fail_test -sleep 3 -./kn revision get || fail_test -./kn service get || fail_test -./kn service create hello --force --image gcr.io/knative-samples/helloworld-go -e TARGET=Awesome || fail_test -./kn service create foo --force --image gcr.io/knative-samples/helloworld-go -e TARGET=foo || fail_test -sleep 5 -./kn revision get || fail_test -./kn service get || fail_test -./kn service describe hello || fail_test -./kn service delete hello || fail_test -./kn service delete foo || fail_test +go_test_e2e ./test/e2e || fail_test + success diff --git a/test/e2e/basic_workflow_test.go b/test/e2e/basic_workflow_test.go new file mode 100644 index 0000000000..d8ca1ad418 --- /dev/null +++ b/test/e2e/basic_workflow_test.go @@ -0,0 +1,126 @@ +// Copyright 2019 The Knative Authors + +// 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. + +// +build e2e + +package e2e + +import ( + "fmt" + "strings" + "testing" +) + +var ( + e env + k kn +) + +const ( + KnDefaultTestImage string = "gcr.io/knative-samples/helloworld-go" +) + +func Setup(t *testing.T) func(t *testing.T) { + e = buildEnv(t) + k = kn{t, e.Namespace, Logger{}} + CreateTestNamespace(t, e.Namespace) + return Teardown +} + +func Teardown(t *testing.T) { + DeleteTestNamespace(t, e.Namespace) +} + +func TestBasicWorkflow(t *testing.T) { + teardown := Setup(t) + defer teardown(t) + + testServiceGetEmpty(t, k) + testServiceCreate(t, k, "hello") + testServiceGet(t, k, "hello") + testServiceDescribe(t, k, "hello") + testServiceDelete(t, k, "hello") + testServiceGetEmpty(t, k) +} + +// Private test functions + +func testServiceGetEmpty(t *testing.T, k kn) { + out, err := k.RunWithOpts([]string{"service", "get"}, runOpts{NoNamespace: false}) + if err != nil { + t.Fatalf(fmt.Sprintf("Error executing 'kn service get' command. Error: %s", err.Error())) + } + + if !strings.Contains(out, "No resources found.") { + t.Fatalf("Expected output 'No resources found.' Instead found:\n%s\n", out) + } +} + +func testServiceCreate(t *testing.T, k kn, serviceName string) { + out, err := k.RunWithOpts([]string{"service", "create", + fmt.Sprintf("%s", serviceName), + "--image", KnDefaultTestImage}, runOpts{NoNamespace: false}) + if err != nil { + t.Fatalf(fmt.Sprintf("Error executing 'kn service create' command. Error: %s", err.Error())) + } + + if !strings.Contains(out, fmt.Sprintf("Service '%s' successfully created in namespace '%s'.", serviceName, k.namespace)) { + t.Fatalf(fmt.Sprintf("Expected to find: Service '%s' successfully created in namespace '%s'. Instead found:\n%s\n", serviceName, k.namespace, out)) + } +} + +func testServiceGet(t *testing.T, k kn, serviceName string) { + out, err := k.RunWithOpts([]string{"service", "get", serviceName}, runOpts{NoNamespace: false}) + if err != nil { + t.Fatalf(fmt.Sprintf("Error executing 'kn service get %s' command. Error: %s", serviceName, err.Error())) + } + + expectedOutput := fmt.Sprintf("%s", serviceName) + if !strings.Contains(out, expectedOutput) { + t.Fatalf("Expected output incorrect, expecting to include:\n%s\n Instead found:\n%s\n", expectedOutput, out) + } +} + +func testServiceDescribe(t *testing.T, k kn, serviceName string) { + out, err := k.RunWithOpts([]string{"service", "describe", serviceName}, runOpts{NoNamespace: false}) + if err != nil { + t.Fatalf(fmt.Sprintf("Error executing 'kn service describe' command. Error: %s", err.Error())) + } + + expectedOutputHeader := `apiVersion: knative.dev/v1alpha1 +kind: Service +metadata:` + if !strings.Contains(out, expectedOutputHeader) { + t.Fatalf(fmt.Sprintf("Expected output incorrect, expecting to include:\n%s\n Instead found:\n%s\n", expectedOutputHeader, out)) + } + + expectedOutput := `generation: 1 + name: %s + namespace: %s` + expectedOutput = fmt.Sprintf(expectedOutput, serviceName, k.namespace) + if !strings.Contains(out, expectedOutput) { + t.Fatalf(fmt.Sprintf("Expected output incorrect, expecting to include:\n%s\n Instead found:\n%s\n", expectedOutput, out)) + } +} + +func testServiceDelete(t *testing.T, k kn, serviceName string) { + out, err := k.RunWithOpts([]string{"service", "delete", serviceName}, runOpts{NoNamespace: false}) + if err != nil { + t.Fatalf(fmt.Sprintf("Error executing 'kn service delete' command. Error: %s", err.Error())) + } + + if !strings.Contains(out, fmt.Sprintf("Service '%s' successfully deleted in namespace '%s'.", serviceName, k.namespace)) { + t.Fatalf(fmt.Sprintf("Expected to find: Service '%s' successfully deleted in namespace '%s'. Instead found:\n%s\n", serviceName, k.namespace, out)) + } +} diff --git a/test/e2e/common.go b/test/e2e/common.go new file mode 100644 index 0000000000..f9aeadd12c --- /dev/null +++ b/test/e2e/common.go @@ -0,0 +1,116 @@ +// Copyright 2019 The Knative Authors + +// 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 e2e + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "regexp" + "strings" + "testing" +) + +type runOpts struct { + NoNamespace bool + AllowError bool + StderrWriter io.Writer + StdoutWriter io.Writer + StdinReader io.Reader + CancelCh chan struct{} + Redact bool +} + +// CreateTestNamespace creates and tests a namesspace creation invoking kubectl +func CreateTestNamespace(t *testing.T, namespace string) { + kubectl := kubectl{t, Logger{}} + out, err := kubectl.RunWithOpts([]string{"create", "namespace", namespace}, runOpts{}) + if err != nil { + t.Fatalf(fmt.Sprintf("Error executing 'kubectl create namespace' command. Error: %s", err.Error())) + } + + expectedOutputRegexp := fmt.Sprintf("namespace?.+%s.+created", namespace) + if !matchRegexp(t, expectedOutputRegexp, out) { + t.Fatalf("Expected output incorrect, expecting to include:\n%s\n Instead found:\n%s\n", expectedOutputRegexp, out) + } +} + +// CreateTestNamespace deletes and tests a namesspace deletion invoking kubectl +func DeleteTestNamespace(t *testing.T, namespace string) { + kubectl := kubectl{t, Logger{}} + out, err := kubectl.RunWithOpts([]string{"delete", "namespace", namespace}, runOpts{}) + if err != nil { + t.Fatalf(fmt.Sprintf("Error executing 'kubectl delete namespace' command. Error: %s", err.Error())) + } + + expectedOutputRegexp := fmt.Sprintf("namespace?.+%s.+deleted", namespace) + if !matchRegexp(t, expectedOutputRegexp, out) { + t.Fatalf("Expected output incorrect, expecting to include:\n%s\n Instead found:\n%s\n", expectedOutputRegexp, out) + } +} + +// Private functions + +func runCLIWithOpts(cli string, args []string, opts runOpts, logger Logger) (string, error) { + logger.Debugf("Running '%s'...\n", cmdCLIDesc(cli, args)) + + var stderr bytes.Buffer + var stdout bytes.Buffer + + cmd := exec.Command(cli, args...) + cmd.Stderr = &stderr + + if opts.CancelCh != nil { + go func() { + select { + case <-opts.CancelCh: + cmd.Process.Signal(os.Interrupt) + } + }() + } + + if opts.StdoutWriter != nil { + cmd.Stdout = opts.StdoutWriter + } else { + cmd.Stdout = &stdout + } + + cmd.Stdin = opts.StdinReader + + err := cmd.Run() + if err != nil { + err = fmt.Errorf("Execution error: stderr: '%s' error: '%s'", stderr.String(), err) + + if !opts.AllowError { + logger.Fatalf("Failed to successfully execute '%s': %v", cmdCLIDesc(cli, args), err) + } + } + + return stdout.String(), err +} + +func cmdCLIDesc(cli string, args []string) string { + return fmt.Sprintf("%s %s", cli, strings.Join(args, " ")) +} + +func matchRegexp(t *testing.T, matchingRegexp, actual string) bool { + matched, err := regexp.MatchString(matchingRegexp, actual) + if err != nil { + t.Fatalf(fmt.Sprintf("Failed to match regexp '%s'. Error: '%s'", matchingRegexp, err.Error())) + } + return matched +} diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go new file mode 100644 index 0000000000..2e3566f99e --- /dev/null +++ b/test/e2e/e2e.go @@ -0,0 +1,40 @@ +// Copyright 2019 The Knative Authors + +// 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 e2e + +import ( + "fmt" + "os" +) + +// Logger default implementation +type Logger struct{} + +// Section prints a message +func (l Logger) Section(msg string, f func()) { + fmt.Printf("==> %s\n", msg) + f() +} + +// Debugf prints a debug message +func (l Logger) Debugf(msg string, args ...interface{}) { + fmt.Printf(msg, args...) +} + +// Fatalf prints a fatal message +func (l Logger) Fatalf(msg string, args ...interface{}) { + fmt.Printf(msg, args...) + os.Exit(1) +} diff --git a/test/e2e/env.go b/test/e2e/env.go new file mode 100644 index 0000000000..7af9fcf7ba --- /dev/null +++ b/test/e2e/env.go @@ -0,0 +1,47 @@ +// Copyright 2019 The Knative Authors + +// 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 e2e + +import ( + "os" + "strings" + "testing" +) + +type env struct { + Namespace string +} + +const defaultKnE2ENamespace = "kne2etests" + +func buildEnv(t *testing.T) env { + env := env{ + Namespace: os.Getenv("KN_E2E_NAMESPACE"), + } + env.validate(t) + return env +} + +func (e *env) validate(t *testing.T) { + errStrs := []string{} + + if e.Namespace == "" { + e.Namespace = defaultKnE2ENamespace + } + + if len(errStrs) > 0 { + t.Fatalf("%s", strings.Join(errStrs, "\n")) + } +} diff --git a/test/e2e/kn.go b/test/e2e/kn.go new file mode 100644 index 0000000000..cdccf0e4a9 --- /dev/null +++ b/test/e2e/kn.go @@ -0,0 +1,40 @@ +// Copyright 2019 The knative Authors + +// 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 e2e + +import ( + "testing" +) + +type kn struct { + t *testing.T + namespace string + l Logger +} + +// Run the 'kn' CLI with args +func (k kn) Run(args []string) string { + out, _ := k.RunWithOpts(args, runOpts{}) + return out +} + +// Run the 'kn' CLI with args and opts +func (k kn) RunWithOpts(args []string, opts runOpts) (string, error) { + if !opts.NoNamespace { + args = append(args, []string{"-n", k.namespace}...) + } + + return runCLIWithOpts("kn", args, opts, k.l) +} diff --git a/test/e2e/kubectl.go b/test/e2e/kubectl.go new file mode 100644 index 0000000000..b48d44d70e --- /dev/null +++ b/test/e2e/kubectl.go @@ -0,0 +1,35 @@ +// Copyright 2019 The Knative Authors + +// 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 e2e + +import ( + "testing" +) + +type kubectl struct { + t *testing.T + l Logger +} + +// Run the 'kubectl' CLI with args +func (k kubectl) Run(args []string) string { + out, _ := k.RunWithOpts(args, runOpts{}) + return out +} + +// Run the 'kubectl' CLI with args and opts +func (k kubectl) RunWithOpts(args []string, opts runOpts) (string, error) { + return runCLIWithOpts("kubectl", args, opts, k.l) +} diff --git a/test/e2e/version_test.go b/test/e2e/version_test.go new file mode 100644 index 0000000000..0952c8b71f --- /dev/null +++ b/test/e2e/version_test.go @@ -0,0 +1,33 @@ +// Copyright 2019 The Knative Authors + +// 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. + +// +build e2e + +package e2e + +import ( + "strings" + "testing" +) + +func TestVersion(t *testing.T) { + env := buildEnv(t) + kn := kn{t, env.Namespace, Logger{}} + + out, _ := kn.RunWithOpts([]string{"version"}, runOpts{NoNamespace: true}) + + if !strings.Contains(out, "Version") { + t.Fatalf("Expected to find client version") + } +}