diff --git a/instrumentation/rediscala-1.8/javaagent/build.gradle.kts b/instrumentation/rediscala-1.8/javaagent/build.gradle.kts index 8442cb3dbf1b..f790124dd665 100644 --- a/instrumentation/rediscala-1.8/javaagent/build.gradle.kts +++ b/instrumentation/rediscala-1.8/javaagent/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("otel.javaagent-instrumentation") + id("otel.scala-conventions") } muzzle { diff --git a/instrumentation/rediscala-1.8/javaagent/src/test/groovy/RediscalaClientTest.groovy b/instrumentation/rediscala-1.8/javaagent/src/test/groovy/RediscalaClientTest.groovy deleted file mode 100644 index e81106cbdf84..000000000000 --- a/instrumentation/rediscala-1.8/javaagent/src/test/groovy/RediscalaClientTest.groovy +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.semconv.incubating.DbIncubatingAttributes -import org.testcontainers.containers.GenericContainer -import redis.ByteStringDeserializerDefault -import redis.ByteStringSerializerLowPriority -import redis.RedisClient -import redis.RedisDispatcher -import scala.Option -import scala.concurrent.Await -import scala.concurrent.duration.Duration -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable - -class RediscalaClientTest extends AgentInstrumentationSpecification { - - private static GenericContainer redisServer = new GenericContainer<>("redis:6.2.3-alpine").withExposedPorts(6379) - - @Shared - int port - - @Shared - def system - - @Shared - RedisClient redisClient - - def setupSpec() { - redisServer.start() - String host = redisServer.getHost() - port = redisServer.getMappedPort(6379) - // latest has separate artifacts for akka an pekko, currently latestDepTestLibrary picks the - // pekko one - try { - def clazz = Class.forName("akka.actor.ActorSystem") - system = clazz.getMethod("create").invoke(null) - } catch (ClassNotFoundException exception) { - def clazz = Class.forName("org.apache.pekko.actor.ActorSystem") - system = clazz.getMethod("create").invoke(null) - } - // latest RedisClient constructor takes username as argument - if (RedisClient.metaClass.getMetaMethod("username") != null) { - redisClient = new RedisClient(host, - port, - Option.apply(null), - Option.apply(null), - Option.apply(null), - "RedisClient", - Option.apply(null), - system, - new RedisDispatcher("rediscala.rediscala-client-worker-dispatcher")) - } else { - redisClient = new RedisClient(host, - port, - Option.apply(null), - Option.apply(null), - "RedisClient", - Option.apply(null), - system, - new RedisDispatcher("rediscala.rediscala-client-worker-dispatcher")) - } - } - - def cleanupSpec() { - redisServer.stop() - system?.terminate() - } - - def "set command"() { - when: - def value = redisClient.set("foo", - "bar", - Option.apply(null), - Option.apply(null), - false, - false, - new ByteStringSerializerLowPriority.String$()) - - - then: - Await.result(value, Duration.apply("3 second")) == true - assertTraces(1) { - trace(0, 1) { - span(0) { - name "SET" - kind CLIENT - attributes { - "$DbIncubatingAttributes.DB_SYSTEM" "redis" - "${maybeStable(DbIncubatingAttributes.DB_OPERATION)}" "SET" - } - } - } - } - } - - def "get command"() { - when: - def (write, value) = runWithSpan("parent") { - def w = redisClient.set("bar", - "baz", - Option.apply(null), - Option.apply(null), - false, - false, - new ByteStringSerializerLowPriority.String$()) - def v = redisClient.get("bar", new ByteStringDeserializerDefault.String$()) - return new Tuple(w, v) - } - - then: - Await.result(write, Duration.apply("3 second")) == true - Await.result(value, Duration.apply("3 second")) == Option.apply("baz") - assertTraces(1) { - trace(0, 3) { - span(0) { - name "parent" - } - span(1) { - name "SET" - kind CLIENT - childOf span(0) - attributes { - "$DbIncubatingAttributes.DB_SYSTEM" "redis" - "${maybeStable(DbIncubatingAttributes.DB_OPERATION)}" "SET" - } - } - span(2) { - name "GET" - kind CLIENT - childOf span(0) - attributes { - "$DbIncubatingAttributes.DB_SYSTEM" "redis" - "${maybeStable(DbIncubatingAttributes.DB_OPERATION)}" "GET" - } - } - } - } - } -} diff --git a/instrumentation/rediscala-1.8/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/rediscala/RediscalaClientTest.scala b/instrumentation/rediscala-1.8/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/rediscala/RediscalaClientTest.scala new file mode 100644 index 000000000000..34292a6d60c5 --- /dev/null +++ b/instrumentation/rediscala-1.8/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/rediscala/RediscalaClientTest.scala @@ -0,0 +1,189 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package rediscala + +import io.opentelemetry.api.trace.SpanKind.CLIENT +import io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension +import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo +import io.opentelemetry.sdk.testing.assertj.{SpanDataAssert, TraceAssert} +import io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION +import io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM +import io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DbSystemIncubatingValues.REDIS +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.{AfterAll, BeforeAll, Test, TestInstance} +import org.junit.jupiter.api.extension.RegisterExtension +import org.testcontainers.containers.GenericContainer +import redis.{RedisClient, RedisDispatcher} + +import java.util.function.Consumer +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, Future} + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class RediscalaClientTest { + + @RegisterExtension val testing = AgentInstrumentationExtension.create + + var system: Object = null + var redisServer: GenericContainer[_] = null + var redisClient: RedisClient = null + + @BeforeAll + def setUp(): Unit = { + redisServer = + new GenericContainer("redis:6.2.3-alpine").withExposedPorts(6379) + redisServer.start() + + val host: String = redisServer.getHost + val port: Integer = redisServer.getMappedPort(6379) + + try { + val clazz = Class.forName("akka.actor.ActorSystem") + system = clazz.getMethod("create").invoke(null) + } catch { + case _: ClassNotFoundException => + val clazz = Class.forName("org.apache.pekko.actor.ActorSystem") + system = clazz.getMethod("create").invoke(null) + } + + try { + // latest RedisClient constructor takes username as argument + classOf[RedisClient].getMethod("username") + redisClient = classOf[RedisClient] + .getConstructors()(0) + .newInstance( + host, + port, + Option.apply(null), + Option.apply(null), + Option.apply(null), + "RedisClient", + Option.apply(null), + system, + RedisDispatcher("rediscala.rediscala-client-worker-dispatcher") + ) + .asInstanceOf[RedisClient] + } catch { + case _: Exception => + redisClient = classOf[RedisClient] + .getConstructors()(0) + .newInstance( + host, + port, + Option.apply(null), + Option.apply(null), + "RedisClient", + Option.apply(null), + system, + RedisDispatcher("rediscala.rediscala-client-worker-dispatcher") + ) + .asInstanceOf[RedisClient] + } + } + + @AfterAll + def tearDown(): Unit = { + if (system != null) { + system.getClass.getMethod("terminate").invoke(system) + } + redisServer.stop() + } + + @Test def testSetCommand(): Unit = { + val value = testing.runWithSpan( + "parent", + new ThrowingSupplier[Future[Boolean], Exception] { + override def get(): Future[Boolean] = { + redisClient.set("foo", "bar") + } + } + ) + + assertThat(Await.result(value, Duration.apply("3 second"))).isTrue + testing.waitAndAssertTraces(new Consumer[TraceAssert] { + override def accept(trace: TraceAssert): Unit = + trace.hasSpansSatisfyingExactly( + new Consumer[SpanDataAssert] { + override def accept(span: SpanDataAssert): Unit = { + span.hasName("parent").hasNoParent + } + }, + new Consumer[SpanDataAssert] { + override def accept(span: SpanDataAssert): Unit = { + span + .hasName("SET") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(DB_SYSTEM, REDIS), + equalTo(SemconvStabilityUtil.maybeStable(DB_OPERATION), "SET") + ) + } + } + ) + }) + } + + @Test def testGetCommand(): Unit = { + val (write, value) = testing.runWithSpan( + "parent", + new ThrowingSupplier[ + (Future[Boolean], Future[Option[String]]), + Exception + ] { + override def get(): (Future[Boolean], Future[Option[String]]) = { + val write = redisClient.set("bar", "baz") + val value = redisClient.get[String]("bar") + (write, value) + } + } + ) + + assertThat(Await.result(write, Duration.apply("3 second"))).isTrue + assertThat( + Await + .result(value, Duration.apply("3 second")) + .get + ).isEqualTo("baz") + + testing.waitAndAssertTraces(new Consumer[TraceAssert] { + override def accept(trace: TraceAssert): Unit = + trace.hasSpansSatisfyingExactly( + new Consumer[SpanDataAssert] { + override def accept(span: SpanDataAssert): Unit = { + span.hasName("parent").hasNoParent + } + }, + new Consumer[SpanDataAssert] { + override def accept(span: SpanDataAssert): Unit = { + span + .hasName("SET") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(DB_SYSTEM, REDIS), + equalTo(SemconvStabilityUtil.maybeStable(DB_OPERATION), "SET") + ) + } + }, + new Consumer[SpanDataAssert] { + override def accept(span: SpanDataAssert): Unit = { + span + .hasName("GET") + .hasKind(CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(DB_SYSTEM, REDIS), + equalTo(SemconvStabilityUtil.maybeStable(DB_OPERATION), "GET") + ) + } + } + ) + }) + } +}