Skip to content

Honor map key comparison semantics with containsOnly assertions - #2167

Merged
joel-costigliola merged 30 commits into
mainfrom
honor_map_key_comparison
May 24, 2021
Merged

joel-costigliola merged 30 commits into
mainfrom
honor_map_key_comparison

Conversation

@joel-costigliola

@joel-costigliola joel-costigliola commented Apr 11, 2021 •

Copy link
Copy Markdown
Member

This affects:

  • assertContainsOnly
  • assertContainsOnlyKeys

Open points:

  • Complete Maps_assertContainsOnlyKeys_Test
  • Complete Maps_assertContainsOnly_Test
  • Evaluate refactoring of assertDoesNotContainKeys(AssertionInfo, Map, Object[])
  • Mention in the javadoc that in some cases the key comparison semantics won't be honored

Check List:

Comment thread src/main/java/org/assertj/core/internal/Maps.java Outdated
@joel-costigliola

Copy link
Copy Markdown
Member Author

@filiphr feel free to review and contribute to this PR if you have time, this one supersedes #2160.

@joel-costigliola

Copy link
Copy Markdown
Member Author

The build fails due to too many lib to shade, not a blocker right now for us to play with objenesis

@filiphr

filiphr commented Apr 11, 2021

Copy link
Copy Markdown
Contributor

I am not sure that using Objenesis or even creating a copy of the map should be the way forward. At least not for all maps.

Have a look at my additional test cases in filiphr@96360d7. Using Collections.unmodifiableMap works, using Guava immutable map works as well. Using SingletonMap doesn't work.

A potential (not 100% cases solution) would be to only create a copy if the map is a case insensitive map. e.g. like in filiphr@d7a77b9.

@joel-costigliola

Copy link
Copy Markdown
Member Author

thanks for fixing the pom @scordio.

@scordio

scordio commented Apr 13, 2021 •

Copy link
Copy Markdown
Member

I try to summarize the topic to see if I got it correctly.

The containsOnly / containsExactly assertions consist of two steps:

  1. Checking if the expected elements are present
  2. Finding the unexpected ones

Elements can be either keys or entries depending on the specific assertion.

Checking the expected elements

The check should always be performed on actual to honor the original semantic.

This is mostly relevant with special implementations like java.util.IdentityHashMap, org.apache.commons.collections4.map.CaseInsensitiveMap or org.springframework.util.LinkedCaseInsensitiveMap.

Finding of unexpected elements

In the ideal case, we want to have a copy of the map and remove the expected elements, so what is left are the unexpected elements.

Being able to do this strongly depends on the map implementation.

  1. Cloneable implementation

    A new instance can be obtained via clone(), then we try to remove() the expected elements

    • remove succeeds → unexpected elements are identified
    • remove fails with UnsupportedOperationException, (i.e., unmodifiable) → new LinkedHashMap initialized with the elements of actual, remove() is repeated again → unexpected elements are identified but with the potential loss of the original semantic.
  2. Non Cloneable implementation, but a new instance can be obtained

    A new instance can be obtained via reflection (e.g., plain, Objenesis), then we try to putAll() the elements of actual

    • putAll() succeeds → we try to remove() the expected elements as in the Cloneable case.
    • putAll() fails with UnsupportedOperationException, (i.e., unmodifiable) → new LinkedHashMap is initialized with the elements of actual, remove() is repeated again → unexpected elements are identified but with the potential loss of the original semantic.
  3. No new instance can be obtained

    New LinkedHashMap is initialized with the elements of actual, the remove() of the expected elements is performed → unexpected elements are identified but with the potential loss of the original semantic.

@joel-costigliola @filiphr what do you think about this approach? Do you see any other cases to be handled?

@joel-costigliola

Copy link
Copy Markdown
Member Author

Sounds good to me, a few comments:

  • containsExactly also checks the order of keys/entries
  • IdentityHashMap is another map implementation that does not rely on equals but being part of the JDK we could simply instantiate it directly.
  • We would need to state in the javadoc that in some cases the key comparison semantics won't honored

@filiphr

filiphr commented Apr 14, 2021

Copy link
Copy Markdown
Contributor

I think that your approach @scordio makes sense.

IdentityHashMap is another map implementation that does not rely on equals but being part of the JDK we could simply instantiate it directly.

@joel-costigliola IdentityHashMap implements Cloneable. Therefore I think that it will be part of the first example from @scordio. I think that we should try and do as little key equal checks, which I think is being only done for containsExactly.

@joel-costigliola joel-costigliola added this to the 3.20 milestone Apr 18, 2021
@scordio
scordio force-pushed the honor_map_key_comparison branch 3 times, most recently from 256201f to a4d8b16 Compare April 21, 2021 01:29
Comment thread src/main/java/org/assertj/core/internal/Maps.java
@scordio
scordio force-pushed the honor_map_key_comparison branch from 4f0ee04 to ae8bbe6 Compare May 9, 2021 19:42
@scordio

scordio commented May 9, 2021 •

Copy link
Copy Markdown
Member

@joel-costigliola @filiphr assertContainsOnly and assertContainsOnlyKeys are in a reviewable shape, happy to get any feedback about the new implementation.

Most likely this PR will also include assertContainsExactly.

@filiphr filiphr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It looks great @scordio. Quite extensive tests and works as I would expect.

I've left some performance comments and one place where you used instantiate instead of clone.

Comment thread src/main/java/org/assertj/core/internal/Maps.java Outdated
Comment thread src/main/java/org/assertj/core/internal/Maps.java Outdated
Comment thread src/main/java/org/assertj/core/internal/Maps.java
Comment thread src/main/java/org/assertj/core/internal/Maps.java Outdated
@scordio

scordio commented May 16, 2021

Copy link
Copy Markdown
Member

Having the same pattern for assertContainsExactly will increase the complexity of this PR as the tests are more complicated due to the ordering requirement.

I will focus on containsOnly related assertions and update assertContainsExactly in a separate PR.

@scordio scordio changed the title Honor map key comparison semantics Honor map key comparison semantics for containsOnly assertions May 16, 2021
@scordio scordio changed the title Honor map key comparison semantics for containsOnly assertions Honor map key comparison semantics with containsOnly assertions May 16, 2021
@scordio
scordio marked this pull request as ready for review May 17, 2021 20:59

@joel-costigliola joel-costigliola left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I still have to look at the tests but I thought I'll share my comments on the rest of the code

Comment thread src/main/java/org/assertj/core/api/AbstractMapAssert.java Outdated
Comment thread src/main/java/org/assertj/core/internal/Maps.java
Comment thread src/main/java/org/assertj/core/internal/Maps.java
Comment thread src/main/java/org/assertj/core/internal/Maps.java
Comment thread src/main/java/org/assertj/core/internal/Maps.java
Comment thread src/main/java/org/assertj/core/internal/Maps.java
Comment thread src/test/java/org/assertj/core/test/Maps.java
Comment thread src/main/java/org/assertj/core/util/Sets.java

@joel-costigliola joel-costigliola left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

just some minor code cleanup and we are good to go!

@joel-costigliola
joel-costigliola merged commit ae1c70e into main May 24, 2021
@joel-costigliola
joel-costigliola deleted the honor_map_key_comparison branch May 24, 2021 05:33
@joel-costigliola

Copy link
Copy Markdown
Member Author

Thanks @filiphr and @scordio, this is now integrated.

schlosna added a commit to schlosna/assertj-core that referenced this pull request Jun 17, 2021
This appears to have broken in PR assertj#2167 specifically with the use of
`Map.remove(Object, Object)` that does not perform deep equality
checking on the map entry value (e.g. arrays).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Map containsOnly assertions don't work correctly with case insensitive keys

4 participants