From 59a0dc41b63382b8e71c79fcd3ae8460c79b956f Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Sun, 12 Oct 2025 16:30:36 +0200 Subject: [PATCH 01/10] fix: adjust wrong pinecone description --- modules/pinecone/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pinecone/build.gradle b/modules/pinecone/build.gradle index 3ad5b97d98f..ad46d3ce9d9 100644 --- a/modules/pinecone/build.gradle +++ b/modules/pinecone/build.gradle @@ -1,4 +1,4 @@ -description = "Testcontainers :: ActiveMQ" +description = "Testcontainers :: Pinecone" dependencies { api project(':testcontainers') From ccf3e0484feb3d9b5bb35a52c5b3f686cce13ce6 Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Sun, 12 Oct 2025 20:51:56 +0200 Subject: [PATCH 02/10] feat: implement ValkeyContainer and test --- modules/valkey/build.gradle | 7 + .../valkey/ValkeyContainer.java | 206 ++++++++++++++++++ .../testcontainers/valkey/ValkeyLogLevel.java | 15 ++ modules/valkey/src/main/resources/import.sh | 4 + .../valkey/ValkeyContainerTest.java | 115 ++++++++++ .../valkey/src/test/resources/initData.valkey | 2 + 6 files changed, 349 insertions(+) create mode 100644 modules/valkey/build.gradle create mode 100644 modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java create mode 100644 modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyLogLevel.java create mode 100644 modules/valkey/src/main/resources/import.sh create mode 100644 modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java create mode 100644 modules/valkey/src/test/resources/initData.valkey diff --git a/modules/valkey/build.gradle b/modules/valkey/build.gradle new file mode 100644 index 00000000000..497b76a4c3f --- /dev/null +++ b/modules/valkey/build.gradle @@ -0,0 +1,7 @@ +description = "Testcontainers :: Valkey" + +dependencies { + api project(':testcontainers') + + testImplementation("io.valkey:valkey-java:5.5.0") +} diff --git a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java new file mode 100644 index 00000000000..b88f525c6d7 --- /dev/null +++ b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java @@ -0,0 +1,206 @@ +package org.testcontainers.valkey; + +import com.google.common.base.Preconditions; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +public class ValkeyContainer extends GenericContainer { + + @AllArgsConstructor + @Getter + private static class SnapshottingSettings { + + int seconds; + int changedKeys; + } + + private static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse( + "valkey/valkey:7.2.5"); + + private static final String DEFAULT_CONFIG_FILE = "/usr/local/valkey.conf"; + + private static final int CONTAINER_PORT = 6379; + + @Getter + private String username; + @Getter + private String password; + private String persistenceVolume; + private String initialImportScriptFile; + private ValkeyLogLevel logLevel; + private SnapshottingSettings snapshottingSettings; + + public ValkeyContainer() { + this(DEFAULT_IMAGE); + } + + public ValkeyContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public ValkeyContainer(DockerImageName dockerImageName) { + super(dockerImageName); + + withExposedPorts(CONTAINER_PORT); + withStartupTimeout(Duration.ofMinutes(2)); + waitingFor(Wait.forLogMessage(".*Ready to accept connections.*", 1)); + } + + public ValkeyContainer withUsername(String username) { + this.username = username; + return this; + } + + public ValkeyContainer withPassword(String password) { + this.password = password; + return this; + } + + /** + * Sets a host path to be mounted as a volume for Valkey persistence. The path must exist on the + * host system. Valkey will store its data in this directory. + */ + public ValkeyContainer withPersistenceVolume(String persistenceVolume) { + this.persistenceVolume = persistenceVolume; + return this; + } + + /** + * Sets an initial import script file to be executed via the Valkey CLI after startup. + *

+ * Example line of an import script file: SET key1 "value1" + */ + public ValkeyContainer withInitialData(String initialImportScriptFile) { + this.initialImportScriptFile = initialImportScriptFile; + return this; + } + + /** + * Sets the log level for the valkey server process. + */ + public ValkeyContainer withLogLevel(ValkeyLogLevel logLevel) { + this.logLevel = logLevel; + return this; + } + + /** + * Sets the snapshotting configuration for the valkey server process. You can configure Valkey + * to have it save the dataset every N seconds if there are at least M changes in the dataset. + * This method allows Valkey to benefit from copy-on-write semantics. + * + * @see + */ + public ValkeyContainer withSnapshotting(int seconds, int changedKeys) { + Preconditions.checkArgument(seconds > 0, "seconds must be greater than 0"); + Preconditions.checkArgument(changedKeys > 0, "changedKeys must be non-negative"); + + this.snapshottingSettings = new SnapshottingSettings(seconds, changedKeys); + return this; + } + + /** + * Sets the config file to be used for the Valkey container. + */ + public ValkeyContainer withConfigFile(String configFile) { + withCopyFileToContainer(MountableFile.forHostPath(configFile), DEFAULT_CONFIG_FILE); + + // TODO check whether config path needs to be specified on startup + + return this; + } + + @Override + public void start() { + List command = new ArrayList<>(); + command.add("valkey-server"); + + if (password != null && !password.isEmpty()) { + command.add("--requirepass"); + command.add(password); + + if (username != null && !username.isEmpty()) { + command.add("--user"); + command.add(username + " on >" + password + " ~* +@all"); + } + } + + if (persistenceVolume != null && !persistenceVolume.isEmpty()) { + command.addAll(Arrays.asList("--appendonly", "yes")); + withFileSystemBind(persistenceVolume, "/data"); + } + + if (snapshottingSettings != null) { + command.addAll(Arrays.asList( + "--save", + snapshottingSettings.getSeconds() + " " + snapshottingSettings.getChangedKeys() + )); + } + + if (logLevel != null) { + command.addAll(Arrays.asList("--loglevel", logLevel.name())); + } + + if (initialImportScriptFile != null && !initialImportScriptFile.isEmpty()) { + withCopyToContainer(MountableFile.forHostPath(initialImportScriptFile), + "/tmp/import.valkey"); + withCopyToContainer(MountableFile.forClasspathResource("import.sh"), + "/tmp/import.sh"); + } + + withCommand(command.toArray(new String[0])); + + super.start(); + + if (initialImportScriptFile != null && !initialImportScriptFile.isEmpty()) { + try { + ExecResult result = this.execInContainer("/bin/sh", "/tmp/import.sh", + password != null ? password : ""); + if (result.getExitCode() != 0 || result.getStdout().contains("ERR")) { + throw new RuntimeException( + "Could not import initial data: " + result.getStdout()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + public int getPort() { + return getMappedPort(CONTAINER_PORT); + } + + public String createConnectionUrl() { + String userInfo = null; + if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) { + userInfo = username + ":" + password; + } else if (password != null && !password.isEmpty()) { + userInfo = password; + } + + try { + URI uri = new URI( + "redis", + userInfo, + this.getHost(), + this.getPort(), + null, + null, + null + ); + return uri.toString(); + } catch (URISyntaxException e) { + throw new RuntimeException("Failed to build Redis URI", e); + } + } + +} diff --git a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyLogLevel.java b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyLogLevel.java new file mode 100644 index 00000000000..ba55c7dddaf --- /dev/null +++ b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyLogLevel.java @@ -0,0 +1,15 @@ +package org.testcontainers.valkey; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum ValkeyLogLevel { + DEBUG("debug"), + VERBOSE("verbose"), + NOTICE("notice"), + WARNING("warning"); + + private final String level; +} diff --git a/modules/valkey/src/main/resources/import.sh b/modules/valkey/src/main/resources/import.sh new file mode 100644 index 00000000000..bfc76a22606 --- /dev/null +++ b/modules/valkey/src/main/resources/import.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e +valkey-cli $([[ -n "$1" ]] && echo "-a $1") < "/tmp/import.valkey" +echo "Imported" diff --git a/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java new file mode 100644 index 00000000000..8ae09aa7a7f --- /dev/null +++ b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java @@ -0,0 +1,115 @@ +package org.testcontainers.valkey; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.valkey.JedisPool; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; + +class ValkeyContainerTest { + + @TempDir + Path tempDir; + + @Test + void shouldWriteAndReadEntry() { + try (ValkeyContainer valkeyContainer = new ValkeyContainer() + .withLogLevel(ValkeyLogLevel.DEBUG) + .withSnapshotting(3, 1)) { + + valkeyContainer.start(); + JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); + + try (io.valkey.Jedis jedis = jedisPool.getResource()) { + jedis.set("key", "value"); + assertThat(jedis.get("key")).isEqualTo("value"); + } + } + } + + @Test + void shouldConfigureServiceWithAuthentication() { + try (ValkeyContainer valkeyContainer = new ValkeyContainer() + .withUsername("testuser") + .withPassword("testpass")) { + + valkeyContainer.start(); + String url = valkeyContainer.createConnectionUrl(); + assertThat(url).contains("testuser:testpass"); + + JedisPool jedisPool = new JedisPool(url); + try (io.valkey.Jedis jedis = jedisPool.getResource()) { + jedis.set("authKey", "authValue"); + assertThat(jedis.get("authKey")).isEqualTo("authValue"); + } + } + } + + @Test + void shouldPersistData() { + Path dataDir = tempDir.resolve("valkey-data"); + dataDir.toFile().mkdirs(); + + try (ValkeyContainer valkeyContainer = new ValkeyContainer() + .withPersistenceVolume(dataDir.toString()) + .withSnapshotting(1, 1)) { + + valkeyContainer.start(); + JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); + + try (io.valkey.Jedis jedis = jedisPool.getResource()) { + jedis.set("persistKey", "persistValue"); + } + + valkeyContainer.stop(); + try (ValkeyContainer restarted = new ValkeyContainer() + .withPersistenceVolume(dataDir.toString())) { + restarted.start(); + JedisPool restartedPool = new JedisPool(restarted.createConnectionUrl()); + + try (io.valkey.Jedis jedis = restartedPool.getResource()) { + assertThat(jedis.get("persistKey")).isEqualTo("persistValue"); + } + } + } + } + + @Test + void shouldValidateSnapshottingConfiguration() { + ValkeyContainer container = new ValkeyContainer(); + assertThatThrownBy(() -> container.withSnapshotting(0, 10)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("seconds must be greater than 0"); + + assertThatThrownBy(() -> container.withSnapshotting(10, 0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("changedKeys must be non-negative"); + } + + @Test + void shouldInitializeDatabaseWithInitialPayload() throws Exception { + Path importFile = tempDir.resolve("import.data"); + String content = "SET key1 \"value1\"\nSET key2 \"value2\""; + Files.write(importFile, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + + try (ValkeyContainer valkeyContainer = new ValkeyContainer() + .withInitialData(importFile.toString())) { + + valkeyContainer.start(); + JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); + + try (io.valkey.Jedis jedis = jedisPool.getResource()) { + assertThat(jedis.get("key1")).isEqualTo("value1"); + assertThat(jedis.get("key2")).isEqualTo("value2"); + } + } + } +} diff --git a/modules/valkey/src/test/resources/initData.valkey b/modules/valkey/src/test/resources/initData.valkey new file mode 100644 index 00000000000..547db15953b --- /dev/null +++ b/modules/valkey/src/test/resources/initData.valkey @@ -0,0 +1,2 @@ +SET "user:001" '{"first_name":"John","last_name":"Doe","dob":"12-JUN-1970"}' +SET "user:002" '{"first_name":"David","last_name":"Bloom","dob":"03-MAR-1981"}' From c4cde21100a22cdfb671849f599c0e0612fce0d5 Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Sun, 12 Oct 2025 22:34:28 +0200 Subject: [PATCH 03/10] feat: implement cli & config support, fix user config expression, refactor tests --- .../valkey/ValkeyContainer.java | 83 ++++++++++++++----- .../valkey/ValkeyContainerTest.java | 57 +++++++++---- .../valkey/src/test/resources/initData.valkey | 4 +- modules/valkey/src/test/resources/valkey.conf | 1 + 4 files changed, 104 insertions(+), 41 deletions(-) create mode 100644 modules/valkey/src/test/resources/valkey.conf diff --git a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java index b88f525c6d7..0b5271f390c 100644 --- a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java +++ b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java @@ -25,18 +25,17 @@ private static class SnapshottingSettings { } private static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse( - "valkey/valkey:7.2.5"); + "valkey/valkey:8.1"); private static final String DEFAULT_CONFIG_FILE = "/usr/local/valkey.conf"; private static final int CONTAINER_PORT = 6379; - @Getter private String username; - @Getter private String password; private String persistenceVolume; private String initialImportScriptFile; + private String configFile; private ValkeyLogLevel logLevel; private SnapshottingSettings snapshottingSettings; @@ -112,9 +111,7 @@ public ValkeyContainer withSnapshotting(int seconds, int changedKeys) { * Sets the config file to be used for the Valkey container. */ public ValkeyContainer withConfigFile(String configFile) { - withCopyFileToContainer(MountableFile.forHostPath(configFile), DEFAULT_CONFIG_FILE); - - // TODO check whether config path needs to be specified on startup + this.configFile = configFile; return this; } @@ -124,13 +121,17 @@ public void start() { List command = new ArrayList<>(); command.add("valkey-server"); + if (configFile != null && !configFile.isEmpty()) { + withCopyToContainer(MountableFile.forHostPath(configFile), DEFAULT_CONFIG_FILE); + command.add(DEFAULT_CONFIG_FILE); + } + if (password != null && !password.isEmpty()) { command.add("--requirepass"); command.add(password); if (username != null && !username.isEmpty()) { - command.add("--user"); - command.add(username + " on >" + password + " ~* +@all"); + command.add("--user " + username + " on >" + password + " ~* +@all"); } } @@ -161,38 +162,56 @@ public void start() { super.start(); - if (initialImportScriptFile != null && !initialImportScriptFile.isEmpty()) { - try { - ExecResult result = this.execInContainer("/bin/sh", "/tmp/import.sh", - password != null ? password : ""); - if (result.getExitCode() != 0 || result.getStdout().contains("ERR")) { - throw new RuntimeException( - "Could not import initial data: " + result.getStdout()); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } + evaluateImportScript(); } public int getPort() { return getMappedPort(CONTAINER_PORT); } + /** + * Executes a command in the Valkey CLI inside the container. + */ + public String executeCli(String cmd, String... flags) { + try { + List args = new ArrayList<>(); + args.add("redis-cli"); + + if (password != null && !password.isEmpty()) { + args.addAll(username != null && !username.isEmpty() + ? Arrays.asList("--user", username, "--pass", password) + : Arrays.asList("--pass", password) + ); + } + + args.add(cmd); + args.addAll(Arrays.asList(flags)); + + ExecResult result = execInContainer(args.toArray(new String[0])); + if (result.getExitCode() != 0) { + throw new RuntimeException(result.getStdout() + result.getStderr()); + } + + return result.getStdout(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public String createConnectionUrl() { String userInfo = null; if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) { userInfo = username + ":" + password; } else if (password != null && !password.isEmpty()) { - userInfo = password; + userInfo = ":" + password; } try { URI uri = new URI( "redis", userInfo, - this.getHost(), - this.getPort(), + getHost(), + getPort(), null, null, null @@ -203,4 +222,22 @@ public String createConnectionUrl() { } } + private void evaluateImportScript() { + if (initialImportScriptFile == null || initialImportScriptFile.isEmpty()) { + return; + } + + try { + ExecResult result = execInContainer("/bin/sh", "/tmp/import.sh", + password != null ? password : ""); + + if (result.getExitCode() != 0 || result.getStdout().contains("ERR")) { + throw new RuntimeException( + "Could not import initial data: " + result.getStdout()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } diff --git a/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java index 8ae09aa7a7f..f1d7ace5773 100644 --- a/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java +++ b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java @@ -3,11 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import io.valkey.Jedis; import io.valkey.JedisPool; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; -import org.junit.jupiter.api.Disabled; +import java.nio.file.Paths; +import lombok.val; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -27,7 +26,7 @@ void shouldWriteAndReadEntry() { valkeyContainer.start(); JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); - try (io.valkey.Jedis jedis = jedisPool.getResource()) { + try (Jedis jedis = jedisPool.getResource()) { jedis.set("key", "value"); assertThat(jedis.get("key")).isEqualTo("value"); } @@ -45,9 +44,9 @@ void shouldConfigureServiceWithAuthentication() { assertThat(url).contains("testuser:testpass"); JedisPool jedisPool = new JedisPool(url); - try (io.valkey.Jedis jedis = jedisPool.getResource()) { - jedis.set("authKey", "authValue"); - assertThat(jedis.get("authKey")).isEqualTo("authValue"); + try (Jedis jedis = jedisPool.getResource()) { + jedis.set("k1", "v2"); + assertThat(jedis.get("k1")).isEqualTo("v2"); } } } @@ -64,7 +63,7 @@ void shouldPersistData() { valkeyContainer.start(); JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); - try (io.valkey.Jedis jedis = jedisPool.getResource()) { + try (Jedis jedis = jedisPool.getResource()) { jedis.set("persistKey", "persistValue"); } @@ -74,7 +73,7 @@ void shouldPersistData() { restarted.start(); JedisPool restartedPool = new JedisPool(restarted.createConnectionUrl()); - try (io.valkey.Jedis jedis = restartedPool.getResource()) { + try (Jedis jedis = restartedPool.getResource()) { assertThat(jedis.get("persistKey")).isEqualTo("persistValue"); } } @@ -94,11 +93,8 @@ void shouldValidateSnapshottingConfiguration() { } @Test - void shouldInitializeDatabaseWithInitialPayload() throws Exception { - Path importFile = tempDir.resolve("import.data"); - String content = "SET key1 \"value1\"\nSET key2 \"value2\""; - Files.write(importFile, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING); + void shouldInitializeDatabaseWithPayload() throws Exception { + Path importFile = Paths.get(getClass().getResource("/initData.valkey").toURI()); try (ValkeyContainer valkeyContainer = new ValkeyContainer() .withInitialData(importFile.toString())) { @@ -106,10 +102,39 @@ void shouldInitializeDatabaseWithInitialPayload() throws Exception { valkeyContainer.start(); JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); - try (io.valkey.Jedis jedis = jedisPool.getResource()) { + try (Jedis jedis = jedisPool.getResource()) { assertThat(jedis.get("key1")).isEqualTo("value1"); assertThat(jedis.get("key2")).isEqualTo("value2"); } } } + + @Test + void shouldExecuteContainerCmdAndReturnResult() { + try (ValkeyContainer valkeyContainer = new ValkeyContainer()) { + valkeyContainer.start(); + + String queryResult = valkeyContainer.executeCli("info", "clients"); + + assertThat(queryResult).contains("connected_clients:1"); + } + } + + @Test + void shouldMountValkeyConfigToContainer() throws Exception { + Path configFile = Paths.get(getClass().getResource("/valkey.conf").toURI()); + + try (ValkeyContainer valkeyContainer = new ValkeyContainer().withConfigFile( + configFile.toString())) { + valkeyContainer.start(); + + JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); + + try (Jedis jedis = jedisPool.getResource()) { + String maxMemory = jedis.configGet("maxmemory").get("maxmemory"); + + assertThat(maxMemory).isEqualTo("2097152"); + } + } + } } diff --git a/modules/valkey/src/test/resources/initData.valkey b/modules/valkey/src/test/resources/initData.valkey index 547db15953b..e2c4c2c8e7b 100644 --- a/modules/valkey/src/test/resources/initData.valkey +++ b/modules/valkey/src/test/resources/initData.valkey @@ -1,2 +1,2 @@ -SET "user:001" '{"first_name":"John","last_name":"Doe","dob":"12-JUN-1970"}' -SET "user:002" '{"first_name":"David","last_name":"Bloom","dob":"03-MAR-1981"}' +SET key1 "value1" +SET key2 "value2" diff --git a/modules/valkey/src/test/resources/valkey.conf b/modules/valkey/src/test/resources/valkey.conf new file mode 100644 index 00000000000..b58609fc4b3 --- /dev/null +++ b/modules/valkey/src/test/resources/valkey.conf @@ -0,0 +1 @@ +maxmemory 2mb From 1027e33e588cc1791ad240ef586cd00a05687bf4 Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Sun, 12 Oct 2025 23:18:24 +0200 Subject: [PATCH 04/10] docs: add module reference to templates --- .github/ISSUE_TEMPLATE/bug_report.yaml | 1 + .github/ISSUE_TEMPLATE/enhancement.yaml | 1 + .github/ISSUE_TEMPLATE/feature.yaml | 1 + .github/dependabot.yml | 5 +++++ .github/labeler.yml | 4 ++++ .../org/testcontainers/valkey/ValkeyContainer.java | 10 ++++++++++ 6 files changed, 22 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index c31dd05e048..f78595fff9e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -68,6 +68,7 @@ body: - ToxiProxy - Trino - Typesense + - Valkey - Vault - Weaviate - YugabyteDB diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml index 9b9a06ecf6a..b63978775af 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -68,6 +68,7 @@ body: - ToxiProxy - Trino - Typesense + - Valkey - Vault - Weaviate - YugabyteDB diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index b655b4ac505..4a26337e90a 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -68,6 +68,7 @@ body: - ToxiProxy - Trino - Typesense + - Valkey - Vault - Weaviate - YugabyteDB diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 72a6d9110b6..7e84bc07e6e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -373,6 +373,11 @@ updates: schedule: interval: "monthly" open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/modules/valkey" + schedule: + interval: "monthly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/vault" schedule: diff --git a/.github/labeler.yml b/.github/labeler.yml index f4649bd7f99..02efaf75f14 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -248,6 +248,10 @@ - changed-files: - any-glob-to-any-file: - modules/typesense/**/* +"modules/valkey": + - changed-files: + - any-glob-to-any-file: + - modules/valkey/**/* "modules/vault": - changed-files: - any-glob-to-any-file: diff --git a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java index 0b5271f390c..3126c5dce7d 100644 --- a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java +++ b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java @@ -14,6 +14,16 @@ import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; +/** + * Testcontainers implementation for Valkey. + *

+ * Supported image: {@code valkey} + *

+ * Exposed ports: + *

+ */ public class ValkeyContainer extends GenericContainer { @AllArgsConstructor From d52602a1398336242c89c70d3aa1cec74d2b620f Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Sun, 12 Oct 2025 23:32:27 +0200 Subject: [PATCH 05/10] refactor: move normal unit test below container tests; cleanup import --- .../valkey/ValkeyContainerTest.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java index f1d7ace5773..7a484c994b3 100644 --- a/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java +++ b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java @@ -6,7 +6,6 @@ import io.valkey.Jedis; import io.valkey.JedisPool; import java.nio.file.Paths; -import lombok.val; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -80,18 +79,6 @@ void shouldPersistData() { } } - @Test - void shouldValidateSnapshottingConfiguration() { - ValkeyContainer container = new ValkeyContainer(); - assertThatThrownBy(() -> container.withSnapshotting(0, 10)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("seconds must be greater than 0"); - - assertThatThrownBy(() -> container.withSnapshotting(10, 0)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("changedKeys must be non-negative"); - } - @Test void shouldInitializeDatabaseWithPayload() throws Exception { Path importFile = Paths.get(getClass().getResource("/initData.valkey").toURI()); @@ -137,4 +124,16 @@ void shouldMountValkeyConfigToContainer() throws Exception { } } } + + @Test + void shouldValidateSnapshottingConfiguration() { + ValkeyContainer container = new ValkeyContainer(); + assertThatThrownBy(() -> container.withSnapshotting(0, 10)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("seconds must be greater than 0"); + + assertThatThrownBy(() -> container.withSnapshotting(10, 0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("changedKeys must be non-negative"); + } } From f04cd7c4a0d64f5735e8cc54603ca4c085e0539c Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Sun, 12 Oct 2025 23:48:40 +0200 Subject: [PATCH 06/10] refactor: adjust formatting to project standard --- .../valkey/ValkeyContainer.java | 61 ++++++++----------- .../valkey/ValkeyContainerTest.java | 43 ++++++------- 2 files changed, 47 insertions(+), 57 deletions(-) diff --git a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java index 3126c5dce7d..9548eccef83 100644 --- a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java +++ b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java @@ -1,12 +1,6 @@ package org.testcontainers.valkey; import com.google.common.base.Preconditions; -import java.net.URI; -import java.net.URISyntaxException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import org.testcontainers.containers.GenericContainer; @@ -14,6 +8,13 @@ import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * Testcontainers implementation for Valkey. *

@@ -31,22 +32,28 @@ public class ValkeyContainer extends GenericContainer { private static class SnapshottingSettings { int seconds; + int changedKeys; } - private static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse( - "valkey/valkey:8.1"); + private static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("valkey/valkey:8.1"); private static final String DEFAULT_CONFIG_FILE = "/usr/local/valkey.conf"; private static final int CONTAINER_PORT = 6379; private String username; + private String password; + private String persistenceVolume; + private String initialImportScriptFile; + private String configFile; + private ValkeyLogLevel logLevel; + private SnapshottingSettings snapshottingSettings; public ValkeyContainer() { @@ -59,7 +66,6 @@ public ValkeyContainer(String dockerImageName) { public ValkeyContainer(DockerImageName dockerImageName) { super(dockerImageName); - withExposedPorts(CONTAINER_PORT); withStartupTimeout(Duration.ofMinutes(2)); waitingFor(Wait.forLogMessage(".*Ready to accept connections.*", 1)); @@ -151,10 +157,9 @@ public void start() { } if (snapshottingSettings != null) { - command.addAll(Arrays.asList( - "--save", - snapshottingSettings.getSeconds() + " " + snapshottingSettings.getChangedKeys() - )); + command.addAll( + Arrays.asList("--save", snapshottingSettings.getSeconds() + " " + snapshottingSettings.getChangedKeys()) + ); } if (logLevel != null) { @@ -162,10 +167,8 @@ public void start() { } if (initialImportScriptFile != null && !initialImportScriptFile.isEmpty()) { - withCopyToContainer(MountableFile.forHostPath(initialImportScriptFile), - "/tmp/import.valkey"); - withCopyToContainer(MountableFile.forClasspathResource("import.sh"), - "/tmp/import.sh"); + withCopyToContainer(MountableFile.forHostPath(initialImportScriptFile), "/tmp/import.valkey"); + withCopyToContainer(MountableFile.forClasspathResource("import.sh"), "/tmp/import.sh"); } withCommand(command.toArray(new String[0])); @@ -188,9 +191,10 @@ public String executeCli(String cmd, String... flags) { args.add("redis-cli"); if (password != null && !password.isEmpty()) { - args.addAll(username != null && !username.isEmpty() - ? Arrays.asList("--user", username, "--pass", password) - : Arrays.asList("--pass", password) + args.addAll( + username != null && !username.isEmpty() + ? Arrays.asList("--user", username, "--pass", password) + : Arrays.asList("--pass", password) ); } @@ -217,15 +221,7 @@ public String createConnectionUrl() { } try { - URI uri = new URI( - "redis", - userInfo, - getHost(), - getPort(), - null, - null, - null - ); + URI uri = new URI("redis", userInfo, getHost(), getPort(), null, null, null); return uri.toString(); } catch (URISyntaxException e) { throw new RuntimeException("Failed to build Redis URI", e); @@ -238,16 +234,13 @@ private void evaluateImportScript() { } try { - ExecResult result = execInContainer("/bin/sh", "/tmp/import.sh", - password != null ? password : ""); + ExecResult result = execInContainer("/bin/sh", "/tmp/import.sh", password != null ? password : ""); if (result.getExitCode() != 0 || result.getStdout().contains("ERR")) { - throw new RuntimeException( - "Could not import initial data: " + result.getStdout()); + throw new RuntimeException("Could not import initial data: " + result.getStdout()); } } catch (Exception e) { throw new RuntimeException(e); } } - } diff --git a/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java index 7a484c994b3..955450ead5e 100644 --- a/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java +++ b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java @@ -1,15 +1,15 @@ package org.testcontainers.valkey; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - import io.valkey.Jedis; import io.valkey.JedisPool; -import java.nio.file.Paths; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class ValkeyContainerTest { @@ -18,10 +18,11 @@ class ValkeyContainerTest { @Test void shouldWriteAndReadEntry() { - try (ValkeyContainer valkeyContainer = new ValkeyContainer() - .withLogLevel(ValkeyLogLevel.DEBUG) - .withSnapshotting(3, 1)) { - + try ( + ValkeyContainer valkeyContainer = new ValkeyContainer() + .withLogLevel(ValkeyLogLevel.DEBUG) + .withSnapshotting(3, 1) + ) { valkeyContainer.start(); JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); @@ -34,10 +35,9 @@ void shouldWriteAndReadEntry() { @Test void shouldConfigureServiceWithAuthentication() { - try (ValkeyContainer valkeyContainer = new ValkeyContainer() - .withUsername("testuser") - .withPassword("testpass")) { - + try ( + ValkeyContainer valkeyContainer = new ValkeyContainer().withUsername("testuser").withPassword("testpass") + ) { valkeyContainer.start(); String url = valkeyContainer.createConnectionUrl(); assertThat(url).contains("testuser:testpass"); @@ -55,10 +55,11 @@ void shouldPersistData() { Path dataDir = tempDir.resolve("valkey-data"); dataDir.toFile().mkdirs(); - try (ValkeyContainer valkeyContainer = new ValkeyContainer() - .withPersistenceVolume(dataDir.toString()) - .withSnapshotting(1, 1)) { - + try ( + ValkeyContainer valkeyContainer = new ValkeyContainer() + .withPersistenceVolume(dataDir.toString()) + .withSnapshotting(1, 1) + ) { valkeyContainer.start(); JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); @@ -67,8 +68,7 @@ void shouldPersistData() { } valkeyContainer.stop(); - try (ValkeyContainer restarted = new ValkeyContainer() - .withPersistenceVolume(dataDir.toString())) { + try (ValkeyContainer restarted = new ValkeyContainer().withPersistenceVolume(dataDir.toString())) { restarted.start(); JedisPool restartedPool = new JedisPool(restarted.createConnectionUrl()); @@ -83,9 +83,7 @@ void shouldPersistData() { void shouldInitializeDatabaseWithPayload() throws Exception { Path importFile = Paths.get(getClass().getResource("/initData.valkey").toURI()); - try (ValkeyContainer valkeyContainer = new ValkeyContainer() - .withInitialData(importFile.toString())) { - + try (ValkeyContainer valkeyContainer = new ValkeyContainer().withInitialData(importFile.toString())) { valkeyContainer.start(); JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); @@ -111,8 +109,7 @@ void shouldExecuteContainerCmdAndReturnResult() { void shouldMountValkeyConfigToContainer() throws Exception { Path configFile = Paths.get(getClass().getResource("/valkey.conf").toURI()); - try (ValkeyContainer valkeyContainer = new ValkeyContainer().withConfigFile( - configFile.toString())) { + try (ValkeyContainer valkeyContainer = new ValkeyContainer().withConfigFile(configFile.toString())) { valkeyContainer.start(); JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); From ff3f95fc99d1a5e82d79afc89c68fc39c428d303 Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Sun, 12 Oct 2025 23:49:00 +0200 Subject: [PATCH 07/10] docs: create valkey module entry --- docs/modules/valkey.md | 34 ++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 35 insertions(+) create mode 100644 docs/modules/valkey.md diff --git a/docs/modules/valkey.md b/docs/modules/valkey.md new file mode 100644 index 00000000000..fd318cbcf3a --- /dev/null +++ b/docs/modules/valkey.md @@ -0,0 +1,34 @@ +# Valkey + +!!! note This module is INCUBATING. +While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. +See our [contributing guidelines](../contributing.md#incubating-modules) for more information on our incubating modules policy. + +Testcontainers module for [Valkey](https://hub.docker.com/r/valkey/valkey) + +## Valkey's usage examples + +You can start a Valkey container instance from any Java application by using: + + +[Default Valkey container](../../modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java) inside_block:container + + +## Adding this module to your project dependencies + +Add the following dependency to your `pom.xml`/`build.gradle` file: + +=== "Gradle" +```groovy +testImplementation "org.testcontainers:valkey:{{latest_version}}" +``` + +=== "Maven" +```xml + +org.testcontainers +valkey +{{latest_version}} +test + +``` diff --git a/mkdocs.yml b/mkdocs.yml index 3e39a67f959..8dedfec2ede 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -108,6 +108,7 @@ nav: - modules/solr.md - modules/toxiproxy.md - modules/typesense.md + - modules/valkey.md - modules/vault.md - modules/weaviate.md - modules/webdriver_containers.md From ef646ba94dd08e36f16412927d4e68849bd0baaf Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Mon, 13 Oct 2025 00:32:36 +0200 Subject: [PATCH 08/10] refactor: don't use lombok for user api --- .../org/testcontainers/valkey/ValkeyContainer.java | 2 +- .../org/testcontainers/valkey/ValkeyLogLevel.java | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java index 9548eccef83..4c1eebcd628 100644 --- a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java +++ b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java @@ -163,7 +163,7 @@ public void start() { } if (logLevel != null) { - command.addAll(Arrays.asList("--loglevel", logLevel.name())); + command.addAll(Arrays.asList("--loglevel", logLevel.getLevel())); } if (initialImportScriptFile != null && !initialImportScriptFile.isEmpty()) { diff --git a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyLogLevel.java b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyLogLevel.java index ba55c7dddaf..b24884d66c4 100644 --- a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyLogLevel.java +++ b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyLogLevel.java @@ -1,10 +1,5 @@ package org.testcontainers.valkey; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter public enum ValkeyLogLevel { DEBUG("debug"), VERBOSE("verbose"), @@ -12,4 +7,12 @@ public enum ValkeyLogLevel { WARNING("warning"); private final String level; + + ValkeyLogLevel(String level) { + this.level = level; + } + + public String getLevel() { + return level; + } } From 9d24f30ce4de6f1b7fe8ec798a0c78e3918cf6ee Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Mon, 13 Oct 2025 11:40:57 +0200 Subject: [PATCH 09/10] refactor: resolve review concerns - shrink try/catch block to cli execution; utilize AutoCloseable iface in tests; make use of StringUtils.isNotEmpty for preconditions --- .../valkey/ValkeyContainer.java | 48 ++++++++------- .../valkey/ValkeyContainerTest.java | 60 +++++++++++-------- 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java index 4c1eebcd628..9555ec1e63c 100644 --- a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java +++ b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java @@ -3,6 +3,7 @@ import com.google.common.base.Preconditions; import lombok.AllArgsConstructor; import lombok.Getter; +import org.apache.commons.lang3.StringUtils; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; @@ -137,12 +138,12 @@ public void start() { List command = new ArrayList<>(); command.add("valkey-server"); - if (configFile != null && !configFile.isEmpty()) { + if (StringUtils.isNotEmpty(configFile)) { withCopyToContainer(MountableFile.forHostPath(configFile), DEFAULT_CONFIG_FILE); command.add(DEFAULT_CONFIG_FILE); } - if (password != null && !password.isEmpty()) { + if (StringUtils.isNotEmpty(password)) { command.add("--requirepass"); command.add(password); @@ -158,7 +159,8 @@ public void start() { if (snapshottingSettings != null) { command.addAll( - Arrays.asList("--save", snapshottingSettings.getSeconds() + " " + snapshottingSettings.getChangedKeys()) + Arrays.asList("--save", + snapshottingSettings.getSeconds() + " " + snapshottingSettings.getChangedKeys()) ); } @@ -167,7 +169,8 @@ public void start() { } if (initialImportScriptFile != null && !initialImportScriptFile.isEmpty()) { - withCopyToContainer(MountableFile.forHostPath(initialImportScriptFile), "/tmp/import.valkey"); + withCopyToContainer(MountableFile.forHostPath(initialImportScriptFile), + "/tmp/import.valkey"); withCopyToContainer(MountableFile.forClasspathResource("import.sh"), "/tmp/import.sh"); } @@ -186,21 +189,21 @@ public int getPort() { * Executes a command in the Valkey CLI inside the container. */ public String executeCli(String cmd, String... flags) { - try { - List args = new ArrayList<>(); - args.add("redis-cli"); - - if (password != null && !password.isEmpty()) { - args.addAll( - username != null && !username.isEmpty() - ? Arrays.asList("--user", username, "--pass", password) - : Arrays.asList("--pass", password) - ); - } + List args = new ArrayList<>(); + args.add("redis-cli"); + + if (StringUtils.isNotEmpty(password)) { + args.addAll( + StringUtils.isNotEmpty(username) + ? Arrays.asList("--user", username, "--pass", password) + : Arrays.asList("--pass", password) + ); + } - args.add(cmd); - args.addAll(Arrays.asList(flags)); + args.add(cmd); + args.addAll(Arrays.asList(flags)); + try { ExecResult result = execInContainer(args.toArray(new String[0])); if (result.getExitCode() != 0) { throw new RuntimeException(result.getStdout() + result.getStderr()); @@ -208,15 +211,15 @@ public String executeCli(String cmd, String... flags) { return result.getStdout(); } catch (Exception e) { - throw new RuntimeException(e); + throw new RuntimeException("failed to execute CLI command", e); } } public String createConnectionUrl() { String userInfo = null; - if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) { + if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) { userInfo = username + ":" + password; - } else if (password != null && !password.isEmpty()) { + } else if (StringUtils.isNotEmpty(password)) { userInfo = ":" + password; } @@ -229,12 +232,13 @@ public String createConnectionUrl() { } private void evaluateImportScript() { - if (initialImportScriptFile == null || initialImportScriptFile.isEmpty()) { + if (StringUtils.isEmpty(initialImportScriptFile)) { return; } try { - ExecResult result = execInContainer("/bin/sh", "/tmp/import.sh", password != null ? password : ""); + ExecResult result = execInContainer("/bin/sh", "/tmp/import.sh", + password != null ? password : ""); if (result.getExitCode() != 0 || result.getStdout().contains("ERR")) { throw new RuntimeException("Could not import initial data: " + result.getStdout()); diff --git a/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java index 955450ead5e..2a93c677e84 100644 --- a/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java +++ b/modules/valkey/src/test/java/org/testcontainers/valkey/ValkeyContainerTest.java @@ -24,9 +24,8 @@ void shouldWriteAndReadEntry() { .withSnapshotting(3, 1) ) { valkeyContainer.start(); - JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); - - try (Jedis jedis = jedisPool.getResource()) { + try (JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); + Jedis jedis = jedisPool.getResource()) { jedis.set("key", "value"); assertThat(jedis.get("key")).isEqualTo("value"); } @@ -36,20 +35,22 @@ void shouldWriteAndReadEntry() { @Test void shouldConfigureServiceWithAuthentication() { try ( - ValkeyContainer valkeyContainer = new ValkeyContainer().withUsername("testuser").withPassword("testpass") + ValkeyContainer valkeyContainer = new ValkeyContainer().withUsername("testuser") + .withPassword("testpass") ) { valkeyContainer.start(); String url = valkeyContainer.createConnectionUrl(); assertThat(url).contains("testuser:testpass"); - JedisPool jedisPool = new JedisPool(url); - try (Jedis jedis = jedisPool.getResource()) { + try (JedisPool jedisPool = new JedisPool(url); + Jedis jedis = jedisPool.getResource()) { jedis.set("k1", "v2"); assertThat(jedis.get("k1")).isEqualTo("v2"); } } } + @Test void shouldPersistData() { Path dataDir = tempDir.resolve("valkey-data"); @@ -61,18 +62,21 @@ void shouldPersistData() { .withSnapshotting(1, 1) ) { valkeyContainer.start(); - JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); - try (Jedis jedis = jedisPool.getResource()) { + String containerConnectionUrl = valkeyContainer.createConnectionUrl(); + try (JedisPool jedisPool = new JedisPool(containerConnectionUrl); + Jedis jedis = jedisPool.getResource()) { jedis.set("persistKey", "persistValue"); } valkeyContainer.stop(); - try (ValkeyContainer restarted = new ValkeyContainer().withPersistenceVolume(dataDir.toString())) { + try (ValkeyContainer restarted = new ValkeyContainer().withPersistenceVolume( + dataDir.toString())) { restarted.start(); - JedisPool restartedPool = new JedisPool(restarted.createConnectionUrl()); + String connectionUrl = restarted.createConnectionUrl(); - try (Jedis jedis = restartedPool.getResource()) { + try (JedisPool restartedPool = new JedisPool(connectionUrl); + Jedis jedis = restartedPool.getResource()) { assertThat(jedis.get("persistKey")).isEqualTo("persistValue"); } } @@ -83,11 +87,13 @@ void shouldPersistData() { void shouldInitializeDatabaseWithPayload() throws Exception { Path importFile = Paths.get(getClass().getResource("/initData.valkey").toURI()); - try (ValkeyContainer valkeyContainer = new ValkeyContainer().withInitialData(importFile.toString())) { + try (ValkeyContainer valkeyContainer = new ValkeyContainer().withInitialData( + importFile.toString())) { valkeyContainer.start(); - JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); + String connectionUrl = valkeyContainer.createConnectionUrl(); - try (Jedis jedis = jedisPool.getResource()) { + try (JedisPool jedisPool = new JedisPool( + connectionUrl); Jedis jedis = jedisPool.getResource()) { assertThat(jedis.get("key1")).isEqualTo("value1"); assertThat(jedis.get("key2")).isEqualTo("value2"); } @@ -109,12 +115,13 @@ void shouldExecuteContainerCmdAndReturnResult() { void shouldMountValkeyConfigToContainer() throws Exception { Path configFile = Paths.get(getClass().getResource("/valkey.conf").toURI()); - try (ValkeyContainer valkeyContainer = new ValkeyContainer().withConfigFile(configFile.toString())) { + try (ValkeyContainer valkeyContainer = new ValkeyContainer().withConfigFile( + configFile.toString())) { valkeyContainer.start(); - JedisPool jedisPool = new JedisPool(valkeyContainer.createConnectionUrl()); - - try (Jedis jedis = jedisPool.getResource()) { + String connectionUrl = valkeyContainer.createConnectionUrl(); + try (JedisPool jedisPool = new JedisPool(connectionUrl); + Jedis jedis = jedisPool.getResource()) { String maxMemory = jedis.configGet("maxmemory").get("maxmemory"); assertThat(maxMemory).isEqualTo("2097152"); @@ -124,13 +131,14 @@ void shouldMountValkeyConfigToContainer() throws Exception { @Test void shouldValidateSnapshottingConfiguration() { - ValkeyContainer container = new ValkeyContainer(); - assertThatThrownBy(() -> container.withSnapshotting(0, 10)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("seconds must be greater than 0"); - - assertThatThrownBy(() -> container.withSnapshotting(10, 0)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("changedKeys must be non-negative"); + try (ValkeyContainer container = new ValkeyContainer()) { + assertThatThrownBy(() -> container.withSnapshotting(0, 10)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("seconds must be greater than 0"); + + assertThatThrownBy(() -> container.withSnapshotting(10, 0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("changedKeys must be non-negative"); + } } } From bfeed7d7e88935157bdc93f8f97bd5b31d39f5f0 Mon Sep 17 00:00:00 2001 From: Wendelin Wisser Date: Mon, 13 Oct 2025 17:11:25 +0200 Subject: [PATCH 10/10] refactor: make use of StringUtils.isNotEmpty --- .../java/org/testcontainers/valkey/ValkeyContainer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java index 9555ec1e63c..e2ed4136e34 100644 --- a/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java +++ b/modules/valkey/src/main/java/org/testcontainers/valkey/ValkeyContainer.java @@ -147,12 +147,12 @@ public void start() { command.add("--requirepass"); command.add(password); - if (username != null && !username.isEmpty()) { + if (StringUtils.isNotEmpty(username)) { command.add("--user " + username + " on >" + password + " ~* +@all"); } } - if (persistenceVolume != null && !persistenceVolume.isEmpty()) { + if (StringUtils.isNotEmpty(persistenceVolume)) { command.addAll(Arrays.asList("--appendonly", "yes")); withFileSystemBind(persistenceVolume, "/data"); } @@ -168,7 +168,7 @@ public void start() { command.addAll(Arrays.asList("--loglevel", logLevel.getLevel())); } - if (initialImportScriptFile != null && !initialImportScriptFile.isEmpty()) { + if (StringUtils.isNotEmpty(initialImportScriptFile)) { withCopyToContainer(MountableFile.forHostPath(initialImportScriptFile), "/tmp/import.valkey"); withCopyToContainer(MountableFile.forClasspathResource("import.sh"), "/tmp/import.sh");