Skip to content

Commit

Permalink
Java: Added Missing Client Tests. (#127) (valkey-io#1120)
Browse files Browse the repository at this point in the history
* Java: Added Missing Client Tests. (#127)

* Minor update.

* Refactored to unicode litterals.
  • Loading branch information
SanHalacogluImproving authored Mar 18, 2024
1 parent 232f077 commit 5c6a79d
Show file tree
Hide file tree
Showing 6 changed files with 489 additions and 95 deletions.
113 changes: 113 additions & 0 deletions java/integTest/src/test/java/glide/SharedClientTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide;

import static glide.TestUtilities.commonClientConfig;
import static glide.TestUtilities.commonClusterClientConfig;
import static glide.TestUtilities.getRandomString;
import static glide.api.BaseClient.OK;
import static org.junit.jupiter.api.Assertions.assertEquals;

import glide.api.BaseClient;
import glide.api.RedisClient;
import glide.api.RedisClusterClient;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;
import lombok.Getter;
import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

@Timeout(25)
public class SharedClientTests {

private static RedisClient standaloneClient = null;
private static RedisClusterClient clusterClient = null;

@Getter private static List<Arguments> clients;

@BeforeAll
@SneakyThrows
public static void init() {
standaloneClient = RedisClient.CreateClient(commonClientConfig().build()).get();
clusterClient =
RedisClusterClient.CreateClient(commonClusterClientConfig().requestTimeout(10000).build())
.get();

clients = List.of(Arguments.of(standaloneClient), Arguments.of(clusterClient));
}

@AfterAll
@SneakyThrows
public static void teardown() {
standaloneClient.close();
clusterClient.close();
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
public void send_and_receive_large_values(BaseClient client) {
int length = 1 << 16;
String key = getRandomString(length);
String value = getRandomString(length);

assertEquals(length, key.length());
assertEquals(length, value.length());
assertEquals(OK, client.set(key, value).get());
assertEquals(value, client.get(key).get());
}

@SneakyThrows
@ParameterizedTest
@MethodSource("getClients")
public void send_and_receive_non_ascii_unicode(BaseClient client) {
String key = "foo";
String value = "\u05E9\u05DC\u05D5\u05DD hello \u6C49\u5B57";

assertEquals(OK, client.set(key, value).get());
assertEquals(value, client.get(key).get());
}

private static Stream<Arguments> clientAndDataSize() {
return Stream.of(
Arguments.of(standaloneClient, 100),
Arguments.of(standaloneClient, 1 << 16),
Arguments.of(clusterClient, 100),
Arguments.of(clusterClient, 1 << 16));
}

@ParameterizedTest
@MethodSource("clientAndDataSize")
public void client_can_handle_concurrent_workload(BaseClient client, int valueSize) {
ExecutorService executorService = Executors.newCachedThreadPool();
CompletableFuture[] futures = new CompletableFuture[100];

for (int i = 0; i < 100; i++) {
futures[i] =
CompletableFuture.runAsync(
() -> {
String key = getRandomString(valueSize);
String value = getRandomString(valueSize);
try {
assertEquals(OK, client.set(key, value).get());
assertEquals(value, client.get(key).get());
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
},
executorService);
}

CompletableFuture.allOf(futures).join();

executorService.shutdown();
}
}
53 changes: 53 additions & 0 deletions java/integTest/src/test/java/glide/TestUtilities.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide;

import static glide.TestConfiguration.CLUSTER_PORTS;
import static glide.TestConfiguration.STANDALONE_PORTS;
import static org.junit.jupiter.api.Assertions.fail;

import glide.api.models.ClusterValue;
import glide.api.models.configuration.NodeAddress;
import glide.api.models.configuration.RedisClientConfiguration;
import glide.api.models.configuration.RedisClusterClientConfiguration;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.experimental.UtilityClass;

@UtilityClass
Expand All @@ -23,4 +32,48 @@ public static int getValueFromInfo(String data, String value) {
public static <T> T getFirstEntryFromMultiValue(ClusterValue<T> data) {
return data.getMultiValue().get(data.getMultiValue().keySet().toArray(String[]::new)[0]);
}

/** Generates a random string of a specified length using ASCII letters. */
public static String getRandomString(int length) {
String asciiLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
SecureRandom random = new SecureRandom();
StringBuilder sb = new StringBuilder(length);

for (int i = 0; i < length; i++) {
int index = random.nextInt(asciiLetters.length());
char randomChar = asciiLetters.charAt(index);
sb.append(randomChar);
}

return sb.toString();
}

/**
* Transforms server info string into a Map, using lines with ":" to create key-value pairs,
* replacing duplicates with the last encountered value.
*/
public static Map<String, String> parseInfoResponseToMap(String serverInfo) {
return serverInfo
.lines()
.filter(line -> line.contains(":"))
.map(line -> line.split(":", 2))
.collect(
Collectors.toMap(
parts -> parts[0],
parts -> parts[1],
(existingValue, newValue) -> newValue,
HashMap::new));
}

public static RedisClientConfiguration.RedisClientConfigurationBuilder<?, ?>
commonClientConfig() {
return RedisClientConfiguration.builder()
.address(NodeAddress.builder().port(STANDALONE_PORTS[0]).build());
}

public static RedisClusterClientConfiguration.RedisClusterClientConfigurationBuilder<?, ?>
commonClusterClientConfig() {
return RedisClusterClientConfiguration.builder()
.address(NodeAddress.builder().port(CLUSTER_PORTS[0]).build());
}
}
48 changes: 0 additions & 48 deletions java/integTest/src/test/java/glide/cluster/ClientTests.java

This file was deleted.

161 changes: 161 additions & 0 deletions java/integTest/src/test/java/glide/cluster/ClusterClientTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.cluster;

import static glide.TestConfiguration.REDIS_VERSION;
import static glide.TestUtilities.commonClusterClientConfig;
import static glide.TestUtilities.getRandomString;
import static glide.api.BaseClient.OK;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import glide.api.RedisClusterClient;
import glide.api.models.configuration.RedisCredentials;
import glide.api.models.exceptions.ClosingException;
import glide.api.models.exceptions.RequestException;
import java.util.concurrent.ExecutionException;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

@Timeout(10)
public class ClusterClientTests {

@SneakyThrows
@Test
public void register_client_name_and_version() {
String minVersion = "7.2.0";
assumeTrue(
REDIS_VERSION.isGreaterThanOrEqualTo(minVersion),
"Redis version required >= " + minVersion);

RedisClusterClient client =
RedisClusterClient.CreateClient(commonClusterClientConfig().build()).get();

String info =
(String) client.customCommand(new String[] {"CLIENT", "INFO"}).get().getSingleValue();
assertTrue(info.contains("lib-name=GlideJava"));
assertTrue(info.contains("lib-ver=unknown"));

client.close();
}

@SneakyThrows
@Test
public void can_connect_with_auth_requirepass() {
RedisClusterClient client =
RedisClusterClient.CreateClient(commonClusterClientConfig().build()).get();

String password = "TEST_AUTH";
client.customCommand(new String[] {"CONFIG", "SET", "requirepass", password}).get();

// Creation of a new client without a password should fail
ExecutionException exception =
assertThrows(
ExecutionException.class,
() -> RedisClusterClient.CreateClient(commonClusterClientConfig().build()).get());
assertTrue(exception.getCause() instanceof ClosingException);

// Creation of a new client with credentials
RedisClusterClient auth_client =
RedisClusterClient.CreateClient(
commonClusterClientConfig()
.credentials(RedisCredentials.builder().password(password).build())
.build())
.get();

String key = getRandomString(10);
String value = getRandomString(10);

assertEquals(OK, auth_client.set(key, value).get());
assertEquals(value, auth_client.get(key).get());

// Reset password
client.customCommand(new String[] {"CONFIG", "SET", "requirepass", ""}).get();

auth_client.close();
client.close();
}

@SneakyThrows
@Test
public void can_connect_with_auth_acl() {
RedisClusterClient client =
RedisClusterClient.CreateClient(commonClusterClientConfig().build()).get();

String username = "testuser";
String password = "TEST_AUTH";
assertEquals(
OK,
client
.customCommand(
new String[] {
"ACL",
"SETUSER",
username,
"on",
"allkeys",
"+get",
"+cluster",
"+ping",
"+info",
"+client",
">" + password,
})
.get()
.getSingleValue());

String key = getRandomString(10);
String value = getRandomString(10);

assertEquals(OK, client.set(key, value).get());

// Creation of a new cluster client with credentials
RedisClusterClient testUserClient =
RedisClusterClient.CreateClient(
commonClusterClientConfig()
.credentials(
RedisCredentials.builder().username(username).password(password).build())
.build())
.get();

assertEquals(value, testUserClient.get(key).get());

ExecutionException executionException =
assertThrows(ExecutionException.class, () -> testUserClient.set("foo", "bar").get());
assertTrue(executionException.getCause() instanceof RequestException);

client.customCommand(new String[] {"ACL", "DELUSER", username}).get();

testUserClient.close();
client.close();
}

@SneakyThrows
@Test
public void client_name() {
RedisClusterClient client =
RedisClusterClient.CreateClient(
commonClusterClientConfig().clientName("TEST_CLIENT_NAME").build())
.get();

String clientInfo =
(String) client.customCommand(new String[] {"CLIENT", "INFO"}).get().getSingleValue();
assertTrue(clientInfo.contains("name=TEST_CLIENT_NAME"));

client.close();
}

@Test
@SneakyThrows
public void closed_client_throws_ExecutionException_with_ClosingException_as_cause() {
RedisClusterClient client =
RedisClusterClient.CreateClient(commonClusterClientConfig().build()).get();

client.close();
ExecutionException executionException =
assertThrows(ExecutionException.class, () -> client.set("foo", "bar").get());
assertTrue(executionException.getCause() instanceof ClosingException);
}
}
Loading

0 comments on commit 5c6a79d

Please sign in to comment.