diff --git a/instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts b/instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts index 92ded4cea19f..a76166fb4baf 100644 --- a/instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts +++ b/instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts @@ -41,8 +41,34 @@ dependencies { testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) testInstrumentation(project(":instrumentation:akka:akka-http-10.0:javaagent")) testInstrumentation(project(":instrumentation:akka:akka-actor-2.3:javaagent")) +} + +testing { + suites { + val latestDepTest by registering(JvmTestSuite::class) { + dependencies { + implementation("com.typesafe.play:play-ahc-ws-standalone_2.13:+") + } + } + } +} - latestDepTestLibrary("com.typesafe.play:play-ahc-ws-standalone_2.13:+") +val testLatestDeps = findProperty("testLatestDeps") as Boolean +tasks { + if (testLatestDeps) { + // disable regular test running and compiling tasks when latest dep test task is run + named("test") { + enabled = false + } + } + + named("latestDepTest") { + enabled = testLatestDeps + } + + check { + dependsOn(testing.suites) + } } if (findProperty("testLatestDeps") as Boolean) { diff --git a/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayJavaStreamedWsClientTest.java b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayJavaStreamedWsClientTest.java new file mode 100644 index 000000000000..d73234b84061 --- /dev/null +++ b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayJavaStreamedWsClientTest.java @@ -0,0 +1,98 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.playws.v2_1; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; +import java.io.IOException; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import play.libs.ws.StandaloneWSClient; +import play.libs.ws.StandaloneWSRequest; +import play.libs.ws.StandaloneWSResponse; +import play.libs.ws.ahc.StandaloneAhcWSClient; + +public class PlayJavaStreamedWsClientTest extends PlayWsClientBaseTest { + + private static StandaloneWSClient wsClient; + private static StandaloneWSClient wsClientWithReadTimeout; + + @BeforeEach + @Override + void setup() { + super.setup(); + wsClient = new StandaloneAhcWSClient(asyncHttpClient, materializer); + wsClientWithReadTimeout = + new StandaloneAhcWSClient(asyncHttpClientWithReadTimeout, materializer); + autoCleanup.deferCleanup(wsClient); + autoCleanup.deferCleanup(wsClientWithReadTimeout); + } + + @AfterEach + @Override + void tearDown() throws IOException { + if (wsClient != null) { + wsClient.close(); + } + if (wsClientWithReadTimeout != null) { + wsClientWithReadTimeout.close(); + } + super.tearDown(); + } + + @Override + public StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { + StandaloneWSRequest request = getClient(uri).url(uri.toString()).setFollowRedirects(true); + headers.forEach(request::addHeader); + request.setMethod(method); + return request; + } + + @Override + public int sendRequest( + StandaloneWSRequest request, String method, URI uri, Map headers) + throws ExecutionException, InterruptedException { + return internalSendRequest(request).toCompletableFuture().get().getStatus(); + } + + @Override + public void sendRequestWithCallback( + StandaloneWSRequest request, + String method, + URI uri, + Map headers, + HttpClientResult requestResult) { + internalSendRequest(request) + .whenComplete( + (response, throwable) -> { + if (throwable != null) { + requestResult.complete(throwable.getCause()); + } else { + requestResult.complete(response.getStatus()); + } + }); + } + + private static CompletionStage internalSendRequest( + StandaloneWSRequest request) { + CompletionStage stream = request.stream(); + // The status can be ready before the body so explicitly call wait for body to be ready + return stream + .thenCompose( + response -> response.getBodyAsSource().runFold("", (acc, out) -> "", materializer)) + .thenCombine(stream, (body, response) -> response); + } + + private static StandaloneWSClient getClient(URI uri) { + if (uri.toString().contains("/read-timeout")) { + return wsClientWithReadTimeout; + } + return wsClient; + } +} diff --git a/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayJavaWsClientTest.java b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayJavaWsClientTest.java new file mode 100644 index 000000000000..83190bb9c46a --- /dev/null +++ b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayJavaWsClientTest.java @@ -0,0 +1,86 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.playws.v2_1; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; +import java.io.IOException; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import play.libs.ws.StandaloneWSClient; +import play.libs.ws.StandaloneWSRequest; +import play.libs.ws.ahc.StandaloneAhcWSClient; + +public class PlayJavaWsClientTest extends PlayWsClientBaseTest { + + private static StandaloneWSClient wsClient; + private static StandaloneWSClient wsClientWithReadTimeout; + + @BeforeEach + @Override + void setup() { + super.setup(); + wsClient = new StandaloneAhcWSClient(asyncHttpClient, materializer); + wsClientWithReadTimeout = + new StandaloneAhcWSClient(asyncHttpClientWithReadTimeout, materializer); + autoCleanup.deferCleanup(wsClient); + autoCleanup.deferCleanup(wsClientWithReadTimeout); + } + + @AfterEach + @Override + void tearDown() throws IOException { + if (wsClient != null) { + wsClient.close(); + } + if (wsClientWithReadTimeout != null) { + wsClientWithReadTimeout.close(); + } + super.tearDown(); + } + + @Override + public StandaloneWSRequest buildRequest(String method, URI uri, Map headers) { + StandaloneWSRequest request = getClient(uri).url(uri.toString()).setFollowRedirects(true); + headers.forEach(request::addHeader); + return request.setMethod(method); + } + + @Override + public int sendRequest( + StandaloneWSRequest request, String method, URI uri, Map headers) + throws ExecutionException, InterruptedException { + return request.execute().toCompletableFuture().get().getStatus(); + } + + @Override + public void sendRequestWithCallback( + StandaloneWSRequest request, + String method, + URI uri, + Map headers, + HttpClientResult requestResult) { + request + .execute() + .whenComplete( + (response, throwable) -> { + if (throwable != null) { + requestResult.complete(throwable); + } else { + requestResult.complete(response.getStatus()); + } + }); + } + + private static StandaloneWSClient getClient(URI uri) { + if (uri.toString().contains("/read-timeout")) { + return wsClientWithReadTimeout; + } + return wsClient; + } +} diff --git a/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayScalaStreamedWsClientTest.java b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayScalaStreamedWsClientTest.java new file mode 100644 index 000000000000..5bbbcba66a4b --- /dev/null +++ b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayScalaStreamedWsClientTest.java @@ -0,0 +1,124 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.playws.v2_1; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; +import java.io.IOException; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import play.api.libs.ws.StandaloneWSClient; +import play.api.libs.ws.StandaloneWSRequest; +import play.api.libs.ws.StandaloneWSResponse; +import play.api.libs.ws.ahc.StandaloneAhcWSClient; +import scala.Function1; +import scala.concurrent.Await; +import scala.concurrent.ExecutionContext; +import scala.concurrent.Future; +import scala.concurrent.duration.Duration; +import scala.jdk.javaapi.CollectionConverters; +import scala.util.Try; + +public class PlayScalaStreamedWsClientTest extends PlayWsClientBaseTest { + + private static StandaloneWSClient wsClient; + private static StandaloneWSClient wsClientWithReadTimeout; + + @BeforeEach + @Override + void setup() { + super.setup(); + wsClient = new StandaloneAhcWSClient(asyncHttpClient, materializer); + wsClientWithReadTimeout = + new StandaloneAhcWSClient(asyncHttpClientWithReadTimeout, materializer); + autoCleanup.deferCleanup(wsClient); + autoCleanup.deferCleanup(wsClientWithReadTimeout); + } + + @AfterEach + @Override + void tearDown() throws IOException { + if (wsClient != null) { + wsClient.close(); + } + if (wsClientWithReadTimeout != null) { + wsClientWithReadTimeout.close(); + } + super.tearDown(); + } + + @Override + public StandaloneWSRequest buildRequest(String method, URI uri, Map headers) + throws Exception { + return getClient(uri) + .url(uri.toURL().toString()) + .withMethod(method) + .withFollowRedirects(true) + .withHttpHeaders(CollectionConverters.asScala(headers).toList()); + } + + @Override + public int sendRequest( + StandaloneWSRequest request, String method, URI uri, Map headers) + throws Exception { + return Await.result(internalSendRequest(request), Duration.apply(10, TimeUnit.SECONDS)) + .status(); + } + + @Override + public void sendRequestWithCallback( + StandaloneWSRequest request, + String method, + URI uri, + Map headers, + HttpClientResult requestResult) { + internalSendRequest(request) + .onComplete( + new Function1, Void>() { + @Override + public Void apply(Try response) { + if (response.isSuccess()) { + requestResult.complete(response.get().status()); + } else { + requestResult.complete(response.failed().get()); + } + return null; + } + }, + ExecutionContext.global()); + } + + private static Future internalSendRequest(StandaloneWSRequest request) { + Future futureResponse = request.stream(); + // The status can be ready before the body so explicitly call wait for body to be ready + Future bodyResponse = + futureResponse.flatMap( + new Function1>() { + @Override + public Future apply(StandaloneWSResponse wsResponse) { + return wsResponse.bodyAsSource().runFold("", (acc, out) -> "", materializer); + } + }, + ExecutionContext.global()); + return bodyResponse.flatMap( + new Function1>() { + @Override + public Future apply(String v1) { + return futureResponse; + } + }, + ExecutionContext.global()); + } + + private static StandaloneWSClient getClient(URI uri) { + if (uri.toString().contains("/read-timeout")) { + return wsClientWithReadTimeout; + } + return wsClient; + } +} diff --git a/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayScalaWsClientTest.java b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayScalaWsClientTest.java new file mode 100644 index 000000000000..f31a4ef238b2 --- /dev/null +++ b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayScalaWsClientTest.java @@ -0,0 +1,110 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.playws.v2_1; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import play.api.libs.ws.StandaloneWSClient; +import play.api.libs.ws.StandaloneWSRequest; +import play.api.libs.ws.StandaloneWSResponse; +import play.api.libs.ws.ahc.StandaloneAhcWSClient; +import scala.Function1; +import scala.concurrent.Await; +import scala.concurrent.ExecutionContext; +import scala.concurrent.Future; +import scala.concurrent.duration.Duration; +import scala.jdk.javaapi.CollectionConverters; +import scala.util.Try; + +public class PlayScalaWsClientTest extends PlayWsClientBaseTest { + + private static StandaloneWSClient wsClient; + private static StandaloneWSClient wsClientWithReadTimeout; + + @BeforeEach + @Override + void setup() { + super.setup(); + wsClient = new StandaloneAhcWSClient(asyncHttpClient, materializer); + wsClientWithReadTimeout = + new StandaloneAhcWSClient(asyncHttpClientWithReadTimeout, materializer); + autoCleanup.deferCleanup(wsClient); + autoCleanup.deferCleanup(wsClientWithReadTimeout); + } + + @AfterEach + @Override + void tearDown() throws IOException { + if (wsClient != null) { + wsClient.close(); + } + if (wsClientWithReadTimeout != null) { + wsClientWithReadTimeout.close(); + } + super.tearDown(); + } + + @Override + public StandaloneWSRequest buildRequest(String method, URI uri, Map headers) + throws MalformedURLException { + return getClient(uri) + .url(uri.toURL().toString()) + .withMethod(method) + .withFollowRedirects(true) + .withHttpHeaders(CollectionConverters.asScala(headers).toSeq()); + } + + @Override + public int sendRequest( + StandaloneWSRequest request, String method, URI uri, Map headers) + throws Exception { + Future futureResponse = request.execute(); + Await.ready(futureResponse, Duration.apply(10, TimeUnit.SECONDS)); + Try value = futureResponse.value().get(); + if (value.isSuccess()) { + return value.get().status(); + } + // Catch the Throwable and rethrow it + throw (Exception) value.failed().get(); + } + + @Override + public void sendRequestWithCallback( + StandaloneWSRequest request, + String method, + URI uri, + Map headers, + HttpClientResult requestResult) { + request + .execute() + .onComplete( + new Function1, Void>() { + @Override + public Void apply(Try response) { + if (response.isSuccess()) { + requestResult.complete(response.get().status()); + } else { + requestResult.complete(response.failed().get()); + } + return null; + } + }, + ExecutionContext.global()); + } + + private static StandaloneWSClient getClient(URI uri) { + if (uri.toString().contains("/read-timeout")) { + return wsClientWithReadTimeout; + } + return wsClient; + } +} diff --git a/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayWsClientBaseTest.java b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayWsClientBaseTest.java new file mode 100644 index 000000000000..738bcb0eb15c --- /dev/null +++ b/instrumentation/play/play-ws/play-ws-2.1/javaagent/src/latestDepTest/java/io/opentelemetry/javaagent/instrumentation/playws/v2_1/PlayWsClientBaseTest.java @@ -0,0 +1,158 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.playws.v2_1; + +import akka.actor.ActorSystem; +import akka.stream.Materializer; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions; +import io.opentelemetry.semconv.NetworkAttributes; +import io.opentelemetry.semconv.ServerAttributes; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import play.shaded.ahc.io.netty.resolver.InetNameResolver; +import play.shaded.ahc.io.netty.util.concurrent.EventExecutor; +import play.shaded.ahc.io.netty.util.concurrent.ImmediateEventExecutor; +import play.shaded.ahc.io.netty.util.concurrent.Promise; +import play.shaded.ahc.org.asynchttpclient.AsyncHttpClient; +import play.shaded.ahc.org.asynchttpclient.AsyncHttpClientConfig; +import play.shaded.ahc.org.asynchttpclient.DefaultAsyncHttpClient; +import play.shaded.ahc.org.asynchttpclient.DefaultAsyncHttpClientConfig; +import play.shaded.ahc.org.asynchttpclient.RequestBuilderBase; + +abstract class PlayWsClientBaseTest extends AbstractHttpClientTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent(); + + static final AutoCleanupExtension autoCleanup = AutoCleanupExtension.create(); + + private static ActorSystem system; + protected static AsyncHttpClient asyncHttpClient; + protected static AsyncHttpClient asyncHttpClientWithReadTimeout; + protected static Materializer materializer; + + @BeforeEach + void setup() { + String name = "play-ws"; + system = ActorSystem.create(name); + materializer = Materializer.matFromSystem(system); + + // Replace dns name resolver with custom implementation that returns only once address for each + // host. This is needed for "connection error dropped request" because in case of connection + // failure ahc will try the next address which isn't necessary for this test. + RequestBuilderBase.DEFAULT_NAME_RESOLVER = + new CustomNameResolver(ImmediateEventExecutor.INSTANCE); + + asyncHttpClient = createClient(false); + asyncHttpClientWithReadTimeout = createClient(true); + autoCleanup.deferCleanup(asyncHttpClient); + autoCleanup.deferCleanup(asyncHttpClientWithReadTimeout); + } + + @AfterEach + void tearDown() throws IOException { + if (system != null) { + system.terminate(); + } + if (asyncHttpClient != null) { + asyncHttpClient.close(); + } + if (asyncHttpClientWithReadTimeout != null) { + asyncHttpClientWithReadTimeout.close(); + } + } + + @Override + public REQUEST buildRequest(String method, URI uri, Map headers) + throws Exception { + return null; + } + + @Override + public int sendRequest(REQUEST request, String method, URI uri, Map headers) + throws Exception { + return 0; + } + + @Override + protected void configure(HttpClientTestOptions.Builder optionsBuilder) { + super.configure(optionsBuilder); + // apparently play ws does not report the 302 status code + optionsBuilder.setResponseCodeOnRedirectError(null); + optionsBuilder.setMaxRedirects(3); + optionsBuilder.spanEndsAfterBody(); + optionsBuilder.setHttpAttributes( + uri -> { + Set> attributes = + new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES); + attributes.remove(NetworkAttributes.NETWORK_PROTOCOL_VERSION); + if (uri.toString().endsWith("/circular-redirect")) { + attributes.remove(ServerAttributes.SERVER_ADDRESS); + attributes.remove(ServerAttributes.SERVER_PORT); + } + return attributes; + }); + } + + private static AsyncHttpClient createClient(boolean readTimeout) { + DefaultAsyncHttpClientConfig.Builder builder = + new DefaultAsyncHttpClientConfig.Builder() + .setMaxRequestRetry(0) + .setShutdownQuietPeriod(0) + .setShutdownTimeout(0) + .setMaxRedirects(3) + .setConnectTimeout(5000); + + if (readTimeout) { + builder.setReadTimeout(2000); + } + + AsyncHttpClientConfig asyncHttpClientConfig = builder.build(); + return new DefaultAsyncHttpClient(asyncHttpClientConfig); + } + + private static class CustomNameResolver extends InetNameResolver { + + public CustomNameResolver(EventExecutor executor) { + super(executor); + } + + @Override + protected void doResolve(String inetHost, Promise promise) throws Exception { + try { + promise.setSuccess(InetAddress.getByName(inetHost)); + } catch (UnknownHostException exception) { + promise.setFailure(exception); + } + } + + @Override + protected void doResolveAll(String inetHost, Promise> promise) + throws Exception { + try { + // default implementation calls InetAddress.getAllByName + promise.setSuccess(Collections.singletonList(InetAddress.getByName(inetHost))); + } catch (UnknownHostException exception) { + promise.setFailure(exception); + } + } + } +}