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

✨ komega: add EqualObject matcher #1833

Merged

Conversation

schrej
Copy link
Member

@schrej schrej commented Mar 7, 2022

This adds an EqualObject matcher to the new komega package which allows to compare objects more conveniently.

It's based on the implementation in cluster-api/internal/test/matchers by @killianmuldoon and @sbueringer but the actual comparison logic is completely rewritten. The reason was the lack of support for arrays/lists, which occur quite frequently (e.g. owner references) and my attempt to add array support wasn't that pretty.
It's now using google's cmp library with a custom reporter. It still allows to ignore and filter the matched paths. The API is mostly the same, with the exception of how paths are specified. Instead of providing the paths as arrays, they can now be written in Go syntax. Both type field names as well as json/yaml names are supported.

[]string{"metadata", "name"}
// turns into
"ObjectMeta.Name"

// arrays
"ObjectMeta.OwnerReferences[0].UID"

// maps, if they do not contain any of .[]/\
"ObjectMeta.Labels.somelabel"

// maps, if they contain any of .[]/\
"ObjectMeta.Labels[kubernetes.io/somelabel]"

CC @fabriziopandini, since you suggested moving it to controller-runtime

/assign @sbueringer

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 7, 2022
pkg/envtest/komega/equalobject.go Outdated Show resolved Hide resolved
pkg/envtest/komega/equalobject.go Show resolved Hide resolved
@schrej schrej force-pushed the feature/komega-equalobjects branch from 1c5ed04 to c28e47d Compare March 8, 2022 10:49
@JoelSpeed
Copy link
Contributor

/lgtm

This looks useful from a testing perspective, code is clean, happy to have this merged if others are

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Mar 8, 2022
@schrej
Copy link
Member Author

schrej commented Mar 8, 2022

/retest

@sbueringer
Copy link
Member

Thank you very much!

At a first glance it looks good.

@killianmuldoon If you have some time, can you please take a look and test if we can use the matcher as is in our tests? (just to make sure there is nothing we're missing by just reviewing)

@schrej schrej force-pushed the feature/komega-equalobjects branch from c28e47d to cf8bf2e Compare March 8, 2022 13:20
@k8s-ci-robot k8s-ci-robot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Mar 8, 2022
@schrej schrej force-pushed the feature/komega-equalobjects branch from cf8bf2e to b611d0e Compare March 8, 2022 14:58
@schrej
Copy link
Member Author

schrej commented Mar 8, 2022

I've just tested it briefly by replacing the import and it seems like it can be used as a drop-in replacement when no custom IgnorePaths are specified (only once so far, and that's a one line change).

Copy link
Contributor

@killianmuldoon killianmuldoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the new way of doing IgnorePaths doesn't work on unstructured.Unstructured objects. When I plug this into our tests as a drop in I get failures across these tests

The issue is that paths for unstructured appear as map[string]interface{} so we get something like

the following fields were expected to match but did not:
        [Object["metadata"]["annotations"] Object["metadata"]["resourceVersion"]]

This is why the library was using json to compare in Cluster API, so as is this doesn't work as a drop in replacement when using unstructured objects.

