Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support client patch and delete collection requests for tests #202

Merged
merged 2 commits into from Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 50 additions & 9 deletions testing/client.go
Expand Up @@ -22,9 +22,11 @@ type clientWrapper struct {
client client.Client
CreateActions []objectAction
UpdateActions []objectAction
PatchActions []PatchAction
DeleteActions []DeleteAction
DeleteCollectionActions []DeleteCollectionAction
StatusUpdateActions []objectAction
StatusPatchActions []PatchAction
genCount int
reactionChain []Reactor
}
Expand All @@ -37,13 +39,16 @@ func NewFakeClient(scheme *runtime.Scheme, objs ...client.Object) *clientWrapper
o[i] = objs[i]
}
client := &clientWrapper{
client: fakeclient.NewFakeClientWithScheme(scheme, o...),
CreateActions: []objectAction{},
UpdateActions: []objectAction{},
DeleteActions: []DeleteAction{},
StatusUpdateActions: []objectAction{},
genCount: 0,
reactionChain: []Reactor{},
client: fakeclient.NewFakeClientWithScheme(scheme, o...),
CreateActions: []objectAction{},
UpdateActions: []objectAction{},
PatchActions: []PatchAction{},
DeleteActions: []DeleteAction{},
DeleteCollectionActions: []DeleteCollectionAction{},
StatusUpdateActions: []objectAction{},
StatusPatchActions: []PatchAction{},
genCount: 0,
reactionChain: []Reactor{},
}
// generate names on create
client.AddReactor("create", "*", func(action Action) (bool, runtime.Object, error) {
Expand Down Expand Up @@ -211,7 +216,25 @@ func (w *clientWrapper) Update(ctx context.Context, obj client.Object, opts ...c
}

func (w *clientWrapper) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
panic(fmt.Errorf("Patch() is not implemented"))
gvr, _, _, err := w.objmeta(obj)
if err != nil {
return err
}
b, err := patch.Data(obj)
if err != nil {
return err
}

// capture action
w.PatchActions = append(w.PatchActions, clientgotesting.NewPatchAction(gvr, obj.GetNamespace(), obj.GetName(), patch.Type(), b))

// call reactor chain
err = w.react(clientgotesting.NewPatchAction(gvr, obj.GetNamespace(), obj.GetName(), patch.Type(), b))
if err != nil {
return err
}

return w.client.Patch(ctx, obj, patch, opts...)
}

func (w *clientWrapper) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
Expand Down Expand Up @@ -269,7 +292,25 @@ func (w *statusWriterWrapper) Update(ctx context.Context, obj client.Object, opt
}

func (w *statusWriterWrapper) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
panic(fmt.Errorf("Patch() is not implemented"))
gvr, _, _, err := w.clientWrapper.objmeta(obj)
if err != nil {
return err
}
b, err := patch.Data(obj)
if err != nil {
return err
}

// capture action
w.clientWrapper.StatusPatchActions = append(w.clientWrapper.StatusPatchActions, clientgotesting.NewPatchSubresourceAction(gvr, obj.GetNamespace(), obj.GetName(), patch.Type(), b, "status"))

// call reactor chain
err = w.clientWrapper.react(clientgotesting.NewPatchSubresourceAction(gvr, obj.GetNamespace(), obj.GetName(), patch.Type(), b, "status"))
if err != nil {
return err
}

return w.statusWriter.Patch(ctx, obj, patch, opts...)
}

// InduceFailure is used in conjunction with reconciler test's WithReactors field.
Expand Down
108 changes: 108 additions & 0 deletions testing/reconciler.go
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/vmware-labs/reconciler-runtime/reconcilers"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
controllerruntime "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -56,10 +58,16 @@ type ReconcilerTestCase struct {
ExpectCreates []client.Object
// ExpectUpdates builds the ordered list of objects expected to be updated during reconciliation
ExpectUpdates []client.Object
// ExpectPatches builds the ordered list of objects expected to be patched during reconciliation
ExpectPatches []PatchRef
// ExpectDeletes holds the ordered list of objects expected to be deleted during reconciliation
ExpectDeletes []DeleteRef
// ExpectDeleteCollections holds the ordered list of collections expected to be deleted during reconciliation
ExpectDeleteCollections []DeleteCollectionRef
// ExpectStatusUpdates builds the ordered list of objects whose status is updated during reconciliation
ExpectStatusUpdates []client.Object
// ExpectStatusPatches builds the ordered list of objects whose status is patched during reconciliation
ExpectStatusPatches []PatchRef

// outputs

Expand Down Expand Up @@ -169,6 +177,7 @@ func (tc *ReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, factory
tc.Verify(t, result, err)
}

