-
Notifications
You must be signed in to change notification settings - Fork 1.4k
🌱 Add composable matchers to internal/testtypes #5219
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 composable matchers to internal/testtypes #5219
Conversation
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
dab9113
to
4371995
Compare
3669a32
to
0fc889e
Compare
@fabriziopandini this is now reworked and ready for review. |
This commit adds composable matching objects to the internal testtypes package. These types can be used for advanced comparison between objects in the Cluster API unit and integration tests. A simple integration test is included to show the basic working of the matchers.
0fc889e
to
9671b1d
Compare
@killianmuldoon: PR needs rebase. 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. |
cmp.Diff(tt.want.MachineDeployments, got.MachineDeployments, ignoreResourceVersion)) | ||
// Use EqualObject where an object is created and passed through the fake client. Elsewhere the Equal methoc | ||
// is enough to establish inequality. | ||
g.Expect(tt.want.ClusterClass).To(EqualObject(got.ClusterClass, IgnoreAutogeneratedMetadata)) |
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.
g.Expect(tt.want.ClusterClass).To(EqualObject(got.ClusterClass, IgnoreAutogeneratedMetadata)) | |
g.Expect(tt.want.ClusterClass).To(EqualObject(got.ClusterClass, IgnoreAutogeneratedMetadata)) |
I like this! super clean
) | ||
|
||
// This code is a modified version of the mergePatch code from the controllers/topology/internal/mergepatch pkg. | ||
// TODO: Create a shared patchdiff library that can be shared for both patching and matching. |
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.
Let's open an issue for 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.
Are we sure we want a separate package for that? Sometimes a little bit copying is better then a new package/dependency
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.
Copying is definitely the right way for now - but having this code in multiple places doesn't seem like the right solution in the long term as its usage might grow - especially if mergepatch goes out of internal. I think having an issue for this is a good idea, and we can decide down the line if we want to actually execute on it.
|
||
// NegatedFailureMessage returns a string comparing the full objects after an unexpected match has occurred. | ||
func (m *Matcher) NegatedFailureMessage(other interface{}) (message string) { | ||
return format.Message(other, "not to match", m.original) |
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.
Let's return diff also here
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 shouldn't be a diff here though right? This will print only when the objects are expected not to match but do so unexpectedly. I can add the diff for now - it will just be an empty string if there's nothing in there.
lgtm apart two nits and rebase |
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.
looks very nice!
@@ -21,9 +21,8 @@ import ( | |||
|
|||
"github.com/pkg/errors" | |||
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4" | |||
"sigs.k8s.io/cluster-api/internal/testtypes" | |||
. "sigs.k8s.io/cluster-api/internal/testtypes" |
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'm not a fan of dot imports to be honest :/ (because of the "magic-factor")
I understand why we want it for matchers, I'm not sure if we should also use it for our builder funcs.
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.
Yeah - it's a compromise here from having one package with the two different functions. We are using . imports across our testing for Gomega. Do you have strong feelings about this? I can definitely see both sides.
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'll move it to the new PR
) | ||
|
||
// This code is a modified version of the mergePatch code from the controllers/topology/internal/mergepatch pkg. | ||
// TODO: Create a shared patchdiff library that can be shared for both patching and matching. |
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.
Are we sure we want a separate package for that? Sometimes a little bit copying is better then a new package/dependency
// These package variables hold pre-created commonly used options that can be used to reduce the manual work involved in | ||
// identifying the paths that need to be compared for testing equality between objects. | ||
var ( | ||
|
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.
nit
{"metadata", "managedFields"}, | ||
{"metadata", "deletionGracePeriodSeconds"}, | ||
{"metadata", "deletionTimestamp"}, | ||
{"metadata", "ownerReferences"}, |
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.
Do we currently need to ignore ownerReferences
? If not, it might be good to remove them from the list. When they are set, they are set intentionally by our code. (afaik)
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.
Not needed and you're right that we do set them. I'll remove it.
options *MatchOptions | ||
} | ||
|
||
// EqualObject applies the MatchOptions and returns a Matcher for the passed Kubernetes runtime.Object. This function |
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'm not sure I would include applies the MatchOptions
it almost sounds like we apply them to the object. I probably would just omit it.
} | ||
} | ||
|
||
// Match compares the object it's based on to the passed object and returns true if the objects are the same according to |
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.
// Match compares the object it's based on to the passed object and returns true if the objects are the same according to | |
// Match compares the current object to the passed object and returns true if the objects are the same according to |
nit
return true, nil | ||
} | ||
if otherIsNil || originalIsNil { | ||
return false, fmt.Errorf("can not compare an object with a nil. original :%v , other %v", m.original, other) |
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.
return false, fmt.Errorf("can not compare an object with a nil. original :%v , other %v", m.original, other) | |
return false, fmt.Errorf("can not compare an object with a nil. original %v , other %v", m.original, other) |
nit (I assume we want :
either for both or for none of the objects)
|
||
// Match compares the object it's based on to the passed object and returns true if the objects are the same according to | ||
// the Matcher MatchOptions. | ||
func (m *Matcher) Match(other interface{}) (success bool, err error) { |
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.
func (m *Matcher) Match(other interface{}) (success bool, err error) { | |
func (m *Matcher) Match(actual interface{}) (success bool, err error) { |
I looked at a gomega matcher and this seems to be called actual
there. I think it would be nice to do the same in our code. (same for other funcs)
return format.Message(other, "not to match", m.original) | ||
} | ||
|
||
// calculateDiff applies the MatchOptions and identified the diff between the Matcher object and the passed object. |
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.
// calculateDiff applies the MatchOptions and identified the diff between the Matcher object and the passed object. | |
// calculateDiff applies the MatchOptions and identifies the diff between the Matcher object and the passed object. |
nit, (or maybe calculates)
} | ||
|
||
// filterDiff limits the diff to allowPaths if given and excludes ignorePaths if given. It returns the altered diff. | ||
func filterDiff(diff []byte, allowedPaths, ignorePaths [][]string) ([]byte, error) { |
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.
func filterDiff(diff []byte, allowedPaths, ignorePaths [][]string) ([]byte, error) { | |
func filterDiff(diff []byte, allowPaths, ignorePaths [][]string) ([]byte, error) { |
nit (to keep it consistent with the field name)
Found a typo while having a read through, otherwise looks great |
@JoelSpeed did you mark the typo in your review? I can't seem to see it |
cmp.Diff(tt.want.ControlPlane, got.ControlPlane, ignoreResourceVersion)) | ||
g.Expect(cmp.Diff(tt.want.MachineDeployments, got.MachineDeployments, ignoreResourceVersion)).To(Equal(""), | ||
cmp.Diff(tt.want.MachineDeployments, got.MachineDeployments, ignoreResourceVersion)) | ||
// Use EqualObject where an object is created and passed through the fake client. Elsewhere the Equal methoc |
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.
// Use EqualObject where an object is created and passed through the fake client. Elsewhere the Equal methoc | |
// Use EqualObject where an object is created and passed through the fake client. Elsewhere the Equal method |
I can't press the submit button very well clearly 😓 |
Happens to us all! |
Note: This PR has been closed in favor of #5259 which has an updated, more appropriate, branch name given the changes in this PR. |
@killianmuldoon Are you referring to the "title" (aka If yes, you can just edit it. EDIT: Okay I saw in your other PR that you're referring to the branch name. |
This commit adds composable matching objects to the internal
testtypes package. These types can be used for advanced comparison
between objects in the Cluster API unit and integration tests.
Fixes #5157