From bf5d64148660a986feb19c1eba079b3753c75832 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Fri, 7 Jan 2022 00:37:17 +0100 Subject: [PATCH] refactor komega package rename Matcher to komega since it's not really a matcher komega.With... methods now return copies allow to specify a Gomega instance to use --- go.mod | 4 +- go.sum | 10 +- pkg/envtest/komega/default.go | 85 +++++++++++ pkg/envtest/komega/interfaces.go | 78 ++++++---- pkg/envtest/komega/komega.go | 128 ++++++++++++++++ pkg/envtest/komega/komega_test.go | 139 ++++++++++++++++++ pkg/envtest/komega/matcher.go | 236 ------------------------------ pkg/envtest/komega/transforms.go | 52 ------- 8 files changed, 412 insertions(+), 320 deletions(-) create mode 100644 pkg/envtest/komega/default.go create mode 100644 pkg/envtest/komega/komega.go create mode 100644 pkg/envtest/komega/komega_test.go delete mode 100644 pkg/envtest/komega/matcher.go delete mode 100644 pkg/envtest/komega/transforms.go diff --git a/go.mod b/go.mod index c843d2031f..5d61be77bf 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,12 @@ require ( github.com/go-logr/logr v1.2.0 github.com/go-logr/zapr v1.2.0 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.17.0 + github.com/onsi/gomega v1.18.1 github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_model v0.2.0 go.uber.org/goleak v1.1.12 go.uber.org/zap v1.19.1 - golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac gomodules.xyz/jsonpatch/v2 v2.2.0 k8s.io/api v0.23.0 diff --git a/go.sum b/go.sum index 1872ab8ce0..5d30f92fa6 100644 --- a/go.sum +++ b/go.sum @@ -233,6 +233,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -353,11 +354,14 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -675,8 +679,8 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 h1:M69LAlWZCshgp0QSzyDcSsSIejIEeuaCVpmwcKwyLMk= -golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/pkg/envtest/komega/default.go b/pkg/envtest/komega/default.go new file mode 100644 index 0000000000..f7710bb854 --- /dev/null +++ b/pkg/envtest/komega/default.go @@ -0,0 +1,85 @@ +package komega + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// defaultK is the Komega used by the package global functions. +var defaultK = &komega{} + +// SetDefaultClient sets the client used by the package global functions. +func SetDefaultClient(c client.Client) { + defaultK = &komega{client: c} +} + +func checkDefaultClient() { + if defaultK.client == nil { + panic("Default Komega's client is not set. Use WithDefaultClient to set it.") + } +} + +// Get returns a function that fetches a resource and returns the occurring error. +// It can be used with gomega.Eventually() like this +// deployment := appsv1.Deployment{ ... } +// gomega.Eventually(komega.Get(&deployment)).To(gomega.Succeed()) +// By calling the returned function directly it can also be used with gomega.Expect(komega.Get(...)()).To(...) +func Get(obj client.Object) func() error { + checkDefaultClient() + return defaultK.Get(obj) +} + +// List returns a function that lists resources and returns the occurring error. +// It can be used with gomega.Eventually() like this +// deployments := v1.DeploymentList{ ... } +// gomega.Eventually(k.List(&deployments)).To(gomega.Succeed()) +// By calling the returned function directly it can also be used as gomega.Expect(k.List(...)()).To(...) +func List(obj client.ObjectList, opts ...client.ListOption) func() error { + checkDefaultClient() + return defaultK.List(obj, opts...) +} + +// Update returns a function that fetches a resource, applies the provided update function and then updates the resource. +// It can be used with gomega.Eventually() like this: +// deployment := appsv1.Deployment{ ... } +// gomega.Eventually(k.Update(&deployment, func (o client.Object) { +// deployment.Spec.Replicas = 3 +// return &deployment +// })).To(gomega.Scucceed()) +// By calling the returned function directly it can also be used as gomega.Expect(k.Update(...)()).To(...) +func Update(obj client.Object, f UpdateFunc, opts ...client.UpdateOption) func() error { + checkDefaultClient() + return defaultK.Update(obj, f, opts...) +} + +// UpdateStatus returns a function that fetches a resource, applies the provided update function and then updates the resource's status. +// It can be used with gomega.Eventually() like this: +// deployment := appsv1.Deployment{ ... } +// gomega.Eventually(k.Update(&deployment, func (o client.Object) { +// deployment.Status.AvailableReplicas = 1 +// return &deployment +// })).To(gomega.Scucceed()) +// By calling the returned function directly it can also be used as gomega.Expect(k.UpdateStatus(...)()).To(...) +func UpdateStatus(obj client.Object, f UpdateFunc, opts ...client.UpdateOption) func() error { + checkDefaultClient() + return defaultK.UpdateStatus(obj, f, opts...) +} + +// Object returns a function that fetches a resource and returns the object. +// It can be used with gomega.Eventually() like this: +// deployment := appsv1.Deployment{ ... } +// gomega.Eventually(k.Object(&deployment)).To(HaveField("Spec.Replicas", gomega.Equal(pointer.Int32(3)))) +// By calling the returned function directly it can also be used as gomega.Expect(k.Object(...)()).To(...) +func Object(obj client.Object) func() (client.Object, error) { + checkDefaultClient() + return defaultK.Object(obj) +} + +// ObjectList returns a function that fetches a resource and returns the object. +// It can be used with gomega.Eventually() like this: +// deployments := appsv1.DeploymentList{ ... } +// gomega.Eventually(k.ObjectList(&deployments)).To(HaveField("Items", HaveLen(1))) +// By calling the returned function directly it can also be used as gomega.Expect(k.ObjectList(...)()).To(...) +func ObjectList(obj client.ObjectList, opts ...client.ListOption) func() (client.ObjectList, error) { + checkDefaultClient() + return defaultK.ObjectList(obj, opts...) +} diff --git a/pkg/envtest/komega/interfaces.go b/pkg/envtest/komega/interfaces.go index 8b355637a0..fb08fcf594 100644 --- a/pkg/envtest/komega/interfaces.go +++ b/pkg/envtest/komega/interfaces.go @@ -18,41 +18,65 @@ package komega import ( "context" - "time" - "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) // Komega is the root interface that the Matcher implements. type Komega interface { - KomegaAsync - KomegaSync - WithContext(context.Context) Komega -} + // Get returns a function that fetches a resource and returns the occurring error. + // It can be used with gomega.Eventually() like this + // deployment := appsv1.Deployment{ ... } + // gomega.Eventually(k.Get(&deployment)).To(gomega.Succeed()) + // By calling the returned function directly it can also be used with gomega.Expect(k.Get(...)()).To(...) + Get(client.Object) func() error -// KomegaSync is the interface for any sync assertions that -// the matcher implements. -type KomegaSync interface { - Create(client.Object, ...client.CreateOption) gomega.GomegaAssertion - Delete(client.Object, ...client.DeleteOption) gomega.GomegaAssertion - WithExtras(...interface{}) KomegaSync -} + // List returns a function that lists resources and returns the occurring error. + // It can be used with gomega.Eventually() like this + // deployments := v1.DeploymentList{ ... } + // gomega.Eventually(k.List(&deployments)).To(gomega.Succeed()) + // By calling the returned function directly it can also be used as gomega.Expect(k.List(...)()).To(...) + List(client.ObjectList, ...client.ListOption) func() error + + // Update returns a function that fetches a resource, applies the provided update function and then updates the resource. + // It can be used with gomega.Eventually() like this: + // deployment := appsv1.Deployment{ ... } + // gomega.Eventually(k.Update(&deployment, func (o client.Object) { + // deployment.Spec.Replicas = 3 + // return &deployment + // })).To(gomega.Scucceed()) + // By calling the returned function directly it can also be used as gomega.Expect(k.Update(...)()).To(...) + Update(client.Object, UpdateFunc, ...client.UpdateOption) func() error -// KomegaAsync is the interface for any async assertions that -// the matcher implements. -type KomegaAsync interface { - Consistently(runtime.Object, ...client.ListOption) gomega.AsyncAssertion - Eventually(runtime.Object, ...client.ListOption) gomega.AsyncAssertion - Get(client.Object) gomega.AsyncAssertion - List(client.ObjectList, ...client.ListOption) gomega.AsyncAssertion - Update(client.Object, UpdateFunc, ...client.UpdateOption) gomega.AsyncAssertion - UpdateStatus(client.Object, UpdateFunc, ...client.UpdateOption) gomega.AsyncAssertion - WithTimeout(time.Duration) KomegaAsync - WithPollInterval(time.Duration) KomegaAsync + // UpdateStatus returns a function that fetches a resource, applies the provided update function and then updates the resource's status. + // It can be used with gomega.Eventually() like this: + // deployment := appsv1.Deployment{ ... } + // gomega.Eventually(k.Update(&deployment, func (o client.Object) { + // deployment.Status.AvailableReplicas = 1 + // return &deployment + // })).To(gomega.Scucceed()) + // By calling the returned function directly it can also be used as gomega.Expect(k.UpdateStatus(...)()).To(...) + UpdateStatus(client.Object, UpdateFunc, ...client.UpdateOption) func() error + + // Object returns a function that fetches a resource and returns the object. + // It can be used with gomega.Eventually() like this: + // deployment := appsv1.Deployment{ ... } + // gomega.Eventually(k.Object(&deployment)).To(HaveField("Spec.Replicas", gomega.Equal(pointer.Int32(3)))) + // By calling the returned function directly it can also be used as gomega.Expect(k.Object(...)()).To(...) + Object(client.Object) func() (client.Object, error) + + // ObjectList returns a function that fetches a resource and returns the object. + // It can be used with gomega.Eventually() like this: + // deployments := appsv1.DeploymentList{ ... } + // gomega.Eventually(k.ObjectList(&deployments)).To(HaveField("Items", HaveLen(1))) + // By calling the returned function directly it can also be used as gomega.Expect(k.ObjectList(...)()).To(...) + ObjectList(client.ObjectList, ...client.ListOption) func() (client.ObjectList, error) + + // WithClient returns a copy that uses the given client. + WithClient(client.Client) Komega + // WithContext returns a copy that uses the given context. + WithContext(context.Context) Komega } -// UpdateFunc modifies the object fetched from the API server before sending -// the update +// UpdateFunc receives an object and expects a modified version of it to be returned. type UpdateFunc func(client.Object) client.Object diff --git a/pkg/envtest/komega/komega.go b/pkg/envtest/komega/komega.go new file mode 100644 index 0000000000..943d8b8e23 --- /dev/null +++ b/pkg/envtest/komega/komega.go @@ -0,0 +1,128 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package komega + +import ( + "context" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// komega is a collection of utilites for writing tests involving a mocked +// Kubernetes API. +type komega struct { + ctx context.Context + client client.Client +} + +var _ Komega = &komega{} + +// New creates a new Komega instance with the given client. +func New(c client.Client) Komega { + return &komega{ + client: c, + } +} + +// WithContext returns a copy that uses the given context. +func (k komega) WithContext(ctx context.Context) Komega { + k.ctx = ctx + return &k +} + +// WithClient returns a copy that uses the given client. +func (k komega) WithClient(c client.Client) Komega { + k.client = c + return &k +} + +// context returns the matcher context if one has been set or context.Background() otherwise. +func (k *komega) context() context.Context { + if k.ctx == nil { + return context.Background() + } + return k.ctx +} + +// Get returns a function that fetches a resource and returns the occurring error. +func (k *komega) Get(obj client.Object) func() error { + key := types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + } + return func() error { + return k.client.Get(k.context(), key, obj) + } +} + +// List returns a function that lists resources and returns the occurring error. +func (k *komega) List(obj client.ObjectList, opts ...client.ListOption) func() error { + return func() error { + return k.client.List(k.context(), obj, opts...) + } +} + +// Update returns a function that fetches a resource, applies the provided update function and then updates the resource. +func (k *komega) Update(obj client.Object, updateFunc UpdateFunc, opts ...client.UpdateOption) func() error { + key := types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + } + return func() error { + err := k.client.Get(k.context(), key, obj) + if err != nil { + return err + } + return k.client.Update(k.context(), updateFunc(obj), opts...) + } +} + +// UpdateStatus returns a function that fetches a resource, applies the provided update function and then updates the resource's status. +func (k *komega) UpdateStatus(obj client.Object, updateFunc UpdateFunc, opts ...client.UpdateOption) func() error { + key := types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + } + return func() error { + err := k.client.Get(k.context(), key, obj) + if err != nil { + return err + } + return k.client.Status().Update(k.context(), updateFunc(obj), opts...) + } +} + +// Object returns a function that fetches a resource and returns the object. +func (k *komega) Object(obj client.Object) func() (client.Object, error) { + key := types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + } + return func() (client.Object, error) { + err := k.client.Get(k.context(), key, obj) + return obj, err + } +} + +// ObjectList returns a function that fetches a resource and returns the object. +func (k *komega) ObjectList(obj client.ObjectList, opts ...client.ListOption) func() (client.ObjectList, error) { + return func() (client.ObjectList, error) { + err := k.client.List(k.context(), obj, opts...) + return obj, err + } +} diff --git a/pkg/envtest/komega/komega_test.go b/pkg/envtest/komega/komega_test.go new file mode 100644 index 0000000000..20dd87afa7 --- /dev/null +++ b/pkg/envtest/komega/komega_test.go @@ -0,0 +1,139 @@ +package komega + +import ( + "testing" + + . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + + "sigs.k8s.io/controller-runtime/pkg/client" + fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func exampleDeployment() *appsv1.Deployment { + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: appsv1.DeploymentSpec{ + Replicas: pointer.Int32(5), + }, + } +} + +func createFakeClient() client.Client { + return fakeclient.NewClientBuilder(). + WithObjects(exampleDeployment()). + Build() +} + +func TestGet(t *testing.T) { + g := NewWithT(t) + + fc := createFakeClient() + k := New(fc) + + fetched := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + } + g.Eventually(k.Get(&fetched)).Should(Succeed()) + + g.Expect(*fetched.Spec.Replicas).To(BeEquivalentTo(5)) +} + +func TestList(t *testing.T) { + g := NewWithT(t) + + fc := createFakeClient() + k := New(fc) + + list := appsv1.DeploymentList{} + g.Eventually(k.List(&list)).Should(Succeed()) + + g.Expect(list.Items).To(HaveLen(1)) + depl := exampleDeployment() + g.Expect(list.Items[0]).To(And( + HaveField("ObjectMeta.Name", Equal(depl.ObjectMeta.Name)), + HaveField("ObjectMeta.Namespace", Equal(depl.ObjectMeta.Namespace)), + )) +} + +func TestUpdate(t *testing.T) { + g := NewWithT(t) + + fc := createFakeClient() + k := New(fc) + + updateDeployment := appsv1.Deployment{ + ObjectMeta: exampleDeployment().ObjectMeta, + } + g.Eventually(k.Update(&updateDeployment, func(o client.Object) client.Object { + updateDeployment.Annotations = map[string]string{"updated": "true"} + return &updateDeployment + })).Should(Succeed()) + + fetched := appsv1.Deployment{ + ObjectMeta: exampleDeployment().ObjectMeta, + } + g.Expect(k.Object(&fetched)()).To(HaveField("ObjectMeta.Annotations", HaveKeyWithValue("updated", "true"))) +} + +func TestUpdateStatus(t *testing.T) { + g := NewWithT(t) + + fc := createFakeClient() + k := New(fc) + + updateDeployment := appsv1.Deployment{ + ObjectMeta: exampleDeployment().ObjectMeta, + } + g.Eventually(k.UpdateStatus(&updateDeployment, func(o client.Object) client.Object { + updateDeployment.Status.AvailableReplicas = 1 + return &updateDeployment + })).Should(Succeed()) + + fetched := appsv1.Deployment{ + ObjectMeta: exampleDeployment().ObjectMeta, + } + g.Expect(k.Object(&fetched)()).To(HaveField("Status.AvailableReplicas", BeEquivalentTo(1))) +} + +func TestObject(t *testing.T) { + g := NewWithT(t) + + fc := createFakeClient() + k := New(fc) + + fetched := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + } + g.Eventually(k.Object(&fetched)).Should(And( + Not(BeNil()), + HaveField("Spec.Replicas", Equal(pointer.Int32(5))), + )) +} + +func TestObjectList(t *testing.T) { + g := NewWithT(t) + + fc := createFakeClient() + k := New(fc) + + list := appsv1.DeploymentList{} + g.Eventually(k.ObjectList(&list)).Should(And( + Not(BeNil()), + HaveField("Items", And( + HaveLen(1), + ContainElement(HaveField("Spec.Replicas", Equal(pointer.Int32(5)))), + )), + )) +} diff --git a/pkg/envtest/komega/matcher.go b/pkg/envtest/komega/matcher.go deleted file mode 100644 index 4f835501aa..0000000000 --- a/pkg/envtest/komega/matcher.go +++ /dev/null @@ -1,236 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package komega - -import ( - "context" - "time" - - "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// Matcher has Gomega Matchers that use the controller-runtime client. -type Matcher struct { - Client client.Client - ctx context.Context - extras []interface{} - timeout time.Duration - pollInterval time.Duration -} - -// WithContext sets the context to be used for the underlying client -// during assertions. -func (m *Matcher) WithContext(ctx context.Context) Komega { - m.ctx = ctx - return m -} - -// context returns the matcher context if one has been set. -// Else it returns the context.TODO(). -func (m *Matcher) context() context.Context { - if m.ctx == nil { - return context.TODO() - } - return m.ctx -} - -// WithExtras sets extra arguments for sync assertions. -// Any extras passed will be expected to be nil during assertion. -func (m *Matcher) WithExtras(extras ...interface{}) KomegaSync { - m.extras = extras - return m -} - -// WithTimeout sets the timeout for any async assertions. -func (m *Matcher) WithTimeout(timeout time.Duration) KomegaAsync { - m.timeout = timeout - return m -} - -// WithPollInterval sets the poll interval for any async assertions. -// Note: This will only work if an explicit timeout has been set with WithTimeout. -func (m *Matcher) WithPollInterval(pollInterval time.Duration) KomegaAsync { - m.pollInterval = pollInterval - return m -} - -// intervals constructs the intervals for async assertions. -// If no timeout is set, the list will be empty. -func (m *Matcher) intervals() []interface{} { - if m.timeout == 0 { - return []interface{}{} - } - out := []interface{}{m.timeout} - if m.pollInterval != 0 { - out = append(out, m.pollInterval) - } - return out -} - -// Create creates the object on the API server. -func (m *Matcher) Create(obj client.Object, opts ...client.CreateOption) gomega.GomegaAssertion { - err := m.Client.Create(m.context(), obj, opts...) - return gomega.Expect(err, m.extras...) -} - -// Delete deletes the object from the API server. -func (m *Matcher) Delete(obj client.Object, opts ...client.DeleteOption) gomega.GomegaAssertion { - err := m.Client.Delete(m.context(), obj, opts...) - return gomega.Expect(err, m.extras...) -} - -// Update udpates the object on the API server by fetching the object -// and applying a mutating UpdateFunc before sending the update. -func (m *Matcher) Update(obj client.Object, fn UpdateFunc, opts ...client.UpdateOption) gomega.GomegaAsyncAssertion { - key := types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - } - update := func() error { - err := m.Client.Get(m.context(), key, obj) - if err != nil { - return err - } - return m.Client.Update(m.context(), fn(obj), opts...) - } - return gomega.Eventually(update, m.intervals()...) -} - -// UpdateStatus udpates the object's status subresource on the API server by -// fetching the object and applying a mutating UpdateFunc before sending the -// update. -func (m *Matcher) UpdateStatus(obj client.Object, fn UpdateFunc, opts ...client.UpdateOption) gomega.GomegaAsyncAssertion { - key := types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - } - update := func() error { - err := m.Client.Get(m.context(), key, obj) - if err != nil { - return err - } - return m.Client.Status().Update(m.context(), fn(obj), opts...) - } - return gomega.Eventually(update, m.intervals()...) -} - -// Get gets the object from the API server. -func (m *Matcher) Get(obj client.Object) gomega.GomegaAsyncAssertion { - key := types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - } - get := func() error { - return m.Client.Get(m.context(), key, obj) - } - return gomega.Eventually(get, m.intervals()...) -} - -// List gets the list object from the API server. -func (m *Matcher) List(obj client.ObjectList, opts ...client.ListOption) gomega.GomegaAsyncAssertion { - list := func() error { - return m.Client.List(m.context(), obj, opts...) - } - return gomega.Eventually(list, m.intervals()...) -} - -// Consistently continually gets the object from the API for comparison. -// It can be used to check for either List types or regular Objects. -func (m *Matcher) Consistently(obj runtime.Object, opts ...client.ListOption) gomega.GomegaAsyncAssertion { - // If the object is a list, return a list - if o, ok := obj.(client.ObjectList); ok { - return m.consistentlyList(o, opts...) - } - if o, ok := obj.(client.Object); ok { - return m.consistentlyObject(o) - } - //Should not get here - panic("Unknown object.") -} - -// consistentlyclient.Object gets an individual object from the API server. -func (m *Matcher) consistentlyObject(obj client.Object) gomega.GomegaAsyncAssertion { - key := types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - } - get := func() client.Object { - err := m.Client.Get(m.context(), key, obj) - if err != nil { - panic(err) - } - return obj - } - return gomega.Consistently(get, m.intervals()...) -} - -// consistentlyList gets an list of objects from the API server. -func (m *Matcher) consistentlyList(obj client.ObjectList, opts ...client.ListOption) gomega.GomegaAsyncAssertion { - list := func() client.ObjectList { - err := m.Client.List(m.context(), obj, opts...) - if err != nil { - panic(err) - } - return obj - } - return gomega.Consistently(list, m.intervals()...) -} - -// Eventually continually gets the object from the API for comparison. -// It can be used to check for either List types or regular Objects. -func (m *Matcher) Eventually(obj runtime.Object, opts ...client.ListOption) gomega.GomegaAsyncAssertion { - // If the object is a list, return a list - if o, ok := obj.(client.ObjectList); ok { - return m.eventuallyList(o, opts...) - } - if o, ok := obj.(client.Object); ok { - return m.eventuallyObject(o) - } - //Should not get here - panic("Unknown object.") -} - -// eventuallyObject gets an individual object from the API server. -func (m *Matcher) eventuallyObject(obj client.Object) gomega.GomegaAsyncAssertion { - key := types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - } - get := func() client.Object { - err := m.Client.Get(m.context(), key, obj) - if err != nil { - panic(err) - } - return obj - } - return gomega.Eventually(get, m.intervals()...) -} - -// eventuallyList gets a list type from the API server. -func (m *Matcher) eventuallyList(obj client.ObjectList, opts ...client.ListOption) gomega.GomegaAsyncAssertion { - list := func() client.ObjectList { - err := m.Client.List(m.context(), obj, opts...) - if err != nil { - panic(err) - } - return obj - } - return gomega.Eventually(list, m.intervals()...) -} diff --git a/pkg/envtest/komega/transforms.go b/pkg/envtest/komega/transforms.go deleted file mode 100644 index 165dd55d7d..0000000000 --- a/pkg/envtest/komega/transforms.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package komega - -import ( - "fmt" - "reflect" - "strings" - - "github.com/onsi/gomega" - gtypes "github.com/onsi/gomega/types" -) - -// WithField gets the value of the named field from the object. -// This is intended to be used in assertions with the Matcher make it easy -// to check the value of a particular field in a resource. -// To access nested fields uses a `.` separator. -// Eg. -// m.Eventually(deployment).Should(WithField("spec.replicas", BeZero())) -// To access nested lists, use one of the Gomega list matchers in conjunction with this. -// Eg. -// m.Eventually(deploymentList).Should(WithField("items", ConsistOf(...))) -func WithField(field string, matcher gtypes.GomegaMatcher) gtypes.GomegaMatcher { - // Addressing Field by . can be recursed - fields := strings.SplitN(field, ".", 2) - if len(fields) == 2 { - matcher = WithField(fields[1], matcher) - } - - return gomega.WithTransform(func(obj interface{}) interface{} { - r := reflect.ValueOf(obj) - f := reflect.Indirect(r).FieldByName(fields[0]) - if !f.IsValid() { - panic(fmt.Sprintf("Object '%s' does not have a field '%s'", reflect.TypeOf(obj), fields[0])) - } - return f.Interface() - }, matcher) -}