Convert lettuce 5.1 Async client tests from groovy to java (#9994)

This commit is contained in:
Jay DeLuca 2023-12-05 11:41:16 -05:00 committed by GitHub
parent 2eb5974ecd
commit d6d6528526
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 477 additions and 494 deletions

View File

@ -1,17 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1
import io.lettuce.core.RedisClient
import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceAsyncClientTest
import io.opentelemetry.instrumentation.test.AgentTestTrait
class LettuceAsyncClientTest extends AbstractLettuceAsyncClientTest implements AgentTestTrait {
@Override
RedisClient createClient(String uri) {
return RedisClient.create(uri)
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1;
import io.lettuce.core.RedisClient;
import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceAsyncClientTest;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;
class LettuceAsyncClientTest extends AbstractLettuceAsyncClientTest {
@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
@Override
public InstrumentationExtension getInstrumentationExtension() {
return testing;
}
@Override
protected RedisClient createClient(String uri) {
return RedisClient.create(uri);
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.lettuce.v5_1
import io.lettuce.core.RedisClient
import io.lettuce.core.resource.ClientResources
import io.opentelemetry.instrumentation.test.LibraryTestTrait
class LettuceAsyncClientTest extends AbstractLettuceAsyncClientTest implements LibraryTestTrait {
@Override
RedisClient createClient(String uri) {
return RedisClient.create(
ClientResources.builder()
.tracing(LettuceTelemetry.create(getOpenTelemetry()).newTracing())
.build(),
uri)
}
@Override
boolean testCallback() {
// context is not propagated into callbacks
return false
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.lettuce.v5_1;
import io.lettuce.core.RedisClient;
import io.lettuce.core.resource.ClientResources;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import org.junit.jupiter.api.extension.RegisterExtension;
class LettuceAsyncClientTest extends AbstractLettuceAsyncClientTest {
@RegisterExtension
static InstrumentationExtension testing = LibraryInstrumentationExtension.create();
@Override
public InstrumentationExtension getInstrumentationExtension() {
return testing;
}
@Override
protected RedisClient createClient(String uri) {
return RedisClient.create(
ClientResources.builder()
.tracing(
LettuceTelemetry.create(getInstrumentationExtension().getOpenTelemetry())
.newTracing())
.build(),
uri);
}
@Override
boolean testCallback() {
// context is not propagated into callbacks
return false;
}
}

View File

@ -1,450 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.lettuce.v5_1
import io.lettuce.core.ConnectionFuture
import io.lettuce.core.RedisClient
import io.lettuce.core.RedisFuture
import io.lettuce.core.RedisURI
import io.lettuce.core.api.StatefulConnection
import io.lettuce.core.api.async.RedisAsyncCommands
import io.lettuce.core.api.sync.RedisCommands
import io.lettuce.core.codec.StringCodec
import io.opentelemetry.instrumentation.api.semconv.network.internal.NetworkAttributes
import io.opentelemetry.instrumentation.test.InstrumentationSpecification
import io.opentelemetry.instrumentation.test.utils.PortUtils
import io.opentelemetry.semconv.SemanticAttributes
import org.testcontainers.containers.GenericContainer
import spock.lang.Shared
import spock.util.concurrent.AsyncConditions
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.function.BiConsumer
import java.util.function.BiFunction
import java.util.function.Consumer
import java.util.function.Function
import static io.opentelemetry.api.trace.SpanKind.CLIENT
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
abstract class AbstractLettuceAsyncClientTest extends InstrumentationSpecification {
public static final int DB_INDEX = 0
private static GenericContainer redisServer = new GenericContainer<>("redis:6.2.3-alpine").withExposedPorts(6379)
abstract RedisClient createClient(String uri)
@Shared
String host
@Shared
String expectedHostAttributeValue
@Shared
int port
@Shared
int incorrectPort
@Shared
String dbUriNonExistent
@Shared
String embeddedDbUri
@Shared
Map<String, String> testHashMap = [
firstname: "John",
lastname : "Doe",
age : "53"
]
RedisClient redisClient
StatefulConnection connection
RedisAsyncCommands<String, ?> asyncCommands
RedisCommands<String, ?> syncCommands
def setup() {
redisServer.start()
port = redisServer.getMappedPort(6379)
host = redisServer.getHost()
String dbAddr = host + ":" + port + "/" + DB_INDEX
embeddedDbUri = "redis://" + dbAddr
expectedHostAttributeValue = host == "127.0.0.1" ? null : host
incorrectPort = PortUtils.findOpenPort()
String dbAddrNonExistent = host + ":" + incorrectPort + "/" + DB_INDEX
dbUriNonExistent = "redis://" + dbAddrNonExistent
redisClient = createClient(embeddedDbUri)
redisClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS)
connection = redisClient.connect()
asyncCommands = connection.async()
syncCommands = connection.sync()
syncCommands.set("TESTKEY", "TESTVAL")
// 1 set
ignoreTracesAndClear(1)
}
def cleanup() {
connection.close()
redisClient.shutdown()
redisServer.stop()
}
boolean testCallback() {
return true
}
def <T> T runWithCallbackSpan(String spanName, Closure callback) {
if (testCallback()) {
return runWithSpan(spanName, callback)
}
return callback.call()
}
def "connect using get on ConnectionFuture"() {
setup:
RedisClient testConnectionClient = RedisClient.create(embeddedDbUri)
testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS)
when:
ConnectionFuture connectionFuture = testConnectionClient.connectAsync(StringCodec.UTF8,
RedisURI.create("redis://${host}:${port}?timeout=3s"))
StatefulConnection connection = connectionFuture.get()
then:
connection != null
// Lettuce tracing does not trace connect
assertTraces(0) {}
cleanup:
connection.close()
testConnectionClient.shutdown()
}
def "connect exception inside the connection future"() {
setup:
RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent)
testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS)
when:
ConnectionFuture connectionFuture = testConnectionClient.connectAsync(StringCodec.UTF8,
RedisURI.create("redis://${host}:${incorrectPort}?timeout=3s"))
StatefulConnection connection = connectionFuture.get()
then:
connection == null
thrown ExecutionException
// Lettuce tracing does not trace connect
assertTraces(0) {}
cleanup:
testConnectionClient.shutdown()
}
def "set command using Future get with timeout"() {
setup:
RedisFuture<String> redisFuture = asyncCommands.set("TESTSETKEY", "TESTSETVAL")
String res = redisFuture.get(3, TimeUnit.SECONDS)
expect:
res == "OK"
assertTraces(1) {
trace(0, 1) {
span(0) {
name "SET"
kind CLIENT
attributes {
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
"$NetworkAttributes.NETWORK_PEER_PORT" port
"$SemanticAttributes.DB_SYSTEM" "redis"
"$SemanticAttributes.DB_STATEMENT" "SET TESTSETKEY ?"
}
event(0) {
eventName "redis.encode.start"
}
event(1) {
eventName "redis.encode.end"
}
}
}
}
}
def "get command chained with thenAccept"() {
setup:
def conds = new AsyncConditions()
Consumer<String> consumer = new Consumer<String>() {
@Override
void accept(String res) {
runWithCallbackSpan("callback") {
conds.evaluate {
assert res == "TESTVAL"
}
}
}
}
when:
runWithSpan("parent") {
RedisFuture<String> redisFuture = asyncCommands.get("TESTKEY")
redisFuture.thenAccept(consumer)
}
then:
conds.await(10)
assertTraces(1) {
trace(0, 2 + (testCallback() ? 1 : 0)) {
span(0) {
name "parent"
kind INTERNAL
hasNoParent()
}
span(1) {
name "GET"
kind CLIENT
attributes {
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
"$NetworkAttributes.NETWORK_PEER_PORT" port
"$SemanticAttributes.DB_SYSTEM" "redis"
"$SemanticAttributes.DB_STATEMENT" "GET TESTKEY"
}
event(0) {
eventName "redis.encode.start"
}
event(1) {
eventName "redis.encode.end"
}
}
if (testCallback()) {
span(2) {
name "callback"
kind INTERNAL
childOf(span(0))
}
}
}
}
}
// to make sure instrumentation's chained completion stages won't interfere with user's, while still
// recording metrics
def "get non existent key command with handleAsync and chained with thenApply"() {
setup:
def conds = new AsyncConditions()
String successStr = "KEY MISSING"
BiFunction<String, Throwable, String> firstStage = new BiFunction<String, Throwable, String>() {
@Override
String apply(String res, Throwable throwable) {
runWithCallbackSpan("callback1") {
conds.evaluate {
assert res == null
assert throwable == null
}
}
return (res == null ? successStr : res)
}
}
Function<String, Object> secondStage = new Function<String, Object>() {
@Override
Object apply(String input) {
runWithCallbackSpan("callback2") {
conds.evaluate {
assert input == successStr
}
}
return null
}
}
when:
runWithSpan("parent") {
RedisFuture<String> redisFuture = asyncCommands.get("NON_EXISTENT_KEY")
redisFuture.handleAsync(firstStage).thenApply(secondStage)
}
then:
conds.await(10)
assertTraces(1) {
trace(0, 2 + (testCallback() ? 2 : 0)) {
span(0) {
name "parent"
kind INTERNAL
hasNoParent()
}
span(1) {
name "GET"
kind CLIENT
childOf(span(0))
attributes {
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
"$NetworkAttributes.NETWORK_PEER_PORT" port
"$SemanticAttributes.DB_SYSTEM" "redis"
"$SemanticAttributes.DB_STATEMENT" "GET NON_EXISTENT_KEY"
}
event(0) {
eventName "redis.encode.start"
}
event(1) {
eventName "redis.encode.end"
}
}
if (testCallback()) {
span(2) {
name "callback1"
kind INTERNAL
childOf(span(0))
}
span(3) {
name "callback2"
kind INTERNAL
childOf(span(0))
}
}
}
}
}
def "command with no arguments using a biconsumer"() {
setup:
def conds = new AsyncConditions()
BiConsumer<String, Throwable> biConsumer = new BiConsumer<String, Throwable>() {
@Override
void accept(String keyRetrieved, Throwable throwable) {
runWithCallbackSpan("callback") {
conds.evaluate {
assert keyRetrieved != null
}
}
}
}
when:
runWithSpan("parent") {
RedisFuture<String> redisFuture = asyncCommands.randomkey()
redisFuture.whenCompleteAsync(biConsumer)
}
then:
conds.await(10)
assertTraces(1) {
trace(0, 2 + (testCallback() ? 1 : 0)) {
span(0) {
name "parent"
kind INTERNAL
hasNoParent()
}
span(1) {
name "RANDOMKEY"
kind CLIENT
childOf(span(0))
attributes {
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
"$NetworkAttributes.NETWORK_PEER_PORT" port
"$SemanticAttributes.DB_STATEMENT" "RANDOMKEY"
"$SemanticAttributes.DB_SYSTEM" "redis"
}
event(0) {
eventName "redis.encode.start"
}
event(1) {
eventName "redis.encode.end"
}
}
if (testCallback()) {
span(2) {
name "callback"
kind INTERNAL
childOf(span(0))
}
}
}
}
}
def "hash set and then nest apply to hash getall"() {
setup:
def conds = new AsyncConditions()
when:
RedisFuture<String> hmsetFuture = asyncCommands.hmset("TESTHM", testHashMap)
hmsetFuture.thenApplyAsync(new Function<String, Object>() {
@Override
Object apply(String setResult) {
conds.evaluate {
assert setResult == "OK"
}
RedisFuture<Map<String, String>> hmGetAllFuture = asyncCommands.hgetall("TESTHM")
hmGetAllFuture.exceptionally(new Function<Throwable, Map<String, String>>() {
@Override
Map<String, String> apply(Throwable throwable) {
println("unexpected:" + throwable.toString())
throwable.printStackTrace()
assert false
return null
}
})
hmGetAllFuture.thenAccept(new Consumer<Map<String, String>>() {
@Override
void accept(Map<String, String> hmGetAllResult) {
conds.evaluate {
assert testHashMap == hmGetAllResult
}
}
})
return null
}
})
then:
conds.await(10)
assertTraces(2) {
trace(0, 1) {
span(0) {
name "HMSET"
kind CLIENT
attributes {
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
"$NetworkAttributes.NETWORK_PEER_PORT" port
"$SemanticAttributes.DB_SYSTEM" "redis"
"$SemanticAttributes.DB_STATEMENT" "HMSET TESTHM firstname ? lastname ? age ?"
}
event(0) {
eventName "redis.encode.start"
}
event(1) {
eventName "redis.encode.end"
}
}
}
trace(1, 1) {
span(0) {
name "HGETALL"
kind CLIENT
attributes {
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
"$NetworkAttributes.NETWORK_PEER_PORT" port
"$SemanticAttributes.DB_SYSTEM" "redis"
"$SemanticAttributes.DB_STATEMENT" "HGETALL TESTHM"
}
event(0) {
eventName "redis.encode.start"
}
event(1) {
eventName "redis.encode.end"
}
}
}
}
}
}

View File

@ -0,0 +1,411 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.lettuce.v5_1;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import com.google.common.collect.ImmutableMap;
import io.lettuce.core.ConnectionFuture;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.codec.StringCodec;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.api.semconv.network.internal.NetworkAttributes;
import io.opentelemetry.instrumentation.test.utils.PortUtils;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import io.opentelemetry.semconv.SemanticAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@SuppressWarnings({"InterruptedExceptionSwallowed"})
public abstract class AbstractLettuceAsyncClientTest extends AbstractLettuceClientTest {
private static String dbUriNonExistent;
private static int incorrectPort;
private static final ImmutableMap<String, String> testHashMap =
ImmutableMap.of(
"firstname", "John",
"lastname", "Doe",
"age", "53");
private static RedisAsyncCommands<String, String> asyncCommands;
@BeforeAll
void setUp() {
redisServer.start();
host = redisServer.getHost();
port = redisServer.getMappedPort(6379);
embeddedDbUri = "redis://" + host + ":" + port + "/" + DB_INDEX;
incorrectPort = PortUtils.findOpenPort();
dbUriNonExistent = "redis://" + host + ":" + incorrectPort + "/" + DB_INDEX;
redisClient = createClient(embeddedDbUri);
redisClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS);
connection = redisClient.connect();
asyncCommands = connection.async();
RedisCommands<String, String> syncCommands = connection.sync();
syncCommands.set("TESTKEY", "TESTVAL");
// 1 set trace
getInstrumentationExtension().waitForTraces(1);
getInstrumentationExtension().clearData();
}
@AfterAll
static void cleanUp() {
connection.close();
redisClient.shutdown();
redisServer.stop();
}
boolean testCallback() {
return true;
}
@Test
void testConnectUsingGetOnConnectionFuture() throws Exception {
RedisClient testConnectionClient = RedisClient.create(embeddedDbUri);
testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS);
ConnectionFuture<StatefulRedisConnection<String, String>> connectionFuture =
testConnectionClient.connectAsync(
StringCodec.UTF8, RedisURI.create("redis://" + host + ":" + port + "?timeout=3s"));
StatefulRedisConnection<String, String> connection1 = connectionFuture.get();
cleanup.deferCleanup(connection1);
cleanup.deferCleanup(testConnectionClient::shutdown);
assertThat(connection1).isNotNull();
// Lettuce tracing does not trace connect
assertThat(getInstrumentationExtension().spans()).isEmpty();
}
@Test
void testConnectExceptionInsideTheConnectionFuture() {
RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent);
testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS);
cleanup.deferCleanup(testConnectionClient::shutdown);
Throwable thrown =
catchThrowable(
() -> {
ConnectionFuture<StatefulRedisConnection<String, String>> connectionFuture =
testConnectionClient.connectAsync(
StringCodec.UTF8,
RedisURI.create("redis://" + host + ":" + incorrectPort + "?timeout=3s"));
StatefulRedisConnection<String, String> connection1 = connectionFuture.get();
cleanup.deferCleanup(connection1);
assertThat(connection1).isNull();
});
assertThat(thrown).isInstanceOf(ExecutionException.class);
// Lettuce tracing does not trace connect
assertThat(getInstrumentationExtension().spans()).isEmpty();
}
@Test
void testSetCommandUsingFutureGetWithTimeout() throws Exception {
RedisFuture<String> redisFuture = asyncCommands.set("TESTSETKEY", "TESTSETVAL");
String res = redisFuture.get(3, TimeUnit.SECONDS);
assertThat(res).isEqualTo("OK");
getInstrumentationExtension()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("SET")
.hasKind(SpanKind.CLIENT)
.hasAttributesSatisfyingExactly(
equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"),
equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"),
equalTo(NetworkAttributes.NETWORK_PEER_PORT, port),
equalTo(SemanticAttributes.DB_SYSTEM, "redis"),
equalTo(SemanticAttributes.DB_STATEMENT, "SET TESTSETKEY ?"))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
}
@Test
void testGetCommandChainedWithThenAccept() throws Exception {
CompletableFuture<String> future = new CompletableFuture<>();
Consumer<String> consumer =
res -> {
if (testCallback()) {
getInstrumentationExtension()
.runWithSpan("callback", () -> assertThat(res).isEqualTo("TESTVAL"));
}
future.complete(res);
};
getInstrumentationExtension()
.runWithSpan(
"parent",
() -> {
RedisFuture<String> redisFuture = asyncCommands.get("TESTKEY");
redisFuture.thenAccept(consumer);
});
assertThat(future.get(10, TimeUnit.SECONDS)).isEqualTo("TESTVAL");
getInstrumentationExtension()
.waitAndAssertTraces(
trace -> {
List<Consumer<SpanDataAssert>> spanAsserts =
new ArrayList<>(
Arrays.asList(
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
span ->
span.hasName("GET")
.hasKind(SpanKind.CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"),
equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"),
equalTo(NetworkAttributes.NETWORK_PEER_PORT, port),
equalTo(SemanticAttributes.DB_SYSTEM, "redis"),
equalTo(SemanticAttributes.DB_STATEMENT, "GET TESTKEY"))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
if (testCallback()) {
spanAsserts.add(
span ->
span.hasName("callback")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)));
}
trace.hasSpansSatisfyingExactly(spanAsserts);
});
}
// to make sure instrumentation's chained completion stages won't interfere with user's, while
// still recording spans
@Test
void testGetNonExistentKeyCommandWithHandleAsyncAndChainedWithThenApply() throws Exception {
CompletableFuture<String> future = new CompletableFuture<>();
String successStr = "KEY MISSING";
BiFunction<String, Throwable, String> firstStage =
(res, error) -> {
if (testCallback()) {
getInstrumentationExtension()
.runWithSpan(
"callback1",
() -> {
assertThat(res).isNull();
assertThat(error).isNull();
});
}
return (res == null ? successStr : res);
};
Function<String, Object> secondStage =
input -> {
if (testCallback()) {
getInstrumentationExtension()
.runWithSpan(
"callback2",
() -> {
assertThat(input).isEqualTo(successStr);
});
}
future.complete(successStr);
return null;
};
getInstrumentationExtension()
.runWithSpan(
"parent",
() -> {
RedisFuture<String> redisFuture = asyncCommands.get("NON_EXISTENT_KEY");
redisFuture.handleAsync(firstStage).thenApply(secondStage);
});
assertThat(future.get(10, TimeUnit.SECONDS)).isEqualTo(successStr);
getInstrumentationExtension()
.waitAndAssertTraces(
trace -> {
List<Consumer<SpanDataAssert>> spanAsserts =
new ArrayList<>(
Arrays.asList(
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
span ->
span.hasName("GET")
.hasKind(SpanKind.CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"),
equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"),
equalTo(NetworkAttributes.NETWORK_PEER_PORT, port),
equalTo(SemanticAttributes.DB_SYSTEM, "redis"),
equalTo(
SemanticAttributes.DB_STATEMENT, "GET NON_EXISTENT_KEY"))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
if (testCallback()) {
spanAsserts.addAll(
Arrays.asList(
span ->
span.hasName("callback1")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)),
span ->
span.hasName("callback2")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0))));
}
trace.hasSpansSatisfyingExactly(spanAsserts);
});
}
@Test
void testCommandWithNoArgumentsUsingBiconsumer() throws Exception {
CompletableFuture<String> future = new CompletableFuture<>();
BiConsumer<String, Throwable> biConsumer =
(keyRetrieved, error) -> {
if (testCallback()) {
getInstrumentationExtension()
.runWithSpan(
"callback",
() -> {
assertThat(keyRetrieved).isNotNull();
});
}
future.complete(keyRetrieved);
};
getInstrumentationExtension()
.runWithSpan(
"parent",
() -> {
RedisFuture<String> redisFuture = asyncCommands.randomkey();
redisFuture.whenCompleteAsync(biConsumer);
});
assertThat(future.get(10, TimeUnit.SECONDS)).isNotNull();
getInstrumentationExtension()
.waitAndAssertTraces(
trace -> {
List<Consumer<SpanDataAssert>> spanAsserts =
new ArrayList<>(
Arrays.asList(
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
span ->
span.hasName("RANDOMKEY")
.hasKind(SpanKind.CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"),
equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"),
equalTo(NetworkAttributes.NETWORK_PEER_PORT, port),
equalTo(SemanticAttributes.DB_SYSTEM, "redis"),
equalTo(SemanticAttributes.DB_STATEMENT, "RANDOMKEY"))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
if (testCallback()) {
spanAsserts.add(
span ->
span.hasName("callback")
.hasKind(SpanKind.INTERNAL)
.hasParent(trace.getSpan(0)));
}
trace.hasSpansSatisfyingExactly(spanAsserts);
});
}
@Test
void testHashSetAndThenNestApplyToHashGetall() throws Exception {
CompletableFuture<Map<String, String>> future = new CompletableFuture<>();
RedisFuture<String> hmsetFuture = asyncCommands.hmset("TESTHM", testHashMap);
hmsetFuture.thenApplyAsync(
setResult -> {
// Wait for 'hmset' trace to get written
getInstrumentationExtension().waitForTraces(1);
if (!"OK".equals(setResult)) {
future.completeExceptionally(new AssertionError("Wrong hmset result " + setResult));
return null;
}
RedisFuture<Map<String, String>> hmGetAllFuture = asyncCommands.hgetall("TESTHM");
hmGetAllFuture.whenComplete(
(result, exception) -> {
if (exception != null) {
future.completeExceptionally(exception);
} else {
future.complete(result);
}
});
return null;
});
assertThat(future.get(10, TimeUnit.SECONDS)).isEqualTo(testHashMap);
getInstrumentationExtension()
.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("HMSET")
.hasKind(SpanKind.CLIENT)
.hasAttributesSatisfyingExactly(
equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"),
equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"),
equalTo(NetworkAttributes.NETWORK_PEER_PORT, port),
equalTo(SemanticAttributes.DB_SYSTEM, "redis"),
equalTo(
SemanticAttributes.DB_STATEMENT,
"HMSET TESTHM firstname ? lastname ? age ?"))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))),
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("HGETALL")
.hasKind(SpanKind.CLIENT)
.hasAttributesSatisfyingExactly(
equalTo(SemanticAttributes.NETWORK_TYPE, "ipv4"),
equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"),
equalTo(NetworkAttributes.NETWORK_PEER_PORT, port),
equalTo(SemanticAttributes.DB_SYSTEM, "redis"),
equalTo(SemanticAttributes.DB_STATEMENT, "HGETALL TESTHM"))
.hasEventsSatisfyingExactly(
event -> event.hasName("redis.encode.start"),
event -> event.hasName("redis.encode.end"))));
}
}