Skip to content

[Commands API interface consistency #1] tests and non-breaking fixes - #3865

Merged
uglide merged 6 commits into
mainfrom
im/api-consistency-tests
Aug 6, 2026
Merged

[Commands API interface consistency #1] tests and non-breaking fixes#3865
uglide merged 6 commits into
mainfrom
im/api-consistency-tests

Conversation

@uglide

@uglide uglide commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Introduces a reflection-based unit-test suite that verifies the sync, async, reactive, Kotlin coroutine and cluster node-selection command interfaces stay in lockstep, replacing the consistency-by-regeneration guarantee of the template-based API generators (removal of the generators follows in #3866).

What the suite checks

  • Sync ↔ async — method parity in both directions plus RedisFuture<T> return-type wrapping. This is load-bearing at runtime: the sync API is a dynamic proxy over async (FutureSyncInvocationHandler), so a mismatch previously failed only at call time.
  • Sync ↔ reactive — parity plus the Mono/Flux mapping rules (collections → Flux, forced-Flux, Value<>-wrapped elements) and @Deprecated on streaming-channel variants.
  • Sync ↔ Kotlin coroutines — presence plus suspend/Flow shape (via kotlin-reflect, new test-scope dependency managed by the Kotlin BOM).
  • Node-selectionExecutions/AsyncExecutions wrapping and the exclusion list, with a staleness check on the list itself.
  • AggregatesRedisCommands, RedisClusterCommands, NodeSelectionCommands, RedisClusterCoroutinesCommands etc. extend every command group they must cover, and the methods declared directly on the aggregates (auth, select, the CLUSTER commands, …) are parity-checked against their async/reactive counterparts.
  • Builder coverage — every command has a same-named method on its command builder and every command-producing builder method is reachable from an interface.

Structure: CommandInterfaces (group catalog — the single place to register a new command group), KnownApiDeviations (all intentional irregularities, ported from the generator rule tables), TypeSignatures (shared reflection helpers). Supersedes SyncAsyncApiConvergenceUnitTests.

Deviations found and fixed

Calibrating the suite against the committed interfaces surfaced real drift, fixed in the second commit:

  • RedisCoroutinesCommands did not extend RediSearchCoroutinesCommands (the impl already delegated to it)
  • Kotlin coroutine API was missing bitopDiff/bitopDiff1/bitopAndor/bitopOne, hgetex(key, fields), hsetex(key, map) and the xackdel/xdelex stream commands
  • reactive hgetdel/hgetex streaming-channel variants were missing @Deprecated

Deviations found and deferred

The node-selection aggregate wiring drift (missing ACL/Array groups; NodeSelectionAsyncCommands extending the sync NodeSelectionStreamCommands) is not fixed here — correcting it changes return types on AsyncNodeSelection and is therefore a breaking change. It is exempted via KnownApiDeviations.NODE_SELECTION_AGGREGATE_PENDING (with a documenting comment) and fixed in #3867, targeted for the 8.0.0 release.

Note: the first commit intentionally fails the new tests (it demonstrates the drift); the second commit makes the suite green.

Make sure that:

  • You have read the contribution guidelines.
  • You have created a feature request first to discuss your contribution intent. Please reference the feature request ticket number in the pull request.
  • You applied code formatting rules using the mvn formatter:format target. Don't submit any formatting related changes.
  • You submit test cases (unit or integration tests) that back your changes.

🤖 Generated with Claude Code


Note

Low Risk
Changes are mostly tests and public interface/Javadoc declarations; runtime behavior is unchanged aside from newly exposed Kotlin coroutine methods.

Overview
Adds a reflection- and source-based test suite so sync, async, reactive, Kotlin coroutines, cluster node-selection, aggregate umbrellas, command builders, and Javadoc stay aligned—replacing the old SyncAsyncApiConvergenceUnitTests and the consistency guarantee that used to come from regenerating templates.

The suite is driven by hand-maintained catalogs (CommandInterfaces, AggregateInterfaces) and deviation registries (KnownApiDeviations, KnownKotlinApiDeviations), with staleness checks so opt-out entries cannot rot. kotlin-reflect is added as a test dependency for coroutine shape checks.

API fixes surfaced while calibrating the suite: RedisCoroutinesCommands now extends RediSearchCoroutinesCommands; cluster coroutine aggregates pick up Function/JSON/VectorSet/Array/RediSearch; Kotlin gains BITOP variants, simpler hsetex/hgetex, and xackdel/xdelex; reactive hgetdel/hgetex channel overloads are @Deprecated. Generated interfaces get broad Javadoc cleanup (@param, @deprecated on getStatefulConnection, etc.). Node-selection aggregate gaps remain documented in deviations for a later breaking fix (#3867).

Reviewed by Cursor Bugbot for commit 8354828. Bugbot is set up for automated code reviews on this repo. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f29cddcb82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/test/java/io/lettuce/core/api/consistency/CommandInterfaces.java Outdated
Comment thread src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionAsyncCommands.java Outdated
@uglide
uglide force-pushed the im/api-consistency-tests branch from f29cddc to 2b19668 Compare July 28, 2026 16:22
@uglide uglide changed the title Commands API interface consistency tests [Commands API interface consistency #1] tests and non-breaking fixes Jul 28, 2026
@uglide
uglide force-pushed the im/api-consistency-tests branch 2 times, most recently from 7d09bed to fddac9d Compare July 28, 2026 16:38

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fddac9d. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fddac9d08f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/lettuce/core/api/reactive/RedisHashReactiveCommands.java Outdated
@uglide
uglide force-pushed the im/api-consistency-tests branch from 16eebdb to 622d675 Compare July 28, 2026 16:57

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 622d6750ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* This catalog is the single place where a new command group must be registered so that the API consistency test suite covers
* it.
*/
public enum CommandInterfaces {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i need more time to digest what is in this PR but one thing i noted is lack of checks around pub/sub interfaces.

@atakavci atakavci left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

other than those small notes i left, LGTM


@ParameterizedTest
@EnumSource(CommandInterfaces.class)
void syncMethodsExistOnAsyncApiWithWrappedReturnType(CommandInterfaces group) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we need same type of tests for among aggregate intefaces; RedisClusterCoroutinesCommands almost has no coverage with those methods in RedisClusterCommands

@uglide
uglide force-pushed the im/api-consistency-tests branch from 4edb596 to 950c691 Compare July 30, 2026 14:16

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 68a27c866d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (method.isSynthetic() || Modifier.isStatic(method.getModifiers()) || Modifier.isPrivate(method.getModifiers())) {
continue;
}
if (io.lettuce.core.protocol.RedisCommand.class.isAssignableFrom(method.getReturnType())) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Import the referenced Java types

Replace this package-qualified RedisCommand reference with an import and simple name; the same new test package also qualifies java.util.Collections in NodeSelectionConsistencyUnitTests and reflection types in KnownApiDeviations. These usages violate the repository’s explicit Java convention and make the new consistency suite itself inconsistent with the required import-driven style.

AGENTS.md reference: AGENTS.md:L150-L152

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8b871be753

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

* @deprecated since 7.7 in favor of consuming large results through the {@link org.reactivestreams.Publisher} returned by
* {@link #hgetex}.
*/
@Deprecated

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Move generated API edits back to templates

If the API generators are run before the generator-removal PR lands, this deprecation and the sibling Java API/Javadoc fixes will be overwritten because the corresponding src/main/templates/io/lettuce/core/api/RedisHashCommands.java entry still has no @Deprecated/@deprecated text; the new consistency suite would then fail its streaming-channel deprecation check. The command interfaces with @generated markers need their source template updated and regenerated rather than patched directly.

AGENTS.md reference: AGENTS.md:L89-L93

Useful? React with 👍 / 👎.

Comment on lines +172 to +174
val unmatched = keys.filterNot { key ->
syncInterfaces.any { syncGroup ->
TypeSignatures.apiMethods(syncGroup).any { KnownApiDeviations.contains(setOf(key), it, syncGroup) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Verify pending coroutine entries still hide real gaps

For tables such as AGGREGATE_PENDING, this staleness check only proves that the key still names a sync method; it does not prove that the coroutine counterpart is still absent. If RedisClusterCoroutinesCommands.clusterLinks is added but the pending entry is left behind, aggregateDeclaredMethodsExistOnCoroutinesAggregate continues to skip shape verification and this test still passes, so the opt-out can permanently hide the newly implemented aggregate method. Make staleness check the condition each table suppresses, not just the existence of the reference method.

Useful? React with 👍 / 👎.

"cfInsertNxValues", "topKAddValues", "topKIncrByValues", "hscanStreaming", "hscanNoValuesStreaming",
"scanStreaming", "sscanStreaming", "zscanStreaming", "hello", "sync", "clusterAddslots", "clusterDelslots",
// connection-level commands exposed via StatefulRedisConnection, not via the command interfaces
"select", "swapdb");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include standalone aggregate commands in builder coverage

select and swapdb are public command methods on the standalone aggregates and have RedisCommandBuilder counterparts, but this table marks the builder methods as internal because builderMethodsAreReachableFromCommandInterfaces() never collects RedisCommands/RedisAsyncCommands. If those aggregate API entries are dropped while the builder methods remain, the new builder-coverage suite still passes, so aggregate-only commands are not actually protected; collect the standalone aggregates instead of whitelisting these names.

Useful? React with 👍 / 👎.

Comment on lines +71 to +75
for (CommandInterfaces group : CommandInterfaces.values()) {
Set<String> names = reachableNamesByBuilder.computeIfAbsent(group.commandBuilderClassName(),
builder -> new HashSet<>());
collectMethodNames(names, group.sync());
collectMethodNames(names, group.async());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add PubSub to command-builder coverage

Because this coverage map is built only from CommandInterfaces plus the cluster aggregates, it never creates an entry for PubSubCommandBuilder or collects the psubscribe/subscribe family declared on RedisPubSubCommands. A PubSub builder method can therefore be removed or left dead while this new bidirectional builder-coverage suite remains green; include the PubSub aggregate and its dedicated builder in the same coverage path.

Useful? React with 👍 / 👎.

@uglide
uglide force-pushed the im/api-consistency-tests branch from 8b871be to 463a98b Compare August 3, 2026 12:25
uglide and others added 6 commits August 6, 2026 15:40
Introduce reflection-based unit tests that verify the sync, async,
reactive, Kotlin coroutine and cluster node-selection command
interfaces stay in lockstep, replacing the consistency-by-regeneration
guarantee of the template-based API generators:

- CommandInterfaces: catalog of all command groups and their flavors
- KnownApiDeviations: single registry of intentional deviations,
  ported from the generator rule tables
- TypeSignatures: shared reflection and type-normalization helpers
- SyncAsync/SyncReactive/NodeSelection/AggregateInterface/
  CommandBuilderCoverage/KotlinCoroutines consistency tests

The suite checks method parity in both directions, the per-flavor
return-type mapping (RedisFuture, Mono/Flux, suspend/Flow,
Executions), aggregate interface wiring and interface-to-command-
builder coverage. Sync/async parity is load-bearing at runtime: the
sync API is a dynamic proxy over async (FutureSyncInvocationHandler).

Supersedes SyncAsyncApiConvergenceUnitTests. Adds kotlin-reflect as a
test dependency.

Note: at this commit the suite documents real drift in the committed
interfaces — several tests fail intentionally; the deviations are
fixed in the follow-up commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real drift found by the new consistency tests, accumulated while the
API generators were out of use:

- RedisCoroutinesCommands did not extend RediSearchCoroutinesCommands
  (the implementation already delegated to it)
- Kotlin coroutine API was missing bitopDiff/bitopDiff1/bitopAndor/
  bitopOne, hgetex(key, fields), hsetex(key, map) and the
  xackdel/xdelex stream commands
- reactive hgetdel/hgetex streaming-channel variants were missing
  their @deprecated marker

The node-selection aggregate wiring drift (missing ACL/Array groups,
async aggregate extending the sync Stream interface) is exempted via
KnownApiDeviations.NODE_SELECTION_AGGREGATE_PENDING — fixing it is a
breaking change scheduled for the 8.0 release.

Makes the API consistency test suite pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- verify the methods declared directly on the aggregate interfaces
  (auth, select, the CLUSTER commands, ...) against their async and
  reactive counterparts, restoring coverage the removed
  SyncAsyncApiConvergenceUnitTests had via RedisCommands.getMethods()
- include RedisClusterCoroutinesCommands in the aggregate wiring check
  and add its missing FUNCTION/JSON/VECTOR_SET/ARRAY/SEARCH groups
  (experimental API; the impl already delegates per group)
- keep the Kotlin generator's FLOW_METHODS in sync with the hand-added
  Flow methods for as long as the generators still exist
- add behavioral coroutine integration tests for the new
  hash-field-expiry, BITOP and stream-deletion commands

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Erased-parameter matching alone would accept flavors whose generic
parameter signatures diverge (e.g. Map<K, V> vs Map<V, K>), and
nothing compared @deprecated across the Java flavors. The suite now
verifies both for sync/async, sync/reactive, node-selection and the
aggregate-declared methods. The reactive dispatch parameter mutation
(CommandOutput<K, V, ?>) and the intentional deprecation of the
CommandOutput-based node-selection dispatch overloads are recorded in
KnownApiDeviations.

Fixes surfaced by the new checks: RedisAdvancedClusterReactiveCommands
.keysLegacy and RedisAdvancedClusterAsyncCommands.masters carried a
@deprecated tag without the matching @deprecated annotation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Coroutine return types are effectively unchecked.
Coroutine parameter generics unchecked.
The catalog isn't verified complete. CommandInterfaces calls itself "the single place where a new command group must be registered", but nothing asserts every io.lettuce.core.api.sync.*Commands interface appears in it. A group added without registering is invisible
PubSub isn't covered at all.
Coroutine aggregates only get an extends check. AGGREGATE_FLAVORS (:101-105) is sync/async/reactive triples; RedisCoroutinesCommands/RedisClusterCoroutinesCommands declare their own methods, and the branch had to hand-patch the latter.
Javadoc is out of scope entirely
Staleness is tested for one table only.
@uglide
uglide force-pushed the im/api-consistency-tests branch from 463a98b to 8354828 Compare August 6, 2026 13:40
@uglide
uglide merged commit 3a81884 into main Aug 6, 2026
10 checks passed
@uglide
uglide deleted the im/api-consistency-tests branch August 6, 2026 13:52
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.

3 participants