fix: Generate consistent reuse hashes by sorting dictionary keys#1554
fix: Generate consistent reuse hashes by sorting dictionary keys#1554HofmeisterAn merged 2 commits intotestcontainers:developfrom
Conversation
✅ Deploy Preview for testcontainers-dotnet ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Summary by CodeRabbit
WalkthroughReplaces bespoke dictionary serialization with a deterministic ordered-key converter, refactors the runtime-label-ignorer to reuse it, applies the ordered converter to resource-configuration hashing, and adds tests that assert reuse-hash stability across different insertion orders. Changes
Sequence Diagram(s)sequenceDiagram
participant RC as ResourceConfiguration
participant JS as JsonSerializer
participant Q as JsonOrderedKeysConverter
RC->>JS: SerializeToUtf8Bytes(this, GetType(), JsonSerializerOptions)
activate JS
JS->>Q: Q.CanConvert(IEnumerable<KeyValuePair<string,string>>)
Q-->>JS: true
JS->>Q: Q.Write(filteredDictionary)
activate Q
Q->>Q: Order keys ascending
Q->>JS: Emit ordered JSON object bytes
deactivate Q
JS-->>RC: Return UTF8 bytes
deactivate JS
RC->>RC: Compute SHA1(representative bytes)
Note: JsonIgnoreRuntimeResourceLabels filters labels before passing the dictionary into JsonOrderedKeysConverter's Write step. Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)✅ Unit Test PR creation complete.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Note that stable dictionary sorting for System.Text.Json was discussed a few years ago in dotnet/runtime#76008 The issue is still open but like many of the 5k+ opened issues in the .NET runtime repository, I don't expect it to be addressed any time soon. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/Testcontainers/Configurations/Commons/JsonIgnoreRuntimeResourceLabels.cs (1)
9-18: Excellent refactoring that eliminates duplication.The inheritance from
JsonOrderedKeysConverteris a clean design that separates concerns: this class filters runtime labels, the base class handles ordered serialization. The previous manual JSON writing code is eliminated, reducing duplication and maintenance burden.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/Testcontainers/Configurations/Commons/JsonIgnoreRuntimeResourceLabels.cs(1 hunks)src/Testcontainers/Configurations/Commons/JsonOrderedKeysConverter.cs(1 hunks)src/Testcontainers/Configurations/Commons/ResourceConfiguration.cs(2 hunks)tests/Testcontainers.Platform.Linux.Tests/ReusableResourceTest.cs(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/Testcontainers/Configurations/Commons/ResourceConfiguration.cs (1)
src/Testcontainers/Configurations/Commons/JsonOrderedKeysConverter.cs (1)
JsonOrderedKeysConverter(9-32)
src/Testcontainers/Configurations/Commons/JsonOrderedKeysConverter.cs (1)
src/Testcontainers/Configurations/Commons/JsonIgnoreRuntimeResourceLabels.cs (1)
Write(13-18)
src/Testcontainers/Configurations/Commons/JsonIgnoreRuntimeResourceLabels.cs (2)
src/Testcontainers/Configurations/Commons/JsonOrderedKeysConverter.cs (3)
JsonOrderedKeysConverter(9-32)Write(21-31)IReadOnlyDictionary(16-19)src/Testcontainers/Clients/TestcontainersClient.cs (3)
TestcontainersClient(21-433)TestcontainersClient(47-57)TestcontainersClient(59-75)
tests/Testcontainers.Platform.Linux.Tests/ReusableResourceTest.cs (2)
src/Testcontainers/Configurations/Commons/ResourceConfiguration.cs (1)
GetReuseHash(96-108)src/Testcontainers/Configurations/Commons/IResourceConfiguration.cs (1)
GetReuseHash(48-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: ci (Testcontainers.ClickHouse, ubuntu-24.04)
- GitHub Check: ci (Testcontainers.Bigtable, ubuntu-24.04)
- GitHub Check: ci (Testcontainers.ActiveMq, ubuntu-24.04)
- GitHub Check: ci (Testcontainers.Cassandra, ubuntu-24.04)
- GitHub Check: analyze (csharp)
🔇 Additional comments (6)
src/Testcontainers/Configurations/Commons/ResourceConfiguration.cs (2)
17-22: LGTM! Thread-safe static initialization.The static constructor correctly initializes the shared
JsonSerializerOptionswith the ordered keys converter. This pattern ensures thread-safe, one-time initialization.
98-98: Correct usage of ordered serialization options.The updated
GetReuseHashnow uses the staticJsonSerializerOptionscontaining the ordered keys converter, ensuring deterministic hash generation regardless of dictionary insertion order.src/Testcontainers/Configurations/Commons/JsonOrderedKeysConverter.cs (2)
11-14: CanConvert correctly handles multiple dictionary types.The check for
IEnumerable<KeyValuePair<string, string>>intentionally matches various dictionary implementations (Dictionary, ConcurrentDictionary, IReadOnlyDictionary, etc.), providing flexibility while ensuring ordered serialization for all.
21-31: Write method correctly implements ordered serialization.The use of
OrderBy(item => item.Key)ensures deterministic alphabetical ordering using the default ordinal string comparer, which is culture-invariant and stable. This is the core fix that ensures identical configurations produce identical hashes.tests/Testcontainers.Platform.Linux.Tests/ReusableResourceTest.cs (2)
105-122: Test correctly validates order-independent hashing.This test directly validates the PR's core objective: configurations with identical content but different insertion orders produce the same reuse hash. Testing both environment variables and labels provides good coverage.
124-138: Hardcoded hash provides regression protection.The hardcoded expected hash (with documented JSON format in the comment) serves as a regression test to detect any unintended changes to the serialization format. This intentional brittleness is valuable for maintaining stable reuse behavior across versions.
11985c7 to
6369cac
Compare
HofmeisterAn
left a comment
There was a problem hiding this comment.
Thanks! It's cool to see contributions from you again 😎.
What does this PR do?
This pull request ensures that the JSON used for hashing the configuration have object keys always in the same (alphabetical) order.
Why is it important?
Hashing JSON data requires the order of the keys to be stable in order to compare equivalent JSON objects by their hashes (which is what is done to identify reused containers).
Related issues
Closes #1553
How to test this PR
New tests have been added to ensure that hashing is stable, see ReuseHashTest.EqualTest.
Follow-ups
Note that this will break reused containers in the next Testcontainers version again, just like it broke between version 4.6.0 and 4.7.0. But reuse is still marked as experimental so we better fix this issue sooner than later.