Skip to content

Commit

Permalink
Update comments; Add CreateClient unit tests
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
acarbonetto committed Jan 3, 2024
1 parent d23001f commit 09fd5c7
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 20 deletions.
1 change: 1 addition & 0 deletions java/client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencies {
testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'

// junit
testImplementation group: 'org.mockito', name: 'mockito-inline', version: '3.12.4'
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: '3.12.4'
}
Expand Down
11 changes: 5 additions & 6 deletions java/client/src/main/java/glide/api/RedisClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
import glide.managers.ConnectionManager;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;

/** Async (non-blocking) client for Redis in Standalone mode. Use {@link #CreateClient()} to
* request a client to Redis. */
/**
* Async (non-blocking) client for Redis in Standalone mode. Use {@link #CreateClient()} to request
* a client to Redis.
*/
public class RedisClient extends BaseClient {

public static CompletableFuture<RedisClient> CreateClient() {
Expand All @@ -25,7 +26,6 @@ public static CompletableFuture<RedisClient> CreateClient(String host, Integer p
RedisClientConfiguration.builder()
.address(NodeAddress.builder().host(host).port(port).build())
.build();

return CreateClient(config);
}

Expand All @@ -40,11 +40,10 @@ public static CompletableFuture<RedisClient> CreateClient(RedisClientConfigurati
ChannelHandler channelHandler = new ChannelHandler(callbackDispatcher, getSocket());
var connectionManager = new ConnectionManager(channelHandler);
var commandManager = new CommandManager(new CompletableFuture<>());
// TODO: send request with configuration to connection Manager as part of a follow-up PR
return CreateClient(config, connectionManager, commandManager);
}

private static CompletableFuture<RedisClient> CreateClient(
protected static CompletableFuture<RedisClient> CreateClient(
RedisClientConfiguration config,
ConnectionManager connectionManager,
CommandManager commandManager) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
import lombok.Singular;
import lombok.experimental.SuperBuilder;

/** Configuration settings class for creating a Redis Client. Shared settings for both a standalone and cluster clients. */
/**
* Configuration settings class for creating a Redis Client. Shared settings for both a standalone
* and cluster clients.
*/
@Getter
@SuperBuilder
public abstract class BaseClientConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public class CallbackDispatcher {
private final AtomicInteger requestId = new AtomicInteger(0);

/**
* Storage of Futures to handle responses. Map key is callback id, which starts from 0.
* Each future is a promise for every submitted by user request.
* Storage of Futures to handle responses. Map key is callback id, which starts from 0. Each
* future is a promise for every submitted by user request.
*/
private final Map<Integer, CompletableFuture<Response>> responses = new ConcurrentHashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import response.ResponseOuterClass.Response;

/**
* Class responsible for handling calls to/from a netty.io {@link Channel}.
* Uses a {@link CallbackDispatcher} to record callbacks of every request sent.
* Class responsible for handling calls to/from a netty.io {@link Channel}. Uses a {@link
* CallbackDispatcher} to record callbacks of every request sent.
*/
public class ChannelHandler {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ private static class Capabilities {
new Capabilities(isKQueueAvailable(), isEPollAvailable(), false, false);

/**
* Thread pools supplied to <em>Netty</em> to perform all async IO.
* Map key is supposed to be pool name + thread count as a string concat product.
* Thread pools supplied to <em>Netty</em> to perform all async IO. Map key is supposed to be pool
* name + thread count as a string concat product.
*/
private static final Map<String, EventLoopGroup> groups = new ConcurrentHashMap<>();

Expand Down Expand Up @@ -123,8 +123,8 @@ public static Class<? extends DomainSocketChannel> getClientUdsNettyChannelType(

/**
* A JVM shutdown hook to be registered. It is responsible for closing connection and freeing
* resources. It is recommended to use a class instead of lambda to ensure that it is called.
* See {@link Runtime#addShutdownHook}.
* resources. It is recommended to use a class instead of lambda to ensure that it is called. See
* {@link Runtime#addShutdownHook}.
*/
private static class ShutdownHook implements Runnable {
@Override
Expand Down
4 changes: 2 additions & 2 deletions java/client/src/main/java/glide/managers/CallbackManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public class CallbackManager {
private final AtomicInteger requestId = new AtomicInteger(0);

/**
* Storage of Futures to handle responses. Map key is callback id, which starts from 0.
* Each future is a promise for every submitted by user request.
* Storage of Futures to handle responses. Map key is callback id, which starts from 0. Each
* future is a promise for every submitted by user request.
*/
private final Map<Integer, CompletableFuture<Response>> responses = new ConcurrentHashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import glide.api.models.configuration.RedisClientConfiguration;
import glide.api.models.configuration.RedisClusterClientConfiguration;
import glide.connectors.handlers.ChannelHandler;
import glide.ffi.resolvers.RedisValueResolver;
import java.util.concurrent.CompletableFuture;
import lombok.RequiredArgsConstructor;
import response.ResponseOuterClass.Response;
Expand Down
153 changes: 153 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientCreateTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package glide.api;

import static glide.api.RedisClient.CreateClient;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;

import glide.api.models.configuration.RedisClientConfiguration;
import glide.ffi.resolvers.SocketListenerResolver;
import glide.managers.CommandManager;
import glide.managers.ConnectionManager;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

public class RedisClientCreateTest {

@Test
@SneakyThrows
public void createClient_withConfig_successfullyReturnsRedisClient() {
try (MockedStatic<RedisClient> mockedClient = Mockito.mockStatic(RedisClient.class);
MockedStatic<SocketListenerResolver> mockedSocketListener =
Mockito.mockStatic(SocketListenerResolver.class)) {

// setup
CompletableFuture<RedisClient> testFuture = new CompletableFuture<>();
RedisClientConfiguration config = RedisClientConfiguration.builder().build();

mockedSocketListener.when(SocketListenerResolver::getSocket).thenReturn("test_socket");
mockedClient.when(() -> CreateClient(any(), any(), any())).thenReturn(testFuture);

// method under test
mockedClient.when(() -> CreateClient(config)).thenCallRealMethod();

// exercise
CompletableFuture<RedisClient> result = CreateClient(config);

// verify
mockedClient.verify(() -> CreateClient(any(), any(), any()));
assertEquals(testFuture, result);
}
}

@Test
@SneakyThrows
public void createClient_noArgs_successfullyReturnsRedisClient() {
try (MockedStatic<RedisClient> mockedClient =
Mockito.mockStatic(RedisClient.class, withSettings().verboseLogging());
MockedStatic<SocketListenerResolver> mockedSocketListener =
Mockito.mockStatic(SocketListenerResolver.class, withSettings().verboseLogging())) {

// setup
mockedSocketListener.when(SocketListenerResolver::getSocket).thenReturn("test_socket");
CompletableFuture<RedisClient> testFuture = new CompletableFuture<>();
mockedClient.when(() -> CreateClient(any(), any(), any())).thenReturn(testFuture);

// method under test
mockedClient.when(RedisClient::CreateClient).thenCallRealMethod();
mockedClient.when(() -> CreateClient(any())).thenCallRealMethod();

// exercise
CompletableFuture<RedisClient> result = CreateClient();

// verify
assertEquals(testFuture, result);
}
}

@Test
@SneakyThrows
public void createClient_withHostPort_successfullyReturnsRedisClient() {
try (MockedStatic<RedisClient> mockedClient =
Mockito.mockStatic(RedisClient.class, withSettings().verboseLogging());
MockedStatic<SocketListenerResolver> mockedSocketListener =
Mockito.mockStatic(SocketListenerResolver.class, withSettings().verboseLogging())) {

// setup
String host = "testhost";
int port = 999;

mockedSocketListener.when(SocketListenerResolver::getSocket).thenReturn("test_socket");
CompletableFuture<RedisClient> testFuture = new CompletableFuture<>();
mockedClient.when(() -> CreateClient(any())).thenReturn(testFuture);

// method under test
mockedClient.when(RedisClient::CreateClient).thenCallRealMethod();
mockedClient.when(() -> CreateClient(host, port)).thenCallRealMethod();

// exercise
CompletableFuture<RedisClient> result = CreateClient();

// verify
assertEquals(testFuture, result);
}
}

@SneakyThrows
@Test
public void createClient_successfulConnectionReturnsRedisClient() {

// setup
ConnectionManager connectionManager = mock(ConnectionManager.class);
CommandManager commandManager = mock(CommandManager.class);
RedisClientConfiguration configuration = RedisClientConfiguration.builder().build();
CompletableFuture<Void> connectionFuture = new CompletableFuture<>();
connectionFuture.complete(null);
when(connectionManager.connectToRedis(eq(configuration))).thenReturn(connectionFuture);

// exercise
CompletableFuture<RedisClient> response =
RedisClient.CreateClient(configuration, connectionManager, commandManager);
RedisClient client = response.get();

// verify
assertEquals(connectionManager, client.connectionManager);
assertEquals(commandManager, client.commandManager);

// teardown
}

@SneakyThrows
@Test
public void createClient_errorOnConnectionThrowsExecutionException() {

// setup
ConnectionManager connectionManager = mock(ConnectionManager.class);
CommandManager commandManager = mock(CommandManager.class);
RedisClientConfiguration configuration = RedisClientConfiguration.builder().build();
CompletableFuture<Void> connectionFuture = new CompletableFuture<>();
RuntimeException exception = new RuntimeException("disconnected");
connectionFuture.completeExceptionally(exception);
when(connectionManager.connectToRedis(eq(configuration))).thenReturn(connectionFuture);

// exercise
CompletableFuture<RedisClient> response =
RedisClient.CreateClient(configuration, connectionManager, commandManager);

ExecutionException executionException =
assertThrows(ExecutionException.class, () -> response.get());

// verify
assertEquals(exception, executionException.getCause());

// teardown
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package glide.api;
package glide.managers;

import static glide.api.models.configuration.NodeAddress.DEFAULT_HOST;
import static glide.api.models.configuration.NodeAddress.DEFAULT_PORT;
Expand All @@ -22,7 +22,6 @@
import glide.api.models.configuration.RedisClusterClientConfiguration;
import glide.api.models.configuration.RedisCredentials;
import glide.connectors.handlers.ChannelHandler;
import glide.managers.ConnectionManager;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.junit.jupiter.api.BeforeEach;
Expand Down

0 comments on commit 09fd5c7

Please sign in to comment.