From cbe9f402193ba3547adb1ef72970ed31ab81d369 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 30 Apr 2021 10:27:02 +0200 Subject: [PATCH] Migrate Redis examples to JUnit 5. See #583. --- .../redis/cluster/BasicUsageTests.java | 35 ++-- .../redis/commands/GeoOperationsTests.java | 33 ++-- .../redis/commands/KeyOperationsTests.java | 32 ++-- .../redis/commands/KeyCommandsTests.java | 27 +-- .../redis/operations/JacksonJsonTests.java | 41 ++-- .../redis/operations/ListOperationsTests.java | 24 +-- .../operations/ValueOperationsTests.java | 24 +-- .../repositories/PersonRepositoryTests.java | 71 +++---- .../reactive/ReactiveStreamApiTests.java | 39 ++-- .../redis/sync/SyncStreamApiTests.java | 34 ++-- redis/util/pom.xml | 5 +- .../test/condition/EnabledOnCommand.java | 53 ++++++ .../condition/EnabledOnCommandCondition.java | 73 +++++++ .../condition/EnabledOnRedisAvailable.java | 48 +++++ .../EnabledOnRedisAvailableCondition.java | 61 ++++++ .../EnabledOnRedisClusterAvailable.java | 48 +++++ .../EnabledOnRedisClusterCondition.java | 64 +++++++ .../EnabledOnRedisSentinelAvailable.java | 48 +++++ .../EnabledOnRedisSentinelCondition.java | 57 ++++++ .../condition/LettuceTestClientResources.java | 48 +++++ .../redis/test/condition/RedisConditions.java | 131 +++++++++++++ .../redis/test/condition/RedisDetector.java | 40 ++++ .../redis/test/condition/ShutdownQueue.java | 55 ++++++ .../redis/test/util/EmbeddedRedisServer.java | 87 --------- .../test/util/ManagedClientResources.java | 62 ------ .../test/util/RequiresRedisSentinel.java | 179 ------------------ .../redis/test/util/RequiresRedisServer.java | 131 ------------- 27 files changed, 876 insertions(+), 674 deletions(-) create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnCommand.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnCommandCondition.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisAvailable.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisAvailableCondition.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisClusterAvailable.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisClusterCondition.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisSentinelAvailable.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisSentinelCondition.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/LettuceTestClientResources.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/RedisConditions.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/RedisDetector.java create mode 100644 redis/util/src/main/java/example/springdata/redis/test/condition/ShutdownQueue.java delete mode 100644 redis/util/src/main/java/example/springdata/redis/test/util/EmbeddedRedisServer.java delete mode 100644 redis/util/src/main/java/example/springdata/redis/test/util/ManagedClientResources.java delete mode 100644 redis/util/src/main/java/example/springdata/redis/test/util/RequiresRedisSentinel.java delete mode 100644 redis/util/src/main/java/example/springdata/redis/test/util/RequiresRedisServer.java diff --git a/redis/cluster/src/test/java/example/springdata/redis/cluster/BasicUsageTests.java b/redis/cluster/src/test/java/example/springdata/redis/cluster/BasicUsageTests.java index 54b25664..b5e275dd 100644 --- a/redis/cluster/src/test/java/example/springdata/redis/cluster/BasicUsageTests.java +++ b/redis/cluster/src/test/java/example/springdata/redis/cluster/BasicUsageTests.java @@ -15,24 +15,20 @@ */ package example.springdata.redis.cluster; -import example.springdata.redis.test.util.RequiresRedisServer; +import static org.assertj.core.api.Assertions.*; + +import example.springdata.redis.test.condition.EnabledOnRedisClusterAvailable; import java.util.Arrays; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.dao.DataAccessException; -import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; /** * {@link BasicUsageTests} shows general usage of {@link RedisTemplate} and {@link RedisOperations} in a clustered @@ -40,17 +36,14 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Christoph Strobl */ -@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { AppConfig.class }) -public class BasicUsageTests { +@EnabledOnRedisClusterAvailable(port = 30001) +class BasicUsageTests { @Autowired RedisTemplate template; - public static @ClassRule RequiresRedisServer redisServerAvailable = RequiresRedisServer.listeningAt("127.0.0.1", - 30001); - - @Before - public void setUp() { + @BeforeEach + void setUp() { template.execute((RedisCallback) connection -> { connection.flushDb(); @@ -63,7 +56,7 @@ public class BasicUsageTests { * -> {@code SLOT 5798} served by {@code 127.0.0.1:30002} */ @Test - public void singleSlotOperation() { + void singleSlotOperation() { template.opsForValue().set("name", "rand al'thor"); // slot 5798 assertThat(template.opsForValue().get("name")).isEqualTo("rand al'thor"); @@ -75,7 +68,7 @@ public class BasicUsageTests { * -> {@code SLOT 14594} served by {@code 127.0.0.1:30003} */ @Test - public void multiSlotOperation() { + void multiSlotOperation() { template.opsForValue().set("name", "matrim cauthon"); // slot 5798 template.opsForValue().set("nickname", "prince of the ravens"); // slot 14594 @@ -89,7 +82,7 @@ public class BasicUsageTests { * -> {@code SLOT 5798} served by {@code 127.0.0.1:30002} */ @Test - public void fixedSlotOperation() { + void fixedSlotOperation() { template.opsForValue().set("{user}.name", "perrin aybara"); // slot 5474 template.opsForValue().set("{user}.nickname", "wolfbrother"); // slot 5474 @@ -105,7 +98,7 @@ public class BasicUsageTests { * -> {@code KEY nickname} served by {@code 127.0.0.1:30003} */ @Test - public void multiNodeOperation() { + void multiNodeOperation() { template.opsForValue().set("name", "rand al'thor"); // slot 5798 template.opsForValue().set("nickname", "dragon reborn"); // slot 14594 diff --git a/redis/example/src/test/java/example/springdata/redis/commands/GeoOperationsTests.java b/redis/example/src/test/java/example/springdata/redis/commands/GeoOperationsTests.java index b6ceb066..b86bc8d8 100644 --- a/redis/example/src/test/java/example/springdata/redis/commands/GeoOperationsTests.java +++ b/redis/example/src/test/java/example/springdata/redis/commands/GeoOperationsTests.java @@ -17,43 +17,34 @@ package example.springdata.redis.commands; import static org.assertj.core.api.Assertions.*; -import example.springdata.redis.test.util.RequiresRedisServer; +import example.springdata.redis.test.condition.EnabledOnCommand; -import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Distance; -import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Point; import org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit; -import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation; import org.springframework.data.redis.core.GeoOperations; import org.springframework.data.redis.core.RedisOperations; -import org.springframework.test.context.junit4.SpringRunner; /** * Show usage of redis geo-index operations using Template API provided by {@link GeoOperations}. * * @author Mark Paluch */ -@RunWith(SpringRunner.class) @SpringBootTest -public class GeoOperationsTests { - - // we only want to run this tests when redis is up an running - public static @ClassRule RequiresRedisServer requiresServer = RequiresRedisServer.onLocalhost().atLeast("3.2"); +@EnabledOnCommand("GEOADD") +class GeoOperationsTests { @Autowired RedisOperations operations; - GeoOperations geoOperations; + private GeoOperations geoOperations; - @Before - public void before() { + @BeforeEach + void before() { geoOperations = operations.opsForGeo(); @@ -66,7 +57,7 @@ public class GeoOperationsTests { * Look up points using a geo-index member as reference. */ @Test - public void geoRadiusByMember() { + void geoRadiusByMember() { var byDistance = geoOperations.geoRadiusByMember("Sicily", "Palermo", new Distance(100, DistanceUnit.KILOMETERS)); @@ -83,7 +74,7 @@ public class GeoOperationsTests { * Lookup points within a circle around coordinates. */ @Test - public void geoRadius() { + void geoRadius() { var circle = new Circle(new Point(13.583333, 37.316667), // new Distance(100, DistanceUnit.KILOMETERS)); @@ -96,7 +87,7 @@ public class GeoOperationsTests { * Calculate the distance between two geo-index members. */ @Test - public void geoDistance() { + void geoDistance() { var distance = geoOperations.geoDist("Sicily", "Catania", "Palermo", DistanceUnit.KILOMETERS); @@ -107,7 +98,7 @@ public class GeoOperationsTests { * Return the geo-hash. */ @Test - public void geoHash() { + void geoHash() { var geohashes = geoOperations.geoHash("Sicily", "Catania", "Palermo"); diff --git a/redis/example/src/test/java/example/springdata/redis/commands/KeyOperationsTests.java b/redis/example/src/test/java/example/springdata/redis/commands/KeyOperationsTests.java index 886d0171..16ee33b3 100644 --- a/redis/example/src/test/java/example/springdata/redis/commands/KeyOperationsTests.java +++ b/redis/example/src/test/java/example/springdata/redis/commands/KeyOperationsTests.java @@ -15,37 +15,29 @@ */ package example.springdata.redis.commands; -import example.springdata.redis.test.util.RequiresRedisServer; +import example.springdata.redis.test.condition.EnabledOnRedisAvailable; -import java.util.Iterator; -import java.util.Set; import java.util.UUID; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.test.context.junit4.SpringRunner; /** * Show usage of operations on redis keys using low level API provided by {@link RedisConnection}. * * @author Christoph Strobl */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class KeyOperationsTests { - - // we only want to run this tests when redis is up an running - public static @ClassRule RequiresRedisServer requiresServer = RequiresRedisServer.onLocalhost(); +@DataRedisTest +@EnabledOnRedisAvailable +class KeyOperationsTests { private static final String PREFIX = KeyOperationsTests.class.getSimpleName(); private static final String KEY_PATTERN = PREFIX + "*"; @@ -55,8 +47,8 @@ public class KeyOperationsTests { private RedisConnection connection; private RedisSerializer serializer = new StringRedisSerializer(); - @Before - public void setUp() { + @BeforeEach + void setUp() { this.connection = connectionFactory.getConnection(); } @@ -66,7 +58,7 @@ public class KeyOperationsTests { * All keys will be loaded within one single operation. */ @Test - public void iterateOverKeysMatchingPrefixUsingKeysCommand() { + void iterateOverKeysMatchingPrefixUsingKeysCommand() { generateRandomKeys(1000); @@ -80,7 +72,7 @@ public class KeyOperationsTests { * All keys will be loaded using multiple operations. */ @Test - public void iterateOverKeysMatchingPrefixUsingScanCommand() { + void iterateOverKeysMatchingPrefixUsingScanCommand() { generateRandomKeys(1000); diff --git a/redis/reactive/src/test/java/example/springdata/redis/commands/KeyCommandsTests.java b/redis/reactive/src/test/java/example/springdata/redis/commands/KeyCommandsTests.java index 7e56088e..1ff35d61 100644 --- a/redis/reactive/src/test/java/example/springdata/redis/commands/KeyCommandsTests.java +++ b/redis/reactive/src/test/java/example/springdata/redis/commands/KeyCommandsTests.java @@ -16,9 +16,8 @@ package example.springdata.redis.commands; import example.springdata.redis.RedisTestConfiguration; -import example.springdata.redis.test.util.RequiresRedisServer; +import example.springdata.redis.test.condition.EnabledOnRedisAvailable; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.nio.ByteBuffer; @@ -26,20 +25,17 @@ import java.time.Duration; import java.util.Collections; import java.util.UUID; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.redis.connection.ReactiveListCommands.PopResult; import org.springframework.data.redis.connection.ReactiveRedisConnection; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.connection.ReactiveStringCommands.SetCommand; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.util.ByteUtils; -import org.springframework.test.context.junit4.SpringRunner; /** * Show usage of reactive operations on Redis keys using low level API provided by @@ -47,12 +43,9 @@ import org.springframework.test.context.junit4.SpringRunner; * * @author Mark Paluch */ -@RunWith(SpringRunner.class) @SpringBootTest(classes = RedisTestConfiguration.class) -public class KeyCommandsTests { - - // we only want to run this tests when redis is up an running - public static @ClassRule RequiresRedisServer requiresServer = RequiresRedisServer.onLocalhost(); +@EnabledOnRedisAvailable +class KeyCommandsTests { private static final String PREFIX = KeyCommandsTests.class.getSimpleName(); private static final String KEY_PATTERN = PREFIX + "*"; @@ -62,8 +55,8 @@ public class KeyCommandsTests { private ReactiveRedisConnection connection; private RedisSerializer serializer = new StringRedisSerializer(); - @Before - public void setUp() { + @BeforeEach + void setUp() { this.connection = connectionFactory.getReactiveConnection(); } @@ -73,7 +66,7 @@ public class KeyCommandsTests { * All keys will be loaded within one single operation. */ @Test - public void iterateOverKeysMatchingPrefixUsingKeysCommand() { + void iterateOverKeysMatchingPrefixUsingKeysCommand() { generateRandomKeys(50); @@ -91,7 +84,7 @@ public class KeyCommandsTests { * Uses {@code RPUSH} to store an item inside a list and {@code BRPOP}
*/ @Test - public void storeToListAndPop() { + void storeToListAndPop() { var popResult = connection.listCommands() .brPop(Collections.singletonList(ByteBuffer.wrap("list".getBytes())), Duration.ofSeconds(5)); diff --git a/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java b/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java index 431aae72..c361dc95 100644 --- a/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java +++ b/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java @@ -18,22 +18,19 @@ package example.springdata.redis.operations; import example.springdata.redis.EmailAddress; import example.springdata.redis.Person; import example.springdata.redis.RedisTestConfiguration; -import example.springdata.redis.test.util.RequiresRedisServer; +import example.springdata.redis.test.condition.EnabledOnRedisAvailable; import lombok.extern.slf4j.Slf4j; -import reactor.core.publisher.Flux; import reactor.test.StepVerifier; import java.nio.ByteBuffer; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.core.ReactiveRedisOperations; import org.springframework.data.redis.util.ByteUtils; -import org.springframework.test.context.junit4.SpringRunner; /** * Show usage of reactive Template API on Redis lists using {@link ReactiveRedisOperations} with Jackson serialization. @@ -41,12 +38,9 @@ import org.springframework.test.context.junit4.SpringRunner; * @author Mark Paluch */ @Slf4j -@RunWith(SpringRunner.class) @SpringBootTest(classes = RedisTestConfiguration.class) -public class JacksonJsonTests { - - // we only want to run this tests when redis is up an running - public static @ClassRule RequiresRedisServer requiresServer = RequiresRedisServer.onLocalhost(); +@EnabledOnRedisAvailable +class JacksonJsonTests { @Autowired ReactiveRedisOperations typedOperations; @@ -60,7 +54,7 @@ public class JacksonJsonTests { * @see RedisTestConfiguration#reactiveJsonPersonRedisTemplate(ReactiveRedisConnectionFactory) */ @Test - public void shouldWriteAndReadPerson() { + void shouldWriteAndReadPerson() { StepVerifier.create(typedOperations.opsForValue().set("homer", new Person("Homer", "Simpson"))) // .expectNext(true) // @@ -70,11 +64,11 @@ public class JacksonJsonTests { .map(ByteUtils::getBytes) // .map(String::new); - StepVerifier.create(get) // + get.as(StepVerifier::create) // .expectNext("{\"firstname\":\"Homer\",\"lastname\":\"Simpson\"}") // .verifyComplete(); - StepVerifier.create(typedOperations.opsForValue().get("homer")) // + typedOperations.opsForValue().get("homer").as(StepVerifier::create) // .expectNext(new Person("Homer", "Simpson")) // .verifyComplete(); } @@ -87,9 +81,10 @@ public class JacksonJsonTests { * @see RedisTestConfiguration#reactiveJsonObjectRedisTemplate(ReactiveRedisConnectionFactory) */ @Test - public void shouldWriteAndReadPersonObject() { + void shouldWriteAndReadPersonObject() { - StepVerifier.create(genericOperations.opsForValue().set("homer", new Person("Homer", "Simpson"))) // + genericOperations.opsForValue().set("homer", new Person("Homer", "Simpson")) // + .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); @@ -97,11 +92,11 @@ public class JacksonJsonTests { .map(ByteUtils::getBytes) // .map(String::new); - StepVerifier.create(get) // + get.as(StepVerifier::create) // .expectNext("{\"_type\":\"example.springdata.redis.Person\",\"firstname\":\"Homer\",\"lastname\":\"Simpson\"}") // .verifyComplete(); - StepVerifier.create(genericOperations.opsForValue().get("homer")) // + genericOperations.opsForValue().get("homer").as(StepVerifier::create) // .expectNext(new Person("Homer", "Simpson")) // .verifyComplete(); } @@ -115,9 +110,10 @@ public class JacksonJsonTests { * @see RedisTestConfiguration#reactiveJsonObjectRedisTemplate(ReactiveRedisConnectionFactory) */ @Test - public void shouldWriteAndReadEmailObject() { + void shouldWriteAndReadEmailObject() { - StepVerifier.create(genericOperations.opsForValue().set("mail", new EmailAddress("homer@the-simpsons.com"))) // + genericOperations.opsForValue().set("mail", new EmailAddress("homer@the-simpsons.com")) // + .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); @@ -125,11 +121,12 @@ public class JacksonJsonTests { .map(ByteUtils::getBytes) // .map(String::new); - StepVerifier.create(get) // + get.as(StepVerifier::create) // .expectNext("{\"_type\":\"example.springdata.redis.EmailAddress\",\"address\":\"homer@the-simpsons.com\"}") // .verifyComplete(); - StepVerifier.create(genericOperations.opsForValue().get("mail")) // + genericOperations.opsForValue().get("mail") // + .as(StepVerifier::create) // .expectNext(new EmailAddress("homer@the-simpsons.com")) // .verifyComplete(); } diff --git a/redis/reactive/src/test/java/example/springdata/redis/operations/ListOperationsTests.java b/redis/reactive/src/test/java/example/springdata/redis/operations/ListOperationsTests.java index 81c2a68a..553d7534 100644 --- a/redis/reactive/src/test/java/example/springdata/redis/operations/ListOperationsTests.java +++ b/redis/reactive/src/test/java/example/springdata/redis/operations/ListOperationsTests.java @@ -16,7 +16,7 @@ package example.springdata.redis.operations; import example.springdata.redis.RedisTestConfiguration; -import example.springdata.redis.test.util.RequiresRedisServer; +import example.springdata.redis.test.condition.EnabledOnRedisAvailable; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -24,15 +24,12 @@ import reactor.test.StepVerifier; import java.time.Duration; import java.util.logging.Level; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.redis.core.ReactiveListOperations; import org.springframework.data.redis.core.ReactiveRedisOperations; -import org.springframework.test.context.junit4.SpringRunner; /** * Show usage of reactive Template API on Redis lists using {@link ReactiveRedisOperations}. @@ -40,17 +37,14 @@ import org.springframework.test.context.junit4.SpringRunner; * @author Mark Paluch */ @Slf4j -@RunWith(SpringRunner.class) @SpringBootTest(classes = RedisTestConfiguration.class) -public class ListOperationsTests { - - // we only want to run this tests when redis is up an running - public static @ClassRule RequiresRedisServer requiresServer = RequiresRedisServer.onLocalhost(); +@EnabledOnRedisAvailable +class ListOperationsTests { @Autowired ReactiveRedisOperations operations; - @Before - public void before() { + @BeforeEach + void before() { StepVerifier.create(operations.execute(it -> it.serverCommands().flushDb())).expectNext("OK").verifyComplete(); } @@ -58,7 +52,7 @@ public class ListOperationsTests { * A simple queue using Redis blocking list commands {@code BLPOP} and {@code LPUSH} to produce the queue message. */ @Test - public void shouldPollAndPopulateQueue() { + void shouldPollAndPopulateQueue() { var queue = "foo"; diff --git a/redis/reactive/src/test/java/example/springdata/redis/operations/ValueOperationsTests.java b/redis/reactive/src/test/java/example/springdata/redis/operations/ValueOperationsTests.java index 893daa74..d036285e 100644 --- a/redis/reactive/src/test/java/example/springdata/redis/operations/ValueOperationsTests.java +++ b/redis/reactive/src/test/java/example/springdata/redis/operations/ValueOperationsTests.java @@ -18,22 +18,19 @@ package example.springdata.redis.operations; import static org.assertj.core.api.Assertions.*; import example.springdata.redis.RedisTestConfiguration; -import example.springdata.redis.test.util.RequiresRedisServer; +import example.springdata.redis.test.condition.EnabledOnRedisAvailable; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.time.Duration; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.ReactiveRedisOperations; -import org.springframework.data.redis.core.ReactiveValueOperations; -import org.springframework.test.context.junit4.SpringRunner; /** * Show usage of reactive Template API on Redis strings using {@link ReactiveRedisOperations}. @@ -41,17 +38,14 @@ import org.springframework.test.context.junit4.SpringRunner; * @author Mark Paluch */ @Slf4j -@RunWith(SpringRunner.class) @SpringBootTest(classes = RedisTestConfiguration.class) -public class ValueOperationsTests { - - // we only want to run this tests when redis is up an running - public static @ClassRule RequiresRedisServer requiresServer = RequiresRedisServer.onLocalhost(); +@EnabledOnRedisAvailable +class ValueOperationsTests { @Autowired ReactiveRedisOperations operations; - @Before - public void before() { + @BeforeEach + void before() { StepVerifier.create(operations.execute(it -> it.serverCommands().flushDb())).expectNext("OK").verifyComplete(); } @@ -59,7 +53,7 @@ public class ValueOperationsTests { * Implement a simple caching sequence using {@code GET} and {@code SETEX} commands. */ @Test - public void shouldCacheValue() { + void shouldCacheValue() { var cacheKey = "foo"; diff --git a/redis/repositories/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java b/redis/repositories/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java index e94b8dd0..d169460e 100644 --- a/redis/repositories/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java +++ b/redis/repositories/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java @@ -17,24 +17,19 @@ package example.springdata.redis.repositories; import static org.assertj.core.api.Assertions.*; -import example.springdata.redis.test.util.EmbeddedRedisServer; -import example.springdata.redis.test.util.RequiresRedisServer; +import example.springdata.redis.test.condition.EnabledOnRedisAvailable; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.List; -import org.junit.After; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.RuleChain; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; import org.springframework.data.domain.Example; -import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.geo.Circle; @@ -45,25 +40,15 @@ import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.index.GeoIndexed; import org.springframework.data.redis.core.index.Indexed; -import org.springframework.test.context.junit4.SpringRunner; /** * @author Christoph Strobl * @author Oliver Gierke * @author Mark Paluch */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class PersonRepositoryTests { - - /** - * We need to have a Redis server instance available.
- * 1) Start/Stop an embedded instance or reuse an already running local installation
- * 2) Ignore tests if startup failed and no server running locally. - */ - public static @ClassRule RuleChain rules = RuleChain - .outerRule(EmbeddedRedisServer.runningAt(6379).suppressExceptions()) - .around(RequiresRedisServer.onLocalhost().atLeast("3.2")); +@DataRedisTest +@EnabledOnRedisAvailable +class PersonRepositoryTests { /** {@link Charset} for String conversion **/ private static final Charset CHARSET = StandardCharsets.UTF_8; @@ -74,17 +59,17 @@ public class PersonRepositoryTests { /* * Set of test users */ - Person eddard = new Person("eddard", "stark", Gender.MALE); - Person robb = new Person("robb", "stark", Gender.MALE); - Person sansa = new Person("sansa", "stark", Gender.FEMALE); - Person arya = new Person("arya", "stark", Gender.FEMALE); - Person bran = new Person("bran", "stark", Gender.MALE); - Person rickon = new Person("rickon", "stark", Gender.MALE); - Person jon = new Person("jon", "snow", Gender.MALE); + private Person eddard = new Person("eddard", "stark", Gender.MALE); + private Person robb = new Person("robb", "stark", Gender.MALE); + private Person sansa = new Person("sansa", "stark", Gender.FEMALE); + private Person arya = new Person("arya", "stark", Gender.FEMALE); + private Person bran = new Person("bran", "stark", Gender.MALE); + private Person rickon = new Person("rickon", "stark", Gender.MALE); + private Person jon = new Person("jon", "snow", Gender.MALE); - @Before - @After - public void setUp() { + @BeforeEach + @AfterEach + void setUp() { operations.execute((RedisConnection connection) -> { connection.flushDb(); @@ -97,7 +82,7 @@ public class PersonRepositoryTests { * Print out the hash structure within Redis. */ @Test - public void saveSingleEntity() { + void saveSingleEntity() { repository.save(eddard); @@ -110,7 +95,7 @@ public class PersonRepositoryTests { * Find entity by a single {@link Indexed} property value. */ @Test - public void findBySingleProperty() { + void findBySingleProperty() { flushTestUsers(); @@ -123,7 +108,7 @@ public class PersonRepositoryTests { * Find entities by multiple {@link Indexed} properties using {@literal AND}. */ @Test - public void findByMultipleProperties() { + void findByMultipleProperties() { flushTestUsers(); @@ -136,7 +121,7 @@ public class PersonRepositoryTests { * Find entities by multiple {@link Indexed} properties using {@literal OR}. */ @Test - public void findByMultiplePropertiesUsingOr() { + void findByMultiplePropertiesUsingOr() { flushTestUsers(); @@ -149,7 +134,7 @@ public class PersonRepositoryTests { * Find entities by {@link Example Query by Example}. */ @Test - public void findByQueryByExample() { + void findByQueryByExample() { flushTestUsers(); @@ -164,7 +149,7 @@ public class PersonRepositoryTests { * Find entities in range defined by {@link Pageable}. */ @Test - public void findByReturningPage() { + void findByReturningPage() { flushTestUsers(); @@ -183,7 +168,7 @@ public class PersonRepositoryTests { * Find entity by a single {@link Indexed} property on an embedded entity. */ @Test - public void findByEmbeddedProperty() { + void findByEmbeddedProperty() { var winterfell = new Address(); winterfell.setCountry("the north"); @@ -202,7 +187,7 @@ public class PersonRepositoryTests { * Find entity by a {@link GeoIndexed} property on an embedded entity. */ @Test - public void findByGeoLocationProperty() { + void findByGeoLocationProperty() { var winterfell = new Address(); winterfell.setCountry("the north"); @@ -236,7 +221,7 @@ public class PersonRepositoryTests { * Print out the hash structure within Redis. */ @Test - public void useReferencesToStoreDataToOtherObjects() { + void useReferencesToStoreDataToOtherObjects() { flushTestUsers(); diff --git a/redis/streams/src/test/java/example/springdata/redis/reactive/ReactiveStreamApiTests.java b/redis/streams/src/test/java/example/springdata/redis/reactive/ReactiveStreamApiTests.java index abe03268..e2d74920 100644 --- a/redis/streams/src/test/java/example/springdata/redis/reactive/ReactiveStreamApiTests.java +++ b/redis/streams/src/test/java/example/springdata/redis/reactive/ReactiveStreamApiTests.java @@ -18,16 +18,19 @@ package example.springdata.redis.reactive; import static org.assertj.core.api.Assertions.*; import static org.springframework.data.redis.connection.stream.StreamOffset.*; +import example.springdata.redis.SensorData; +import example.springdata.redis.test.condition.EnabledOnCommand; +import reactor.test.StepVerifier; + import java.time.Duration; -import example.springdata.redis.SensorData; -import example.springdata.redis.test.util.RequiresRedisServer; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration; +import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.stream.MapRecord; import org.springframework.data.redis.connection.stream.ReadOffset; @@ -36,26 +39,22 @@ import org.springframework.data.redis.connection.stream.StreamOffset; import org.springframework.data.redis.core.ReactiveStreamOperations; import org.springframework.data.redis.core.ReactiveStringRedisTemplate; import org.springframework.data.redis.stream.StreamReceiver; -import org.springframework.test.context.junit4.SpringRunner; -import reactor.core.publisher.Flux; -import reactor.test.StepVerifier; /** * @author Christoph Strobl */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class ReactiveStreamApiTests { - - public static @ClassRule RequiresRedisServer server = RequiresRedisServer.onLocalhost().atLeast("5.0"); +@DataRedisTest +@EnabledOnCommand("XADD") +@ImportAutoConfiguration(RedisReactiveAutoConfiguration.class) +class ReactiveStreamApiTests { @Autowired ReactiveStringRedisTemplate template; @Autowired StreamReceiver> streamReceiver; - ReactiveStreamOperations streamOps; + private ReactiveStreamOperations streamOps; - @Before - public void setUp() { + @BeforeEach + void setUp() { // clear all template.getConnectionFactory().getReactiveConnection() @@ -67,7 +66,7 @@ public class ReactiveStreamApiTests { } @Test - public void basics() { + void basics() { // XADD with fixed id streamOps.add(SensorData.RECORD_1234_0) @@ -111,7 +110,7 @@ public class ReactiveStreamApiTests { } @Test - public void continuousRead() { + void continuousRead() { var messages = streamReceiver.receive(fromStart(SensorData.KEY)); diff --git a/redis/streams/src/test/java/example/springdata/redis/sync/SyncStreamApiTests.java b/redis/streams/src/test/java/example/springdata/redis/sync/SyncStreamApiTests.java index c0054675..e09aa66d 100644 --- a/redis/streams/src/test/java/example/springdata/redis/sync/SyncStreamApiTests.java +++ b/redis/streams/src/test/java/example/springdata/redis/sync/SyncStreamApiTests.java @@ -18,17 +18,16 @@ package example.springdata.redis.sync; import static org.assertj.core.api.Assertions.*; import static org.springframework.data.redis.connection.stream.StreamOffset.*; -import java.util.List; +import example.springdata.redis.SensorData; +import example.springdata.redis.test.condition.EnabledOnCommand; + import java.util.concurrent.TimeUnit; -import example.springdata.redis.SensorData; -import example.springdata.redis.test.util.RequiresRedisServer; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.stream.MapRecord; import org.springframework.data.redis.connection.stream.ReadOffset; @@ -37,25 +36,22 @@ import org.springframework.data.redis.connection.stream.StreamOffset; import org.springframework.data.redis.core.StreamOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.stream.StreamMessageListenerContainer; -import org.springframework.test.context.junit4.SpringRunner; /** * @author Christoph Strobl * @author Mark Paluch */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class SyncStreamApiTests { - - public static @ClassRule RequiresRedisServer server = RequiresRedisServer.onLocalhost().atLeast("5.0"); +@DataRedisTest +@EnabledOnCommand("XADD") +class SyncStreamApiTests { @Autowired StringRedisTemplate template; @Autowired StreamMessageListenerContainer> messageListenerContainer; - StreamOperations streamOps; + private StreamOperations streamOps; - @Before - public void setUp() { + @BeforeEach + void setUp() { // clear all template.getConnectionFactory().getConnection().flushAll(); @@ -64,7 +60,7 @@ public class SyncStreamApiTests { } @Test - public void basics() { + void basics() { // XADD with fixed id var fixedId1 = streamOps.add(SensorData.RECORD_1234_0); @@ -97,7 +93,7 @@ public class SyncStreamApiTests { } @Test - public void continuousRead() throws InterruptedException { + void continuousRead() throws InterruptedException { // container autostart is disabled by default if (!messageListenerContainer.isRunning()) { diff --git a/redis/util/pom.xml b/redis/util/pom.xml index 3cdd19d3..09db062a 100644 --- a/redis/util/pom.xml +++ b/redis/util/pom.xml @@ -15,8 +15,9 @@ - junit - junit + org.junit.jupiter + junit-jupiter-api + compile diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnCommand.java b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnCommand.java new file mode 100644 index 00000000..323030bd --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnCommand.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledOnCommand} is used to signal that the annotated test class or test method is only enabled if + * the specified command is available. + *

+ * When applied at the class level, all test methods within that class will be enabled. + * + * @author Mark Paluch + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@ExtendWith(EnabledOnCommandCondition.class) +public @interface EnabledOnCommand { + + String host() default "localhost"; + + /** + * Redis port number. + */ + int port() default 6379; + + /** + * Name of the Redis command to be available. + */ + String value(); +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnCommandCondition.java b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnCommandCondition.java new file mode 100644 index 00000000..b5930581 --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnCommandCondition.java @@ -0,0 +1,73 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.*; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; + +import java.time.Duration; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.util.AnnotationUtils; + +/** + * {@link ExecutionCondition} for {@link EnabledOnCommandCondition @EnabledOnCommand}. + * + * @see EnabledOnCommandCondition + * @author Mark Paluch + * @author Christoph Strobl + */ +class EnabledOnCommandCondition implements ExecutionCondition { + + private static final ConditionEvaluationResult ENABLED_BY_DEFAULT = enabled("@EnabledOnCommand is not present"); + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(RedisConditions.class); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + + var optional = AnnotationUtils.findAnnotation(context.getElement(), EnabledOnCommand.class); + + if (!optional.isPresent()) { + return ENABLED_BY_DEFAULT; + } + + var annotation = optional.get(); + var command = annotation.value(); + + var store = context.getStore(NAMESPACE); + var conditions = store.getOrComputeIfAbsent(RedisConditions.class, ignore -> { + + var redisClient = RedisClient.create(LettuceTestClientResources.getSharedClientResources(), + RedisURI.create(annotation.host(), annotation.port())); + + var connection = redisClient.connect(); + var redisConditions = RedisConditions.of(connection); + + connection.close(); + redisClient.shutdown(Duration.ZERO, Duration.ZERO); + return redisConditions; + }, RedisConditions.class); + + var hasCommand = conditions.hasCommand(command); + return hasCommand ? enabled("Enabled on command " + command) + : disabled("Disabled, command " + command + " not available on Redis version " + conditions.getRedisVersion()); + } + +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisAvailable.java b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisAvailable.java new file mode 100644 index 00000000..d5216413 --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisAvailable.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledOnRedisAvailable} is used to signal that the annotated test class or test method is only + * enabled if Redis is running at {@link #port() port}. + *

+ * When applied at the class level, all test methods within that class will be enabled. + * + * @author Mark Paluch + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@ExtendWith(EnabledOnRedisAvailableCondition.class) +public @interface EnabledOnRedisAvailable { + + String host() default "localhost"; + + /** + * Redis port number. + */ + int port() default 6379; +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisAvailableCondition.java b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisAvailableCondition.java new file mode 100644 index 00000000..d3638230 --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisAvailableCondition.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.*; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.util.AnnotationUtils; + +/** + * {@link ExecutionCondition} for {@link EnabledOnRedisAvailableCondition @EnabledOnRedisAvailable}. + * + * @author Mark Paluch + * @author Christoph Strobl + * @see EnabledOnRedisAvailableCondition + */ +class EnabledOnRedisAvailableCondition implements ExecutionCondition { + + private static final ConditionEvaluationResult ENABLED_BY_DEFAULT = enabled( + "@EnabledOnRedisAvailable is not present"); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + + var optional = AnnotationUtils.findAnnotation(context.getElement(), EnabledOnRedisAvailable.class); + + if (!optional.isPresent()) { + return ENABLED_BY_DEFAULT; + } + + var annotation = optional.get(); + + try (var socket = new Socket()) { + socket.connect(new InetSocketAddress(annotation.host(), annotation.port()), 100); + + return enabled(String.format("Connection successful to Redis at %s:%d", annotation.host(), annotation.port())); + } catch (IOException e) { + return disabled(String.format("Cannot connect to Redis at %s:%d (%s)", annotation.host(), annotation.port(), e)); + } + } + +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisClusterAvailable.java b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisClusterAvailable.java new file mode 100644 index 00000000..72afbdaa --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisClusterAvailable.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledOnRedisClusterAvailable} is used to signal that the annotated test class or test method is only + * enabled if Redis Cluster is running. + *

+ * When applied at the class level, all test methods within that class will be enabled. + * + * @author Mark Paluch + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@ExtendWith(EnabledOnRedisClusterCondition.class) +public @interface EnabledOnRedisClusterAvailable { + + String host() default "localhost"; + + /** + * Redis port number. + */ + int port(); +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisClusterCondition.java b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisClusterCondition.java new file mode 100644 index 00000000..47cd9a54 --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisClusterCondition.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.*; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.util.AnnotationUtils; + +/** + * {@link ExecutionCondition} for {@link EnabledOnRedisClusterCondition @EnabledOnRedisClusterAvailable}. + * + * @author Mark Paluch + * @author Christoph Strobl + * @see EnabledOnRedisClusterCondition + */ +class EnabledOnRedisClusterCondition implements ExecutionCondition { + + private static final ConditionEvaluationResult ENABLED_BY_DEFAULT = enabled( + "@EnabledOnClusterAvailable is not present"); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + + var optional = AnnotationUtils.findAnnotation(context.getElement(), EnabledOnRedisClusterAvailable.class); + + if (!optional.isPresent()) { + return ENABLED_BY_DEFAULT; + } + + var annotation = optional.get(); + + try (var socket = new Socket()) { + + socket.connect(new InetSocketAddress(annotation.host(), annotation.port()), 100); + + return enabled( + String.format("Connection successful to Redis Cluster at %s:%d", annotation.host(), annotation.port())); + } catch (IOException e) { + return disabled( + String.format("Cannot connect to Redis Cluster at %s:%d (%s)", annotation.host(), annotation.port(), e)); + } + } + +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisSentinelAvailable.java b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisSentinelAvailable.java new file mode 100644 index 00000000..dfc6e4dd --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisSentinelAvailable.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * {@code @EnabledOnRedisSentinelAvailable} is used to signal that the annotated test class or test method is only + * enabled if Redis Sentinel is running. + *

+ * When applied at the class level, all test methods within that class will be enabled. + * + * @author Mark Paluch + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@ExtendWith(EnabledOnRedisSentinelCondition.class) +public @interface EnabledOnRedisSentinelAvailable { + + String host() default "localhost"; + + /** + * Sentinel port number. + */ + int value() default 26379; +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisSentinelCondition.java b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisSentinelCondition.java new file mode 100644 index 00000000..492c9a8b --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/EnabledOnRedisSentinelCondition.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.*; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.util.AnnotationUtils; + +/** + * {@link ExecutionCondition} for {@link EnabledOnRedisSentinelCondition @EnabledOnRedisSentinelAvailable}. + * + * @author Mark Paluch + * @author Christoph Strobl + * @see EnabledOnRedisSentinelCondition + */ +class EnabledOnRedisSentinelCondition implements ExecutionCondition { + + private static final ConditionEvaluationResult ENABLED_BY_DEFAULT = enabled( + "@EnabledOnSentinelAvailable is not present"); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + + var optional = AnnotationUtils.findAnnotation(context.getElement(), EnabledOnRedisSentinelAvailable.class); + + if (!optional.isPresent()) { + return ENABLED_BY_DEFAULT; + } + + var annotation = optional.get(); + + if (RedisDetector.canConnectToPort(annotation.host(), annotation.value())) { + + return enabled( + String.format("Connection successful to Redis Sentinel at %s:%d", annotation.host(), annotation.value())); + } + + return disabled(String.format("Cannot connect to Redis Sentinel at %s:%d", annotation.host(), annotation.value())); + + } +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/LettuceTestClientResources.java b/redis/util/src/main/java/example/springdata/redis/test/condition/LettuceTestClientResources.java new file mode 100644 index 00000000..caac6af5 --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/LettuceTestClientResources.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import io.lettuce.core.resource.ClientResources; +import io.lettuce.core.resource.DefaultClientResources; + +import java.util.concurrent.TimeUnit; + +/** + * Client-Resources suitable for testing. Every time a new {@link LettuceTestClientResources} instance is created, a + * {@link Runtime#addShutdownHook(Thread) shutdown hook} is added to close the client resources. + * + * @author Mark Paluch + * @author Christoph Strobl + */ +class LettuceTestClientResources { + + private static final ClientResources SHARED_CLIENT_RESOURCES; + + static { + + SHARED_CLIENT_RESOURCES = DefaultClientResources.builder().build(); + ShutdownQueue.INSTANCE.register(() -> SHARED_CLIENT_RESOURCES.shutdown(0, 0, TimeUnit.MILLISECONDS)); + } + + private LettuceTestClientResources() {} + + /** + * @return the client resources. + */ + public static ClientResources getSharedClientResources() { + return SHARED_CLIENT_RESOURCES; + } +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/RedisConditions.java b/redis/util/src/main/java/example/springdata/redis/test/condition/RedisConditions.java new file mode 100644 index 00000000..0ddcbf85 --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/RedisConditions.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; +import io.lettuce.core.models.command.CommandDetail; +import io.lettuce.core.models.command.CommandDetailParser; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +import org.springframework.data.util.Version; + +/** + * Collection of utility methods to test conditions during test execution. + * + * @author Mark Paluch + */ +class RedisConditions { + + private final Map commands; + private final Version version; + + private RedisConditions(RedisClusterCommands commands) { + + var result = CommandDetailParser.parse(commands.command()); + + this.commands = result.stream() + .collect(Collectors.toMap(commandDetail -> commandDetail.getName().toUpperCase(), CommandDetail::getArity)); + + var info = commands.info("server"); + + try { + + var inputStream = new ByteArrayInputStream(info.getBytes()); + var p = new Properties(); + p.load(inputStream); + + version = Version.parse(p.getProperty("redis_version")); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Create {@link RedisCommands} given {@link StatefulRedisConnection}. + * + * @param connection must not be {@code null}. + * @return + */ + public static RedisConditions of(StatefulRedisConnection connection) { + return new RedisConditions(connection.sync()); + } + + /** + * Create {@link RedisCommands} given {@link StatefulRedisClusterConnection}. + * + * @param connection must not be {@code null}. + * @return + */ + public static RedisConditions of(StatefulRedisClusterConnection connection) { + return new RedisConditions(connection.sync()); + } + + /** + * Create {@link RedisConditions} given {@link RedisCommands}. + * + * @param commands must not be {@code null}. + * @return + */ + public static RedisConditions of(RedisClusterCommands commands) { + return new RedisConditions(commands); + } + + /** + * @return the Redis {@link Version}. + */ + public Version getRedisVersion() { + return version; + } + + /** + * @param command + * @return {@code true} if the command is present. + */ + public boolean hasCommand(String command) { + return commands.containsKey(command.toUpperCase()); + } + + /** + * @param command command name. + * @param arity expected arity. + * @return {@code true} if the command is present with the given arity. + */ + public boolean hasCommandArity(String command, int arity) { + + if (!hasCommand(command)) { + throw new IllegalStateException("Unknown command: " + command + " in " + commands); + } + + return commands.get(command.toUpperCase()) == arity; + } + + /** + * @param versionNumber + * @return {@code true} if the version number is met. + */ + public boolean hasVersionGreaterOrEqualsTo(String versionNumber) { + return version.isGreaterThanOrEqualTo(Version.parse(versionNumber)); + } + +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/RedisDetector.java b/redis/util/src/main/java/example/springdata/redis/test/condition/RedisDetector.java new file mode 100644 index 00000000..a4fc1684 --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/RedisDetector.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + +/** + * Utility to detect Redis operation modes. + * + * @author Mark Paluch + */ +public class RedisDetector { + + public static boolean canConnectToPort(String host, int port) { + + try (var socket = new Socket()) { + socket.connect(new InetSocketAddress(host, port), 100); + + return true; + } catch (IOException e) { + return false; + } + } + +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/condition/ShutdownQueue.java b/redis/util/src/main/java/example/springdata/redis/test/condition/ShutdownQueue.java new file mode 100644 index 00000000..fe7c02ce --- /dev/null +++ b/redis/util/src/main/java/example/springdata/redis/test/condition/ShutdownQueue.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.redis.test.condition; + +import java.io.Closeable; +import java.util.LinkedList; + +/** + * Shutdown queue allowing ordered resource shutdown (LIFO). + * + * @author Mark Paluch + */ +enum ShutdownQueue { + + INSTANCE; + + static { + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + + Closeable closeable; + while ((closeable = INSTANCE.closeables.pollLast()) != null) { + try { + closeable.close(); + } catch (Exception o_O) { + // ignore + o_O.printStackTrace(); + } + } + + } + }); + } + + private final LinkedList closeables = new LinkedList<>(); + + public static void register(Closeable closeable) { + INSTANCE.closeables.add(closeable); + } +} diff --git a/redis/util/src/main/java/example/springdata/redis/test/util/EmbeddedRedisServer.java b/redis/util/src/main/java/example/springdata/redis/test/util/EmbeddedRedisServer.java deleted file mode 100644 index ce07551a..00000000 --- a/redis/util/src/main/java/example/springdata/redis/test/util/EmbeddedRedisServer.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2016-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package example.springdata.redis.test.util; - -import java.io.IOException; - -import org.junit.rules.ExternalResource; - -import redis.embedded.RedisServer; - -/** - * JUnit rule implementation to start and shut down an embedded Redis instance. - * - * @author Christoph Strobl - * @author Oliver Gierke - */ -public class EmbeddedRedisServer extends ExternalResource { - - private static final int DEFAULT_PORT = 6379; - private RedisServer server; - private int port = DEFAULT_PORT; - private boolean suppressExceptions = false; - - public EmbeddedRedisServer() { - - } - - protected EmbeddedRedisServer(int port) { - this.port = port; - } - - public static EmbeddedRedisServer runningAt(Integer port) { - return new EmbeddedRedisServer(port != null ? port : DEFAULT_PORT); - } - - public EmbeddedRedisServer suppressExceptions() { - this.suppressExceptions = true; - return this; - } - - /* - * (non-Javadoc) - * @see org.junit.rules.ExternalResource#before() - */ - @Override - protected void before() throws IOException { - - try { - - this.server = new RedisServer(this.port); - this.server.start(); - } catch (Exception e) { - if (!suppressExceptions) { - throw e; - } - } - } - - /* - * (non-Javadoc) - * @see org.junit.rules.ExternalResource#after() - */ - @Override - protected void after() { - - try { - this.server.stop(); - } catch (Exception e) { - if (!suppressExceptions) { - throw e; - } - } - } -} diff --git a/redis/util/src/main/java/example/springdata/redis/test/util/ManagedClientResources.java b/redis/util/src/main/java/example/springdata/redis/test/util/ManagedClientResources.java deleted file mode 100644 index 6d7ddf4b..00000000 --- a/redis/util/src/main/java/example/springdata/redis/test/util/ManagedClientResources.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2017-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package example.springdata.redis.test.util; - -import io.lettuce.core.resource.ClientResources; -import io.lettuce.core.resource.DefaultClientResources; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Utility to keep track of a single {@link ClientResources} instance used from test rules to prevent costly - * creation/disposal of threading resources. - * - * @author Mark Paluch - */ -class ManagedClientResources { - - private static final ManagedClientResources instance = new ManagedClientResources(); - - private final AtomicReference clientResources = new AtomicReference<>(); - - /** - * Obtain a managed instance of {@link ClientResources}. Allocates an instance if {@link ManagedClientResources} was - * not initialized already. - * - * @return the {@link ClientResources}. - */ - static ClientResources getClientResources() { - - var ref = instance.clientResources; - - var clientResources = ref.get(); - if (clientResources != null) { - return clientResources; - } - - clientResources = DefaultClientResources.create(); - - if (ref.compareAndSet(null, clientResources)) { - return clientResources; - } - - clientResources.shutdown(0, 0, TimeUnit.SECONDS); - - return ref.get(); - } - -} diff --git a/redis/util/src/main/java/example/springdata/redis/test/util/RequiresRedisSentinel.java b/redis/util/src/main/java/example/springdata/redis/test/util/RequiresRedisSentinel.java deleted file mode 100644 index 1cee9fb8..00000000 --- a/redis/util/src/main/java/example/springdata/redis/test/util/RequiresRedisSentinel.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2014-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package example.springdata.redis.test.util; - -import io.lettuce.core.RedisClient; -import io.lettuce.core.RedisURI; -import io.lettuce.core.api.StatefulRedisConnection; - -import java.time.Duration; - -import org.junit.internal.AssumptionViolatedException; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.springframework.data.redis.connection.RedisNode; -import org.springframework.data.redis.connection.RedisSentinelConfiguration; - -/** - * @author Christoph Strobl - * @author Mark Paluch - */ -public class RequiresRedisSentinel implements TestRule { - - public enum SentinelsAvailable { - ALL_ACTIVE, ONE_ACTIVE, NONE_ACTIVE - } - - private static final RedisSentinelConfiguration DEFAULT_SENTINEL_CONFIG = new RedisSentinelConfiguration() - .master("mymaster").sentinel("127.0.0.1", 26379).sentinel("127.0.0.1", 26380).sentinel("127.0.0.1", 26381); - - private RedisSentinelConfiguration sentinelConfig; - private SentinelsAvailable requiredSentinels; - - protected RequiresRedisSentinel(RedisSentinelConfiguration config) { - this.sentinelConfig = config; - } - - /** - * Create new {@link RequiresRedisSentinel} for given {@link RedisSentinelConfiguration}. - * - * @param config - * @return - */ - public static RequiresRedisSentinel forConfig(RedisSentinelConfiguration config) { - return new RequiresRedisSentinel(config != null ? config : DEFAULT_SENTINEL_CONFIG); - } - - /** - * Create new {@link RequiresRedisSentinel} using default configuration. - * - * @return - */ - public static RequiresRedisSentinel withDefaultConfig() { - return new RequiresRedisSentinel(DEFAULT_SENTINEL_CONFIG); - } - - public RequiresRedisSentinel sentinelsDisabled() { - - this.requiredSentinels = SentinelsAvailable.NONE_ACTIVE; - return this; - } - - /** - * Verifies all {@literal Sentinel} nodes are available. - * - * @return - */ - public RequiresRedisSentinel allActive() { - - this.requiredSentinels = SentinelsAvailable.ALL_ACTIVE; - return this; - } - - /** - * Verifies at least one {@literal Sentinel} node is available. - * - * @return - */ - public RequiresRedisSentinel oneActive() { - - this.requiredSentinels = SentinelsAvailable.ONE_ACTIVE; - return this; - } - - /** - * Will only check {@link RedisSentinelConfiguration} configuration in case {@link RequiresRedisSentinel} is detected - * on test method. - * - * @return - */ - public RequiresRedisSentinel dynamicModeSelection() { - this.requiredSentinels = null; - return this; - } - - /* - * (non-Javadoc) - * @see org.junit.rules.TestRule#apply(org.junit.runners.model.Statement, org.junit.runner.Description) - */ - @Override - public Statement apply(final Statement base, final Description description) { - - return new Statement() { - - @Override - public void evaluate() throws Throwable { - - if (description.isTest()) { - if (RequiresRedisSentinel.this.requiredSentinels != null) { - verify(RequiresRedisSentinel.this.requiredSentinels); - } - - } else { - verify(RequiresRedisSentinel.this.requiredSentinels); - } - - base.evaluate(); - } - }; - } - - private void verify(SentinelsAvailable verificationMode) { - - var failed = 0; - for (var node : sentinelConfig.getSentinels()) { - if (!isAvailable(node)) { - failed++; - } - } - - if (failed > 0) { - if (SentinelsAvailable.ALL_ACTIVE.equals(verificationMode)) { - throw new AssumptionViolatedException( - String.format("Expected all Redis Sentinels to respone but %s of %s did not responde", failed, - sentinelConfig.getSentinels().size())); - } - - if (SentinelsAvailable.ONE_ACTIVE.equals(verificationMode) && sentinelConfig.getSentinels().size() - 1 < failed) { - throw new AssumptionViolatedException( - "Expected at least one sentinel to respond but it seems all are offline - Game Over!"); - } - } - - if (SentinelsAvailable.NONE_ACTIVE.equals(verificationMode) && failed != sentinelConfig.getSentinels().size()) { - throw new AssumptionViolatedException( - String.format("Expected to have no sentinels online but found that %s are still alive.", - (sentinelConfig.getSentinels().size() - failed))); - } - } - - private boolean isAvailable(RedisNode node) { - - var redisClient = RedisClient.create(ManagedClientResources.getClientResources(), - RedisURI.create(node.getHost(), node.getPort())); - - try (var connection = redisClient.connect()) { - connection.sync().ping(); - } catch (Exception e) { - return false; - } finally { - redisClient.shutdown(Duration.ZERO, Duration.ZERO); - } - - return true; - } -} diff --git a/redis/util/src/main/java/example/springdata/redis/test/util/RequiresRedisServer.java b/redis/util/src/main/java/example/springdata/redis/test/util/RequiresRedisServer.java deleted file mode 100644 index f6f74e6f..00000000 --- a/redis/util/src/main/java/example/springdata/redis/test/util/RequiresRedisServer.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2014-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package example.springdata.redis.test.util; - -import io.lettuce.core.RedisClient; -import io.lettuce.core.RedisURI; -import io.lettuce.core.api.StatefulRedisConnection; - -import java.net.InetSocketAddress; -import java.net.Socket; -import java.time.Duration; - -import org.junit.AssumptionViolatedException; -import org.junit.rules.ExternalResource; -import org.springframework.data.redis.connection.lettuce.LettuceConverters; -import org.springframework.data.util.Version; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Implementation of junit rule {@link ExternalResource} to verify Redis (or at least something on the defined host and - * port) is up and running. Allows optionally to require a specific Redis version. - * - * @author Christoph Strobl - * @author Mark Paluch - */ -public class RequiresRedisServer extends ExternalResource { - - public static final Version NO_VERSION = Version.parse("0.0.0"); - - private int timeout = 30; - private Version requiredVersion = NO_VERSION; - - private final String host; - private final int port; - - private RequiresRedisServer(String host, int port) { - this(host, port, NO_VERSION); - } - - private RequiresRedisServer(String host, int port, Version requiredVersion) { - - this.host = host; - this.port = port; - this.requiredVersion = requiredVersion; - } - - /** - * Require a Redis instance listening on {@code localhost:6379}. - * - * @return - */ - public static RequiresRedisServer onLocalhost() { - return new RequiresRedisServer("localhost", 6379); - } - - /** - * Require a Redis instance listening {@code host:port}. - * - * @param host - * @param port - * @return - */ - public static RequiresRedisServer listeningAt(String host, int port) { - return new RequiresRedisServer(StringUtils.hasText(host) ? host : "127.0.0.1", port); - } - - /** - * Require a specific Redis version. - * - * @param version must not be {@literal null} or empty. - * @return - */ - public RequiresRedisServer atLeast(String version) { - - Assert.hasText(version, "Version must not be empty!"); - - return new RequiresRedisServer(host, port, Version.parse(version)); - } - - /* - * (non-Javadoc) - * @see org.junit.rules.ExternalResource#before() - */ - @Override - protected void before() throws Throwable { - - try (var socket = new Socket()) { - socket.setTcpNoDelay(true); - socket.setSoLinger(true, 0); - socket.connect(new InetSocketAddress(host, port), timeout); - } catch (Exception e) { - throw new AssumptionViolatedException(String.format("Seems as Redis is not running at %s:%s.", host, port), e); - } - - if (NO_VERSION.equals(requiredVersion)) { - return; - } - - var redisClient = RedisClient.create(ManagedClientResources.getClientResources(), - RedisURI.create(host, port)); - - try (var connection = redisClient.connect()) { - - var infoServer = connection.sync().info("server"); - var redisVersion = LettuceConverters.stringToProps().convert(infoServer).getProperty("redis_version"); - var runningVersion = Version.parse(redisVersion); - - if (runningVersion.isLessThan(requiredVersion)) { - throw new AssumptionViolatedException(String - .format("This test requires Redis version %s but you run version %s", requiredVersion, runningVersion)); - } - - } finally { - redisClient.shutdown(Duration.ZERO, Duration.ZERO); - } - } -}