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
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:
- Get access to client objects
- Make requests against deployed services
- Check Knative Serving resources
- Verify resource state transitions
- Generate boilerplate CRDs
See knative/pkg/test
to:
- Use common test flags
- Output logs
- Emit metrics
- Ensure test cleanup
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.
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.
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(),
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
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, 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.
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 := 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:
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
configuration.go. For kubernetes objects see
kube_checks.go.
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 v1/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
v1testing.IsRevisionReady
:
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
:
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 route.go.
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.