// compare tracks
actualTracks := tracker.getTrackRequests()
for i, exp := range tc.ExpectTracks {
if i >= len(actualTracks) {
Expand All @@ -186,6 +195,7 @@ func (tc *ReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, factory
}
}

// compare events
actualEvents := recorder.events
for i, exp := range tc.ExpectEvents {
if i >= len(actualEvents) {
Expand All @@ -203,9 +213,31 @@ func (tc *ReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, factory
}
}

// compare create
CompareActions(t, "create", tc.ExpectCreates, clientWrapper.CreateActions, IgnoreLastTransitionTime, SafeDeployDiff, IgnoreTypeMeta, IgnoreResourceVersion, cmpopts.EquateEmpty())

// compare update
CompareActions(t, "update", tc.ExpectUpdates, clientWrapper.UpdateActions, IgnoreLastTransitionTime, SafeDeployDiff, IgnoreTypeMeta, IgnoreResourceVersion, cmpopts.EquateEmpty())

// compare patches
for i, exp := range tc.ExpectPatches {
if i >= len(clientWrapper.PatchActions) {
t.Errorf("Missing patch: %#v", exp)
continue
}
actual := NewPatchRef(clientWrapper.PatchActions[i])

if diff := cmp.Diff(exp, actual); diff != "" {
t.Errorf("Unexpected patch (-expected, +actual): %s", diff)
}
}
if actual, expected := len(clientWrapper.PatchActions), len(tc.ExpectPatches); actual > expected {
for _, extra := range clientWrapper.PatchActions[expected:] {
t.Errorf("Extra patch: %#v", extra)
}
}

// compare deletes
for i, exp := range tc.ExpectDeletes {
if i >= len(clientWrapper.DeleteActions) {
t.Errorf("Missing delete: %#v", exp)
Expand All @@ -223,8 +255,45 @@ func (tc *ReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, factory
}
}

// compare delete collections
for i, exp := range tc.ExpectDeleteCollections {
if i >= len(clientWrapper.DeleteCollectionActions) {
t.Errorf("Missing delete collection: %#v", exp)
continue
}
actual := NewDeleteCollectionRef(clientWrapper.DeleteCollectionActions[i])

if diff := cmp.Diff(exp, actual); diff != "" {
t.Errorf("Unexpected delete collection (-expected, +actual): %s", diff)
}
}
if actual, expected := len(clientWrapper.DeleteCollectionActions), len(tc.ExpectDeleteCollections); actual > expected {
for _, extra := range clientWrapper.DeleteCollectionActions[expected:] {
t.Errorf("Extra delete collection: %#v", extra)
}
}

// compare status update
CompareActions(t, "status update", tc.ExpectStatusUpdates, clientWrapper.StatusUpdateActions, statusSubresourceOnly, IgnoreLastTransitionTime, SafeDeployDiff, cmpopts.EquateEmpty())

// status patch
for i, exp := range tc.ExpectStatusPatches {
if i >= len(clientWrapper.StatusPatchActions) {
t.Errorf("Missing status patch: %#v", exp)
continue
}
actual := NewPatchRef(clientWrapper.StatusPatchActions[i])

if diff := cmp.Diff(exp, actual); diff != "" {
t.Errorf("Unexpected status patch (-expected, +actual): %s", diff)
}
}
if actual, expected := len(clientWrapper.StatusPatchActions), len(tc.ExpectStatusPatches); actual > expected {
for _, extra := range clientWrapper.StatusPatchActions[expected:] {
t.Errorf("Extra status patch: %#v", extra)
}
}

// Validate the given objects are not mutated by reconciliation
if diff := cmp.Diff(originalGivenObjects, givenObjects, SafeDeployDiff, IgnoreResourceVersion, cmpopts.EquateEmpty()); diff != "" {
t.Errorf("Given objects mutated by test %q (-expected, +actual): %v", tc.Name, diff)
Expand All @@ -240,6 +309,7 @@ func normalizeResult(result controllerruntime.Result) controllerruntime.Result {
}

func CompareActions(t *testing.T, actionName string, expectedActionFactories []client.Object, actualActions []objectAction, diffOptions ...cmp.Option) {
// TODO(scothis) this could be a really good place to play with generics
t.Helper()
for i, exp := range expectedActionFactories {
if i >= len(actualActions) {
Expand Down Expand Up @@ -317,6 +387,26 @@ func (ts ReconcilerTestSuite) Run(t *testing.T, scheme *runtime.Scheme, factory
// and FakeStatsReporter to capture stats.
type ReconcilerFactory func(t *testing.T, rtc *ReconcilerTestCase, c reconcilers.Config) reconcile.Reconciler

type PatchRef struct {
Group string
Kind string
Namespace string
Name string
PatchType types.PatchType
Patch []byte
}

func NewPatchRef(action PatchAction) PatchRef {
return PatchRef{
Group: action.GetResource().Group,
Kind: action.GetResource().Resource,
Namespace: action.GetNamespace(),
Name: action.GetName(),
PatchType: action.GetPatchType(),
Patch: action.GetPatch(),
}
}

type DeleteRef struct {
Group string
Kind string
Expand All @@ -332,3 +422,21 @@ func NewDeleteRef(action DeleteAction) DeleteRef {
Name: action.GetName(),
}
}

type DeleteCollectionRef struct {
Group string
Kind string
Namespace string
Labels labels.Selector
Fields fields.Selector
}

func NewDeleteCollectionRef(action DeleteCollectionAction) DeleteCollectionRef {
return DeleteCollectionRef{
Group: action.GetResource().Group,
Kind: action.GetResource().Resource,
Namespace: action.GetNamespace(),
Labels: action.GetListRestrictions().Labels,
Fields: action.GetListRestrictions().Fields,
}
}
48 changes: 48 additions & 0 deletions testing/subreconciler.go
Expand Up @@ -59,8 +59,12 @@ type SubReconcilerTestCase struct {
ExpectCreates []client.Object
// ExpectUpdates builds the ordered list of objects expected to be updated during reconciliation
ExpectUpdates []client.Object
// ExpectPatches builds the ordered list of objects expected to be patched during reconciliation
ExpectPatches []PatchRef
// ExpectDeletes holds the ordered list of objects expected to be deleted during reconciliation
ExpectDeletes []DeleteRef
// ExpectDeleteCollections holds the ordered list of collections expected to be deleted during reconciliation
ExpectDeleteCollections []DeleteCollectionRef

// outputs

Expand Down Expand Up @@ -188,6 +192,7 @@ func (tc *SubReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, facto
tc.Verify(t, result, err)
}

// compare parent
expectedParent := tc.Parent.DeepCopyObject().(client.Object)
if tc.ExpectParent != nil {
expectedParent = tc.ExpectParent.DeepCopyObject().(client.Object)
Expand All @@ -196,6 +201,7 @@ func (tc *SubReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, facto
t.Errorf("Unexpected parent mutations(-expected, +actual): %s", diff)
}

// compare stashed
for key, expected := range tc.ExpectStashedValues {
if f, ok := expected.(runtime.Object); ok {
expected = f.DeepCopyObject()
Expand All @@ -206,6 +212,7 @@ func (tc *SubReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, facto
}
}

// compare tracks
actualTracks := tracker.getTrackRequests()
for i, exp := range tc.ExpectTracks {
if i >= len(actualTracks) {
Expand All @@ -223,6 +230,7 @@ func (tc *SubReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, facto
}
}

// compare events
actualEvents := recorder.events
for i, exp := range tc.ExpectEvents {
if i >= len(actualEvents) {
Expand All @@ -240,9 +248,31 @@ func (tc *SubReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, facto
}
}

// compare create
CompareActions(t, "create", tc.ExpectCreates, clientWrapper.CreateActions, IgnoreLastTransitionTime, SafeDeployDiff, IgnoreTypeMeta, IgnoreResourceVersion, cmpopts.EquateEmpty())

// compare update
CompareActions(t, "update", tc.ExpectUpdates, clientWrapper.UpdateActions, IgnoreLastTransitionTime, SafeDeployDiff, IgnoreTypeMeta, IgnoreResourceVersion, cmpopts.EquateEmpty())

// compare patches
for i, exp := range tc.ExpectPatches {
if i >= len(clientWrapper.PatchActions) {
t.Errorf("Missing patch: %#v", exp)
continue
}
actual := NewPatchRef(clientWrapper.PatchActions[i])

if diff := cmp.Diff(exp, actual); diff != "" {
t.Errorf("Unexpected patch (-expected, +actual): %s", diff)
}
}
if actual, expected := len(clientWrapper.PatchActions), len(tc.ExpectPatches); actual > expected {
for _, extra := range clientWrapper.PatchActions[expected:] {
t.Errorf("Extra patch: %#v", extra)
}
}

// compare deletes
for i, exp := range tc.ExpectDeletes {
if i >= len(clientWrapper.DeleteActions) {
t.Errorf("Missing delete: %#v", exp)
Expand All @@ -260,6 +290,24 @@ func (tc *SubReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, facto
}
}

// compare delete collections
for i, exp := range tc.ExpectDeleteCollections {
if i >= len(clientWrapper.DeleteCollectionActions) {
t.Errorf("Missing delete collection: %#v", exp)
continue
}
actual := NewDeleteCollectionRef(clientWrapper.DeleteCollectionActions[i])

if diff := cmp.Diff(exp, actual); diff != "" {
t.Errorf("Unexpected delete collection (-expected, +actual): %s", diff)
}
}
if actual, expected := len(clientWrapper.DeleteCollectionActions), len(tc.ExpectDeleteCollections); actual > expected {
for _, extra := range clientWrapper.DeleteCollectionActions[expected:] {
t.Errorf("Extra delete collection: %#v", extra)
}
}

// Validate the given objects are not mutated by reconciliation
if diff := cmp.Diff(originalGivenObjects, givenObjects, SafeDeployDiff, IgnoreResourceVersion, cmpopts.EquateEmpty()); diff != "" {
t.Errorf("Given objects mutated by test %q (-expected, +actual): %v", tc.Name, diff)
Expand Down