Project: Knative (Orchestration & Management / Scheduling & Orchestration)
File: adding_tests.md

If you are you may need to add or change: Both tests can use our . Reviewers of conformance and e2e tests (i.e. ) 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` so that `go test ./...` can be used to run only , i.e.: ```go // +build e2e ``` In the 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 . The libs in this dir can: See to: Output logs Emit metrics Ensure test cleanup These flags are useful for running against an existing cluster, making use of your existing . By importing `knative.dev/pkg/test` you get access to a global variable called `test.Flags` which holds the values of . ```go imagePath := strings.Join([]string{test.Flags.DockerRepo, image}, "/")) ``` _See ._ To initialize client objects that you can use the command line flags that describe the environment: ```go 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`: ```go _, 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: ```go 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 . 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`: ```go _, err := pkgTest.WaitForEndpointState( clients.KubeClient, logger, updatedRoute.Status.URL.URL(), spoof.IsStatusOK, "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 to determine if the ingress should be used or the domain should be used directly. See . 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. ```go // Error handling elided for brevity, but you know better. client, err := pkgTest.NewSpoofingClient(clients.KubeClient, 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

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 . To poll they use and the return values of the function you provide behave the same as : 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: ```go var revisionName string err := v1testing.WaitForConfigurationState(clients.ServingClient, configName, func(c *v1.Configuration) (bool, error) { if c.Status.LatestCreatedRevisionName != "" { revisionName = c.Status.LatestCreatedRevisionName return true, nil } return false, nil }, "ConfigurationUpdatedWithRevision") ``` v1testing is alias for package `knative.dev/serving/pkg/testing/v1` We also have `Check*` variants of many of these methods with identical signatures, same example: ```go var revisionName string err := v1testing.CheckConfigurationState(clients.ServingClient, configName, func(c *v1.Configuration) (bool, error) { if c.Status.LatestCreatedRevisionName != "" { revisionName = c.Status.LatestCreatedRevisionName return true, nil } return false, nil }) ``` v1testing is alias for package `knative.dev/serving/pkg/testing/v1` _For knative crd state, for example `Config`. You can see the code in . For kubernetes objects see ._ To use the you must provide a function to check the state. Some of the expected transition states (as defined in ) , for example `v1/Revision` state, are expressed in function in . 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 `v1testing.IsRevisionReady`: ```go err := v1testing.WaitForRevisionState(clients.ServingClient, revName, v1testing.IsRevisionReady, "RevisionIsReady") if err != nil { t.Fatalf("The Revision %q did not become ready: %v", revName, err) } ``` v1testing is alias for package `knative.dev/serving/pkg/testing/v1` Once the `Revision` is created, all traffic for a `Route` should be routed to it. This can be polled with `v1testing.AllRouteTrafficAtRevision`: ```go err := v1testing.CheckRouteState(clients.ServingClient, names.Route, v1testing.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 . 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: ```go 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 .