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(); + } + } + +}