Skip to content

Latest commit

 

History

History
287 lines (219 loc) · 9.22 KB

adding_tests.md

File metadata and controls

287 lines (219 loc) · 9.22 KB

Adding tests

If you are developing knative you may need to add or change:

Both tests can use our test library.

Reviewers of conformance and e2e tests (i.e. OWNERS) are responsible for the style and quality of the resulting tests. In order to not discourage contributions, when style change are required, the reviewers can make the changes themselves.

All e2e and conformance tests must be marked with the e2e build constraint so that go test ./... can be used to run only the unit tests, i.e.:

// +build e2e

Test library

In the test dir you will find several libraries in the test package you can use in your tests.

This library exists partially in this directory and partially in knative/pkg/test.

The libs in this dir can:

See knative/pkg/test to:

Use common test flags

These flags are useful for running against an existing cluster, making use of your existing environment setup.

By importing knative.dev/pkg/test you get access to a global variable called test.Flags which holds the values of the command line flags.

imagePath := strings.Join([]string{test.Flags.DockerRepo, image}, "/"))

See e2e_flags.go.

Get access to client objects

To initialize client objects that you can use the command line flags that describe the environment:

import (
	testing

	knative.dev/serving/test
	pkgTest "knative.dev/pkg/test"
)

func Setup(t *testing.T) *test.Clients {
	clients, err := test.NewClients(pkgTest.Flags.Kubeconfig, pkgTest.Flags.Cluster, namespaceName)
	if err != nil {
		t.Fatalf("Couldn't initialize clients: %v", err)
	}
	return clients
}

The Clients struct contains initialized clients for accessing:

  • Kubernetes objects
  • Services
  • Routes
  • Configurations
  • Revisions
  • Knative ingress
  • ServerlessServices
  • Istio objects

For example, to create a Route:

_, err = clients.ServingClient.Routes.Create(v1test.Route(
	test.ResourceNames{
		Route:  routeName,
		Config: configName,
	}))

v1test is alias for package knative.dev/serving/pkg/testing/v1

And you can use the client to clean up Route and Configuration resources created by your test:

import "knative.dev/serving/test"

func tearDown(clients *test.Clients) {
	if clients != nil {
		clients.ServingClient.Routes.Delete(routeName, nil)
		clients.ServingClient.Configs.Delete(configName, nil)
	}
}

See clients.go.

Make requests against deployed services

After deploying (i.e. creating a Route and a Configuration) an endpoint will not be ready to serve requests right away. To poll a deployed endpoint and wait for it to be in the state you want it to be in (or timeout) use WaitForEndpointState by importing knative.dev/pkg/test with alias pkgTest:

_, err := pkgTest.WaitForEndpointState(
	clients.KubeClient,
	logger,
	updatedRoute.Status.URL.URL(),
	pkgTest.EventuallyMatchesBody(expectedText),
	"SomeDescription",
	test.ServingFlags.ResolvableDomain)
if err != nil {
	t.Fatalf("The endpoint for Route %s at domain %s didn't serve the expected text \"%s\": %v", routeName, updatedRoute.Status.Domain, expectedText, err)
}

This function makes use of the environment flag resolvableDomain to determine if the ingress should be used or the domain should be used directly.

See request.go.

If you need more low-level access to the http request or response against a deployed service, you can directly use the SpoofingClient that WaitForEndpointState wraps.

// Error handling elided for brevity, but you know better.
client, err := pkgTest.NewSpoofingClient(clients.KubeClient.Kube, logger, route.Status.Domain, test.ServingFlags.ResolvableDomain,test.AddRootCAtoTransport(t.Logf, clients, test.ServingFlags.Https))
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s", route.Status.Domain), nil)

// Single request.
resp, err := client.Do(req)

// Polling until we meet some condition.
resp, err := client.Poll(req, test.BodyMatches(expectedText))

See spoof.go.

Check Knative Serving resources

After creating Knative Serving resources or making changes to them, you will need to wait for the system to realize those changes. You can use the Knative Serving CRD check and polling methods to check the resources are either in or reach the desired state.

The WaitFor* functions use the kubernetes wait package. To poll they use PollImmediate and the return values of the function you provide behave the same as ConditionFunc: a bool to indicate if the function should stop or continue polling, and an error to indicate if there has been an error.

For example, you can poll a Configuration object to find the name of the Revision that was created for it:

var revisionName string
err := v1alpha1testing.WaitForConfigurationState(clients.ServingClient, configName, func(c *v1alpha1.Configuration) (bool, error) {
	if c.Status.LatestCreatedRevisionName != "" {
		revisionName = c.Status.LatestCreatedRevisionName
		return true, nil
	}
	return false, nil
}, "ConfigurationUpdatedWithRevision")

v1alpha1testing is alias for package knative.dev/serving/pkg/testing/v1alpha1

We also have Check* variants of many of these methods with identical signatures, same example:

var revisionName string
err := v1alpha1testing.CheckConfigurationState(clients.ServingClient, configName, func(c *v1alpha1.Configuration) (bool, error) {
	if c.Status.LatestCreatedRevisionName != "" {
		revisionName = c.Status.LatestCreatedRevisionName
		return true, nil
	}
	return false, nil
})

v1alpha1testing is alias for package knative.dev/serving/pkg/testing/v1alpha1

For knative crd state, for example Config. You can see the code in configuration.go. For kubernetes objects see kube_checks.go.

Verify resource state transitions

To use the check functions you must provide a function to check the state. Some of the expected transition states (as defined in the Knative Serving spec) , for example v1alpha1/Revision state, are expressed in function in revision.go.

For example when a Revision has been created, the system will start the resources required to actually serve it, and then the Revision object will be updated to indicate it is ready. This can be polled with v1alpha1testing.IsRevisionReady:

err := v1alpha1testing.WaitForRevisionState(clients.ServingAlphaClient, revName, v1alpha1testing.IsRevisionReady, "RevisionIsReady")
if err != nil {
	t.Fatalf("The Revision %q did not become ready: %v", revName, err)
}

v1alpha1testing is alias for package knative.dev/serving/pkg/testing/v1alpha1

Once the Revision is created, all traffic for a Route should be routed to it. This can be polled with v1alpha1testing.AllRouteTrafficAtRevision:

err := v1alpha1testing.CheckRouteState(clients.ServingAlphaClient, names.Route, v1alpha1testing.AllRouteTrafficAtRevision(names))
if err != nil {
	t.Fatalf("The Route %s was not updated to route traffic to the Revision %s: %v", names.Route, names.Revision, err)
}

See route.go.

Generate boilerplate CRDs

Your tests will probably need to create Route and Configuration objects. You can use the existing boilerplate to describe them.

You can also use the function AppendRandomString to create a random name for your crd so that your tests can use unique names each time they run.

For example to create a Configuration object that uses a certain docker image with a randomized name:

func TestSomeAwesomeFeature(t *testing.T) {
	var names test.ResourceNames
	names.Config := test.ObjectNameForTest(t)
	_, err := clients.ServingClient.Create(test.Configuration(namespaceName, names, imagePath))
	if err != nil {
		// handle error case
	}
	// more testing
}

test is package knative.dev/serving/test

Please expand these functions as more use cases are tested.

See crd.go.