I think there's a couple of ways to solve this:

  1. Go back to generic json comparisons
  2. Add handling for filtering paths so that it handles both the e.g. ObjectMeta.ResourceVersion case and the Object[\"metadata\"][\"resourceVersion\"], case

I think the second option fits best with what you're trying to do here and would turn this into a drop in replacement. I'm excited to remove the matchers from Cluster API 😆 so thanks for that @schrej

@schrej
Copy link
Member Author

schrej commented Mar 8, 2022

Then I didn't test thoroughly enough. Should we add another set of default ignores for Unstructured, or do we just add those to the existing one, so it's an actual drop-in replacement?

We could also allow to use single quotes in the path, which would make writing tests that involve maps easier.

@killianmuldoon
Copy link
Contributor

I think it might be better to handle the replicas when doing the filterDiffPaths stage because that would also let any future ignore paths work with Unstructured objects too.

@sbueringer
Copy link
Member

sbueringer commented Mar 9, 2022

I think it might be better to handle the replicas when doing the filterDiffPaths stage because that would also let any future ignore paths work with Unstructured objects too.

I think we can't infer json paths automatically from field names. This will probably work with the builtin metadata fields, but this doesn't work generically.

I think it's even hard to do with metadata:

{"metadata", "uid"}, => "ObjectMeta.UID",
{"metadata", "generation"}, => "ObjectMeta.Generation",
{"metadata", "creationTimestamp"}, => "ObjectMeta.CreationTimestamp",
{"metadata", "resourceVersion"}, => "ObjectMeta.ResourceVersion",
{"metadata", "managedFields"}, => "ObjectMeta.ManagedFields",
{"metadata", "deletionGracePeriodSeconds"}, => "ObjectMeta.DeletionGracePeriodSeconds",
{"metadata", "deletionTimestamp"}, => "ObjectMeta.DeletionTimestamp",
{"metadata", "selfLink"}, => "ObjectMeta.SelfLink",
{"metadata", "generateName"}, => "ObjectMeta.GenerateName",

I guess we can do something by going over JSON tags with reflection but not sure if it's worth the complexity

Might be good enough to just add the metadata paths for unstructured and "real" objects to IgnoreAutogeneratedMetadata. From a user perspective it's just not ideal that depending on if an Unstructured or a real object should be compared different path formats have to be used :/ If somehow possible I would prefer converging on the JSON field names.

@schrej
Copy link
Member Author

schrej commented Mar 9, 2022

I've tried to do so, but wasn't able to find a way quickly. But I've had an idea yesterday evening, so let's see if that works out.

@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 15, 2022
@schrej schrej force-pushed the feature/komega-equalobjects branch from b611d0e to 7938d06 Compare May 5, 2022 09:41
@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 5, 2022
@schrej
Copy link
Member Author

schrej commented May 5, 2022

After quite a while I finally got around to working on this again. It's now using a bit of reflection and supports both, go types and yaml. For unstructured only the latter is really useful.
I've changed the ignore syntax a little bit, ptal at the description.

I also tested it against sigs.k8s.io/cluster-api/internal/controllers/topology/cluster and it seems to work. But it requires to use a more generic "metadata.annotations" ignore rule. This is because of unstructured.Unstructured not even containing metadata.annotations at all if there are no annotations, which causes the comparison library not descending deeper in the tree, only adding metadata.annotations to the diverging paths, not metadata.annotations[topology.cluster.x-k8s.io/managed-field-paths] as it should.

@schrej schrej force-pushed the feature/komega-equalobjects branch from 7938d06 to d86c00b Compare May 5, 2022 14:04
@k8s-ci-robot k8s-ci-robot removed the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label May 5, 2022
@k8s-ci-robot k8s-ci-robot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label May 5, 2022
@schrej schrej force-pushed the feature/komega-equalobjects branch 2 times, most recently from ab4f064 to 5099a25 Compare May 5, 2022 14:07
@schrej
Copy link
Member Author

schrej commented May 5, 2022

I've added all the test cases from the "old" Matcher now. All work, with the exception of three:
As mentioned in the previous comment as well, cmp.Diff doesn't provide the full paths when fields mismatch and one of the compared objects is not nested as deep as the other. This is only the case for unstructured.Unstructured and pointers.

I've looked for a solution, but haven't found any. I'm also not sure if it's worth investing too much time into it. I've kept the tests as comments, maybe we'll be able to improve it in the future.

@killianmuldoon could you take another look?

@schrej schrej requested a review from sbueringer May 12, 2022 15:10
Copy link
Member

@sbueringer sbueringer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

Sorry for the late response.

Just that I understood the limitation correctly. Example:
original has ".spec.parent.child"
modified only has ".spec"

If we want to ignore this diff we have to ignore the entire .spec and not only e.g. .spec.parent.child because the comparison will stop at .spec as modified doesn't have parent/child?

pkg/envtest/komega/equalobject_test.go Show resolved Hide resolved
pkg/envtest/komega/equalobject_test.go Outdated Show resolved Hide resolved
pkg/envtest/komega/equalobject_test.go Outdated Show resolved Hide resolved
pkg/envtest/komega/equalobject_test.go Outdated Show resolved Hide resolved
@schrej
Copy link
Member Author

schrej commented Jun 14, 2022

Just that I understood the limitation correctly. Example: original has ".spec.parent.child" modified only has ".spec"

If we want to ignore this diff we have to ignore the entire .spec and not only e.g. .spec.parent.child because the comparison will stop at .spec as modified doesn't have parent/child?

No. You can ignore .spec.parent.
It does notice when a field is present on one object but not the other. It just will not iterate over children of that field. So in your example it would report that .spec.parent does not match, but it will not report .spec.parent.child.

@schrej schrej force-pushed the feature/komega-equalobjects branch 3 times, most recently from def3332 to c37d3a0 Compare June 14, 2022 13:07
@sbueringer
Copy link
Member

No. You can ignore .spec.parent.
It does notice when a field is present on one object but not the other. It just will not iterate over children of that field. So in your example it would report that .spec.parent does not match, but it will not report .spec.parent.child.

Makes sense, thx!

Not sure if it's necessary in this repo, but let's squash and then merge

Co-authored-by: killianmuldoon <[email protected]>
Co-authored-by: Stefan Bueringer <[email protected]>
@schrej schrej force-pushed the feature/komega-equalobjects branch from c37d3a0 to 8fe64fa Compare June 15, 2022 07:37
@schrej schrej requested a review from JoelSpeed June 15, 2022 07:37
@schrej
Copy link
Member Author

schrej commented Jun 15, 2022

Doesn't hurt to squash. Was only split in two commits so it's easier to see what changed from your implementation.

@JoelSpeed could you also take another look?

@sbueringer
Copy link
Member

Makes sense.

Thank you very much!!
/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 15, 2022
@JoelSpeed
Copy link
Contributor

Looks good thanks

/lgtm

@sbueringer
Copy link
Member

/approve

cc @alvaroaleman (we need top-level approval for the go.mod change)

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: alvaroaleman, sbueringer, schrej

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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jun 17, 2022
@k8s-ci-robot k8s-ci-robot merged commit 165a8c8 into kubernetes-sigs:master Jun 17, 2022
@k8s-ci-robot k8s-ci-robot added this to the v0.10.x milestone Jun 17, 2022
@schrej schrej deleted the feature/komega-equalobjects branch July 21, 2022 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants