Skip to content

Conversation

@Vishalup29
Copy link
Contributor

Description

This PR adds consistent equals and hashCode implementations to the SDK’s
EvaluationContext implementations (ImmutableContext and LayeredEvaluationContext).

A user reported in Issue #1754 that comparing context instances did not behave as expected,
especially after the introduction of LayeredEvaluationContext. Because the SDK does not
guarantee object identity, equality should be defined based on the resolved attribute map
returned by asMap().

This PR implements that behavior and adds regression tests to ensure we maintain consistent
equality semantics going forward.

Changes

  • Added equals(Object) and hashCode() to:
    • ImmutableContext
    • LayeredEvaluationContext
  • Equality is now defined in terms of their asMap() representations.
  • Added unit tests validating:
    • Equality for two identical ImmutableContext instances.
    • Equality for two identical LayeredEvaluationContext instances.
    • Map-based equality between ImmutableContext and LayeredEvaluationContext.

Related Issue

Fixes #1754

Notes

  • Equality is intentionally based on asMap() rather than structural depth or source layering.
  • This aligns the context behavior with user expectations and simplifies future comparisons.

@Vishalup29 Vishalup29 requested review from a team as code owners November 29, 2025 09:26
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Vishalup29, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a reported issue where EvaluationContext instances, particularly LayeredEvaluationContext, were not comparing as expected due to a lack of consistent equals and hashCode implementations. The changes introduce these methods to ImmutableContext and LayeredEvaluationContext, ensuring that context equality is determined by their resolved attribute maps. This aligns comparison behavior with user expectations, simplifies future context comparisons, and fixes a reported bug, making the SDK more robust.

Highlights

  • Consistent Equality: Implemented equals and hashCode methods for ImmutableContext and LayeredEvaluationContext to ensure consistent comparison behavior across different context types.
  • Attribute-Based Comparison: Defined equality for EvaluationContext implementations based on their resolved attribute maps (asMap()), ensuring that contexts are considered equal if their effective attributes match, regardless of their internal structure or layering.
  • Comprehensive Testing: Added new unit tests to validate equality for identical ImmutableContext and LayeredEvaluationContext instances, as well as map-based equality between different context types, providing regression coverage for future changes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Vishalup29 Vishalup29 changed the title Issue #1754 Implement equals/hashCode for EvaluationContext and add tests fix: Define equality behavior for EvaluationContext implementations Nov 29, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This PR adds consistent equals and hashCode implementations to ImmutableContext and LayeredEvaluationContext, which is a great improvement for SDK users. The logic to base equality on the resolved map from asMap() is correct and aligns with user expectations.

My review focuses on two main areas:

  1. Performance: The current implementations of equals and hashCode call asMap() on every invocation. This can be inefficient, particularly for LayeredEvaluationContext. I've suggested caching strategies for both classes to mitigate potential performance bottlenecks.
  2. Testing: One of the new tests can be improved to more directly validate the equals contract between different context types.

Additionally, I noticed that ImmutableContext still has a Lombok @EqualsAndHashCode annotation. Since you've provided a manual implementation, this annotation is now misleading and should be removed to improve code clarity, though I was unable to comment on that line directly as it was not part of the diff.

@toddbaert
Copy link
Member

toddbaert commented Nov 29, 2025

@Vishalup29 Gemini's comments here are accurate, I think. Lazily building and caching this map is probably a good idea, instead of doing it again with every call... Though I think there's memory trade offs here as well...

The comment about the Lombok impl is also right, I think that needs to be removed.

cc @chrfwow because I'd like your insights here as well. What do you think? I think either way we'll generate a heap object doing 'asmap' during 'hashcode', so we might as well cache it in case we need it again, right? I think this is optimal as long as we do it lazily?

@chrfwow
Copy link
Contributor

chrfwow commented Dec 1, 2025

Yes, we should definetly cache the map when we create it. I have to say I don't really like that the first invocation of equals or hashcode will be quite expensive, but if we want to provide this assurance, I don't see a way around it.
We should also keep in mind that the asMap implementation of ImmutableContext (ImmutableStructure) is quite expensive, so maybe we can cache this as well

@toddbaert
Copy link
Member

Yes, we should definetly cache the map when we create it. I have to say I don't really like that the first invocation of equals or hashcode will be quite expensive, but if we want to provide this assurance, I don't see a way around it

@chrfwow

Yes, I feel exactly the same way. It will be expensive, and allocate memory once, but I think it's still worth having. At least the use case we know of it was just used in test assertions - specifically to validate/assert in a test that a context was set... perhaps this will be the most common use-case.

@Vishalup29
Copy link
Contributor Author

Vishalup29 commented Dec 5, 2025

Thanks for the detailed feedback!

  • Updated ImmutableContext to cache its hash code lazily (and removed the Lombok @EqualsAndHashCode annotation).
  • Refactored LayeredEvaluationContext to lazily cache the resolved attribute map via a new getResolvedMap() helper, with invalidation in putHookContext. asMap(), equals, and hashCode now all use this helper.
  • Kept equality semantics based on asMap() as discussed and expanded tests to cover mixed-context and layered equality including hashCode.

cc: @toddbaert @chrfwow Thanks again for your review. Really appreciate it

Copy link
Contributor

@chrfwow chrfwow left a comment

Choose a reason for hiding this comment

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

Looks very good! I just have some minor comments

@Vishalup29 Vishalup29 force-pushed the issue_1754 branch 2 times, most recently from 5efa0d5 to 6b7f8dc Compare December 5, 2025 07:49
@Vishalup29
Copy link
Contributor Author

@chrfwow Addressed all your comments.
Thanks again for the clear review comments — really appreciate the help getting this contribution into good shape. 🙂

Copy link
Contributor

@chrfwow chrfwow left a comment

Choose a reason for hiding this comment

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

Nice work

@Vishalup29
Copy link
Contributor Author

Vishalup29 commented Dec 7, 2025

thanks, can this be merged?
cc: @toddbaert

…xt and add tests.

Signed-off-by: vishalup29 <vishalupadhyay977@gmail.com>
@toddbaert
Copy link
Member

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces equals and hashCode implementations for EvaluationContext to provide consistent equality behavior based on resolved attributes, which is a great improvement. The implementations for ImmutableContext and LayeredEvaluationContext are well-structured, with appropriate caching strategies for both immutable and mutable scenarios. The accompanying tests are thorough and cover the new equality semantics effectively. I've identified a few opportunities to enhance performance in the equals and hashCode methods by avoiding unnecessary object allocations, and I've provided specific suggestions in the comments below. Overall, this is a solid contribution that enhances the SDK's functionality.

@open-feature open-feature deleted a comment from codecov bot Dec 8, 2025
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 8, 2025

@codecov
Copy link

codecov bot commented Dec 8, 2025

Codecov Report

❌ Patch coverage is 80.48780% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.20%. Comparing base (1f211af) to head (6ff4d75).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
.../dev/openfeature/sdk/LayeredEvaluationContext.java 80.76% 2 Missing and 3 partials ⚠️
...ain/java/dev/openfeature/sdk/ImmutableContext.java 80.00% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1756      +/-   ##
============================================
+ Coverage     92.53%   93.20%   +0.67%     
- Complexity      599      604       +5     
============================================
  Files            55       55              
  Lines          1406     1427      +21     
  Branches        154      156       +2     
============================================
+ Hits           1301     1330      +29     
+ Misses           64       53      -11     
- Partials         41       44       +3     
Flag Coverage Δ
unittests 93.20% <80.48%> (+0.67%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@toddbaert toddbaert self-requested a review December 8, 2025 19:13
@toddbaert toddbaert changed the title fix: Define equality behavior for EvaluationContext implementations fix: equivalent EvaluationContext .equals() not returning true Dec 8, 2025
@toddbaert toddbaert merged commit 6f3a30a into open-feature:main Dec 8, 2025
13 of 14 checks passed
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.

Cannot compare (.equals) various context implementaations

3 participants