Skip to main content

Getting started

Install​

go get github.com/yyewolf/petri@v0.37

petri needs Go 1.26 or newer, the same as Kubernetes 1.37.

Pick the petri minor that matches your k8s.io/client-go minor. A test binary can link only one API server, and it is built from the same Kubernetes release as your client libraries:

petriKubernetesk8s.io/client-go
v0.37.x1.37v0.37.x

controller-runtime works with petri as it is: pass cp.Config() to ctrl.NewManager. The optional sub-module adds a drop-in replacement for envtest and a few test helpers; see controller-runtime. To use it:

go get github.com/yyewolf/petri/controllerruntime@v0.37

A first test​

quickstart_test.go
package quickstart_test

import (
"testing"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/yyewolf/petri"
)

func TestDeploymentIsReconciled(t *testing.T) {
t.Parallel()

cp := petri.Start(t) // the control plane shared by this test binary
ns := cp.Namespace(t) // a fresh namespace, deleted when the test ends
cs := cp.Client() // an admin clientset; cp.Config() gives a *rest.Config
cp.EnableControllers(t, petri.DeploymentController)

labels := map[string]string{"app": "web"}
deployments := cs.AppsV1().Deployments(ns)
_, err := deployments.Create(t.Context(), &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "web"},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: labels},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: labels},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Name: "web", Image: "nginx"}},
},
},
},
}, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}

// The Deployment controller has seen the Deployment once it reports the
// generation it acted on.
petri.WaitFor(t, deployments, "web", func(d *appsv1.Deployment) (bool, error) {
return d != nil && d.Status.ObservedGeneration == d.Generation, nil
})
}
  • petri.Start(t) returns the control plane that every test in the binary shares, and starts it on first use. You set nothing up in TestMain and tear nothing down.
  • cp.Namespace(t) gives the test its own namespace, so parallel tests do not see each other's objects. The namespace is deleted when the test ends.
  • cp.Client() and cp.Config() give admin access, like envtest's config.
  • The namespace controller and the garbage collector always run. A test enables any other controller it needs, such as the Deployment controller here, with EnableControllers.
  • petri.WaitFor watches the object until the condition holds. It fails the test after 30 seconds.

Run it​

go test ./...

The first build compiles the API server and takes a few minutes. Later runs use Go's build cache. Builds with -race need much more memory than with envtest, because the race detector instruments the API server too; see resource usage.