Improves(e2e): imrpoves integration tests and to better run locally (#274)

1. refactors basic workflow (move code to common)
2. use different namespace accross test runs to isolate
3. include wait logic in CreateNamespace to allow multiple runs
4. include wait logic in DeleteNamespace to allow multiple runs

This is important since we need more integration tests and some of
these changes should allow `./test/e2e-tests-local.sh` to run on
local clusters (minikube or something other than test-infra) and
correctly behave during mutiple runs.
This commit is contained in:
dr.max 2019-07-23 10:13:34 -07:00 committed by Knative Prow Robot
parent 75a41def4e
commit 9513dedfba
5 changed files with 161 additions and 86 deletions

View File

@ -25,99 +25,80 @@ import (
"gotest.tools/assert"
)
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)
test := NewE2eTest(t)
test.Setup(t)
defer test.Teardown(t)
t.Run("returns no service before running tests", func(t *testing.T) {
testServiceListEmpty(t, k)
test.serviceListEmpty(t)
})
t.Run("create hello service and returns no error", func(t *testing.T) {
testServiceCreate(t, k, "hello")
test.serviceCreate(t, "hello")
})
t.Run("returns valid info about hello service", func(t *testing.T) {
testServiceList(t, k, "hello")
testServiceDescribe(t, k, "hello")
test.serviceList(t, "hello")
test.serviceDescribe(t, "hello")
})
t.Run("update hello service's configuration and returns no error", func(t *testing.T) {
testServiceUpdate(t, k, "hello", []string{"--env", "TARGET=kn", "--port", "8888"})
test.serviceUpdate(t, "hello", []string{"--env", "TARGET=kn", "--port", "8888"})
})
t.Run("create another service and returns no error", func(t *testing.T) {
testServiceCreate(t, k, "svc2")
test.serviceCreate(t, "svc2")
})
t.Run("returns a list of revisions associated with hello and svc2 services", func(t *testing.T) {
testRevisionListForService(t, k, "hello")
testRevisionListForService(t, k, "svc2")
test.revisionListForService(t, "hello")
test.revisionListForService(t, "svc2")
})
t.Run("returns a list of routes associated with hello and svc2 services", func(t *testing.T) {
testRouteList(t, k)
testRouteListWithArgument(t, k, "hello")
test.routeList(t)
test.routeListWithArgument(t, "hello")
})
t.Run("delete hello and svc2 services and returns no error", func(t *testing.T) {
testServiceDelete(t, k, "hello")
testServiceDelete(t, k, "svc2")
test.serviceDelete(t, "hello")
test.serviceDelete(t, "svc2")
})
t.Run("returns no service after completing tests", func(t *testing.T) {
testServiceListEmpty(t, k)
test.serviceListEmpty(t)
})
}
// Private test functions
func testServiceListEmpty(t *testing.T, k kn) {
out, err := k.RunWithOpts([]string{"service", "list"}, runOpts{NoNamespace: false})
func (test *e2eTest) serviceListEmpty(t *testing.T) {
out, err := test.kn.RunWithOpts([]string{"service", "list"}, runOpts{NoNamespace: false})
assert.NilError(t, err)
assert.Check(t, util.ContainsAll(out, "No resources found."))
}
func testServiceCreate(t *testing.T, k kn, serviceName string) {
out, err := k.RunWithOpts([]string{"service", "create",
func (test *e2eTest) serviceCreate(t *testing.T, serviceName string) {
out, err := test.kn.RunWithOpts([]string{"service", "create",
fmt.Sprintf("%s", serviceName),
"--image", KnDefaultTestImage}, runOpts{NoNamespace: false})
assert.NilError(t, err)
assert.Check(t, util.ContainsAll(out, "Service", serviceName, "successfully created in namespace", k.namespace, "OK"))
assert.Check(t, util.ContainsAll(out, "Service", serviceName, "successfully created in namespace", test.kn.namespace, "OK"))
}
func testServiceList(t *testing.T, k kn, serviceName string) {
out, err := k.RunWithOpts([]string{"service", "list", serviceName}, runOpts{NoNamespace: false})
func (test *e2eTest) serviceList(t *testing.T, serviceName string) {
out, err := test.kn.RunWithOpts([]string{"service", "list", serviceName}, runOpts{NoNamespace: false})
assert.NilError(t, err)
expectedOutput := fmt.Sprintf("%s", serviceName)
assert.Check(t, util.ContainsAll(out, expectedOutput))
}
func testRevisionListForService(t *testing.T, k kn, serviceName string) {
out, err := k.RunWithOpts([]string{"revision", "list", "-s", serviceName}, runOpts{NoNamespace: false})
func (test *e2eTest) revisionListForService(t *testing.T, serviceName string) {
out, err := test.kn.RunWithOpts([]string{"revision", "list", "-s", serviceName}, runOpts{NoNamespace: false})
assert.NilError(t, err)
outputLines := strings.Split(out, "\n")
@ -129,8 +110,8 @@ func testRevisionListForService(t *testing.T, k kn, serviceName string) {
}
}
func testServiceDescribe(t *testing.T, k kn, serviceName string) {
out, err := k.RunWithOpts([]string{"service", "describe", serviceName}, runOpts{NoNamespace: false})
func (test *e2eTest) serviceDescribe(t *testing.T, serviceName string) {
out, err := test.kn.RunWithOpts([]string{"service", "describe", serviceName}, runOpts{NoNamespace: false})
assert.NilError(t, err)
expectedOutputHeader := `apiVersion: serving.knative.dev/v1alpha1
@ -139,37 +120,37 @@ metadata:`
expectedOutput := `generation: 1
name: %s
namespace: %s`
expectedOutput = fmt.Sprintf(expectedOutput, serviceName, k.namespace)
expectedOutput = fmt.Sprintf(expectedOutput, serviceName, test.kn.namespace)
assert.Check(t, util.ContainsAll(out, expectedOutputHeader, expectedOutput))
}
func testServiceUpdate(t *testing.T, k kn, serviceName string, args []string) {
out, err := k.RunWithOpts(append([]string{"service", "update", serviceName}, args...), runOpts{NoNamespace: false})
func (test *e2eTest) serviceUpdate(t *testing.T, serviceName string, args []string) {
out, err := test.kn.RunWithOpts(append([]string{"service", "update", serviceName}, args...), runOpts{NoNamespace: false})
assert.NilError(t, err)
expectedOutput := fmt.Sprintf("Service '%s' updated", serviceName)
assert.Check(t, util.ContainsAll(out, expectedOutput))
}
func testRouteList(t *testing.T, k kn) {
out, err := k.RunWithOpts([]string{"route", "list"}, runOpts{})
func (test *e2eTest) routeList(t *testing.T) {
out, err := test.kn.RunWithOpts([]string{"route", "list"}, runOpts{})
assert.NilError(t, err)
expectedHeaders := []string{"NAME", "URL", "AGE", "CONDITIONS", "TRAFFIC"}
assert.Check(t, util.ContainsAll(out, expectedHeaders...))
}
func testRouteListWithArgument(t *testing.T, k kn, routeName string) {
out, err := k.RunWithOpts([]string{"route", "list", routeName}, runOpts{})
func (test *e2eTest) routeListWithArgument(t *testing.T, routeName string) {
out, err := test.kn.RunWithOpts([]string{"route", "list", routeName}, runOpts{})
assert.NilError(t, err)
expectedOutput := fmt.Sprintf("100%% -> %s", routeName)
assert.Check(t, util.ContainsAll(out, routeName, expectedOutput))
}
func testServiceDelete(t *testing.T, k kn, serviceName string) {
out, err := k.RunWithOpts([]string{"service", "delete", serviceName}, runOpts{NoNamespace: false})
func (test *e2eTest) serviceDelete(t *testing.T, serviceName string) {
out, err := test.kn.RunWithOpts([]string{"service", "delete", serviceName}, runOpts{NoNamespace: false})
assert.NilError(t, err)
assert.Check(t, util.ContainsAll(out, "Service", serviceName, "successfully deleted in namespace", k.namespace))
assert.Check(t, util.ContainsAll(out, "Service", serviceName, "successfully deleted in namespace", test.kn.namespace))
}

View File

@ -23,6 +23,7 @@ import (
"regexp"
"strings"
"testing"
"time"
)
type runOpts struct {
@ -35,22 +36,53 @@ type runOpts 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()))
const (
KnDefaultTestImage string = "gcr.io/knative-samples/helloworld-go"
MaxRetries int = 10
RetrySleepDuration time.Duration = 30 * time.Second
)
type e2eTest struct {
env env
kn kn
}
func NewE2eTest(t *testing.T) *e2eTest {
return &e2eTest{
env: buildEnv(t),
}
}
// Setup set up an enviroment for kn integration test returns the Teardown cleanup function
func (test *e2eTest) Setup(t *testing.T) {
test.env.Namespace = fmt.Sprintf("%s%d", test.env.Namespace, namespaceCount)
namespaceCount++
test.kn = kn{t, test.env.Namespace, Logger{}}
test.CreateTestNamespace(t, test.env.Namespace)
}
// Teardown clean up
func (test *e2eTest) Teardown(t *testing.T) {
test.DeleteTestNamespace(t, test.env.Namespace)
}
// CreateTestNamespace creates and tests a namesspace creation invoking kubectl
func (test *e2eTest) CreateTestNamespace(t *testing.T, namespace string) {
logger := Logger{}
expectedOutputRegexp := fmt.Sprintf("namespace?.+%s.+created", namespace)
out, err := createNamespace(t, namespace, MaxRetries, logger)
if err != nil {
logger.Fatalf("Could not create namespace, giving up")
}
// check that last output indeed show 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) {
func (test *e2eTest) DeleteTestNamespace(t *testing.T, namespace string) {
kubectl := kubectl{t, Logger{}}
out, err := kubectl.RunWithOpts([]string{"delete", "namespace", namespace}, runOpts{})
if err != nil {
@ -63,7 +95,61 @@ func DeleteTestNamespace(t *testing.T, namespace string) {
}
}
// WaitForNamespaceDeleted wait until namespace is deleted
func (test *e2eTest) WaitForNamespaceDeleted(t *testing.T, namespace string) {
logger := Logger{}
deleted := checkNamespaceDeleted(t, namespace, MaxRetries, logger)
if !deleted {
t.Fatalf(fmt.Sprintf("Error deleting namespace %s, timed out", namespace))
}
}
// Private functions
func checkNamespaceDeleted(t *testing.T, namespace string, maxRetries int, logger Logger) bool {
kubectlGetNamespace := func() (string, error) {
kubectl := kubectl{t, logger}
return kubectl.RunWithOpts([]string{"get", "namespace"}, runOpts{})
}
retries := 0
for retries < MaxRetries {
output, _ := kubectlGetNamespace()
if !strings.Contains(output, namespace) {
return true
}
retries++
logger.Debugf("Namespace is terminating, waiting %ds, and trying again: %d of %d\n", int(RetrySleepDuration.Seconds()), retries, maxRetries)
time.Sleep(RetrySleepDuration)
}
return true
}
func createNamespace(t *testing.T, namespace string, maxRetries int, logger Logger) (string, error) {
kubectlCreateNamespace := func() (string, error) {
kubectl := kubectl{t, logger}
return kubectl.RunWithOpts([]string{"create", "namespace", namespace}, runOpts{AllowError: true})
}
var (
retries int
err error
out string
)
for retries < maxRetries {
out, err := kubectlCreateNamespace()
if err == nil {
return out, nil
}
retries++
logger.Debugf("Could not create namespace, waiting %ds, and trying again: %d of %d\n", int(RetrySleepDuration.Seconds()), retries, maxRetries)
time.Sleep(RetrySleepDuration)
}
return out, err
}
func runCLIWithOpts(cli string, args []string, opts runOpts, logger Logger) (string, error) {
logger.Debugf("Running '%s'...\n", cmdCLIDesc(cli, args))

View File

@ -26,6 +26,8 @@ type env struct {
const defaultKnE2ENamespace = "kne2etests"
var namespaceCount = 0
func buildEnv(t *testing.T) env {
env := env{
Namespace: os.Getenv("KN_E2E_NAMESPACE"),

View File

@ -25,31 +25,34 @@ import (
)
func TestRevisionWorkflow(t *testing.T) {
teardown := Setup(t)
defer teardown(t)
test := NewE2eTest(t)
test.Setup(t)
defer test.Teardown(t)
t.Run("create hello service and returns no error", func(t *testing.T) {
testServiceCreate(t, k, "hello")
test.serviceCreate(t, "hello")
})
t.Run("delete latest revision from hello service and returns no error", func(t *testing.T) {
testDeleteRevision(t, k, "hello")
test.deleteRevision(t, "hello")
})
t.Run("delete hello service and returns no error", func(t *testing.T) {
testServiceDelete(t, k, "hello")
test.serviceDelete(t, "hello")
})
}
func testDeleteRevision(t *testing.T, k kn, serviceName string) {
revName, err := k.RunWithOpts([]string{"revision", "list", "-o=jsonpath={.items[0].metadata.name}"}, runOpts{})
// Private
func (test *e2eTest) deleteRevision(t *testing.T, serviceName string) {
revName, err := test.kn.RunWithOpts([]string{"revision", "list", "-o=jsonpath={.items[0].metadata.name}"}, runOpts{})
assert.NilError(t, err)
if strings.Contains(revName, "No resources found.") {
t.Errorf("Could not find revision name.")
}
out, err := k.RunWithOpts([]string{"revision", "delete", revName}, runOpts{})
out, err := test.kn.RunWithOpts([]string{"revision", "delete", revName}, runOpts{})
assert.NilError(t, err)
assert.Check(t, util.ContainsAll(out, "Revision", revName, "deleted", "namespace", k.namespace))
assert.Check(t, util.ContainsAll(out, "Revision", revName, "deleted", "namespace", test.kn.namespace))
}

View File

@ -25,50 +25,53 @@ import (
)
func TestServiceOptions(t *testing.T) {
teardown := Setup(t)
defer teardown(t)
test := NewE2eTest(t)
test.Setup(t)
defer test.Teardown(t)
t.Run("create hello service with concurrency options and returns no error", func(t *testing.T) {
testServiceCreateWithOptions(t, k, "hello", []string{"--concurrency-limit", "250", "--concurrency-target", "300"})
test.serviceCreateWithOptions(t, "hello", []string{"--concurrency-limit", "250", "--concurrency-target", "300"})
})
t.Run("returns valid concurrency options for hello service", func(t *testing.T) {
testServiceDescribeConcurrencyLimit(t, k, "hello", "250")
testServiceDescribeConcurrencyTarget(t, k, "hello", "300")
test.serviceDescribeConcurrencyLimit(t, "hello", "250")
test.serviceDescribeConcurrencyTarget(t, "hello", "300")
})
t.Run("update concurrency limit for hello service and returns no error", func(t *testing.T) {
testServiceUpdate(t, k, "hello", []string{"--concurrency-limit", "300"})
test.serviceUpdate(t, "hello", []string{"--concurrency-limit", "300"})
})
t.Run("returns correct concurrency limit for hello service", func(t *testing.T) {
testServiceDescribeConcurrencyLimit(t, k, "hello", "300")
test.serviceDescribeConcurrencyLimit(t, "hello", "300")
})
t.Run("delete hello service and returns no error", func(t *testing.T) {
testServiceDelete(t, k, "hello")
test.serviceDelete(t, "hello")
})
}
func testServiceCreateWithOptions(t *testing.T, k kn, serviceName string, options []string) {
// Private
func (test *e2eTest) serviceCreateWithOptions(t *testing.T, serviceName string, options []string) {
command := []string{"service", "create", serviceName, "--image", KnDefaultTestImage}
command = append(command, options...)
out, err := k.RunWithOpts(command, runOpts{NoNamespace: false})
out, err := test.kn.RunWithOpts(command, runOpts{NoNamespace: false})
assert.NilError(t, err)
assert.Check(t, util.ContainsAll(out, "Service", serviceName, "successfully created in namespace", k.namespace, "OK"))
assert.Check(t, util.ContainsAll(out, "Service", serviceName, "successfully created in namespace", test.kn.namespace, "OK"))
}
func testServiceDescribeConcurrencyLimit(t *testing.T, k kn, serviceName, concurrencyLimit string) {
out, err := k.RunWithOpts([]string{"service", "describe", serviceName}, runOpts{NoNamespace: false})
func (test *e2eTest) serviceDescribeConcurrencyLimit(t *testing.T, serviceName, concurrencyLimit string) {
out, err := test.kn.RunWithOpts([]string{"service", "describe", serviceName}, runOpts{NoNamespace: false})
assert.NilError(t, err)
expectedOutput := fmt.Sprintf("containerConcurrency: %s", concurrencyLimit)
assert.Check(t, util.ContainsAll(out, expectedOutput))
}
func testServiceDescribeConcurrencyTarget(t *testing.T, k kn, serviceName, concurrencyTarget string) {
out, err := k.RunWithOpts([]string{"service", "describe", serviceName}, runOpts{NoNamespace: false})
func (test *e2eTest) serviceDescribeConcurrencyTarget(t *testing.T, serviceName, concurrencyTarget string) {
out, err := test.kn.RunWithOpts([]string{"service", "describe", serviceName}, runOpts{NoNamespace: false})
assert.NilError(t, err)
expectedOutput := fmt.Sprintf("autoscaling.knative.dev/target: \"%s\"", concurrencyTarget)