Skip to content

Commit

Permalink
Adds a Golang version of e2e Basic workflow that can execute locally (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
maximilien authored and knative-prow-robot committed Jun 12, 2019
1 parent 0d94947 commit 39d9947
Show file tree
Hide file tree
Showing 9 changed files with 471 additions and 15 deletions.
29 changes: 29 additions & 0 deletions test/e2e-tests-local.sh
Original file line number Diff line number Diff line change
@@ -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'
20 changes: 5 additions & 15 deletions test/e2e-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
126 changes: 126 additions & 0 deletions test/e2e/basic_workflow_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
}
116 changes: 116 additions & 0 deletions test/e2e/common.go
Original file line number Diff line number Diff line change
@@ -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
}
40 changes: 40 additions & 0 deletions test/e2e/e2e.go
Original file line number Diff line number Diff line change
@@ -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)
}
47 changes: 47 additions & 0 deletions test/e2e/env.go
Original file line number Diff line number Diff line change
@@ -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"))
}
}
Loading

0 comments on commit 39d9947

Please sign in to comment.