Add DurableStateStore TCK to persistence-tck module#2833
Add DurableStateStore TCK to persistence-tck module#2833pjfanning wants to merge 3 commits intoapache:mainfrom
Conversation
Agent-Logs-Url: https://github.com/pjfanning/incubator-pekko/sessions/77803e79-3631-4111-98a8-4a1cf6f7beca Co-authored-by: pjfanning <11783444+pjfanning@users.noreply.github.com>
…d integration tests for all supported databases Agent-Logs-Url: https://github.com/pjfanning/incubator-pekko-persistence-jdbc/sessions/c3116ae6-042e-496b-b5bc-f480e95f7a01 Co-authored-by: pjfanning <11783444+pjfanning@users.noreply.github.com>
He-Pin
left a comment
There was a problem hiding this comment.
Deep CR: PR #2833 - Add DurableStateStore TCK to persistence-tck module
Architecture Review
This PR fills a notable gap in the Pekko persistence TCK. Prior to this change, only Journal and SnapshotStore had standardized TCK specs. DurableStateStore plugin authors had no reference test suite to validate their implementations against.
Design follows established patterns:
DurableStateStoreCapabilityFlagsmirrorsJournalCapabilityFlagsandSnapshotStoreCapabilityFlagsDurableStateStoreSpecfollows the same structure asJournalSpec(extendsPluginSpec, usesMayVerb/OptionalTests)JavaDurableStateStoreSpecfollows the identical boilerplate pattern asJavaJournalSpec- The reference test (
PersistenceTestKitDurableStateStoreTCKSpec) validates the TCK itself works
Capability Flag Design: The supportsDeleteWithRevisionCheck flag is well-chosen as the first capability flag. It represents a behavioral difference that not all stores support (some may not track revisions strictly). The OptionalTests mixin pattern handles this correctly - tests with CapabilityFlag.off() are skipped rather than failed.
Binary Compatibility
New public classes and traits are added to persistence-tck:
DurableStateStoreCapabilityFlagstraitDurableStateStoreSpecabstract classJavaDurableStateStoreSpecclass
These are all additive changes to the TCK module. Existing code is not affected. No MiMa exclusions needed.
Test Coverage Analysis
The TCK covers 5 mandatory tests and 1 optional:
Mandatory tests:
not find a non-existing object- basic read misspersist a state and retrieve it- basic write/read roundtripupdate a state- revision incrementingdelete a state- deletionhandle different persistence IDs independently- isolation
Optional test:
6. fail to delete a state when the revision does not match - optimistic locking
Missing test scenarios:
deleteObjectwith a revision that matches - should succeed. Currently only tests deletion with revision=2L after upsert with revision=1L, which implicitly tests the happy path, but does not explicitly verify thatdeleteObject(pid, 1L)afterupsertObject(pid, 1L, ...)behaves correctly (the revision mismatch case testsdeleteObject(pid, 99L)).- Concurrent upsert with same revision - what happens if two clients try to upsert the same persistence ID with the same revision? This is a key scenario for distributed systems.
- Large state values - no test for state objects that exceed typical size limits.
getObjectafterdeleteObjectwith wrong revision - the optional test verifies the delete fails, but does not verify the state is still accessible with the original revision. Actually wait, it does - the test checksresult.value shouldBe Some(value)andresult.revision shouldBe 1L. Good.- Revision ordering - what happens if you upsert with revision 5 then revision 3? Should revision 3 be rejected or accepted?
Code Quality
durableStateStore() default plugin ID: The method uses an empty string "" as the plugin ID:
DurableStateStoreRegistry(system).durableStateStoreFor[DurableStateUpdateStore[Any]]("")This is actually correct behavior - when an empty string is passed, DurableStateStoreRegistry falls back to the default plugin configured under pekko.persistence.state.plugin. This matches how JournalSpec works (it uses the default journal plugin from config). Plugin authors configure the plugin path in their test config, and the TCK picks it up automatically.
intercept[Exception]: The optional test uses intercept[Exception] which is broad. However, this is intentional - different store implementations may throw different exception types (e.g., OptimisticLockingException, IllegalStateException, or a store-specific exception). The TCK cannot mandate a specific exception type without constraining plugin APIs. This is acceptable for a TCK.
Java API Coverage
The JavaDurableStateStoreSpec provides a Java-consumable TCK following the established pattern. The boilerplate override def methods (14 of them) are necessary for JUnit compatibility with ScalaTest. This is consistent with JavaJournalSpec and JavaSnapshotStoreSpec.
Suggestions
- Consider adding a test for
deleteObjectwith a matching revision to explicitly verify the happy path deletion. - Consider documenting in the scaladoc what plugin authors should expect regarding revision ordering behavior (is it enforced by the TCK or left to the implementation?).
- The PR description references #2831 - ensure that issue is linked properly in the final PR.
Summary
See #2831
Adds a standardised Technology Compatibility Kit (TCK) for
DurableStateStoreimplementations to thepersistence-tckmodule. This allows plugin authors (such as those working onpekko-persistence-jdbc,pekko-persistence-r2dbc, andpekko-persistence-cassandra) to include a consistent test suite in their projects.Changes
persistence-tck/src/main/scala/org/apache/pekko/persistence/CapabilityFlags.scalaDurableStateStoreCapabilityFlagstrait with asupportsDeleteWithRevisionCheckcapability flag, following the same pattern asJournalCapabilityFlagsandSnapshotStoreCapabilityFlags.persistence-tck/src/main/scala/org/apache/pekko/persistence/state/DurableStateStoreSpec.scala(new)Scala TCK spec that plugin authors extend. Tests:
getObjecton a non-existing persistence ID returnsNoneupsertObjectfollowed bygetObjectreturns the stored value and correct revisiondeleteObject(persistenceId, revision)makes the state unavailablesupportsDeleteWithRevisionCheck): deleting with a mismatched revision fails and leaves the original state intactpersistence-tck/src/main/scala/org/apache/pekko/persistence/japi/state/JavaDurableStateStoreSpec.scala(new)Java/JUnit-consumable wrapper around
DurableStateStoreSpec, following the same pattern asJavaJournalSpecandJavaSnapshotStoreSpec.persistence-testkit/src/test/scala/…/PersistenceTestKitDurableStateStoreTCKSpec.scala(new)Reference test implementation that exercises the new TCK using the existing in-memory
PersistenceTestKitDurableStateStore, verifying the TCK itself works correctly.Usage by plugin authors
Agent-Logs-Url: https://github.com/pjfanning/incubator-pekko/sessions/77803e79-3631-4111-98a8-4a1cf6f7beca