From 3dff44874f78fb9ecfddaf620466129fea7191ad Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Mon, 8 Mar 2021 18:36:47 +0100 Subject: [PATCH] Fix RestTemplateInterceptor so that it calls endExceptionally() on exception (#2516) --- gradle/dependencies.gradle | 7 +- .../instrumentation-api.gradle | 2 +- .../servlet-2.2/servlet-2.2.gradle | 2 +- .../library/aws-lambda-1.0-library.gradle | 2 +- .../library/aws-sdk-2.2-library.gradle | 2 +- .../kafka-clients-0.11-javaagent.gradle | 2 +- .../kafka-streams-0.11-javaagent.gradle | 2 +- .../library/runtime-metrics-library.gradle | 2 +- .../aspects/WithSpanAspectTest.java | 47 +++----- .../spring/spring-web-3.1/library/README.md | 10 +- .../library/spring-web-3.1-library.gradle | 7 ++ .../httpclients/RestTemplateInterceptor.java | 4 +- .../RestTemplateInstrumentationTest.groovy | 48 ++++++++ .../RestTemplateInterceptorTest.java | 54 +++++++++ javaagent-api/javaagent-api.gradle | 2 +- .../javaagent-bootstrap.gradle | 2 +- javaagent-tooling/javaagent-tooling.gradle | 2 +- .../test/utils/TraceUtils.groovy | 2 +- .../testing/util/ThrowingRunnable.java | 16 +++ .../testing/util/ThrowingSupplier.java | 18 +++ .../testing/util/TraceUtils.java | 108 ++++++++++++++++++ testing-common/testing-common.gradle | 2 +- 22 files changed, 290 insertions(+), 53 deletions(-) create mode 100644 instrumentation/spring/spring-web-3.1/library/src/test/groovy/RestTemplateInstrumentationTest.groovy create mode 100644 instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptorTest.java create mode 100644 testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/ThrowingRunnable.java create mode 100644 testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/ThrowingSupplier.java create mode 100644 testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/TraceUtils.java diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 48e18ffb5d5d..8d44e04527dc 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -31,6 +31,7 @@ ext { prometheus : "0.9.0", assertj : '3.19.0', awaitility : '4.0.3', + mockito : '3.6.0', // Caffeine 2.x to support Java 8+. 3.x is 11+. caffeine : '2.9.0', testcontainers : '1.15.2' @@ -101,6 +102,10 @@ ext { coroutines : dependencies.create(group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: "${versions.coroutines}"), junitApi : "org.junit.jupiter:junit-jupiter-api:${versions.junit5}", assertj : "org.assertj:assertj-core:${versions.assertj}", - awaitility : "org.awaitility:awaitility:${versions.awaitility}" + awaitility : "org.awaitility:awaitility:${versions.awaitility}", + mockito : [ + "org.mockito:mockito-core:${versions.mockito}", + "org.mockito:mockito-junit-jupiter:${versions.mockito}" + ] ] } diff --git a/instrumentation-api/instrumentation-api.gradle b/instrumentation-api/instrumentation-api.gradle index 9041acd7872c..ecfa1343aab3 100644 --- a/instrumentation-api/instrumentation-api.gradle +++ b/instrumentation-api/instrumentation-api.gradle @@ -30,7 +30,7 @@ dependencies { annotationProcessor deps.autoValue testImplementation project(':testing-common') - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito testImplementation deps.assertj testImplementation deps.awaitility } diff --git a/instrumentation-core/servlet-2.2/servlet-2.2.gradle b/instrumentation-core/servlet-2.2/servlet-2.2.gradle index 5d4b79504a04..0e2fe0036237 100644 --- a/instrumentation-core/servlet-2.2/servlet-2.2.gradle +++ b/instrumentation-core/servlet-2.2/servlet-2.2.gradle @@ -9,6 +9,6 @@ dependencies { compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.2' testImplementation group: 'javax.servlet', name: 'servlet-api', version: '2.2' - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito testImplementation deps.assertj } diff --git a/instrumentation/aws-lambda-1.0/library/aws-lambda-1.0-library.gradle b/instrumentation/aws-lambda-1.0/library/aws-lambda-1.0-library.gradle index 831d51ffac32..1a7a7ae80c1b 100644 --- a/instrumentation/aws-lambda-1.0/library/aws-lambda-1.0-library.gradle +++ b/instrumentation/aws-lambda-1.0/library/aws-lambda-1.0-library.gradle @@ -45,6 +45,6 @@ dependencies { testImplementation deps.guava testImplementation project(':instrumentation:aws-lambda-1.0:testing') - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito testImplementation deps.assertj } diff --git a/instrumentation/aws-sdk/aws-sdk-2.2/library/aws-sdk-2.2-library.gradle b/instrumentation/aws-sdk/aws-sdk-2.2/library/aws-sdk-2.2-library.gradle index 374406696c53..70dbddcb7571 100644 --- a/instrumentation/aws-sdk/aws-sdk-2.2/library/aws-sdk-2.2-library.gradle +++ b/instrumentation/aws-sdk/aws-sdk-2.2/library/aws-sdk-2.2-library.gradle @@ -9,7 +9,7 @@ dependencies { testImplementation project(':instrumentation:aws-sdk:aws-sdk-2.2:testing') testImplementation deps.assertj - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito } test { diff --git a/instrumentation/kafka-clients-0.11/javaagent/kafka-clients-0.11-javaagent.gradle b/instrumentation/kafka-clients-0.11/javaagent/kafka-clients-0.11-javaagent.gradle index c89d14f4e5ed..0e566da1cb64 100644 --- a/instrumentation/kafka-clients-0.11/javaagent/kafka-clients-0.11-javaagent.gradle +++ b/instrumentation/kafka-clients-0.11/javaagent/kafka-clients-0.11-javaagent.gradle @@ -16,7 +16,7 @@ dependencies { testLibrary group: 'org.springframework.kafka', name: 'spring-kafka-test', version: '1.3.3.RELEASE' testImplementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.3' testLibrary group: 'org.assertj', name: 'assertj-core', version: '2.9.+' - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito // Include latest version of kafka itself along with latest version of client libs. // This seems to help with jar compatibility hell. diff --git a/instrumentation/kafka-streams-0.11/javaagent/kafka-streams-0.11-javaagent.gradle b/instrumentation/kafka-streams-0.11/javaagent/kafka-streams-0.11-javaagent.gradle index cbc17f617327..91dada85329a 100644 --- a/instrumentation/kafka-streams-0.11/javaagent/kafka-streams-0.11-javaagent.gradle +++ b/instrumentation/kafka-streams-0.11/javaagent/kafka-streams-0.11-javaagent.gradle @@ -19,7 +19,7 @@ dependencies { testLibrary group: 'org.springframework.kafka', name: 'spring-kafka-test', version: '1.3.3.RELEASE' testImplementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.3' testLibrary group: 'org.assertj', name: 'assertj-core', version: '2.9.+' - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito // Include latest version of kafka itself along with latest version of client libs. diff --git a/instrumentation/runtime-metrics/library/runtime-metrics-library.gradle b/instrumentation/runtime-metrics/library/runtime-metrics-library.gradle index e56517c4e86a..a7f4c1f611b7 100644 --- a/instrumentation/runtime-metrics/library/runtime-metrics-library.gradle +++ b/instrumentation/runtime-metrics/library/runtime-metrics-library.gradle @@ -5,5 +5,5 @@ dependencies { testImplementation deps.opentelemetrySdkMetrics testImplementation project(':testing-common') - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito } \ No newline at end of file diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectTest.java index d5d5bb1ceef4..1896a09e8ef9 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/aspects/WithSpanAspectTest.java @@ -8,7 +8,9 @@ import static io.opentelemetry.api.trace.SpanKind.CLIENT; import static io.opentelemetry.api.trace.SpanKind.INTERNAL; import static io.opentelemetry.api.trace.SpanKind.SERVER; -import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace; +import static io.opentelemetry.instrumentation.testing.util.TraceUtils.withClientSpan; +import static io.opentelemetry.instrumentation.testing.util.TraceUtils.withServerSpan; +import static io.opentelemetry.instrumentation.testing.util.TraceUtils.withSpan; import static io.opentelemetry.sdk.testing.assertj.TracesAssert.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -49,18 +51,12 @@ public String testWithSpanWithException() throws Exception { } @WithSpan(kind = CLIENT) - public String testWithClientSpan(Runnable nestedCall) { - if (nestedCall != null) { - nestedCall.run(); - } + public String testWithClientSpan() { return "Span with name testWithClientSpan and SpanKind.CLIENT was created"; } @WithSpan(kind = SpanKind.SERVER) - public String testWithServerSpan(Runnable nestedCall) { - if (nestedCall != null) { - nestedCall.run(); - } + public String testWithServerSpan() { return "Span with name testWithServerSpan and SpanKind.SERVER was created"; } } @@ -83,14 +79,9 @@ static void tearDown() { @Test @DisplayName("when method is annotated with @WithSpan should wrap method execution in a Span") - void withSpan() throws Throwable { + void withSpanWithDefaults() throws Throwable { // when - runUnderTrace( - "parent", - () -> { - withSpanTester.testWithSpan(); - return null; - }); + withSpan("parent", () -> withSpanTester.testWithSpan()); // then List> traces = instrumentation.waitForTraces(1); @@ -112,12 +103,7 @@ void withSpan() throws Throwable { "when @WithSpan value is set should wrap method execution in a Span with custom name") void withSpanName() throws Throwable { // when - runUnderTrace( - "parent", - () -> { - withSpanTester.testWithSpanWithValue(); - return null; - }); + withSpan("parent", () -> withSpanTester.testWithSpanWithValue()); // then List> traces = instrumentation.waitForTraces(1); @@ -155,12 +141,7 @@ void withSpanError() throws Throwable { "when method is annotated with @WithSpan(kind=CLIENT) should build span with the declared SpanKind") void withSpanKind() throws Throwable { // when - runUnderTrace( - "parent", - () -> { - withSpanTester.testWithClientSpan(null); - return null; - }); + withSpan("parent", () -> withSpanTester.testWithClientSpan()); // then List> traces = instrumentation.waitForTraces(1); @@ -180,7 +161,7 @@ void withSpanKind() throws Throwable { "when method is annotated with @WithSpan(kind=CLIENT) and context already contains a CLIENT span should suppress span") void suppressClientSpan() throws Throwable { // when - withSpanTester.testWithClientSpan(() -> withSpanTester.testWithClientSpan(null)); + withClientSpan("parent", () -> withSpanTester.testWithClientSpan()); // then List> traces = instrumentation.waitForTraces(1); @@ -188,8 +169,7 @@ void suppressClientSpan() throws Throwable { .hasTracesSatisfyingExactly( trace -> trace.hasSpansSatisfyingExactly( - parentSpan -> - parentSpan.hasName("WithSpanTester.testWithClientSpan").hasKind(CLIENT))); + parentSpan -> parentSpan.hasName("parent").hasKind(CLIENT))); } @Test @@ -197,7 +177,7 @@ void suppressClientSpan() throws Throwable { "when method is annotated with @WithSpan(kind=SERVER) and context already contains a SERVER span should suppress span") void suppressServerSpan() throws Throwable { // when - withSpanTester.testWithServerSpan(() -> withSpanTester.testWithServerSpan(null)); + withServerSpan("parent", () -> withSpanTester.testWithServerSpan()); // then List> traces = instrumentation.waitForTraces(1); @@ -205,7 +185,6 @@ void suppressServerSpan() throws Throwable { .hasTracesSatisfyingExactly( trace -> trace.hasSpansSatisfyingExactly( - parentSpan -> - parentSpan.hasName("WithSpanTester.testWithServerSpan").hasKind(SERVER))); + parentSpan -> parentSpan.hasName("parent").hasKind(SERVER))); } } diff --git a/instrumentation/spring/spring-web-3.1/library/README.md b/instrumentation/spring/spring-web-3.1/library/README.md index ce53b8eeed26..298b376a72db 100644 --- a/instrumentation/spring/spring-web-3.1/library/README.md +++ b/instrumentation/spring/spring-web-3.1/library/README.md @@ -10,7 +10,7 @@ Replace `SPRING_VERSION` with the version of spring you're using. `Minimum version: 3.1` Replace `OPENTELEMETRY_VERSION` with the latest stable [release](https://mvnrepository.com/artifact/io.opentelemetry). -`Minimum version: 0.8.0` +`Minimum version: 0.17.0` For Maven add to your `pom.xml`: ```xml @@ -60,8 +60,8 @@ interface. An example is shown below: ```java -import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor -import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor; +import io.opentelemetry.api.OpenTelemetry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -73,10 +73,10 @@ import org.springframework.web.client.RestTemplate; public class RestTemplateConfig { @Bean - public RestTemplate restTemplate(Tracer tracer) { + public RestTemplate restTemplate(OpenTelemetry openTelemetry) { RestTemplate restTemplate = new RestTemplate(); - RestTemplateInterceptor restTemplateInterceptor = new RestTemplateInterceptor(tracer); + RestTemplateInterceptor restTemplateInterceptor = new RestTemplateInterceptor(openTelemetry); restTemplate.getInterceptors().add(restTemplateInterceptor); return restTemplate; diff --git a/instrumentation/spring/spring-web-3.1/library/spring-web-3.1-library.gradle b/instrumentation/spring/spring-web-3.1/library/spring-web-3.1-library.gradle index ac1217e564a3..997437bad1e4 100644 --- a/instrumentation/spring/spring-web-3.1/library/spring-web-3.1-library.gradle +++ b/instrumentation/spring/spring-web-3.1/library/spring-web-3.1-library.gradle @@ -2,4 +2,11 @@ apply from: "$rootDir/gradle/instrumentation-library.gradle" dependencies { compileOnly "org.springframework:spring-web:3.1.0.RELEASE" + + testImplementation "org.springframework:spring-web:3.1.0.RELEASE" + + testImplementation project(':testing-common') + testImplementation deps.assertj + testImplementation deps.mockito + testImplementation deps.opentelemetrySdkTesting } diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptor.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptor.java index c5f78cac88f5..557b7491f8bc 100644 --- a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptor.java +++ b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptor.java @@ -36,7 +36,9 @@ public ClientHttpResponse intercept( ClientHttpResponse response = execution.execute(request, body); tracer.end(context, response); return response; + } catch (Throwable t) { + tracer.endExceptionally(context, t); + throw t; } - // TODO: endExceptionally? } } diff --git a/instrumentation/spring/spring-web-3.1/library/src/test/groovy/RestTemplateInstrumentationTest.groovy b/instrumentation/spring/spring-web-3.1/library/src/test/groovy/RestTemplateInstrumentationTest.groovy new file mode 100644 index 000000000000..1f585a3ac4d0 --- /dev/null +++ b/instrumentation/spring/spring-web-3.1/library/src/test/groovy/RestTemplateInstrumentationTest.groovy @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.spring.httpclients.RestTemplateInterceptor +import io.opentelemetry.instrumentation.test.LibraryTestTrait +import io.opentelemetry.instrumentation.test.base.HttpClientTest +import org.springframework.http.HttpMethod +import org.springframework.web.client.ResourceAccessException +import org.springframework.web.client.RestTemplate +import spock.lang.Shared + +class RestTemplateInstrumentationTest extends HttpClientTest implements LibraryTestTrait { + @Shared + RestTemplate restTemplate + + def setupSpec() { + if (restTemplate == null) { + restTemplate = new RestTemplate() + restTemplate.getInterceptors().add(new RestTemplateInterceptor(getOpenTelemetry())) + } + } + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + try { + return restTemplate.execute(uri, HttpMethod.valueOf(method), { request -> + headers.forEach(request.getHeaders().&add) + }, { response -> + callback?.call() + response.statusCode.value() + }) + } catch (ResourceAccessException exception) { + throw exception.getCause() + } + } + + @Override + boolean testCircularRedirects() { + false + } + + @Override + boolean testRemoteConnection() { + false + } +} diff --git a/instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptorTest.java b/instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptorTest.java new file mode 100644 index 000000000000..224bdc0c8c3d --- /dev/null +++ b/instrumentation/spring/spring-web-3.1/library/src/test/java/io/opentelemetry/instrumentation/spring/httpclients/RestTemplateInterceptorTest.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.httpclients; + +import static io.opentelemetry.instrumentation.testing.util.TraceUtils.withClientSpan; +import static io.opentelemetry.sdk.testing.assertj.TracesAssert.assertThat; +import static org.mockito.BDDMockito.then; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; + +@ExtendWith(MockitoExtension.class) +class RestTemplateInterceptorTest { + @RegisterExtension + static final LibraryInstrumentationExtension instrumentation = + LibraryInstrumentationExtension.create(); + + @Mock HttpRequest httpRequestMock; + @Mock ClientHttpRequestExecution requestExecutionMock; + byte[] requestBody = new byte[0]; + + @Test + void shouldSkipWhenContextHasClientSpan() throws Exception { + // given + RestTemplateInterceptor interceptor = + new RestTemplateInterceptor(instrumentation.getOpenTelemetry()); + + // when + withClientSpan( + "parent", + () -> { + interceptor.intercept(httpRequestMock, requestBody, requestExecutionMock); + }); + + // then + then(requestExecutionMock).should().execute(httpRequestMock, requestBody); + + assertThat(instrumentation.waitForTraces(1)) + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.CLIENT))); + } +} diff --git a/javaagent-api/javaagent-api.gradle b/javaagent-api/javaagent-api.gradle index 11c4489823fd..83f22663425d 100644 --- a/javaagent-api/javaagent-api.gradle +++ b/javaagent-api/javaagent-api.gradle @@ -13,6 +13,6 @@ dependencies { implementation project(':instrumentation-api') testImplementation project(':testing-common') - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito testImplementation deps.assertj } diff --git a/javaagent-bootstrap/javaagent-bootstrap.gradle b/javaagent-bootstrap/javaagent-bootstrap.gradle index d992740a66b8..7fffea5d3e18 100644 --- a/javaagent-bootstrap/javaagent-bootstrap.gradle +++ b/javaagent-bootstrap/javaagent-bootstrap.gradle @@ -40,6 +40,6 @@ dependencies { implementation project(':instrumentation-api') testImplementation project(':testing-common') - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito testImplementation deps.assertj } diff --git a/javaagent-tooling/javaagent-tooling.gradle b/javaagent-tooling/javaagent-tooling.gradle index 5719a5d22320..e0403d43c32e 100644 --- a/javaagent-tooling/javaagent-tooling.gradle +++ b/javaagent-tooling/javaagent-tooling.gradle @@ -62,7 +62,7 @@ dependencies { testImplementation project(':testing-common') testImplementation deps.assertj - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + testImplementation deps.mockito instrumentationMuzzle sourceSets.main.output } diff --git a/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/utils/TraceUtils.groovy b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/utils/TraceUtils.groovy index 23b7cf0a5141..80436c77fc94 100644 --- a/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/utils/TraceUtils.groovy +++ b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/utils/TraceUtils.groovy @@ -16,10 +16,10 @@ import io.opentelemetry.instrumentation.test.asserts.AttributesAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.server.ServerTraceUtils import io.opentelemetry.sdk.trace.data.SpanData - import java.util.concurrent.Callable import java.util.concurrent.ExecutionException +// TODO: convert all usages of this class to the Java TraceUtils one class TraceUtils { private static final Tracer tracer = GlobalOpenTelemetry.getTracer("test") diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/ThrowingRunnable.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/ThrowingRunnable.java new file mode 100644 index 000000000000..7fe2a5ae778a --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/ThrowingRunnable.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.util; + +/** + * A utility interface representing a {@link Runnable} that may throw. + * + * @param Thrown exception type. + */ +@FunctionalInterface +public interface ThrowingRunnable { + void run() throws E; +} diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/ThrowingSupplier.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/ThrowingSupplier.java new file mode 100644 index 000000000000..649ef6360c3c --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/ThrowingSupplier.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.util; + +import java.util.function.Supplier; + +/** + * A utility interface representing a {@link Supplier} that may throw. + * + * @param Thrown exception type. + */ +@FunctionalInterface +public interface ThrowingSupplier { + T get() throws E; +} diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/TraceUtils.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/TraceUtils.java new file mode 100644 index 000000000000..26067e22bf02 --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/TraceUtils.java @@ -0,0 +1,108 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.util; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.tracer.BaseTracer; + +/** Utility class for creating spans in tests. */ +public final class TraceUtils { + + private static final TestTracer TRACER = new TestTracer(); + + public static void withSpan(String spanName, ThrowingRunnable callback) + throws E { + withSpan( + spanName, + () -> { + callback.run(); + return null; + }); + } + + public static T withSpan( + String spanName, ThrowingSupplier callback) throws E { + Context context = TRACER.startSpan(spanName); + try (Scope ignored = context.makeCurrent()) { + T result = callback.get(); + TRACER.end(context); + return result; + } catch (Throwable t) { + TRACER.endExceptionally(context, t); + throw t; + } + } + + public static void withClientSpan( + String spanName, ThrowingRunnable callback) throws E { + withClientSpan( + spanName, + () -> { + callback.run(); + return null; + }); + } + + public static T withClientSpan( + String spanName, ThrowingSupplier callback) throws E { + Context context = TRACER.startClientSpan(spanName); + try (Scope ignored = context.makeCurrent()) { + T result = callback.get(); + TRACER.end(context); + return result; + } catch (Throwable t) { + TRACER.endExceptionally(context, t); + throw t; + } + } + + public static void withServerSpan( + String spanName, ThrowingRunnable callback) throws E { + withServerSpan( + spanName, + () -> { + callback.run(); + return null; + }); + } + + public static T withServerSpan( + String spanName, ThrowingSupplier callback) throws E { + Context context = TRACER.startServerSpan(spanName); + try (Scope ignored = context.makeCurrent()) { + T result = callback.get(); + TRACER.end(context); + return result; + } catch (Throwable t) { + TRACER.endExceptionally(context, t); + throw t; + } + } + + private static final class TestTracer extends BaseTracer { + @Override + protected String getInstrumentationName() { + return "test"; + } + + Context startClientSpan(String name) { + Context parentContext = Context.current(); + Span span = spanBuilder(parentContext, name, SpanKind.CLIENT).startSpan(); + return withClientSpan(parentContext, span); + } + + Context startServerSpan(String name) { + Context parentContext = Context.current(); + Span span = spanBuilder(parentContext, name, SpanKind.SERVER).startSpan(); + return withServerSpan(parentContext, span); + } + } + + private TraceUtils() {} +} diff --git a/testing-common/testing-common.gradle b/testing-common/testing-common.gradle index c65cee4001c5..7859dad69ea2 100644 --- a/testing-common/testing-common.gradle +++ b/testing-common/testing-common.gradle @@ -25,6 +25,7 @@ dependencies { implementation deps.slf4j implementation deps.testLogging implementation deps.opentelemetryExtAnnotations + implementation project(':instrumentation-api') api group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.0' api group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '4.9.0' @@ -37,7 +38,6 @@ dependencies { testImplementation deps.assertj - testImplementation project(':instrumentation-api') testImplementation project(':javaagent-api') testImplementation project(':javaagent-tooling') testImplementation project(':instrumentation:external-annotations:javaagent')