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
Add ginkgo tests for machineset controller #544
Add ginkgo tests for machineset controller #544
Conversation
// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and | ||
// writes the request to requests after Reconcile is finished. | ||
func SetupTestReconcile(inner reconcile.Reconciler) reconcile.Reconciler { | ||
fn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) { | ||
result, err := inner.Reconcile(req) | ||
return result, err | ||
}) | ||
return fn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this actually does anything for us? I've seen this pattern before where it writes to a channel, but I always found relying on that to be annoying and not help us to write good tests, my suggestion would be to remove this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I thought about removing it too
By("setting up a new manager") | ||
mgr, err := manager.New(cfg, manager.Options{MetricsBindAddress: "0"}) | ||
Expect(err).NotTo(HaveOccurred()) | ||
|
||
// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and | ||
// writes the request to requests after Reconcile is finished. | ||
func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan reconcile.Request) { | ||
requests := make(chan reconcile.Request) | ||
fn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) { | ||
result, err := inner.Reconcile(req) | ||
requests <- req | ||
return result, err | ||
}) | ||
return fn, requests | ||
} | ||
k8sClient = mgr.GetClient() | ||
|
||
// StartTestManager adds recFn | ||
func StartTestManager(mgr manager.Manager, t *testing.T) (chan struct{}, chan error) { | ||
t.Helper() | ||
By("setting up a new reconciler") | ||
r := newReconciler(mgr) | ||
reconciler = SetupTestReconcile(r) | ||
|
||
stop := make(chan struct{}) | ||
errs := make(chan error, 1) | ||
err = add(mgr, reconciler, r.MachineToMachineSets) | ||
Expect(err).NotTo(HaveOccurred()) | ||
|
||
By("starting the manager") | ||
go func() { | ||
errs <- mgr.Start(stop) | ||
Expect(mgr.Start(doneMgr)).To(Succeed()) | ||
}() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could potentially be done in a BeforeEach
within the test suite, that way we start a new manager and reconciler for each test and there is less potential for side effects between individual tests
go func() { | ||
errs <- mgr.Start(stop) | ||
Expect(mgr.Start(doneMgr)).To(Succeed()) | ||
}() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate go routines calling gomega expectations require a defer GinkgoRecover()
at the top
AfterEach(func() { | ||
By("Deleting the namespace") | ||
Expect(k8sClient.Delete(ctx, namespace)).To(Succeed()) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no GC running so you'll need to delete all Machines and MachineSets too, probably worth a helper function that does this (list all machines/machinesets, iterate through and delete). Doing this way means you don't have to clean up within your test IT's, just rely on the suite to clean everything
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Eventually(func() int { | ||
if err := k8sClient.List(ctx, machines, client.InNamespace(namespace.Name)); err != nil { | ||
return -1 | ||
} | ||
return len(machines.Items) | ||
}, timeout).Should(BeEquivalentTo(replicas)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can return a second parameter here (I learnt this recently) and Eventually will check the non-first params are their zero values/nil, that way you don't have to make up "error" values as we have done before. Might be worth doing the same on the others so that errors are surfaced properly
Eventually(func() int { | |
if err := k8sClient.List(ctx, machines, client.InNamespace(namespace.Name)); err != nil { | |
return -1 | |
} | |
return len(machines.Items) | |
}, timeout).Should(BeEquivalentTo(replicas)) | |
Eventually(func() (int, error) { | |
if err := k8sClient.List(ctx, machines, client.InNamespace(namespace.Name)); err != nil { | |
return 0, err | |
} | |
return len(machines.Items), nil | |
}, timeout).Should(BeEquivalentTo(replicas)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
32e6558
to
ec2b70a
Compare
d6c8739
to
5feae08
Compare
@@ -262,3 +266,67 @@ func TestAdoptOrphan(t *testing.T) { | |||
} | |||
} | |||
} | |||
|
|||
func TestMachineSetReconcile(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to use Ginkgo for these two tests as well?
var _ = Describe("MachineSet Reconcile", func() {
var r reconciler
var result reconile.Result
var reconcileErr error
BeforeEach(func() {
// set up reconciler as r with scheme and new fake recorder
})
JustBeforeEach(func() {
result, reconcileErr = r.Reconcile(request)
})
Context("with a valid machineset", func() {
BeforeEach(func() {
// set up machinset
// set up fake client
// override r.client
})
It("has the expected result", func() {
Expect(result).To(Equal())
})
It("has the expected error", func() {
Expect(reconcileErr).To(Equal())
})
It("did something with events", func() {
g.Eventually(r.recorder.Events).Should(Receive())
})
})
Context("with an invalid machineset", func() {
// This should look basically the same as above
})
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
By("Deleting the namespace") | ||
Expect(k8sClient.Delete(ctx, namespace)).To(Succeed()) | ||
|
||
By("Deleting the machinesets") | ||
cleanResources() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would swap the order of these around so you clean the resources before deleting the namespace
return 0, err | ||
} | ||
return len(machines.Items), nil | ||
}, timeout).Should(BeEquivalentTo(replicas)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume there's a type mismatch if you use Equal
here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right
// Verify that the Machine has been deleted. | ||
Eventually(func() bool { | ||
key := client.ObjectKey{Name: machineToBeDeleted.Name, Namespace: machineToBeDeleted.Namespace} | ||
if err := k8sClient.Get(ctx, key, &machineToBeDeleted); apierrors.IsNotFound(err) || !machineToBeDeleted.DeletionTimestamp.IsZero() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything adding a finalizer to the Machines? Do you expect them not to be deleted straight away?, I'd be tempted to remove the second check and see if this still passes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, the check can be removed
Eventually(func() (ready int) { | ||
if err := k8sClient.List(ctx, machines, client.InNamespace(namespace.Name)); err != nil { | ||
return -1 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eventually(func() (ready int) { | |
if err := k8sClient.List(ctx, machines, client.InNamespace(namespace.Name)); err != nil { | |
return -1 | |
} | |
Eventually(func() (int, error) { | |
ready := 0 | |
if err := k8sClient.List(ctx, machines, client.InNamespace(namespace.Name)); err != nil { | |
return 0, err | |
} |
for _, machineSet := range machineSets.Items { | ||
if err := k8sClient.Delete(ctx, &machineSet); err != nil { | ||
return err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taking the address of a loop variable in go is asking for trouble! 😅 Do this instead to avoid confusion later 😉
for _, machineSet := range machineSets.Items { | |
if err := k8sClient.Delete(ctx, &machineSet); err != nil { | |
return err | |
} | |
for _, machineSet := range machineSets.Items { | |
ms := machineSet | |
if err := k8sClient.Delete(ctx, &ms); err != nil { | |
return err | |
} |
While I don't think it makes a problem here, it can in certain cases so it's best to just avoid it
Have a read of https://github.com/golang/go/wiki/CommonMistakes#using-reference-to-loop-iterator-variable for more info
Please also fix the one below
We aren't reducing the test cases right, as far as I can see the new file with the tests in that call Reconcile directly match two of the cases removed from the ones converted to Ginkgo? |
5feae08
to
536ca27
Compare
@JoelSpeed Yes, we are not reducing coverage and all comments are fixed now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In terms of refactoring, looks great! Added a couple of suggestions for improving the test naming and also potentially for adding some extra cases but I'm not fussed about those going in this PR, just ideas for future improvements
Expect(result).To(Equal(reconcile.Result{})) | ||
}) | ||
|
||
It("has the expected error", func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It("has the expected error", func() { | |
It("does not return an error", func() { |
It("did something with events", func() { | ||
Eventually(rec.Events).Should(Receive()) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to also check the result and error here? Do you know what they're expected to be? /could you work it out from the code? (Could be a follow up)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should be possible with Eventually(rec.Events).Should(Receive(Equal("..")))
It("has the expected result", func() { | ||
Expect(result).To(Equal(reconcile.Result{})) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be any events in this case? Could check those? (Could be a follow up)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, in this case, there shouldn't
r.Client = fake.NewFakeClientWithScheme(scheme.Scheme, ms) | ||
}) | ||
|
||
It("has the expected result", func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It("has the expected result", func() { | |
It("returns an empty result", func() { |
536ca27
to
74e9ded
Compare
@JoelSpeed Can one more review round? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
Maybe worth a follow up to improve the reconcile test but that's another concern
/approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: enxebre The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
/retest Please review the full test history for this PR and help us cut down flakes. |
1 similar comment
/retest Please review the full test history for this PR and help us cut down flakes. |
74e9ded
to
5947d94
Compare
/lgtm |
/retest Please review the full test history for this PR and help us cut down flakes. |
1 similar comment
/retest Please review the full test history for this PR and help us cut down flakes. |
@alexander-demichev: The following tests failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
/retest Please review the full test history for this PR and help us cut down flakes. |
This PR adds ginkgo unit tests for machineset controller and one reconcile test, the tests are similar to upstream https://github.com/kubernetes-sigs/cluster-api/blob/09f87a2aadcd590952e17684f5c05259337e4da9/controllers/machineset_controller_test.go