Skip to content

GH-3840: Migrate Redis tests to Testcontainers #3847

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ configure(javaProjects) { subproject ->
exclude group: 'org.hamcrest'
}
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testImplementation 'org.junit.jupiter:junit-jupiter-params'
testImplementation("com.willowtreeapps.assertk:assertk-jvm:$assertkVersion") {
exclude group: 'org.jetbrains.kotlin'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2022 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.
Expand Down Expand Up @@ -28,17 +28,18 @@
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.geode.cache.CacheFactory;
import org.junit.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.gemfire.metadata.GemfireMetadataStore;
import org.springframework.integration.jdbc.metadata.JdbcMetadataStore;
import org.springframework.integration.metadata.ConcurrentMetadataStore;
import org.springframework.integration.redis.RedisContainerTest;
import org.springframework.integration.redis.metadata.RedisMetadataStore;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
Expand All @@ -48,23 +49,30 @@
* @author Gary Russell
* @author Artem Bilan
* @author Bojan Vukasovic
* @author Artem Vozhdayenko
*
* @since 4.0
*
*/
public class PersistentAcceptOnceFileListFilterExternalStoreTests extends RedisAvailableTests {
public class PersistentAcceptOnceFileListFilterExternalStoreTests implements RedisContainerTest {

static RedisConnectionFactory redisConnectionFactory;

@BeforeAll
static void setupConnectionFactory() {
redisConnectionFactory = RedisContainerTest.connectionFactory();
}

@Test
@RedisAvailable
public void testFileSystemWithRedisMetadataStore() throws Exception {
RedisTemplate<String, ?> template = new RedisTemplate<>();
template.setConnectionFactory(this.getConnectionFactoryForTest());
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
template.delete("persistentAcceptOnceFileListFilterRedisTests");

try {
this.testFileSystem(new RedisMetadataStore(this.getConnectionFactoryForTest(),
this.testFileSystem(new RedisMetadataStore(redisConnectionFactory,
"persistentAcceptOnceFileListFilterRedisTests"));
}
finally {
Expand Down Expand Up @@ -95,8 +103,8 @@ public void testFileSystemWithJdbcMetadataStore() throws Exception {
List<Map<String, Object>> metaData = new JdbcTemplate(dataSource)
.queryForList("SELECT * FROM INT_METADATA_STORE");

assertThat(metaData.size()).isEqualTo(1);
assertThat(metaData.get(0).get("METADATA_VALUE")).isEqualTo("43");
assertThat(metaData).hasSize(1);
assertThat(metaData.get(0)).containsEntry("METADATA_VALUE", "43");
}
finally {
dataSource.shutdown();
Expand Down Expand Up @@ -127,28 +135,28 @@ private void testFileSystem(ConcurrentMetadataStore store) throws Exception {
final FileSystemPersistentAcceptOnceFileListFilter filter =
new FileSystemPersistentAcceptOnceFileListFilter(store, "foo:");
final File file = File.createTempFile("foo", ".txt");
assertThat(filter.filterFiles(new File[] { file }).size()).isEqualTo(1);
assertThat(filter.filterFiles(new File[] {file})).hasSize(1);
String ts = store.get("foo:" + file.getAbsolutePath());
assertThat(ts).isEqualTo(String.valueOf(file.lastModified()));
assertThat(filter.filterFiles(new File[] { file }).size()).isEqualTo(0);
file.setLastModified(file.lastModified() + 5000L);
assertThat(filter.filterFiles(new File[] { file }).size()).isEqualTo(1);
assertThat(filter.filterFiles(new File[] {file})).isEmpty();
assertThat(file.setLastModified(file.lastModified() + 5000L)).isTrue();
assertThat(filter.filterFiles(new File[] {file})).hasSize(1);
ts = store.get("foo:" + file.getAbsolutePath());
assertThat(ts).isEqualTo(String.valueOf(file.lastModified()));
assertThat(filter.filterFiles(new File[] { file }).size()).isEqualTo(0);
assertThat(filter.filterFiles(new File[] {file})).isEmpty();

suspend.set(true);
file.setLastModified(file.lastModified() + 5000L);
assertThat(file.setLastModified(file.lastModified() + 5000L)).isTrue();

Future<Integer> result = Executors.newSingleThreadExecutor()
.submit(() -> filter.filterFiles(new File[] { file }).size());
.submit(() -> filter.filterFiles(new File[] {file}).size());
assertThat(latch2.await(10, TimeUnit.SECONDS)).isTrue();
store.put("foo:" + file.getAbsolutePath(), "43");
latch1.countDown();
Integer theResult = result.get(10, TimeUnit.SECONDS);
assertThat(theResult).isEqualTo(Integer.valueOf(0)); // lost the race, key changed

file.delete();
assertThat(file.delete()).isTrue();
filter.close();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@
* limitations under the License.
*/

package org.springframework.integration.redis.rules;
package org.springframework.integration.redis;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import java.time.Duration;

import org.junit.jupiter.api.BeforeAll;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisTemplate;
Expand All @@ -33,36 +38,64 @@
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;

import io.lettuce.core.ClientOptions;
import io.lettuce.core.SocketOptions;

/**
* The base contract for all tests requiring a Redis connection.
* The Testcontainers 'reuse' option must be disabled, so, Ryuk container is started
* and will clean all the containers up from this test suite after JVM exit.
* Since the Redis container instance is shared via static property, it is going to be
* started only once per JVM, therefore the target Docker container is reused automatically.
*
* @author Artem Vozhdayenko
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*
* @since 6.0
*/
public abstract class RedisAvailableTests {
@Testcontainers(disabledWithoutDocker = true)
public interface RedisContainerTest {

@Rule
public RedisAvailableRule redisAvailableRule = new RedisAvailableRule();
GenericContainer<?> REDIS_CONTAINER = new GenericContainer<>("redis:7.0.2")
.withExposedPorts(6379);

@BeforeClass
public static void setupConnectionFactory() {
RedisAvailableRule.setupConnectionFactory();
@BeforeAll
static void startContainer() {
REDIS_CONTAINER.start();
}

@AfterClass
public static void cleanUpConnectionFactoryIfAny() {
RedisAvailableRule.cleanUpConnectionFactoryIfAny();
/**
* A primary method which should be used to connect to the test Redis instance.
* Can be used in any JUnit lifecycle methods if a test class implements this interface.
*/
static LettuceConnectionFactory connectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setPort(REDIS_CONTAINER.getFirstMappedPort());

LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
.clientOptions(
ClientOptions.builder()
.socketOptions(
SocketOptions.builder()
.connectTimeout(Duration.ofMillis(10000))
.keepAlive(true)
.build())
.build())
.commandTimeout(Duration.ofSeconds(10000))
.build();

var connectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfiguration);
connectionFactory.afterPropertiesSet();
return connectionFactory;
}

protected RedisConnectionFactory getConnectionFactoryForTest() {
return RedisAvailableRule.connectionFactory;
}

protected void awaitContainerSubscribed(RedisMessageListenerContainer container) throws Exception {
static void awaitContainerSubscribed(RedisMessageListenerContainer container) throws Exception {
awaitContainerSubscribedNoWait(container);
}

private void awaitContainerSubscribedNoWait(RedisMessageListenerContainer container) throws InterruptedException {
static void awaitContainerSubscribedNoWait(RedisMessageListenerContainer container) throws InterruptedException {
RedisConnection connection = null;

int n = 0;
Expand All @@ -82,8 +115,8 @@ private void awaitContainerSubscribedNoWait(RedisMessageListenerContainer contai
assertThat(n < 300).as("RedisMessageListenerContainer Failed to Subscribe").isTrue();
}

protected void awaitContainerSubscribedWithPatterns(RedisMessageListenerContainer container) throws Exception {
this.awaitContainerSubscribed(container);
static void awaitContainerSubscribedWithPatterns(RedisMessageListenerContainer container) throws Exception {
awaitContainerSubscribed(container);
RedisConnection connection = TestUtils.getPropertyValue(container, "subscriber.connection",
RedisConnection.class);

Expand All @@ -96,7 +129,7 @@ protected void awaitContainerSubscribedWithPatterns(RedisMessageListenerContaine
Thread.sleep(1000);
}

protected void awaitFullySubscribed(RedisMessageListenerContainer container, RedisTemplate<?, ?> redisTemplate,
static void awaitFullySubscribed(RedisMessageListenerContainer container, RedisTemplate<?, ?> redisTemplate,
String redisChannelName, QueueChannel channel, Object message) throws Exception {
awaitContainerSubscribedNoWait(container);
drain(channel);
Expand All @@ -110,13 +143,13 @@ protected void awaitFullySubscribed(RedisMessageListenerContainer container, Red
assertThat(received).as("Container failed to fully start").isNotNull();
}

private void drain(QueueChannel channel) {
static void drain(QueueChannel channel) {
while (channel.receive(0) != null) {
// drain
}
}

protected void prepareList(RedisConnectionFactory connectionFactory) {
static void prepareList(RedisConnectionFactory connectionFactory) {

StringRedisTemplate redisTemplate = createStringRedisTemplate(connectionFactory);
redisTemplate.delete("presidents");
Expand All @@ -139,7 +172,7 @@ protected void prepareList(RedisConnectionFactory connectionFactory) {
ops.rightPush("George Washington");
}

protected void prepareZset(RedisConnectionFactory connectionFactory) {
static void prepareZset(RedisConnectionFactory connectionFactory) {

StringRedisTemplate redisTemplate = createStringRedisTemplate(connectionFactory);

Expand All @@ -163,20 +196,19 @@ protected void prepareZset(RedisConnectionFactory connectionFactory) {
ops.add("George Washington", 18);
}

protected void deletePresidents(RedisConnectionFactory connectionFactory) {
this.deleteKey(connectionFactory, "presidents");
static void deletePresidents(RedisConnectionFactory connectionFactory) {
deleteKey(connectionFactory, "presidents");
}

protected void deleteKey(RedisConnectionFactory connectionFactory, String key) {
static void deleteKey(RedisConnectionFactory connectionFactory, String key) {
StringRedisTemplate redisTemplate = createStringRedisTemplate(connectionFactory);
redisTemplate.delete(key);
}

protected StringRedisTemplate createStringRedisTemplate(RedisConnectionFactory connectionFactory) {
static StringRedisTemplate createStringRedisTemplate(RedisConnectionFactory connectionFactory) {
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
Expand All @@ -26,14 +26,14 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.junit.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.redis.RedisContainerTest;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
Expand All @@ -43,22 +43,26 @@
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @author Artem Vozhdayenko
* @since 2.0
*/
public class SubscribableRedisChannelTests extends RedisAvailableTests {
class SubscribableRedisChannelTests implements RedisContainerTest {
private static RedisConnectionFactory redisConnectionFactory;

@BeforeAll
static void setupConnection() {
redisConnectionFactory = RedisContainerTest.connectionFactory();
}

@Test
@RedisAvailable
public void pubSubChannelTest() throws Exception {
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
void pubSubChannelTest() throws Exception {

SubscribableRedisChannel channel = new SubscribableRedisChannel(connectionFactory, "si.test.channel");
SubscribableRedisChannel channel = new SubscribableRedisChannel(redisConnectionFactory, "si.test.channel");
channel.setBeanFactory(mock(BeanFactory.class));
channel.afterPropertiesSet();
channel.start();

this.awaitContainerSubscribed(TestUtils.getPropertyValue(channel, "container",
RedisContainerTest.awaitContainerSubscribed(TestUtils.getPropertyValue(channel, "container",
RedisMessageListenerContainer.class));

final CountDownLatch latch = new CountDownLatch(3);
Expand All @@ -72,11 +76,9 @@ public void pubSubChannelTest() throws Exception {
}

@Test
@RedisAvailable
public void dispatcherHasNoSubscribersTest() throws Exception {
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
void dispatcherHasNoSubscribersTest() throws Exception {

SubscribableRedisChannel channel = new SubscribableRedisChannel(connectionFactory, "si.test.channel.no.subs");
SubscribableRedisChannel channel = new SubscribableRedisChannel(redisConnectionFactory, "si.test.channel.no.subs");
channel.setBeanName("dhnsChannel");
channel.setBeanFactory(mock(BeanFactory.class));
channel.afterPropertiesSet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-redis="http://www.springframework.org/schema/integration/redis"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/redis https://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
http://www.springframework.org/schema/integration/redis https://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd">

<util:constant id="redisConnectionFactory"
static-field="org.springframework.integration.redis.rules.RedisAvailableRule.connectionFactory"/>
<bean id="redisConnectionFactory" class="org.springframework.integration.redis.RedisContainerTest"
factory-method="connectionFactory"/>

<int-redis:publish-subscribe-channel id="redisChannel" topic-name="si.test.topic.parser"
serializer="redisSerializer"/>
Expand Down
Loading