diff --git a/.github/workflows/doctests.yml b/.github/workflows/doctests.yml
new file mode 100644
index 000000000..e57e5498e
--- /dev/null
+++ b/.github/workflows/doctests.yml
@@ -0,0 +1,40 @@
+name: Documentation Tests
+
+on:
+ push:
+ tags-ignore:
+ - '*'
+ branches:
+ - 'main'
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ doctests:
+ runs-on: ubuntu-latest
+ services:
+ redis-stack:
+ image: redis/redis-stack-server:latest
+ options: >-
+ --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
+ ports:
+ - 6379:6379
+
+ steps:
+ - name: Checkout project
+ uses: actions/checkout@v4
+ - name: Set up Java
+ uses: actions/setup-java@v2
+ with:
+ java-version: '11'
+ distribution: 'temurin'
+ - name: Cache local Maven repository
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Run doctests
+ run: |
+ mvn -Pdoctests test
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index ddfc4877b..81bd33933 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1288,6 +1288,25 @@
+
+ doctests
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/examples/reactive/*Example.java
+ **/examples/async/*Example.java
+
+ true
+
+
+
+
+
+
diff --git a/src/test/java/io/redis/examples/async/StringExample.java b/src/test/java/io/redis/examples/async/StringExample.java
new file mode 100644
index 000000000..9be29395b
--- /dev/null
+++ b/src/test/java/io/redis/examples/async/StringExample.java
@@ -0,0 +1,127 @@
+// EXAMPLE: set_tutorial
+package io.redis.examples.async;
+
+import io.lettuce.core.*;
+import io.lettuce.core.api.async.RedisAsyncCommands;
+import io.lettuce.core.api.StatefulRedisConnection;
+
+// REMOVE_START
+import org.junit.jupiter.api.Test;
+// REMOVE_END
+
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+// REMOVE_START
+import static org.assertj.core.api.Assertions.assertThat;
+// REMOVE_END
+
+public class StringExample {
+
+ // REMOVE_START
+ @Test
+ // REMOVE_END
+ public void run() {
+ RedisClient redisClient = RedisClient.create("redis://localhost:6379");
+
+ try (StatefulRedisConnection connection = redisClient.connect()) {
+ RedisAsyncCommands asyncCommands = connection.async();
+
+ // STEP_START set_get
+ CompletableFuture setAndGet = asyncCommands.set("bike:1", "Deimos").thenCompose(v -> {
+ System.out.println(v); // OK
+ // REMOVE_START
+ assertThat(v).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.get("bike:1");
+ })
+ // REMOVE_START
+ .thenApply(res -> {
+ assertThat(res).isEqualTo("Deimos");
+ return res;
+ })
+ // REMOVE_END
+ .thenAccept(System.out::println) // Deimos
+ .toCompletableFuture();
+ // STEP_END
+
+ // STEP_START setnx_xx
+ CompletableFuture setnx = asyncCommands.setnx("bike:1", "bike").thenCompose(v -> {
+ System.out.println(v); // false (because key already exists)
+ // REMOVE_START
+ assertThat(v).isFalse();
+ // REMOVE_END
+ return asyncCommands.get("bike:1");
+ })
+ // REMOVE_START
+ .thenApply(res -> {
+ assertThat(res).isEqualTo("Deimos");
+ return res;
+ })
+ // REMOVE_END
+ .thenAccept(System.out::println) // Deimos (value is unchanged)
+ .toCompletableFuture();
+
+ // set the value to "bike" if it already exists
+ CompletableFuture setxx = asyncCommands.set("bike:1", "bike", SetArgs.Builder.xx())
+ // REMOVE_START
+ .thenApply(res -> {
+ assertThat(res).isEqualTo("OK");
+ return res;
+ })
+ // REMOVE_END
+ .thenAccept(System.out::println) // OK
+ .toCompletableFuture();
+ // STEP_END
+
+ // STEP_START mset
+ Map bikeMap = new HashMap<>();
+ bikeMap.put("bike:1", "Deimos");
+ bikeMap.put("bike:2", "Ares");
+ bikeMap.put("bike:3", "Vanth");
+
+ CompletableFuture mset = asyncCommands.mset(bikeMap).thenCompose(v -> {
+ System.out.println(v); // OK
+ return asyncCommands.mget("bike:1", "bike:2", "bike:3");
+ })
+ // REMOVE_START
+ .thenApply(res -> {
+ List> expected = new ArrayList<>(
+ Arrays.asList(KeyValue.just("bike:1", "Deimos"), KeyValue.just("bike:2", "Ares"),
+ KeyValue.just("bike:3", "Vanth")));
+ assertThat(res).isEqualTo(expected);
+ return res;
+ })
+ // REMOVE_END
+ .thenAccept(System.out::println) // [KeyValue[bike:1, Deimos], KeyValue[bike:2, Ares], KeyValue[bike:3,
+ // Vanth]]
+ .toCompletableFuture();
+ // STEP_END
+
+ // STEP_START incr
+ CompletableFuture incrby = asyncCommands.set("total_crashes", "0")
+ .thenCompose(v -> asyncCommands.incr("total_crashes")).thenCompose(v -> {
+ System.out.println(v); // 1
+ // REMOVE_START
+ assertThat(v).isEqualTo(1L);
+ // REMOVE_END
+ return asyncCommands.incrby("total_crashes", 10);
+ })
+ // REMOVE_START
+ .thenApply(res -> {
+ assertThat(res).isEqualTo(11L);
+ return res;
+ })
+ // REMOVE_END
+ .thenAccept(System.out::println) // 11
+ .toCompletableFuture();
+ // STEP_END
+
+ CompletableFuture.allOf(setAndGet, setnx, setxx, mset, incrby).join();
+
+ } finally {
+ redisClient.shutdown();
+ }
+ }
+
+}
diff --git a/src/test/java/io/redis/examples/reactive/StringExample.java b/src/test/java/io/redis/examples/reactive/StringExample.java
new file mode 100644
index 000000000..24d2443e4
--- /dev/null
+++ b/src/test/java/io/redis/examples/reactive/StringExample.java
@@ -0,0 +1,104 @@
+// EXAMPLE: set_tutorial
+package io.redis.examples.reactive;
+
+import io.lettuce.core.*;
+import io.lettuce.core.api.reactive.RedisReactiveCommands;
+import io.lettuce.core.api.StatefulRedisConnection;
+// REMOVE_START
+import org.junit.jupiter.api.Test;
+// REMOVE_END
+import reactor.core.publisher.Mono;
+
+import java.util.*;
+
+// REMOVE_START
+import static org.assertj.core.api.Assertions.assertThat;
+// REMOVE_END
+
+public class StringExample {
+
+ // REMOVE_START
+ @Test
+ // REMOVE_END
+ public void run() {
+ RedisClient redisClient = RedisClient.create("redis://localhost:6379");
+
+ try (StatefulRedisConnection connection = redisClient.connect()) {
+ RedisReactiveCommands reactiveCommands = connection.reactive();
+
+ // STEP_START set_get
+ Mono setAndGet = reactiveCommands.set("bike:1", "Deimos").doOnNext(v -> {
+ System.out.println(v); // OK
+ // REMOVE_START
+ assertThat(v).isEqualTo("OK");
+ // REMOVE_END
+ }).flatMap(v -> reactiveCommands.get("bike:1")).doOnNext(res -> {
+ // REMOVE_START
+ assertThat(res).isEqualTo("Deimos");
+ // REMOVE_END
+ System.out.println(res); // Deimos
+ }).then();
+ // STEP_END
+
+ // STEP_START setnx_xx
+ Mono setnx = reactiveCommands.setnx("bike:1", "bike").doOnNext(v -> {
+ System.out.println(v); // false (because key already exists)
+ // REMOVE_START
+ assertThat(v).isFalse();
+ // REMOVE_END
+ }).flatMap(v -> reactiveCommands.get("bike:1")).doOnNext(res -> {
+ // REMOVE_START
+ assertThat(res).isEqualTo("Deimos");
+ // REMOVE_END
+ System.out.println(res); // Deimos (value is unchanged)
+ }).then();
+
+ Mono setxx = reactiveCommands.set("bike:1", "bike", SetArgs.Builder.xx()).doOnNext(res -> {
+ // REMOVE_START
+ assertThat(res).isEqualTo("OK");
+ // REMOVE_END
+ System.out.println(res); // OK
+ }).then();
+ // STEP_END
+
+ // STEP_START mset
+ Map bikeMap = new HashMap<>();
+ bikeMap.put("bike:1", "Deimos");
+ bikeMap.put("bike:2", "Ares");
+ bikeMap.put("bike:3", "Vanth");
+
+ Mono mset = reactiveCommands.mset(bikeMap).doOnNext(System.out::println) // OK
+ .flatMap(v -> reactiveCommands.mget("bike:1", "bike:2", "bike:3").collectList()).doOnNext(res -> {
+ List> expected = new ArrayList<>(
+ Arrays.asList(KeyValue.just("bike:1", "Deimos"), KeyValue.just("bike:2", "Ares"),
+ KeyValue.just("bike:3", "Vanth")));
+ // REMOVE_START
+ assertThat(res).isEqualTo(expected);
+ // REMOVE_END
+ System.out.println(res); // [KeyValue[bike:1, Deimos], KeyValue[bike:2, Ares], KeyValue[bike:3, Vanth]]
+ }).then();
+ // STEP_END
+
+ // STEP_START incr
+ Mono incrby = reactiveCommands.set("total_crashes", "0").flatMap(v -> reactiveCommands.incr("total_crashes"))
+ .doOnNext(v -> {
+ System.out.println(v); // 1
+ // REMOVE_START
+ assertThat(v).isEqualTo(1L);
+ // REMOVE_END
+ }).flatMap(v -> reactiveCommands.incrby("total_crashes", 10)).doOnNext(res -> {
+ // REMOVE_START
+ assertThat(res).isEqualTo(11L);
+ // REMOVE_END
+ System.out.println(res); // 11
+ }).then();
+ // STEP_END
+
+ Mono.when(setAndGet, setnx, setxx, mset, incrby).block();
+
+ } finally {
+ redisClient.shutdown();
+ }
+ }
+
+}