From 26fd7f46193150481152a5ab2483c0d122249510 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Sun, 7 Dec 2025 08:03:05 -0500 Subject: [PATCH 1/9] fix first test --- .../javaagent/build.gradle.kts | 7 +- .../v5_0/server/SpringWebfluxTest.java | 743 +---------------- .../testing-webflux7/build.gradle.kts | 30 + .../webflux/v7_0/SpringWebfluxTest.java | 32 + .../testing/build.gradle.kts | 1 + .../server/AbstractSpringWebfluxTest.java | 762 ++++++++++++++++++ .../src/main}/java/server/EchoHandler.java | 0 .../java/server/EchoHandlerFunction.java | 0 .../src/main}/java/server/FooModel.java | 0 .../main}/java/server/RedirectComponent.java | 0 .../server/SpringWebFluxTestApplication.java | 28 +- .../src/main}/java/server/TestController.java | 0 ...ditionalLibraryIgnoredTypesConfigurer.java | 1 + settings.gradle.kts | 1 + 14 files changed, 850 insertions(+), 755 deletions(-) create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringWebfluxTest.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebfluxTest.java rename instrumentation/spring/spring-webflux/spring-webflux-5.0/{javaagent/src/test => testing/src/main}/java/server/EchoHandler.java (100%) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/{javaagent/src/test => testing/src/main}/java/server/EchoHandlerFunction.java (100%) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/{javaagent/src/test => testing/src/main}/java/server/FooModel.java (100%) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/{javaagent/src/test => testing/src/main}/java/server/RedirectComponent.java (100%) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/{javaagent/src/test => testing/src/main}/java/server/SpringWebFluxTestApplication.java (84%) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/{javaagent/src/test => testing/src/main}/java/server/TestController.java (100%) diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/build.gradle.kts index 8dd2bf8eabc1..2d6f7d1db069 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/build.gradle.kts @@ -60,10 +60,9 @@ dependencies { testLibrary("org.springframework.boot:spring-boot-starter-test:2.0.0.RELEASE") testLibrary("org.springframework.boot:spring-boot-starter-reactor-netty:2.0.0.RELEASE") - // tests don't work with spring boot 4 yet - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-webflux:3.+") // documented limitation - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // documented limitation - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-reactor-netty:3.+") // documented limitation + latestDepTestLibrary("org.springframework.boot:spring-boot-starter-webflux:3.+") // see testing-webflux7 module + latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // see testing-webflux7 module + latestDepTestLibrary("org.springframework.boot:spring-boot-starter-reactor-netty:3.+") // see testing-webflux7 module } val latestDepTest = findProperty("testLatestDeps") as Boolean diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/SpringWebfluxTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/SpringWebfluxTest.java index 6a0294a5a2c6..4e7941213fe0 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/SpringWebfluxTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/SpringWebfluxTest.java @@ -5,67 +5,15 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server; -import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions; -import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionPrefixAssertions; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static io.opentelemetry.semconv.ClientAttributes.CLIENT_ADDRESS; -import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; -import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; -import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; -import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; -import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; -import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; -import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE; -import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_ADDRESS; -import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT; -import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; -import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; -import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; -import static io.opentelemetry.semconv.UrlAttributes.URL_PATH; -import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME; -import static io.opentelemetry.semconv.UserAgentAttributes.USER_AGENT_ORIGINAL; -import static org.junit.jupiter.api.Named.named; - -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; -import io.opentelemetry.sdk.testing.assertj.EventDataAssert; -import io.opentelemetry.sdk.testing.assertj.TraceAssert; -import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.testing.internal.armeria.client.WebClient; -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; -import io.opentelemetry.testing.internal.armeria.common.HttpStatus; -import java.time.Duration; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebfluxTest; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.test.context.junit.jupiter.SpringExtension; -import server.EchoHandlerFunction; -import server.FooModel; import server.SpringWebFluxTestApplication; -import server.TestController; -@SuppressWarnings("deprecation") // using deprecated semconv @ExtendWith(SpringExtension.class) @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, @@ -73,7 +21,7 @@ SpringWebFluxTestApplication.class, SpringWebfluxTest.ForceNettyAutoConfiguration.class }) -class SpringWebfluxTest { +class SpringWebfluxTest extends AbstractSpringWebfluxTest { @TestConfiguration static class ForceNettyAutoConfiguration { @Bean @@ -81,691 +29,4 @@ NettyReactiveWebServerFactory nettyFactory() { return new NettyReactiveWebServerFactory(); } } - - @RegisterExtension - static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - - private static final String INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX = - SpringWebFluxTestApplication.class.getName() + "$"; - private static final String SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX = - SpringWebFluxTestApplication.class.getSimpleName() + "$"; - - // can't use @LocalServerPort annotation since it moved packages between Spring Boot 2 and 3 - @Value("${local.server.port}") - private int port; - - private WebClient client; - - @BeforeEach - void beforeEach() { - client = WebClient.builder("h1c://localhost:" + port).followRedirects().build(); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("provideParameters") - void basicGetTest(Parameter parameter) { - AggregatedHttpResponse response = client.get(parameter.urlPath).aggregate().join(); - - assertThat(response.status().code()).isEqualTo(200); - assertThat(response.contentUtf8()).isEqualTo(parameter.expectedResponseBody); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("GET " + parameter.urlPathWithVariables) - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, parameter.urlPath), - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 200), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, parameter.urlPathWithVariables)), - span -> { - if (parameter.annotatedMethod == null) { - // Functional API - assertThat(trace.getSpan(1).getName()) - .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); - } else { - // Annotation API - span.hasName( - TestController.class.getSimpleName() + "." + parameter.annotatedMethod); - } - span.hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); - })); - } - - private static List assertCodeFunction(Parameter parameter) { - String expectedFunctionName = - parameter.annotatedMethod == null ? "handle" : parameter.annotatedMethod; - String expectedPrefix = - parameter.annotatedMethod == null - ? INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX - : TestController.class.getName(); - - return codeFunctionPrefixAssertions(expectedPrefix, expectedFunctionName); - } - - private static Stream provideParameters() { - return Stream.of( - Arguments.of( - named( - "functional API without parameters", - new Parameter( - "/greet", - "/greet", - null, - SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE))), - Arguments.of( - named( - "functional API with one parameter", - new Parameter( - "/greet/WORLD", - "/greet/{name}", - null, - SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " WORLD"))), - Arguments.of( - named( - "functional API with two parameters", - new Parameter( - "/greet/World/Test1", - "/greet/{name}/{word}", - null, - SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE - + " World Test1"))), - Arguments.of( - named( - "functional API delayed response", - new Parameter( - "/greet-delayed", - "/greet-delayed", - null, - SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE))), - Arguments.of( - named( - "annotation API without parameters", - new Parameter( - "/foo", "/foo", "getFooModel", new FooModel(0L, "DEFAULT").toString()))), - Arguments.of( - named( - "annotation API with one parameter", - new Parameter( - "/foo/1", "/foo/{id}", "getFooModel", new FooModel(1L, "pass").toString()))), - Arguments.of( - named( - "annotation API with two parameters", - new Parameter( - "/foo/2/world", - "/foo/{id}/{name}", - "getFooModel", - new FooModel(2L, "world").toString()))), - Arguments.of( - named( - "annotation API delayed response", - new Parameter( - "/foo-delayed", - "/foo-delayed", - "getFooDelayed", - new FooModel(3L, "delayed").toString()))), - Arguments.of( - named( - "annotation API without parameters no mono", - new Parameter( - "/foo-no-mono", - "/foo-no-mono", - "getFooModelNoMono", - new FooModel(0L, "DEFAULT").toString())))); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("provideAsyncParameters") - void getAsyncResponseTest(Parameter parameter) { - AggregatedHttpResponse response = client.get(parameter.urlPath).aggregate().join(); - - assertThat(response.status().code()).isEqualTo(200); - assertThat(response.contentUtf8()).isEqualTo(parameter.expectedResponseBody); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("GET " + parameter.urlPathWithVariables) - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, parameter.urlPath), - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 200), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, parameter.urlPathWithVariables)), - span -> { - if (parameter.annotatedMethod == null) { - // Functional API - assertThat(trace.getSpan(1).getName()) - .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); - } else { - // Annotation API - span.hasName( - TestController.class.getSimpleName() + "." + parameter.annotatedMethod); - } - span.hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); - }, - span -> - span.hasName("tracedMethod") - .hasParent(trace.getSpan(1)) - .hasTotalAttributeCount(0))); - } - - private static Stream provideAsyncParameters() { - return Stream.of( - Arguments.of( - named( - "functional API traced method from mono", - new Parameter( - "/greet-mono-from-callable/4", - "/greet-mono-from-callable/{id}", - null, - SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " 4"))), - Arguments.of( - named( - "functional API traced method with delay", - new Parameter( - "/greet-delayed-mono/6", - "/greet-delayed-mono/{id}", - null, - SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " 6"))), - Arguments.of( - named( - "annotation API traced method from mono", - new Parameter( - "/foo-mono-from-callable/7", - "/foo-mono-from-callable/{id}", - "getMonoFromCallable", - new FooModel(7L, "tracedMethod").toString()))), - Arguments.of( - named( - "annotation API traced method with delay", - new Parameter( - "/foo-delayed-mono/9", - "/foo-delayed-mono/{id}", - "getFooDelayedMono", - new FooModel(9L, "tracedMethod").toString())))); - } - - /* - This test differs from the previous in one important aspect. - The test above calls endpoints which does not create any spans during their invocation. - They merely assemble reactive pipeline where some steps create spans. - Thus all those spans are created when WebFlux span created by DispatcherHandlerInstrumentation - has already finished. Therefore, they have `SERVER` span as their parent. - - This test below calls endpoints which do create spans right inside endpoint handler. - Therefore, in theory, those spans should have INTERNAL span created by DispatcherHandlerInstrumentation - as their parent. But there is a difference how Spring WebFlux handles functional endpoints - (created in server.SpringWebFluxTestApplication.greetRouterFunction) and annotated endpoints - (created in server.TestController). - In the former case org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter.handle - calls handler function directly. Thus "tracedMethod" span below has INTERNAL handler span as its parent. - In the latter case org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.handle - merely wraps handler call into Mono and thus actual invocation of handler function happens later, - when INTERNAL handler span has already finished. Thus, "tracedMethod" has SERVER Netty span as its parent. - */ - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("provideAsyncHandlerFuncParameters") - void createSpanDuringHandlerFunctionTest(Parameter parameter) { - AggregatedHttpResponse response = client.get(parameter.urlPath).aggregate().join(); - - assertThat(response.status().code()).isEqualTo(200); - assertThat(response.contentUtf8()).isEqualTo(parameter.expectedResponseBody); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("GET " + parameter.urlPathWithVariables) - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, parameter.urlPath), - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 200), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, parameter.urlPathWithVariables)), - span -> { - if (parameter.annotatedMethod == null) { - // Functional API - assertThat(trace.getSpan(1).getName()) - .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); - } else { - // Annotation API - span.hasName( - TestController.class.getSimpleName() + "." + parameter.annotatedMethod); - } - span.hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); - }, - span -> - span.hasName("tracedMethod") - .hasParent(trace.getSpan(1)) - .hasTotalAttributeCount(0))); - } - - private static Stream provideAsyncHandlerFuncParameters() { - return Stream.of( - Arguments.of( - named( - "functional API traced method", - new Parameter( - "/greet-traced-method/5", - "/greet-traced-method/{id}", - null, - SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " 5"))), - Arguments.of( - named( - "annotation API traced method", - new Parameter( - "/foo-traced-method/8", - "/foo-traced-method/{id}", - "getTracedMethod", - new FooModel(8L, "tracedMethod").toString())))); - } - - @Test - void get404Test() { - AggregatedHttpResponse response = client.get("/notfoundgreet").aggregate().join(); - - assertThat(response.status().code()).isEqualTo(404); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("GET /**") - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasStatus(StatusData.unset()) - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, "/notfoundgreet"), - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 404), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, "/**")), - span -> - span.hasName("ResourceWebHandler.handle") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasStatus(StatusData.error()) - .hasEventsSatisfyingExactly(SpringWebfluxTest::resource404Exception) - .hasAttributesSatisfyingExactly( - codeFunctionAssertions( - "org.springframework.web.reactive.resource.ResourceWebHandler", - "handle")))); - } - - private static void resource404Exception(EventDataAssert event) { - if (Boolean.getBoolean("testLatestDeps")) { - event - .hasName("exception") - .hasAttributesSatisfyingExactly( - equalTo( - EXCEPTION_TYPE, - "org.springframework.web.reactive.resource.NoResourceFoundException"), - satisfies(EXCEPTION_MESSAGE, val -> val.isInstanceOf(String.class)), - satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class))); - } else { - event - .hasName("exception") - .hasAttributesSatisfyingExactly( - equalTo(EXCEPTION_TYPE, "org.springframework.web.server.ResponseStatusException"), - equalTo(EXCEPTION_MESSAGE, "Response status 404"), - satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class))); - } - } - - @Test - void basicPostTest() { - String echoString = "TEST"; - AggregatedHttpResponse response = client.post("/echo", echoString).aggregate().join(); - - assertThat(response.status().code()).isEqualTo(202); - assertThat(response.contentUtf8()).isEqualTo(echoString); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("POST /echo") - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, "/echo"), - equalTo(HTTP_REQUEST_METHOD, "POST"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 202), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, "/echo")), - span -> - span.hasName(EchoHandlerFunction.class.getSimpleName() + ".handle") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - codeFunctionAssertions(EchoHandlerFunction.class, "handle")), - span -> - span.hasName("echo").hasParent(trace.getSpan(1)).hasTotalAttributeCount(0))); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("provideBadEndpointParameters") - void getToBadEndpointTest(Parameter parameter) { - AggregatedHttpResponse response = client.get(parameter.urlPath).aggregate().join(); - - assertThat(response.status().code()).isEqualTo(500); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("GET " + parameter.urlPathWithVariables) - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasStatus(StatusData.error()) - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, parameter.urlPath), - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 500), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, parameter.urlPathWithVariables), - equalTo(ERROR_TYPE, "500")), - span -> { - if (parameter.annotatedMethod == null) { - // Functional API - assertThat(trace.getSpan(1).getName()) - .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); - } else { - // Annotation API - span.hasName( - TestController.class.getSimpleName() + "." + parameter.annotatedMethod); - } - span.hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasStatus(StatusData.error()) - .hasEventsSatisfyingExactly( - event -> - event - .hasName("exception") - .hasAttributesSatisfyingExactly( - equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), - equalTo(EXCEPTION_MESSAGE, "bad things happen"), - satisfies( - EXCEPTION_STACKTRACE, - val -> val.isInstanceOf(String.class)))) - .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); - })); - } - - private static Stream provideBadEndpointParameters() { - return Stream.of( - Arguments.of( - named( - "functional API fail fast", - new Parameter("/greet-failfast/1", "/greet-failfast/{id}", null, null))), - Arguments.of( - named( - "functional API fail Mono", - new Parameter("/greet-failmono/1", "/greet-failmono/{id}", null, null))), - Arguments.of( - named( - "annotation API fail fast", - new Parameter("/foo-failfast/1", "/foo-failfast/{id}", "getFooFailFast", null))), - Arguments.of( - named( - "annotation API fail Mono", - new Parameter("/foo-failmono/1", "/foo-failmono/{id}", "getFooFailMono", null)))); - } - - @Test - void redirectTest() { - AggregatedHttpResponse response = client.get("/double-greet-redirect").aggregate().join(); - - assertThat(response.status().code()).isEqualTo(200); - testing.waitAndAssertTraces( - trace -> - // TODO: why order of spans is different in these traces? - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("GET /double-greet-redirect") - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, "/double-greet-redirect"), - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 307), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, "/double-greet-redirect")), - span -> - span.hasName("RedirectComponent$$Lambda.handle") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - codeFunctionPrefixAssertions( - "server.RedirectComponent$$Lambda", "handle"))), - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("GET /double-greet") - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, "/double-greet"), - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 200), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, "/double-greet")), - span -> { - assertThat(trace.getSpan(1).getName()) - .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); - span.hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - codeFunctionPrefixAssertions( - INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX, "handle")); - })); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("provideMultipleDelayingRouteParameters") - void multipleGetsToDelayingRoute(Parameter parameter) { - int requestsCount = 50; - - List responses = - IntStream.rangeClosed(0, requestsCount - 1) - .mapToObj(n -> client.get(parameter.urlPath).aggregate().join()) - .collect(Collectors.toList()); - - assertThat(responses) - .extracting(AggregatedHttpResponse::status) - .extracting(HttpStatus::code) - .containsOnly(200); - assertThat(responses) - .extracting(AggregatedHttpResponse::contentUtf8) - .containsOnly(parameter.expectedResponseBody); - - Consumer traceAssertion = - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("GET " + parameter.urlPathWithVariables) - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, parameter.urlPath), - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(HTTP_RESPONSE_STATUS_CODE, 200), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, parameter.urlPathWithVariables)), - span -> { - if (parameter.annotatedMethod == null) { - // Functional API - assertThat(trace.getSpan(1).getName()) - .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); - } else { - // Annotation API - span.hasName( - TestController.class.getSimpleName() + "." + parameter.annotatedMethod); - } - span.hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); - }); - - testing.waitAndAssertTraces(Collections.nCopies(requestsCount, traceAssertion)); - } - - private static Stream provideMultipleDelayingRouteParameters() { - return Stream.of( - Arguments.of( - named( - "functional API delayed response", - new Parameter( - "/greet-delayed", - "/greet-delayed", - null, - SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE))), - Arguments.of( - named( - "annotation API delayed response", - new Parameter( - "/foo-delayed", - "/foo-delayed", - "getFooDelayed", - new FooModel(3L, "delayed").toString())))); - } - - @Test - void cancelRequestTest() throws Exception { - // fails with SingleThreadedSpringWebfluxTest - Assumptions.assumeTrue(this.getClass() == SpringWebfluxTest.class); - - WebClient client = - WebClient.builder("h1c://localhost:" + port) - .responseTimeout(Duration.ofSeconds(1)) - .followRedirects() - .build(); - try { - client.get("/slow").aggregate().get(); - } catch (ExecutionException ignore) { - // ignore - } - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("GET /slow") - .hasKind(SpanKind.SERVER) - .hasNoParent() - .hasStatus(StatusData.unset()) - .hasAttributesSatisfyingExactly( - equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), - equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), - satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(SERVER_ADDRESS, "localhost"), - satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), - equalTo(CLIENT_ADDRESS, "127.0.0.1"), - equalTo(URL_PATH, "/slow"), - equalTo(HTTP_REQUEST_METHOD, "GET"), - equalTo(URL_SCHEME, "http"), - satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), - equalTo(HTTP_ROUTE, "/slow"), - equalTo(ERROR_TYPE, "_OTHER")), - span -> - span.hasName("SpringWebFluxTestApplication$$Lambda.handle") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - codeFunctionPrefixAssertions( - "server.SpringWebFluxTestApplication$$Lambda", "handle")))); - - SpringWebFluxTestApplication.resumeSlowRequest(); - } - - private static class Parameter { - final String urlPath; - final String urlPathWithVariables; - final String annotatedMethod; - final String expectedResponseBody; - - Parameter( - String urlPath, - String urlPathWithVariables, - String annotatedMethod, - String expectedResponseBody) { - this.urlPath = urlPath; - this.urlPathWithVariables = urlPathWithVariables; - this.annotatedMethod = annotatedMethod; - this.expectedResponseBody = expectedResponseBody; - } - } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts new file mode 100644 index 000000000000..89b463b51e03 --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + id("otel.java-conventions") + id("otel.javaagent-testing") +} + +dependencies { + // Register instrumentations with the test agent + testInstrumentation(project(":instrumentation:spring:spring-core-2.0:javaagent")) + testInstrumentation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.0:javaagent")) + testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) + testInstrumentation(project(":instrumentation:reactor:reactor-3.1:javaagent")) + testInstrumentation(project(":instrumentation:reactor:reactor-netty:reactor-netty-1.0:javaagent")) + + testImplementation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.0:testing")) + + testImplementation("org.springframework.boot:spring-boot-starter-webflux:4.0.0") + testImplementation("org.springframework.boot:spring-boot-starter-test:4.0.0") + testImplementation("org.springframework.boot:spring-boot-starter-reactor-netty:4.0.0") +} + +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} + +tasks.withType().configureEach { + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringWebfluxTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringWebfluxTest.java new file mode 100644 index 000000000000..423bec098e50 --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringWebfluxTest.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0; + +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebfluxTest; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import server.SpringWebFluxTestApplication; + +@ExtendWith(SpringExtension.class) +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = { + SpringWebFluxTestApplication.class, + SpringWebfluxTest.ForceNettyAutoConfiguration.class + }) +class SpringWebfluxTest extends AbstractSpringWebfluxTest { + @TestConfiguration + static class ForceNettyAutoConfiguration { + @Bean + NettyReactiveWebServerFactory nettyFactory() { + return new NettyReactiveWebServerFactory(); + } + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/build.gradle.kts b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/build.gradle.kts index e3f5c23a436c..7682bc791998 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/build.gradle.kts +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/build.gradle.kts @@ -8,4 +8,5 @@ dependencies { compileOnly("org.springframework:spring-webflux:5.0.0.RELEASE") compileOnly("org.springframework.boot:spring-boot-starter-reactor-netty:2.0.0.RELEASE") compileOnly("org.springframework.boot:spring-boot:2.0.0.RELEASE") + compileOnly("org.springframework.boot:spring-boot-starter-test:2.0.0.RELEASE") } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebfluxTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebfluxTest.java new file mode 100644 index 000000000000..0fee99444ce7 --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebfluxTest.java @@ -0,0 +1,762 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.webflux.server; + +import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions; +import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionPrefixAssertions; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.ClientAttributes.CLIENT_ADDRESS; +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; +import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; +import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; +import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_ADDRESS; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.UrlAttributes.URL_PATH; +import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME; +import static io.opentelemetry.semconv.UserAgentAttributes.USER_AGENT_ORIGINAL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Named.named; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.sdk.testing.assertj.EventDataAssert; +import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.testing.internal.armeria.client.WebClient; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import io.opentelemetry.testing.internal.armeria.common.HttpStatus; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Value; +import server.EchoHandlerFunction; +import server.FooModel; +import server.SpringWebFluxTestApplication; +import server.TestController; + +@SuppressWarnings("deprecation") // using deprecated semconv +public abstract class AbstractSpringWebfluxTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private static final String INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX = + SpringWebFluxTestApplication.class.getName() + "$"; + private static final String SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX = + SpringWebFluxTestApplication.class.getSimpleName() + "$"; + + // can't use @LocalServerPort annotation since it moved packages between Spring Boot 2 and 3 + @Value("${local.server.port}") + private int port; + + private WebClient client; + + @BeforeEach + void beforeEach() { + client = WebClient.builder("h1c://localhost:" + port).followRedirects().build(); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("provideParameters") + void basicGetTest(Parameter parameter) { + AggregatedHttpResponse response = client.get(parameter.urlPath).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(200); + assertThat(response.contentUtf8()).isEqualTo(parameter.expectedResponseBody); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET " + parameter.urlPathWithVariables) + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, parameter.urlPath), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, parameter.urlPathWithVariables)), + span -> { + if (parameter.annotatedMethod == null) { + // Functional API + assertThat(trace.getSpan(1).getName()) + .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); + } else { + // Annotation API + span.hasName( + TestController.class.getSimpleName() + "." + parameter.annotatedMethod); + } + span.hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); + })); + } + + private static List assertCodeFunction(Parameter parameter) { + String expectedFunctionName = + parameter.annotatedMethod == null ? "handle" : parameter.annotatedMethod; + String expectedPrefix = + parameter.annotatedMethod == null + ? INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX + : TestController.class.getName(); + + return codeFunctionPrefixAssertions(expectedPrefix, expectedFunctionName); + } + + private static Stream provideParameters() { + return Stream.of( + Arguments.of( + named( + "functional API without parameters", + new Parameter( + "/greet", + "/greet", + null, + SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE))), + Arguments.of( + named( + "functional API with one parameter", + new Parameter( + "/greet/WORLD", + "/greet/{name}", + null, + SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " WORLD"))), + Arguments.of( + named( + "functional API with two parameters", + new Parameter( + "/greet/World/Test1", + "/greet/{name}/{word}", + null, + SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + + " World Test1"))), + Arguments.of( + named( + "functional API delayed response", + new Parameter( + "/greet-delayed", + "/greet-delayed", + null, + SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE))), + Arguments.of( + named( + "annotation API without parameters", + new Parameter( + "/foo", "/foo", "getFooModel", new FooModel(0L, "DEFAULT").toString()))), + Arguments.of( + named( + "annotation API with one parameter", + new Parameter( + "/foo/1", "/foo/{id}", "getFooModel", new FooModel(1L, "pass").toString()))), + Arguments.of( + named( + "annotation API with two parameters", + new Parameter( + "/foo/2/world", + "/foo/{id}/{name}", + "getFooModel", + new FooModel(2L, "world").toString()))), + Arguments.of( + named( + "annotation API delayed response", + new Parameter( + "/foo-delayed", + "/foo-delayed", + "getFooDelayed", + new FooModel(3L, "delayed").toString()))), + Arguments.of( + named( + "annotation API without parameters no mono", + new Parameter( + "/foo-no-mono", + "/foo-no-mono", + "getFooModelNoMono", + new FooModel(0L, "DEFAULT").toString())))); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("provideAsyncParameters") + void getAsyncResponseTest(Parameter parameter) { + AggregatedHttpResponse response = client.get(parameter.urlPath).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(200); + assertThat(response.contentUtf8()).isEqualTo(parameter.expectedResponseBody); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET " + parameter.urlPathWithVariables) + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, parameter.urlPath), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, parameter.urlPathWithVariables)), + span -> { + if (parameter.annotatedMethod == null) { + // Functional API + assertThat(trace.getSpan(1).getName()) + .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); + } else { + // Annotation API + span.hasName( + TestController.class.getSimpleName() + "." + parameter.annotatedMethod); + } + span.hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); + }, + span -> + span.hasName("tracedMethod") + .hasParent(trace.getSpan(1)) + .hasTotalAttributeCount(0))); + } + + private static Stream provideAsyncParameters() { + return Stream.of( + Arguments.of( + named( + "functional API traced method from mono", + new Parameter( + "/greet-mono-from-callable/4", + "/greet-mono-from-callable/{id}", + null, + SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " 4"))), + Arguments.of( + named( + "functional API traced method with delay", + new Parameter( + "/greet-delayed-mono/6", + "/greet-delayed-mono/{id}", + null, + SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " 6"))), + Arguments.of( + named( + "annotation API traced method from mono", + new Parameter( + "/foo-mono-from-callable/7", + "/foo-mono-from-callable/{id}", + "getMonoFromCallable", + new FooModel(7L, "tracedMethod").toString()))), + Arguments.of( + named( + "annotation API traced method with delay", + new Parameter( + "/foo-delayed-mono/9", + "/foo-delayed-mono/{id}", + "getFooDelayedMono", + new FooModel(9L, "tracedMethod").toString())))); + } + + /* + This test differs from the previous in one important aspect. + The test above calls endpoints which does not create any spans during their invocation. + They merely assemble reactive pipeline where some steps create spans. + Thus all those spans are created when WebFlux span created by DispatcherHandlerInstrumentation + has already finished. Therefore, they have `SERVER` span as their parent. + + This test below calls endpoints which do create spans right inside endpoint handler. + Therefore, in theory, those spans should have INTERNAL span created by DispatcherHandlerInstrumentation + as their parent. But there is a difference how Spring WebFlux handles functional endpoints + (created in server.SpringWebFluxTestApplication.greetRouterFunction) and annotated endpoints + (created in server.TestController). + In the former case org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter.handle + calls handler function directly. Thus "tracedMethod" span below has INTERNAL handler span as its parent. + In the latter case org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.handle + merely wraps handler call into Mono and thus actual invocation of handler function happens later, + when INTERNAL handler span has already finished. Thus, "tracedMethod" has SERVER Netty span as its parent. + */ + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("provideAsyncHandlerFuncParameters") + void createSpanDuringHandlerFunctionTest(Parameter parameter) { + AggregatedHttpResponse response = client.get(parameter.urlPath).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(200); + assertThat(response.contentUtf8()).isEqualTo(parameter.expectedResponseBody); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET " + parameter.urlPathWithVariables) + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, parameter.urlPath), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, parameter.urlPathWithVariables)), + span -> { + if (parameter.annotatedMethod == null) { + // Functional API + assertThat(trace.getSpan(1).getName()) + .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); + } else { + // Annotation API + span.hasName( + TestController.class.getSimpleName() + "." + parameter.annotatedMethod); + } + span.hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); + }, + span -> + span.hasName("tracedMethod") + .hasParent(trace.getSpan(1)) + .hasTotalAttributeCount(0))); + } + + private static Stream provideAsyncHandlerFuncParameters() { + return Stream.of( + Arguments.of( + named( + "functional API traced method", + new Parameter( + "/greet-traced-method/5", + "/greet-traced-method/{id}", + null, + SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE + " 5"))), + Arguments.of( + named( + "annotation API traced method", + new Parameter( + "/foo-traced-method/8", + "/foo-traced-method/{id}", + "getTracedMethod", + new FooModel(8L, "tracedMethod").toString())))); + } + + @Test + void get404Test() { + AggregatedHttpResponse response = client.get("/notfoundgreet").aggregate().join(); + + assertThat(response.status().code()).isEqualTo(404); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /**") + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasStatus(StatusData.unset()) + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, "/notfoundgreet"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 404), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, "/**")), + span -> + span.hasName("ResourceWebHandler.handle") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasStatus(StatusData.error()) + .hasEventsSatisfyingExactly(AbstractSpringWebfluxTest::resource404Exception) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions( + "org.springframework.web.reactive.resource.ResourceWebHandler", + "handle")))); + } + + private static void resource404Exception(EventDataAssert event) { + if (Boolean.getBoolean("testLatestDeps")) { + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + equalTo( + EXCEPTION_TYPE, + "org.springframework.web.reactive.resource.NoResourceFoundException"), + satisfies(EXCEPTION_MESSAGE, val -> val.isInstanceOf(String.class)), + satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class))); + } else { + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + satisfies( + EXCEPTION_TYPE, + val -> + val.satisfiesAnyOf( + v -> + assertThat(v) + .isEqualTo( + "org.springframework.web.server.ResponseStatusException"), + // Changed in spring 7+ + v -> + assertThat(v) + .isEqualTo( + "org.springframework.web.reactive.resource.NoResourceFoundException"))), + satisfies(EXCEPTION_MESSAGE, val -> val.contains("404")), + satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class))); + } + } + + @Test + void basicPostTest() { + String echoString = "TEST"; + AggregatedHttpResponse response = client.post("/echo", echoString).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(202); + assertThat(response.contentUtf8()).isEqualTo(echoString); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("POST /echo") + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, "/echo"), + equalTo(HTTP_REQUEST_METHOD, "POST"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 202), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, "/echo")), + span -> + span.hasName(EchoHandlerFunction.class.getSimpleName() + ".handle") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionAssertions(EchoHandlerFunction.class, "handle")), + span -> + span.hasName("echo").hasParent(trace.getSpan(1)).hasTotalAttributeCount(0))); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("provideBadEndpointParameters") + void getToBadEndpointTest(Parameter parameter) { + AggregatedHttpResponse response = client.get(parameter.urlPath).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(500); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET " + parameter.urlPathWithVariables) + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasStatus(StatusData.error()) + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, parameter.urlPath), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 500), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, parameter.urlPathWithVariables), + equalTo(ERROR_TYPE, "500")), + span -> { + if (parameter.annotatedMethod == null) { + // Functional API + assertThat(trace.getSpan(1).getName()) + .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); + } else { + // Annotation API + span.hasName( + TestController.class.getSimpleName() + "." + parameter.annotatedMethod); + } + span.hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasStatus(StatusData.error()) + .hasEventsSatisfyingExactly( + event -> + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), + equalTo(EXCEPTION_MESSAGE, "bad things happen"), + satisfies( + EXCEPTION_STACKTRACE, + val -> val.isInstanceOf(String.class)))) + .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); + })); + } + + private static Stream provideBadEndpointParameters() { + return Stream.of( + Arguments.of( + named( + "functional API fail fast", + new Parameter("/greet-failfast/1", "/greet-failfast/{id}", null, null))), + Arguments.of( + named( + "functional API fail Mono", + new Parameter("/greet-failmono/1", "/greet-failmono/{id}", null, null))), + Arguments.of( + named( + "annotation API fail fast", + new Parameter("/foo-failfast/1", "/foo-failfast/{id}", "getFooFailFast", null))), + Arguments.of( + named( + "annotation API fail Mono", + new Parameter("/foo-failmono/1", "/foo-failmono/{id}", "getFooFailMono", null)))); + } + + @Test + void redirectTest() { + AggregatedHttpResponse response = client.get("/double-greet-redirect").aggregate().join(); + + assertThat(response.status().code()).isEqualTo(200); + testing.waitAndAssertTraces( + trace -> + // TODO: why order of spans is different in these traces? + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /double-greet-redirect") + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, "/double-greet-redirect"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 307), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, "/double-greet-redirect")), + span -> + span.hasName("RedirectComponent$$Lambda.handle") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionPrefixAssertions( + "server.RedirectComponent$$Lambda", "handle"))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /double-greet") + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, "/double-greet"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, "/double-greet")), + span -> { + assertThat(trace.getSpan(1).getName()) + .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); + span.hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionPrefixAssertions( + INNER_HANDLER_FUNCTION_CLASS_TAG_PREFIX, "handle")); + })); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("provideMultipleDelayingRouteParameters") + void multipleGetsToDelayingRoute(Parameter parameter) { + int requestsCount = 50; + + List responses = + IntStream.rangeClosed(0, requestsCount - 1) + .mapToObj(n -> client.get(parameter.urlPath).aggregate().join()) + .collect(Collectors.toList()); + + assertThat(responses) + .extracting(AggregatedHttpResponse::status) + .extracting(HttpStatus::code) + .containsOnly(200); + assertThat(responses) + .extracting(AggregatedHttpResponse::contentUtf8) + .containsOnly(parameter.expectedResponseBody); + + Consumer traceAssertion = + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET " + parameter.urlPathWithVariables) + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, parameter.urlPath), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, parameter.urlPathWithVariables)), + span -> { + if (parameter.annotatedMethod == null) { + // Functional API + assertThat(trace.getSpan(1).getName()) + .contains(SPRING_APP_CLASS_ANON_NESTED_CLASS_PREFIX, ".handle"); + } else { + // Annotation API + span.hasName( + TestController.class.getSimpleName() + "." + parameter.annotatedMethod); + } + span.hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(assertCodeFunction(parameter)); + }); + + testing.waitAndAssertTraces(Collections.nCopies(requestsCount, traceAssertion)); + } + + private static Stream provideMultipleDelayingRouteParameters() { + return Stream.of( + Arguments.of( + named( + "functional API delayed response", + new Parameter( + "/greet-delayed", + "/greet-delayed", + null, + SpringWebFluxTestApplication.GreetingHandler.DEFAULT_RESPONSE))), + Arguments.of( + named( + "annotation API delayed response", + new Parameter( + "/foo-delayed", + "/foo-delayed", + "getFooDelayed", + new FooModel(3L, "delayed").toString())))); + } + + @Test + void cancelRequestTest() throws InterruptedException { + // fails with SingleThreadedSpringWebfluxTest + // Assumptions.assumeTrue(this.getClass() == SpringWebfluxTest.class); + + WebClient client = + WebClient.builder("h1c://localhost:" + port) + .responseTimeout(Duration.ofSeconds(1)) + .followRedirects() + .build(); + try { + client.get("/slow").aggregate().get(); + } catch (ExecutionException ignore) { + // ignore + } + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /slow") + .hasKind(SpanKind.SERVER) + .hasNoParent() + .hasStatus(StatusData.unset()) + .hasAttributesSatisfyingExactly( + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies(NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + satisfies(SERVER_PORT, val -> val.isInstanceOf(Long.class)), + equalTo(CLIENT_ADDRESS, "127.0.0.1"), + equalTo(URL_PATH, "/slow"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(URL_SCHEME, "http"), + satisfies(USER_AGENT_ORIGINAL, val -> val.isInstanceOf(String.class)), + equalTo(HTTP_ROUTE, "/slow"), + equalTo(ERROR_TYPE, "_OTHER")), + span -> + span.hasName("SpringWebFluxTestApplication$$Lambda.handle") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + codeFunctionPrefixAssertions( + "server.SpringWebFluxTestApplication$$Lambda", "handle")))); + + SpringWebFluxTestApplication.resumeSlowRequest(); + } + + private static class Parameter { + final String urlPath; + final String urlPathWithVariables; + final String annotatedMethod; + final String expectedResponseBody; + + Parameter( + String urlPath, + String urlPathWithVariables, + String annotatedMethod, + String expectedResponseBody) { + this.urlPath = urlPath; + this.urlPathWithVariables = urlPathWithVariables; + this.annotatedMethod = annotatedMethod; + this.expectedResponseBody = expectedResponseBody; + } + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/EchoHandler.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/EchoHandler.java similarity index 100% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/EchoHandler.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/EchoHandler.java diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/EchoHandlerFunction.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/EchoHandlerFunction.java similarity index 100% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/EchoHandlerFunction.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/EchoHandlerFunction.java diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/FooModel.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/FooModel.java similarity index 100% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/FooModel.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/FooModel.java diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/RedirectComponent.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/RedirectComponent.java similarity index 100% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/RedirectComponent.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/RedirectComponent.java diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/SpringWebFluxTestApplication.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/SpringWebFluxTestApplication.java similarity index 84% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/SpringWebFluxTestApplication.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/SpringWebFluxTestApplication.java index 0151501226ed..94a36c08a6b2 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/SpringWebFluxTestApplication.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/SpringWebFluxTestApplication.java @@ -81,7 +81,9 @@ RouterFunction greetRouterFunction(GreetingHandler greetingHandl throw new IllegalStateException(e); } return Mono.delay(Duration.ofMillis(100)) - .then(ServerResponse.ok().body(BodyInserters.fromObject("ok"))); + .then( + ServerResponse.ok() + .body(BodyInserters.fromPublisher(Mono.just("ok"), String.class))); }); } @@ -96,31 +98,37 @@ public static class GreetingHandler { Mono defaultGreet() { return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) - .body(BodyInserters.fromObject(DEFAULT_RESPONSE)); + .body(BodyInserters.fromPublisher(Mono.just(DEFAULT_RESPONSE), String.class)); } Mono doubleGreet() { return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) - .body(BodyInserters.fromObject(DEFAULT_RESPONSE + DEFAULT_RESPONSE)); + .body( + BodyInserters.fromPublisher( + Mono.just(DEFAULT_RESPONSE + DEFAULT_RESPONSE), String.class)); } Mono customGreet(ServerRequest request) { return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) - .body(BodyInserters.fromObject(DEFAULT_RESPONSE + " " + request.pathVariable("name"))); + .body( + BodyInserters.fromPublisher( + Mono.just(DEFAULT_RESPONSE + " " + request.pathVariable("name")), String.class)); } Mono customGreetWithWord(ServerRequest request) { return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .body( - BodyInserters.fromObject( - DEFAULT_RESPONSE - + " " - + request.pathVariable("name") - + " " - + request.pathVariable("word"))); + BodyInserters.fromPublisher( + Mono.just( + DEFAULT_RESPONSE + + " " + + request.pathVariable("name") + + " " + + request.pathVariable("word")), + String.class)); } Mono intResponse(Mono mono) { diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/TestController.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/TestController.java similarity index 100% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/server/TestController.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/server/TestController.java diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java index 3ef71a9ab6d2..32491266e876 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java @@ -126,6 +126,7 @@ public void configure(IgnoredTypesBuilder builder) { .allowClass( "org.springframework.boot.autoconfigure.web.WebProperties$Resources$Cache$Cachecontrol$$Lambda") .allowClass("org.springframework.boot.web.embedded.netty.NettyWebServer$") + .allowClass("org.springframework.boot.reactor.netty.NettyWebServer$") .allowClass("org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext$$Lambda") .allowClass("org.springframework.boot.tomcat.TomcatEmbeddedContext$$Lambda") .allowClass("org.springframework.boot.tomcat.TomcatWebServer$") diff --git a/settings.gradle.kts b/settings.gradle.kts index c175394c2dd4..80ea086b6811 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -646,6 +646,7 @@ include(":instrumentation:spring:spring-web:spring-web-3.1:library") include(":instrumentation:spring:spring-web:spring-web-6.0:javaagent") include(":instrumentation:spring:spring-webflux:spring-webflux-5.0:javaagent") include(":instrumentation:spring:spring-webflux:spring-webflux-5.0:testing") +include(":instrumentation:spring:spring-webflux:spring-webflux-5.0:testing-webflux7") include(":instrumentation:spring:spring-webflux:spring-webflux-5.3:library") include(":instrumentation:spring:spring-webflux:spring-webflux-5.3:testing") include(":instrumentation:spring:spring-webflux:spring-webflux-5.3:testing-webflux7") From a5b5a0eff756c07c57374c8166f69d6d2cab64bd Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Sun, 7 Dec 2025 10:36:08 -0500 Subject: [PATCH 2/9] debugging --- .../server/HandlerAdapterInstrumentation.java | 36 ++++- .../testing-webflux7/build.gradle.kts | 1 + .../v7_0/SpringThreadedSpringWebfluxTest.java | 38 +++++ ...pringWebfluxClientInstrumentationTest.java | 36 +++++ .../ControllerSpringWebFluxServerTest.java | 78 ++++++++++ ...ayedControllerSpringWebFluxServerTest.java | 52 +++++++ ...DelayedHandlerSpringWebFluxServerTest.java | 60 ++++++++ .../base/HandlerSpringWebFluxServerTest.java | 78 ++++++++++ ...iateControllerSpringWebFluxServerTest.java | 51 +++++++ ...mediateHandlerSpringWebFluxServerTest.java | 51 +++++++ .../server/base/ServerTestController.java | 144 ++++++++++++++++++ .../server/base/ServerTestRouteFactory.java | 141 +++++++++++++++++ .../server/base/SpringWebFluxServerTest.java | 67 ++++++++ .../server/AbstractSpringWebfluxTest.java | 3 +- 14 files changed, 831 insertions(+), 5 deletions(-) create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringThreadedSpringWebfluxTest.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/client/SpringWebfluxClientInstrumentationTest.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ControllerSpringWebFluxServerTest.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedControllerSpringWebFluxServerTest.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateControllerSpringWebFluxServerTest.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/SpringWebFluxServerTest.java diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/HandlerAdapterInstrumentation.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/HandlerAdapterInstrumentation.java index 2f3391bdbef6..dbac8d9c4e61 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/HandlerAdapterInstrumentation.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/HandlerAdapterInstrumentation.java @@ -47,6 +47,10 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer transformer) { + // Get the class name being transformed - this requires accessing transformer internals + // For now, just log that we're transforming + System.out.println("=== HandlerAdapterInstrumentation.transform() called ==="); + System.out.println(" Applying advice to 'handle' method"); transformer.applyAdviceToMethod( isMethod() .and(isPublic()) @@ -55,6 +59,7 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(1, Object.class)) .and(takesArguments(2)), this.getClass().getName() + "$HandleAdvice"); + System.out.println("=== HandlerAdapterInstrumentation advice applied ==="); } @SuppressWarnings("unused") @@ -71,7 +76,13 @@ private AdviceScope(Context context, Scope scope) { @Nullable public static AdviceScope enter(ServerWebExchange exchange, Object handler) { + System.out.println("=== HandlerAdapter.enter() called ==="); + System.out.println( + " Handler: " + (handler != null ? handler.getClass().getName() : "null")); + System.out.println(" Thread: " + Thread.currentThread().getName()); + Context parentContext = Context.current(); + System.out.println(" Parent context: " + parentContext); // HttpRouteSource.CONTROLLER has useFirst true, and it will update http.route only once // using the last portion of the nested path. @@ -82,13 +93,20 @@ public static AdviceScope enter(ServerWebExchange exchange, Object handler) { parentContext, HttpServerRouteSource.NESTED_CONTROLLER, httpRouteGetter(), exchange); if (handler == null) { + System.out.println(" Handler is null, returning null"); return null; } - if (!instrumenter().shouldStart(parentContext, handler)) { + boolean shouldStart = instrumenter().shouldStart(parentContext, handler); + System.out.println(" shouldStart: " + shouldStart); + + if (!shouldStart) { + System.out.println(" instrumenter().shouldStart returned false, returning null"); return null; } + Context context = instrumenter().start(parentContext, handler); + System.out.println(" Started new context: " + context); return new AdviceScope(context, context.makeCurrent()); } @@ -114,10 +132,13 @@ public Mono exit( } @Nullable - @Advice.OnMethodEnter(suppress = Throwable.class) + @Advice.OnMethodEnter public static AdviceScope methodEnter( @Advice.Argument(0) ServerWebExchange exchange, @Advice.Argument(1) Object handler) { - return AdviceScope.enter(exchange, handler); + System.out.println("=== HandlerAdapter.methodEnter() CALLED ==="); + AdviceScope scope = AdviceScope.enter(exchange, handler); + System.out.println(" Returning AdviceScope: " + scope); + return scope; } @AssignReturned.ToReturned @@ -129,11 +150,18 @@ public static Mono methodExit( @Advice.Thrown Throwable throwable, @Advice.Enter @Nullable AdviceScope adviceScope) { + System.out.println("=== HandlerAdapter.methodExit() CALLED ==="); + System.out.println(" AdviceScope: " + adviceScope); + System.out.println(" Throwable: " + throwable); + if (adviceScope == null) { + System.out.println(" AdviceScope is null, returning original mono"); return mono; } - return adviceScope.exit(throwable, exchange, handler, mono); + Mono result = adviceScope.exit(throwable, exchange, handler, mono); + System.out.println(" Returning wrapped mono"); + return result; } } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts index 89b463b51e03..526adc1ad54d 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { testInstrumentation(project(":instrumentation:reactor:reactor-netty:reactor-netty-1.0:javaagent")) testImplementation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.0:testing")) + testImplementation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.3:testing")) testImplementation("org.springframework.boot:spring-boot-starter-webflux:4.0.0") testImplementation("org.springframework.boot:spring-boot-starter-test:4.0.0") diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringThreadedSpringWebfluxTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringThreadedSpringWebfluxTest.java new file mode 100644 index 000000000000..2e6e12437a0b --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringThreadedSpringWebfluxTest.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0; + +import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import reactor.netty.resources.LoopResources; +import server.SpringWebFluxTestApplication; + +/** + * Run all Webflux tests under netty event loop having only 1 thread. Some of the bugs are better + * visible in this setup because same thread is reused for different requests. + */ +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = { + SpringWebFluxTestApplication.class, + SpringThreadedSpringWebfluxTest.ForceSingleThreadedNettyAutoConfiguration.class + }) +class SpringThreadedSpringWebfluxTest extends SpringWebfluxTest { + + @TestConfiguration + static class ForceSingleThreadedNettyAutoConfiguration { + @Bean + NettyReactiveWebServerFactory nettyFactory() { + NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(); + // Configure single-threaded event loop for Spring Boot 4 + factory.addServerCustomizers( + server -> server.runOn(LoopResources.create("my-http", 1, true))); + return factory; + } + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/client/SpringWebfluxClientInstrumentationTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/client/SpringWebfluxClientInstrumentationTest.java new file mode 100644 index 000000000000..b67fabb15c21 --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/client/SpringWebfluxClientInstrumentationTest.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.client; + +import io.opentelemetry.instrumentation.spring.webflux.client.AbstractSpringWebfluxClientInstrumentationTest; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.web.reactive.function.client.WebClient; + +class SpringWebfluxClientInstrumentationTest + extends AbstractSpringWebfluxClientInstrumentationTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent(); + + @Override + protected WebClient.Builder instrument(WebClient.Builder builder) { + return builder; + } + + @Override + protected void configure(HttpClientTestOptions.Builder optionsBuilder) { + super.configure(optionsBuilder); + + // Disable remote connection tests on Windows due to reactor-netty creating extra spans + if (OS.WINDOWS.isCurrentOs()) { + optionsBuilder.setTestRemoteConnection(false); + } + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ControllerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ControllerSpringWebFluxServerTest.java new file mode 100644 index 000000000000..f88a37bf20ee --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ControllerSpringWebFluxServerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; +import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; +import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.util.Locale; + +public abstract class ControllerSpringWebFluxServerTest extends SpringWebFluxServerTest { + + @Override + protected SpanDataAssert assertHandlerSpan( + SpanDataAssert span, String method, ServerEndpoint endpoint) { + String handlerSpanName = + ServerTestController.class.getSimpleName() + "." + endpoint.name().toLowerCase(Locale.ROOT); + if (endpoint == NOT_FOUND) { + handlerSpanName = "ResourceWebHandler.handle"; + } + span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL); + if (endpoint == EXCEPTION) { + span.hasStatus(StatusData.error()); + span.hasEventsSatisfyingExactly( + event -> + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), + equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()), + satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); + } else if (endpoint == NOT_FOUND) { + span.hasStatus(StatusData.error()); + // Spring 7 uses NoResourceFoundException instead of ResponseStatusException + span.hasEventsSatisfyingExactly( + event -> + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + equalTo( + EXCEPTION_TYPE, + "org.springframework.web.reactive.resource.NoResourceFoundException"), + satisfies( + EXCEPTION_MESSAGE, + val -> val.startsWith("404 NOT_FOUND \"No static resource notFound")), + satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); + } + return span; + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + // TODO (trask) it seems like in this case ideally the controller span (which ends when the + // Mono that the controller returns completes) should end before the server span (which needs + // the result of the Mono) + options.setVerifyServerSpanEndTime(false); + + options.setResponseCodeOnNonStandardHttpMethod(405); + + // TODO fails on java 21 + // span name set to "HTTP + // org.springframework.web.reactive.function.server.RequestPredicates$$Lambda/0x00007fa574969238@4aaf6fa2" + options.disableTestNonStandardHttpMethod(); + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedControllerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedControllerSpringWebFluxServerTest.java new file mode 100644 index 000000000000..5555315930da --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedControllerSpringWebFluxServerTest.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.time.Duration; +import java.util.function.Supplier; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +/** + * Tests the case which uses annotated controller methods, and where "controller" span is created + * within a Mono map step, which follows a delay step. For exception endpoint, the exception is + * thrown within the last map step. + */ +class DelayedControllerSpringWebFluxServerTest extends ControllerSpringWebFluxServerTest { + @Override + protected Class getApplicationClass() { + return Application.class; + } + + @Configuration + @EnableAutoConfiguration + static class Application { + @Bean + Controller controller() { + return new Controller(); + } + + @Bean + NettyReactiveWebServerFactory nettyFactory() { + return new NettyReactiveWebServerFactory(); + } + } + + @RestController + static class Controller extends ServerTestController { + @Override + protected Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler) { + return Mono.just("") + .delayElement(Duration.ofMillis(10)) + .map(unused -> controller(endpoint, handler)); + } + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java new file mode 100644 index 000000000000..a351d549af6f --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.time.Duration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +/** + * Tests the case which uses route handlers, and where "controller" span is created within a Mono + * map step, which follows a delay step. For exception endpoint, the exception is thrown within the + * last map step. + */ +class DelayedHandlerSpringWebFluxServerTest extends HandlerSpringWebFluxServerTest { + @Override + protected Class getApplicationClass() { + return Application.class; + } + + @Configuration + @EnableAutoConfiguration + static class Application { + @Bean + RouterFunction router() { + return new RouteFactory().createRoutes(); + } + + @Bean + NettyReactiveWebServerFactory nettyFactory() { + return new NettyReactiveWebServerFactory(); + } + } + + static class RouteFactory extends ServerTestRouteFactory { + + @Override + protected Mono wrapResponse( + ServerEndpoint endpoint, Mono response, Runnable spanAction) { + return response + .delayElement(Duration.ofMillis(10)) + .map( + original -> + controller( + endpoint, + () -> { + spanAction.run(); + return original; + })); + } + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java new file mode 100644 index 000000000000..6efa518eb74c --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; +import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; +import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.StatusData; + +public abstract class HandlerSpringWebFluxServerTest extends SpringWebFluxServerTest { + + @Override + protected SpanDataAssert assertHandlerSpan( + SpanDataAssert span, String method, ServerEndpoint endpoint) { + String handlerSpanName = ServerTestRouteFactory.class.getSimpleName() + "$$Lambda.handle"; + if (endpoint == NOT_FOUND) { + handlerSpanName = "ResourceWebHandler.handle"; + } + span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL); + if (endpoint == EXCEPTION) { + span.hasStatus(StatusData.error()); + span.hasEventsSatisfyingExactly( + event -> + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), + equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()), + satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); + } else if (endpoint == NOT_FOUND) { + span.hasStatus(StatusData.error()); + // Spring 7 uses NoResourceFoundException + span.hasEventsSatisfyingExactly( + event -> + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + equalTo( + EXCEPTION_TYPE, + "org.springframework.web.reactive.resource.NoResourceFoundException"), + equalTo(EXCEPTION_MESSAGE, "404 NOT_FOUND \"No static resource notFound.\""), + satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); + } + return span; + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + // TODO (trask) it seems like in this case ideally the controller span (which ends when the + // Mono that the controller returns completes) should end before the server span (which needs + // the result of the Mono) + options.setVerifyServerSpanEndTime(false); + + options.setResponseCodeOnNonStandardHttpMethod(404); + } + + @Override + public String expectedHttpRoute(ServerEndpoint endpoint, String method) { + if (HttpConstants._OTHER.equals(method)) { + return getContextPath() + "/**"; + } + return super.expectedHttpRoute(endpoint, method); + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateControllerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateControllerSpringWebFluxServerTest.java new file mode 100644 index 000000000000..3bfbb02acf0f --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateControllerSpringWebFluxServerTest.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.util.function.Supplier; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +/** + * Tests the case where "controller" span is created within the controller method scope, and the + * + *

{@code Mono} from a handler is already a fully constructed response with no deferred + * actions. For exception endpoint, the exception is thrown within controller method scope. + */ +class ImmediateControllerSpringWebFluxServerTest extends ControllerSpringWebFluxServerTest { + @Override + protected Class getApplicationClass() { + return Application.class; + } + + @Configuration + @EnableAutoConfiguration + static class Application { + @Bean + Controller controller() { + return new Controller(); + } + + @Bean + NettyReactiveWebServerFactory nettyFactory() { + return new NettyReactiveWebServerFactory(); + } + } + + @RestController + static class Controller extends ServerTestController { + @Override + protected Mono wrapControllerMethod( + ServerEndpoint endpoint, Supplier controllerMethod) { + return Mono.just(controller(endpoint, controllerMethod)); + } + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java new file mode 100644 index 000000000000..06bf736266e6 --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +/** + * Tests the case where {@code Mono} from a router function is already a fully + * constructed response with no deferred actions. For exception endpoint, the exception is thrown + * within router function scope. + */ +class ImmediateHandlerSpringWebFluxServerTest extends HandlerSpringWebFluxServerTest { + @Override + protected Class getApplicationClass() { + return Application.class; + } + + @Configuration + @EnableAutoConfiguration + static class Application { + + @Bean + RouterFunction router() { + return new RouteFactory().createRoutes(); + } + + @Bean + NettyReactiveWebServerFactory nettyFactory() { + return new NettyReactiveWebServerFactory(); + } + } + + static class RouteFactory extends ServerTestRouteFactory { + @Override + protected Mono wrapResponse( + ServerEndpoint endpoint, Mono response, Runnable spanAction) { + spanAction.run(); + return response; + } + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java new file mode 100644 index 000000000000..5e44ea568807 --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java @@ -0,0 +1,144 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; + +import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base.SpringWebFluxServerTest.NESTED_PATH; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.net.URI; +import java.util.function.Supplier; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import reactor.core.publisher.Mono; + +public abstract class ServerTestController { + @GetMapping("/success") + public Mono success(ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.SUCCESS; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return endpoint.getBody(); + }); + } + + @GetMapping("/query") + public Mono query_param(ServerHttpRequest request, ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return request.getURI().getRawQuery(); + }); + } + + @GetMapping("/redirect") + public Mono redirect(ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.REDIRECT; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + response.getHeaders().setLocation(URI.create(endpoint.getBody())); + return ""; + }); + } + + @GetMapping("/error-status") + Mono error(ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.ERROR; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return endpoint.getBody(); + }); + } + + @GetMapping("/exception") + Mono exception() { + ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; + + return wrapControllerMethod( + endpoint, + () -> { + throw new IllegalStateException(endpoint.getBody()); + }); + } + + @GetMapping("/path/{id}/param") + Mono path_param(ServerHttpResponse response, @PathVariable("id") String id) { + ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return id; + }); + } + + @GetMapping("/child") + Mono indexed_child(ServerHttpRequest request, ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; + + return wrapControllerMethod( + endpoint, + () -> { + endpoint.collectSpanAttributes(it -> request.getQueryParams().getFirst(it)); + setStatus(response, endpoint); + return endpoint.getBody(); + }); + } + + @GetMapping("/captureHeaders") + public Mono capture_headers(ServerHttpRequest request, ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + response + .getHeaders() + .set("X-Test-Response", request.getHeaders().getFirst("X-Test-Request")); + return endpoint.getBody(); + }); + } + + @GetMapping("/nestedPath") + public Mono nested_path(ServerHttpRequest request, ServerHttpResponse response) { + ServerEndpoint endpoint = NESTED_PATH; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return endpoint.getBody(); + }); + } + + protected abstract Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler); + + private static void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { + response.setStatusCode(HttpStatus.resolve(endpoint.getStatus())); + } + + protected T controller(ServerEndpoint endpoint, Supplier handler) { + return endpoint.getBody() == null && endpoint != ServerEndpoint.EXCEPTION + ? null + : handler.get(); + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java new file mode 100644 index 000000000000..8f89db280c01 --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java @@ -0,0 +1,141 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; + +import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base.SpringWebFluxServerTest.NESTED_PATH; +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RequestPredicates.path; +import static org.springframework.web.reactive.function.server.RouterFunctions.nest; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.util.function.Supplier; +import org.springframework.http.HttpHeaders; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder; +import reactor.core.publisher.Mono; + +public abstract class ServerTestRouteFactory { + public RouterFunction createRoutes() { + return route( + GET("/success"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.SUCCESS; + + return respond(endpoint, null, null, null); + }) + .andRoute( + GET("/query"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; + + return respond(endpoint, null, request.uri().getRawQuery(), null); + }) + .andRoute( + GET("/redirect"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.REDIRECT; + + return respond( + endpoint, + ServerResponse.status(endpoint.getStatus()) + .header(HttpHeaders.LOCATION, endpoint.getBody()), + "", + null); + }) + .andRoute( + GET("/error-status"), + redirect -> { + ServerEndpoint endpoint = ServerEndpoint.ERROR; + + return respond(endpoint, null, null, null); + }) + .andRoute( + GET("/exception"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; + + return respond( + endpoint, + ServerResponse.ok(), + "", + () -> { + throw new IllegalStateException(endpoint.getBody()); + }); + }) + .andRoute( + GET("/path/{id}/param"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; + + return respond(endpoint, null, request.pathVariable("id"), null); + }) + .andRoute( + GET("/child"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; + + return respond( + endpoint, + null, + null, + () -> + Span.current() + .setAttribute( + "test.request.id", Long.parseLong(request.queryParam("id").get()))); + }) + .andRoute( + GET("/captureHeaders"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; + + return respond( + endpoint, + ServerResponse.status(endpoint.getStatus()) + .header( + "X-Test-Response", + request.headers().asHttpHeaders().getFirst("X-Test-Request")), + null, + null); + }) + .andNest( + path("/nestedPath"), + nest( + path("/hello"), + route( + path("/world"), + request -> { + ServerEndpoint endpoint = NESTED_PATH; + return respond(endpoint, null, null, null); + }))); + } + + protected Mono respond( + ServerEndpoint endpoint, BodyBuilder bodyBuilder, String body, Runnable spanAction) { + if (bodyBuilder == null) { + bodyBuilder = ServerResponse.status(endpoint.getStatus()); + } + if (body == null) { + body = endpoint.getBody() != null ? endpoint.getBody() : ""; + } + if (spanAction == null) { + spanAction = () -> {}; + } + + return wrapResponse(endpoint, bodyBuilder.bodyValue(body), spanAction); + } + + protected abstract Mono wrapResponse( + ServerEndpoint endpoint, Mono response, Runnable spanAction); + + protected T controller(ServerEndpoint endpoint, Supplier handler) { + return endpoint.getBody() == null && endpoint != ServerEndpoint.EXCEPTION + ? null + : handler.get(); + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/SpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/SpringWebFluxServerTest.java new file mode 100644 index 000000000000..29182114b71a --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/SpringWebFluxServerTest.java @@ -0,0 +1,67 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +public abstract class SpringWebFluxServerTest + extends AbstractHttpServerTest { + + protected static final ServerEndpoint NESTED_PATH = + new ServerEndpoint("NESTED_PATH", "nestedPath/hello/world", 200, "nested path"); + + protected abstract Class getApplicationClass(); + + @RegisterExtension + static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); + + @Override + public ConfigurableApplicationContext setupServer() { + SpringApplication app = new SpringApplication(getApplicationClass()); + Map properties = new HashMap<>(); + properties.put("server.port", port); + properties.put("server.context-path", getContextPath()); + properties.put("server.servlet.contextPath", getContextPath()); + properties.put("server.error.include-message", "always"); + app.setDefaultProperties(properties); + return app.run(); + } + + @Override + public void stopServer(ConfigurableApplicationContext configurableApplicationContext) { + configurableApplicationContext.close(); + } + + @Override + public String expectedHttpRoute(ServerEndpoint endpoint, String method) { + if (endpoint.equals(PATH_PARAM)) { + return getContextPath() + "/path/{id}/param"; + } else if (endpoint.equals(NOT_FOUND)) { + return "/**"; + } else if (endpoint.equals(NESTED_PATH)) { + return "/nestedPath/hello/world"; + } + return super.expectedHttpRoute(endpoint, method); + } + + @Override + protected void configure(HttpServerTestOptions options) { + options.setTestPathParam(true); + options.setHasHandlerSpan(unused -> true); + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebfluxTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebfluxTest.java index 0fee99444ce7..a9c0444cff14 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebfluxTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebfluxTest.java @@ -46,6 +46,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -697,7 +698,7 @@ private static Stream provideMultipleDelayingRouteParameters() { @Test void cancelRequestTest() throws InterruptedException { // fails with SingleThreadedSpringWebfluxTest - // Assumptions.assumeTrue(this.getClass() == SpringWebfluxTest.class); + Assumptions.assumeTrue(this.getClass().getSimpleName().startsWith("SpringWebfluxTest")); WebClient client = WebClient.builder("h1c://localhost:" + port) From 76b9b26679c4242f8d0e1ba51e481a0b7f97fcc9 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Sun, 7 Dec 2025 14:53:32 -0500 Subject: [PATCH 3/9] spring notes --- ...ayedControllerSpringWebFluxServerTest.java | 4 +- ...iateControllerSpringWebFluxServerTest.java | 4 +- ...mediateHandlerSpringWebFluxServerTest.java | 119 +++----- .../server/base/ServerTestController.java | 276 ++++++++--------- .../testing-webflux7/build.gradle.kts | 2 +- ...ayedControllerSpringWebFluxServerTest.java | 13 +- .../base/HandlerSpringWebFluxServerTest.java | 5 +- ...iateControllerSpringWebFluxServerTest.java | 13 +- ...mediateHandlerSpringWebFluxServerTest.java | 68 ++--- .../server/base/ServerTestController.java | 288 +++++++++--------- .../server/base/ServerTestRouteFactory.java | 9 +- ...actControllerSpringWebFluxServerTest.java} | 53 +++- ...mediateHandlerSpringWebFluxServerTest.java | 78 +++++ .../AbstractSpringWebFluxServerTest.java} | 6 +- .../HandlerSpringWebFluxServerTest.java} | 22 +- .../webflux/server/ServerTestController.java | 139 +++++++++ .../server/ServerTestRouteFactory.java | 135 ++++++++ ...ditionalLibraryIgnoredTypesConfigurer.java | 1 + 18 files changed, 804 insertions(+), 431 deletions(-) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/{testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ControllerSpringWebFluxServerTest.java => testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractControllerSpringWebFluxServerTest.java} (57%) create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java rename instrumentation/spring/spring-webflux/spring-webflux-5.0/{testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/SpringWebFluxServerTest.java => testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebFluxServerTest.java} (93%) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/{javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ControllerSpringWebFluxServerTest.java => testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/HandlerSpringWebFluxServerTest.java} (84%) create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestController.java create mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/DelayedControllerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/DelayedControllerSpringWebFluxServerTest.java index cf199a549bb1..43b3123efa26 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/DelayedControllerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/DelayedControllerSpringWebFluxServerTest.java @@ -5,6 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractControllerSpringWebFluxServerTest; +import io.opentelemetry.instrumentation.spring.webflux.server.ServerTestController; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.time.Duration; import java.util.function.Supplier; @@ -20,7 +22,7 @@ * within a Mono map step, which follows a delay step. For exception endpoint, the exception is * thrown within the last map step. */ -class DelayedControllerSpringWebFluxServerTest extends ControllerSpringWebFluxServerTest { +class DelayedControllerSpringWebFluxServerTest extends AbstractControllerSpringWebFluxServerTest { @Override protected Class getApplicationClass() { return Application.class; diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateControllerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateControllerSpringWebFluxServerTest.java index a029ffe587c3..794c0309b784 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateControllerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateControllerSpringWebFluxServerTest.java @@ -5,6 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractControllerSpringWebFluxServerTest; +import io.opentelemetry.instrumentation.spring.webflux.server.ServerTestController; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.util.function.Supplier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -20,7 +22,7 @@ *

{@code Mono} from a handler is already a fully constructed response with no deferred * actions. For exception endpoint, the exception is thrown within controller method scope. */ -class ImmediateControllerSpringWebFluxServerTest extends ControllerSpringWebFluxServerTest { +class ImmediateControllerSpringWebFluxServerTest extends AbstractControllerSpringWebFluxServerTest { @Override protected Class getApplicationClass() { return Application.class; diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java index 63b0a03eaa45..a0cecb320bb4 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java @@ -5,73 +5,54 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; - -/** - * Tests the case where "controller" span is created within the route handler method scope, and the - * - *

{@code Mono} from a handler is already a fully constructed response with no - * deferred actions. For exception endpoint, the exception is thrown within route handler method - * scope. - */ -class ImmediateHandlerSpringWebFluxServerTest extends HandlerSpringWebFluxServerTest { - @Override - protected Class getApplicationClass() { - return Application.class; - } - - @Configuration - @EnableAutoConfiguration - static class Application { - @Bean - RouterFunction router() { - return new RouteFactory().createRoutes(); - } - - @Bean - NettyReactiveWebServerFactory nettyFactory() { - return new NettyReactiveWebServerFactory(); - } - } - - static class RouteFactory extends ServerTestRouteFactory { - - @Override - protected Mono wrapResponse( - ServerEndpoint endpoint, Mono response, Runnable spanAction) { - return controller( - endpoint, - () -> { - spanAction.run(); - return response; - }); - } - } - - @Test - void nestedPath() { - assumeTrue(Boolean.getBoolean("testLatestDeps")); - - String method = "GET"; - AggregatedHttpRequest request = request(NESTED_PATH, method); - AggregatedHttpResponse response = client.execute(request).aggregate().join(); - assertThat(response.status().code()).isEqualTo(NESTED_PATH.getStatus()); - assertThat(response.contentUtf8()).isEqualTo(NESTED_PATH.getBody()); - assertResponseHasCustomizedHeaders(response, NESTED_PATH, null); - - assertTheTraces(1, null, null, null, method, NESTED_PATH); - } +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractImmediateHandlerSpringWebFluxServerTest; + +class ImmediateHandlerSpringWebFluxServerTest + extends AbstractImmediateHandlerSpringWebFluxServerTest { + // @Override + // protected Class getApplicationClass() { + // return Application.class; + // } + // + // @Configuration + // @EnableAutoConfiguration + // static class Application { + // @Bean + // RouterFunction router() { + // return new RouteFactory().createRoutes(); + // } + // + // @Bean + // NettyReactiveWebServerFactory nettyFactory() { + // return new NettyReactiveWebServerFactory(); + // } + // } + // + // static class RouteFactory extends ServerTestRouteFactory { + // + // @Override + // protected Mono wrapResponse( + // ServerEndpoint endpoint, Mono response, Runnable spanAction) { + // return controller( + // endpoint, + // () -> { + // spanAction.run(); + // return response; + // }); + // } + // } + // + // @Test + // void nestedPath() { + // assumeTrue(Boolean.getBoolean("testLatestDeps")); + // + // String method = "GET"; + // AggregatedHttpRequest request = request(NESTED_PATH, method); + // AggregatedHttpResponse response = client.execute(request).aggregate().join(); + // assertThat(response.status().code()).isEqualTo(NESTED_PATH.getStatus()); + // assertThat(response.contentUtf8()).isEqualTo(NESTED_PATH.getBody()); + // assertResponseHasCustomizedHeaders(response, NESTED_PATH, null); + // + // assertTheTraces(1, null, null, null, method, NESTED_PATH); + // } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestController.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestController.java index 432f23eb961a..87de4d7d46e7 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestController.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestController.java @@ -1,138 +1,138 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; - -import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base.SpringWebFluxServerTest.NESTED_PATH; - -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import java.net.URI; -import java.util.function.Supplier; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import reactor.core.publisher.Mono; - -public abstract class ServerTestController { - @GetMapping("/success") - public Mono success(ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.SUCCESS; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return endpoint.getBody(); - }); - } - - @GetMapping("/query") - public Mono query_param(ServerHttpRequest request, ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return request.getURI().getRawQuery(); - }); - } - - @GetMapping("/redirect") - public Mono redirect(ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.REDIRECT; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - response.getHeaders().setLocation(URI.create(endpoint.getBody())); - return ""; - }); - } - - @GetMapping("/error-status") - Mono error(ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.ERROR; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return endpoint.getBody(); - }); - } - - @GetMapping("/exception") - Mono exception() { - ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; - - return wrapControllerMethod( - endpoint, - () -> { - throw new IllegalStateException(endpoint.getBody()); - }); - } - - @GetMapping("/path/{id}/param") - Mono path_param(ServerHttpResponse response, @PathVariable("id") String id) { - ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return id; - }); - } - - @GetMapping("/child") - Mono indexed_child(ServerHttpRequest request, ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; - - return wrapControllerMethod( - endpoint, - () -> { - endpoint.collectSpanAttributes(it -> request.getQueryParams().getFirst(it)); - setStatus(response, endpoint); - return endpoint.getBody(); - }); - } - - @GetMapping("/captureHeaders") - public Mono capture_headers(ServerHttpRequest request, ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - response - .getHeaders() - .set("X-Test-Response", request.getHeaders().getFirst("X-Test-Request")); - return endpoint.getBody(); - }); - } - - @GetMapping("/nestedPath") - public Mono nested_path(ServerHttpRequest request, ServerHttpResponse response) { - ServerEndpoint endpoint = NESTED_PATH; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return endpoint.getBody(); - }); - } - - protected abstract Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler); - - private static void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { - response.setStatusCode(HttpStatus.resolve(endpoint.getStatus())); - } -} +///* +// * Copyright The OpenTelemetry Authors +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; +// +//import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base.SpringWebFluxServerTest.NESTED_PATH; +// +//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +//import java.net.URI; +//import java.util.function.Supplier; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.server.reactive.ServerHttpRequest; +//import org.springframework.http.server.reactive.ServerHttpResponse; +//import org.springframework.web.bind.annotation.GetMapping; +//import org.springframework.web.bind.annotation.PathVariable; +//import reactor.core.publisher.Mono; +// +//public abstract class ServerTestController { +// @GetMapping("/success") +// public Mono success(ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.SUCCESS; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return endpoint.getBody(); +// }); +// } +// +// @GetMapping("/query") +// public Mono query_param(ServerHttpRequest request, ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return request.getURI().getRawQuery(); +// }); +// } +// +// @GetMapping("/redirect") +// public Mono redirect(ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.REDIRECT; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// response.getHeaders().setLocation(URI.create(endpoint.getBody())); +// return ""; +// }); +// } +// +// @GetMapping("/error-status") +// Mono error(ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.ERROR; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return endpoint.getBody(); +// }); +// } +// +// @GetMapping("/exception") +// Mono exception() { +// ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// throw new IllegalStateException(endpoint.getBody()); +// }); +// } +// +// @GetMapping("/path/{id}/param") +// Mono path_param(ServerHttpResponse response, @PathVariable("id") String id) { +// ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return id; +// }); +// } +// +// @GetMapping("/child") +// Mono indexed_child(ServerHttpRequest request, ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// endpoint.collectSpanAttributes(it -> request.getQueryParams().getFirst(it)); +// setStatus(response, endpoint); +// return endpoint.getBody(); +// }); +// } +// +// @GetMapping("/captureHeaders") +// public Mono capture_headers(ServerHttpRequest request, ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// response +// .getHeaders() +// .set("X-Test-Response", request.getHeaders().getFirst("X-Test-Request")); +// return endpoint.getBody(); +// }); +// } +// +// @GetMapping("/nestedPath") +// public Mono nested_path(ServerHttpRequest request, ServerHttpResponse response) { +// ServerEndpoint endpoint = NESTED_PATH; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return endpoint.getBody(); +// }); +// } +// +// protected abstract Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler); +// +// private static void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { +// response.setStatusCode(HttpStatus.resolve(endpoint.getStatus())); +// } +//} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts index 526adc1ad54d..b7cd8289e23b 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts @@ -4,7 +4,6 @@ plugins { } dependencies { - // Register instrumentations with the test agent testInstrumentation(project(":instrumentation:spring:spring-core-2.0:javaagent")) testInstrumentation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.0:javaagent")) testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) @@ -15,6 +14,7 @@ dependencies { testImplementation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.3:testing")) testImplementation("org.springframework.boot:spring-boot-starter-webflux:4.0.0") + testImplementation("org.springframework:spring-web:7.0.0") testImplementation("org.springframework.boot:spring-boot-starter-test:4.0.0") testImplementation("org.springframework.boot:spring-boot-starter-reactor-netty:4.0.0") } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedControllerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedControllerSpringWebFluxServerTest.java index 5555315930da..202ce3492881 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedControllerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedControllerSpringWebFluxServerTest.java @@ -5,6 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractControllerSpringWebFluxServerTest; +import io.opentelemetry.instrumentation.spring.webflux.server.ServerTestController; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.time.Duration; import java.util.function.Supplier; @@ -12,6 +14,8 @@ import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @@ -20,7 +24,7 @@ * within a Mono map step, which follows a delay step. For exception endpoint, the exception is * thrown within the last map step. */ -class DelayedControllerSpringWebFluxServerTest extends ControllerSpringWebFluxServerTest { +class DelayedControllerSpringWebFluxServerTest extends AbstractControllerSpringWebFluxServerTest { @Override protected Class getApplicationClass() { return Application.class; @@ -46,7 +50,12 @@ static class Controller extends ServerTestController { protected Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler) { return Mono.just("") .delayElement(Duration.ofMillis(10)) - .map(unused -> controller(endpoint, handler)); + .map(unused -> controller(endpoint, handler::get)); + } + + @Override + protected void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { + response.setStatusCode(HttpStatusCode.valueOf(endpoint.getStatus())); } } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java index 6efa518eb74c..3b919f970345 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java @@ -15,12 +15,13 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.api.internal.HttpConstants; +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.trace.data.StatusData; -public abstract class HandlerSpringWebFluxServerTest extends SpringWebFluxServerTest { +public abstract class HandlerSpringWebFluxServerTest extends AbstractSpringWebFluxServerTest { @Override protected SpanDataAssert assertHandlerSpan( @@ -65,6 +66,8 @@ protected void configure(HttpServerTestOptions options) { // the result of the Mono) options.setVerifyServerSpanEndTime(false); + options.setHasHandlerSpan(t -> false); + options.setResponseCodeOnNonStandardHttpMethod(404); } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateControllerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateControllerSpringWebFluxServerTest.java index 3bfbb02acf0f..bb127e4d604c 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateControllerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateControllerSpringWebFluxServerTest.java @@ -5,12 +5,16 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractControllerSpringWebFluxServerTest; +import io.opentelemetry.instrumentation.spring.webflux.server.ServerTestController; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.util.function.Supplier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @@ -20,7 +24,7 @@ *

{@code Mono} from a handler is already a fully constructed response with no deferred * actions. For exception endpoint, the exception is thrown within controller method scope. */ -class ImmediateControllerSpringWebFluxServerTest extends ControllerSpringWebFluxServerTest { +class ImmediateControllerSpringWebFluxServerTest extends AbstractControllerSpringWebFluxServerTest { @Override protected Class getApplicationClass() { return Application.class; @@ -45,7 +49,12 @@ static class Controller extends ServerTestController { @Override protected Mono wrapControllerMethod( ServerEndpoint endpoint, Supplier controllerMethod) { - return Mono.just(controller(endpoint, controllerMethod)); + return Mono.just(controller(endpoint, controllerMethod::get)); + } + + @Override + protected void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { + response.setStatusCode(HttpStatusCode.valueOf(endpoint.getStatus())); } } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java index 06bf736266e6..4cf6d286e176 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java @@ -5,47 +5,41 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractImmediateHandlerSpringWebFluxServerTest; /** * Tests the case where {@code Mono} from a router function is already a fully * constructed response with no deferred actions. For exception endpoint, the exception is thrown * within router function scope. */ -class ImmediateHandlerSpringWebFluxServerTest extends HandlerSpringWebFluxServerTest { - @Override - protected Class getApplicationClass() { - return Application.class; - } - - @Configuration - @EnableAutoConfiguration - static class Application { - - @Bean - RouterFunction router() { - return new RouteFactory().createRoutes(); - } - - @Bean - NettyReactiveWebServerFactory nettyFactory() { - return new NettyReactiveWebServerFactory(); - } - } - - static class RouteFactory extends ServerTestRouteFactory { - @Override - protected Mono wrapResponse( - ServerEndpoint endpoint, Mono response, Runnable spanAction) { - spanAction.run(); - return response; - } - } +class ImmediateHandlerSpringWebFluxServerTest + extends AbstractImmediateHandlerSpringWebFluxServerTest { + // @Override + // protected Class getApplicationClass() { + // return Application.class; + // } + // + // @Configuration + // @EnableAutoConfiguration + // static class Application { + // + // @Bean + // RouterFunction router() { + // return new RouteFactory().createRoutes(); + // } + // + // @Bean + // NettyReactiveWebServerFactory nettyFactory() { + // return new NettyReactiveWebServerFactory(); + // } + // } + // + // static class RouteFactory extends ServerTestRouteFactory { + // @Override + // protected Mono wrapResponse( + // ServerEndpoint endpoint, Mono response, Runnable spanAction) { + // spanAction.run(); + // return response; + // } + // } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java index 5e44ea568807..309bbafad326 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java @@ -1,144 +1,144 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; - -import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base.SpringWebFluxServerTest.NESTED_PATH; - -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import java.net.URI; -import java.util.function.Supplier; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import reactor.core.publisher.Mono; - -public abstract class ServerTestController { - @GetMapping("/success") - public Mono success(ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.SUCCESS; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return endpoint.getBody(); - }); - } - - @GetMapping("/query") - public Mono query_param(ServerHttpRequest request, ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return request.getURI().getRawQuery(); - }); - } - - @GetMapping("/redirect") - public Mono redirect(ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.REDIRECT; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - response.getHeaders().setLocation(URI.create(endpoint.getBody())); - return ""; - }); - } - - @GetMapping("/error-status") - Mono error(ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.ERROR; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return endpoint.getBody(); - }); - } - - @GetMapping("/exception") - Mono exception() { - ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; - - return wrapControllerMethod( - endpoint, - () -> { - throw new IllegalStateException(endpoint.getBody()); - }); - } - - @GetMapping("/path/{id}/param") - Mono path_param(ServerHttpResponse response, @PathVariable("id") String id) { - ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return id; - }); - } - - @GetMapping("/child") - Mono indexed_child(ServerHttpRequest request, ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; - - return wrapControllerMethod( - endpoint, - () -> { - endpoint.collectSpanAttributes(it -> request.getQueryParams().getFirst(it)); - setStatus(response, endpoint); - return endpoint.getBody(); - }); - } - - @GetMapping("/captureHeaders") - public Mono capture_headers(ServerHttpRequest request, ServerHttpResponse response) { - ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - response - .getHeaders() - .set("X-Test-Response", request.getHeaders().getFirst("X-Test-Request")); - return endpoint.getBody(); - }); - } - - @GetMapping("/nestedPath") - public Mono nested_path(ServerHttpRequest request, ServerHttpResponse response) { - ServerEndpoint endpoint = NESTED_PATH; - - return wrapControllerMethod( - endpoint, - () -> { - setStatus(response, endpoint); - return endpoint.getBody(); - }); - } - - protected abstract Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler); - - private static void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { - response.setStatusCode(HttpStatus.resolve(endpoint.getStatus())); - } - - protected T controller(ServerEndpoint endpoint, Supplier handler) { - return endpoint.getBody() == null && endpoint != ServerEndpoint.EXCEPTION - ? null - : handler.get(); - } -} +///* +// * Copyright The OpenTelemetry Authors +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; +// +//import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base.SpringWebFluxServerTest.NESTED_PATH; +// +//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +//import java.net.URI; +//import java.util.function.Supplier; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.server.reactive.ServerHttpRequest; +//import org.springframework.http.server.reactive.ServerHttpResponse; +//import org.springframework.web.bind.annotation.GetMapping; +//import org.springframework.web.bind.annotation.PathVariable; +//import reactor.core.publisher.Mono; +// +//public abstract class ServerTestController { +// @GetMapping("/success") +// public Mono success(ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.SUCCESS; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return endpoint.getBody(); +// }); +// } +// +// @GetMapping("/query") +// public Mono query_param(ServerHttpRequest request, ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return request.getURI().getRawQuery(); +// }); +// } +// +// @GetMapping("/redirect") +// public Mono redirect(ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.REDIRECT; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// response.getHeaders().setLocation(URI.create(endpoint.getBody())); +// return ""; +// }); +// } +// +// @GetMapping("/error-status") +// Mono error(ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.ERROR; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return endpoint.getBody(); +// }); +// } +// +// @GetMapping("/exception") +// Mono exception() { +// ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// throw new IllegalStateException(endpoint.getBody()); +// }); +// } +// +// @GetMapping("/path/{id}/param") +// Mono path_param(ServerHttpResponse response, @PathVariable("id") String id) { +// ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return id; +// }); +// } +// +// @GetMapping("/child") +// Mono indexed_child(ServerHttpRequest request, ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// endpoint.collectSpanAttributes(it -> request.getQueryParams().getFirst(it)); +// setStatus(response, endpoint); +// return endpoint.getBody(); +// }); +// } +// +// @GetMapping("/captureHeaders") +// public Mono capture_headers(ServerHttpRequest request, ServerHttpResponse response) { +// ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// response +// .getHeaders() +// .set("X-Test-Response", request.getHeaders().getFirst("X-Test-Request")); +// return endpoint.getBody(); +// }); +// } +// +// @GetMapping("/nestedPath") +// public Mono nested_path(ServerHttpRequest request, ServerHttpResponse response) { +// ServerEndpoint endpoint = NESTED_PATH; +// +// return wrapControllerMethod( +// endpoint, +// () -> { +// setStatus(response, endpoint); +// return endpoint.getBody(); +// }); +// } +// +// protected abstract Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler); +// +// private static void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { +// response.setStatusCode(HttpStatus.resolve(endpoint.getStatus())); +// } +// +// protected T controller(ServerEndpoint endpoint, Supplier handler) { +// return endpoint.getBody() == null && endpoint != ServerEndpoint.EXCEPTION +// ? null +// : handler.get(); +// } +//} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java index 8f89db280c01..47f8d7f21da5 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; -import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base.SpringWebFluxServerTest.NESTED_PATH; +import static io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest.NESTED_PATH; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.path; import static org.springframework.web.reactive.function.server.RouterFunctions.nest; @@ -107,12 +107,7 @@ public RouterFunction createRoutes() { path("/nestedPath"), nest( path("/hello"), - route( - path("/world"), - request -> { - ServerEndpoint endpoint = NESTED_PATH; - return respond(endpoint, null, null, null); - }))); + route(path("/world"), request -> respond(NESTED_PATH, null, null, null)))); } protected Mono respond( diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ControllerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractControllerSpringWebFluxServerTest.java similarity index 57% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ControllerSpringWebFluxServerTest.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractControllerSpringWebFluxServerTest.java index f88a37bf20ee..9f0f6bd0ef96 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ControllerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractControllerSpringWebFluxServerTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; +package io.opentelemetry.instrumentation.spring.webflux.server; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; @@ -12,6 +12,7 @@ import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; @@ -20,7 +21,8 @@ import io.opentelemetry.sdk.trace.data.StatusData; import java.util.Locale; -public abstract class ControllerSpringWebFluxServerTest extends SpringWebFluxServerTest { +public abstract class AbstractControllerSpringWebFluxServerTest + extends AbstractSpringWebFluxServerTest { @Override protected SpanDataAssert assertHandlerSpan( @@ -43,19 +45,40 @@ protected SpanDataAssert assertHandlerSpan( satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); } else if (endpoint == NOT_FOUND) { span.hasStatus(StatusData.error()); - // Spring 7 uses NoResourceFoundException instead of ResponseStatusException - span.hasEventsSatisfyingExactly( - event -> - event - .hasName("exception") - .hasAttributesSatisfyingExactly( - equalTo( - EXCEPTION_TYPE, - "org.springframework.web.reactive.resource.NoResourceFoundException"), - satisfies( - EXCEPTION_MESSAGE, - val -> val.startsWith("404 NOT_FOUND \"No static resource notFound")), - satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); + if (Boolean.getBoolean("testLatestDeps")) { + span.hasEventsSatisfyingExactly( + event -> + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + equalTo( + EXCEPTION_TYPE, + "org.springframework.web.reactive.resource.NoResourceFoundException"), + equalTo( + EXCEPTION_MESSAGE, "404 NOT_FOUND \"No static resource notFound.\""), + satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); + } else { + span.hasEventsSatisfyingExactly( + event -> + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + satisfies( + EXCEPTION_TYPE, + val -> + val.satisfiesAnyOf( + v -> + assertThat(v) + .isEqualTo( + "org.springframework.web.server.ResponseStatusException"), + // Changed in spring 7+ + v -> + assertThat(v) + .isEqualTo( + "org.springframework.web.reactive.resource.NoResourceFoundException"))), + satisfies(EXCEPTION_MESSAGE, val -> val.contains("404")), + satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); + } } return span; } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java new file mode 100644 index 000000000000..1bc976ac8854 --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.webflux.server; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +/** + * Tests the case where "controller" span is created within the route handler method scope, and the + * + *

{@code Mono} from a handler is already a fully constructed response with no + * deferred actions. For exception endpoint, the exception is thrown within route handler method + * scope. + */ +public class AbstractImmediateHandlerSpringWebFluxServerTest + extends HandlerSpringWebFluxServerTest { + @Override + protected Class getApplicationClass() { + return Application.class; + } + + @Configuration + @EnableAutoConfiguration + static class Application { + @Bean + RouterFunction router() { + return new RouteFactory().createRoutes(); + } + + @Bean + NettyReactiveWebServerFactory nettyFactory() { + return new NettyReactiveWebServerFactory(); + } + } + + static class RouteFactory extends ServerTestRouteFactory { + + @Override + protected Mono wrapResponse( + ServerEndpoint endpoint, Mono response, Runnable spanAction) { + return controller( + endpoint, + () -> { + spanAction.run(); + return response; + }); + } + } + + @Test + void nestedPath() { + assumeTrue(Boolean.getBoolean("testLatestDeps")); + + String method = "GET"; + AggregatedHttpRequest request = request(NESTED_PATH, method); + AggregatedHttpResponse response = client.execute(request).aggregate().join(); + assertThat(response.status().code()).isEqualTo(NESTED_PATH.getStatus()); + assertThat(response.contentUtf8()).isEqualTo(NESTED_PATH.getBody()); + assertResponseHasCustomizedHeaders(response, NESTED_PATH, null); + + assertTheTraces(1, null, null, null, method, NESTED_PATH); + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/SpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebFluxServerTest.java similarity index 93% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/SpringWebFluxServerTest.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebFluxServerTest.java index 29182114b71a..038714b3c793 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/SpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractSpringWebFluxServerTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; +package io.opentelemetry.instrumentation.spring.webflux.server; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM; @@ -19,10 +19,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; -public abstract class SpringWebFluxServerTest +public abstract class AbstractSpringWebFluxServerTest extends AbstractHttpServerTest { - protected static final ServerEndpoint NESTED_PATH = + public static final ServerEndpoint NESTED_PATH = new ServerEndpoint("NESTED_PATH", "nestedPath/hello/world", 200, "nested path"); protected abstract Class getApplicationClass(); diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ControllerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/HandlerSpringWebFluxServerTest.java similarity index 84% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ControllerSpringWebFluxServerTest.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/HandlerSpringWebFluxServerTest.java index ea5c7be2bd1a..ffeb1d1aee3b 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ControllerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/HandlerSpringWebFluxServerTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; +package io.opentelemetry.instrumentation.spring.webflux.server; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; @@ -14,19 +14,18 @@ import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.trace.data.StatusData; -import java.util.Locale; -public abstract class ControllerSpringWebFluxServerTest extends SpringWebFluxServerTest { +public abstract class HandlerSpringWebFluxServerTest extends AbstractSpringWebFluxServerTest { @Override protected SpanDataAssert assertHandlerSpan( SpanDataAssert span, String method, ServerEndpoint endpoint) { - String handlerSpanName = - ServerTestController.class.getSimpleName() + "." + endpoint.name().toLowerCase(Locale.ROOT); + String handlerSpanName = ServerTestRouteFactory.class.getSimpleName() + "$$Lambda.handle"; if (endpoint == NOT_FOUND) { handlerSpanName = "ResourceWebHandler.handle"; } @@ -79,11 +78,14 @@ protected void configure(HttpServerTestOptions options) { // the result of the Mono) options.setVerifyServerSpanEndTime(false); - options.setResponseCodeOnNonStandardHttpMethod(405); + options.setResponseCodeOnNonStandardHttpMethod(404); + } - // TODO fails on java 21 - // span name set to "HTTP - // org.springframework.web.reactive.function.server.RequestPredicates$$Lambda/0x00007fa574969238@4aaf6fa2" - options.disableTestNonStandardHttpMethod(); + @Override + public String expectedHttpRoute(ServerEndpoint endpoint, String method) { + if (HttpConstants._OTHER.equals(method)) { + return getContextPath() + "/**"; + } + return super.expectedHttpRoute(endpoint, method); } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestController.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestController.java new file mode 100644 index 000000000000..db78715c51ab --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestController.java @@ -0,0 +1,139 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.webflux.server; + +import static io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest.NESTED_PATH; + +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.net.URI; +import java.util.function.Supplier; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import reactor.core.publisher.Mono; + +@SuppressWarnings("IdentifierName") // method names are snake_case to match endpoints +public abstract class ServerTestController { + @GetMapping("/success") + public Mono success(ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.SUCCESS; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return endpoint.getBody(); + }); + } + + @GetMapping("/query") + public Mono query_param(ServerHttpRequest request, ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return request.getURI().getRawQuery(); + }); + } + + @GetMapping("/redirect") + public Mono redirect(ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.REDIRECT; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + response.getHeaders().setLocation(URI.create(endpoint.getBody())); + return ""; + }); + } + + @GetMapping("/error-status") + Mono error(ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.ERROR; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return endpoint.getBody(); + }); + } + + @GetMapping("/exception") + Mono exception() { + ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; + + return wrapControllerMethod( + endpoint, + () -> { + throw new IllegalStateException(endpoint.getBody()); + }); + } + + @GetMapping("/path/{id}/param") + Mono path_param(ServerHttpResponse response, @PathVariable("id") String id) { + ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return id; + }); + } + + @GetMapping("/child") + Mono indexed_child(ServerHttpRequest request, ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; + + return wrapControllerMethod( + endpoint, + () -> { + endpoint.collectSpanAttributes(it -> request.getQueryParams().getFirst(it)); + setStatus(response, endpoint); + return endpoint.getBody(); + }); + } + + @GetMapping("/captureHeaders") + public Mono capture_headers(ServerHttpRequest request, ServerHttpResponse response) { + ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + response + .getHeaders() + .set("X-Test-Response", request.getHeaders().getFirst("X-Test-Request")); + return endpoint.getBody(); + }); + } + + @GetMapping("/nestedPath") + public Mono nested_path(ServerHttpRequest request, ServerHttpResponse response) { + ServerEndpoint endpoint = NESTED_PATH; + + return wrapControllerMethod( + endpoint, + () -> { + setStatus(response, endpoint); + return endpoint.getBody(); + }); + } + + protected abstract Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler); + + protected void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { + response.setStatusCode(HttpStatus.resolve(endpoint.getStatus())); + } +} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java new file mode 100644 index 000000000000..de7e606f03c7 --- /dev/null +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java @@ -0,0 +1,135 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.webflux.server; + +import static io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest.NESTED_PATH; +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RequestPredicates.path; +import static org.springframework.web.reactive.function.server.RouterFunctions.nest; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder; +import reactor.core.publisher.Mono; + +public abstract class ServerTestRouteFactory { + public RouterFunction createRoutes() { + return route( + GET("/success"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.SUCCESS; + + return respond(endpoint, null, null, null); + }) + .andRoute( + GET("/query"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; + + return respond(endpoint, null, request.uri().getRawQuery(), null); + }) + .andRoute( + GET("/redirect"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.REDIRECT; + + return respond( + endpoint, + ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())) + .header(HttpHeaders.LOCATION, endpoint.getBody()), + "", + null); + }) + .andRoute( + GET("/error-status"), + redirect -> { + ServerEndpoint endpoint = ServerEndpoint.ERROR; + + return respond(endpoint, null, null, null); + }) + .andRoute( + GET("/exception"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; + + return respond( + endpoint, + ServerResponse.ok(), + "", + () -> { + throw new IllegalStateException(endpoint.getBody()); + }); + }) + .andRoute( + GET("/path/{id}/param"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; + + return respond(endpoint, null, request.pathVariable("id"), null); + }) + .andRoute( + GET("/child"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; + + return respond( + endpoint, + null, + null, + () -> + Span.current() + .setAttribute( + "test.request.id", Long.parseLong(request.queryParam("id").get()))); + }) + .andRoute( + GET("/captureHeaders"), + request -> { + ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; + + return respond( + endpoint, + ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())) + .header( + "X-Test-Response", + request.headers().asHttpHeaders().getFirst("X-Test-Request")), + null, + null); + }) + .andNest( + path("/nestedPath"), + nest( + path("/hello"), + route( + path("/world"), + request -> { + ServerEndpoint endpoint = NESTED_PATH; + return respond(endpoint, null, null, null); + }))); + } + + protected Mono respond( + ServerEndpoint endpoint, BodyBuilder bodyBuilder, String body, Runnable spanAction) { + if (bodyBuilder == null) { + bodyBuilder = ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())); + } + if (body == null) { + body = endpoint.getBody() != null ? endpoint.getBody() : ""; + } + if (spanAction == null) { + spanAction = () -> {}; + } + + return wrapResponse(endpoint, bodyBuilder.syncBody(body), spanAction); + } + + protected abstract Mono wrapResponse( + ServerEndpoint endpoint, Mono response, Runnable spanAction); +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java index 32491266e876..1244902a486e 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java @@ -108,6 +108,7 @@ public void configure(IgnoredTypesBuilder builder) { .allowClass("org.springframework.boot.servlet.filter.") .allowClass("org.springframework.boot.web.server.servlet.context.") .allowClass("org.springframework.boot.web.embedded.netty.GracefulShutdown$$Lambda") + .allowClass("org.springframework.boot.reactor.netty.GracefulShutdown$$Lambda") .allowClass("org.springframework.boot.web.embedded.tomcat.GracefulShutdown$$Lambda") .allowClass("org.springframework.boot.tomcat.GracefulShutdown$$Lambda") .allowClass("org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory$$Lambda") From a3788e7a9c70a612c2bda21479d310d93fd4aea5 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 8 Dec 2025 12:43:22 -0500 Subject: [PATCH 4/9] more test stuff --- ...DelayedHandlerSpringWebFluxServerTest.java | 3 +- .../base/HandlerSpringWebFluxServerTest.java | 182 ++++++------ .../SpringThreadedSpringWebfluxTest.java | 2 +- .../v7_0/{ => server}/SpringWebfluxTest.java | 2 +- ...DelayedHandlerSpringWebFluxServerTest.java | 3 +- .../base/HandlerSpringWebFluxServerTest.java | 162 +++++------ ...mediateHandlerSpringWebFluxServerTest.java | 64 +++-- ...stractHandlerSpringWebFluxServerTest.java} | 29 +- ...mediateHandlerSpringWebFluxServerTest.java | 72 +++-- .../server/ServerTestRouteFactory.java | 270 +++++++++--------- .../testing/TestInstrumenters.java | 19 +- .../junit/http/AbstractHttpServerTest.java | 9 + 12 files changed, 431 insertions(+), 386 deletions(-) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/{ => server}/SpringThreadedSpringWebfluxTest.java (98%) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/{ => server}/SpringWebfluxTest.java (98%) rename instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/{HandlerSpringWebFluxServerTest.java => AbstractHandlerSpringWebFluxServerTest.java} (73%) diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/DelayedHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/DelayedHandlerSpringWebFluxServerTest.java index f956798dc314..650ffd4c8e3f 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/DelayedHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/DelayedHandlerSpringWebFluxServerTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractHandlerSpringWebFluxServerTest; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.time.Duration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -20,7 +21,7 @@ * map step, which follows a delay step. For exception endpoint, the exception is thrown within the * last map step. */ -class DelayedHandlerSpringWebFluxServerTest extends HandlerSpringWebFluxServerTest { +class DelayedHandlerSpringWebFluxServerTest extends AbstractHandlerSpringWebFluxServerTest { @Override protected Class getApplicationClass() { return Application.class; diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/HandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/HandlerSpringWebFluxServerTest.java index 637c5b8124ca..3aa9d2ebf597 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/HandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/HandlerSpringWebFluxServerTest.java @@ -1,91 +1,91 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; -import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; -import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; - -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.api.internal.HttpConstants; -import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -import io.opentelemetry.sdk.trace.data.StatusData; - -public abstract class HandlerSpringWebFluxServerTest extends SpringWebFluxServerTest { - - @Override - protected SpanDataAssert assertHandlerSpan( - SpanDataAssert span, String method, ServerEndpoint endpoint) { - String handlerSpanName = ServerTestRouteFactory.class.getSimpleName() + "$$Lambda.handle"; - if (endpoint == NOT_FOUND) { - handlerSpanName = "ResourceWebHandler.handle"; - } - span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL); - if (endpoint == EXCEPTION) { - span.hasStatus(StatusData.error()); - span.hasEventsSatisfyingExactly( - event -> - event - .hasName("exception") - .hasAttributesSatisfyingExactly( - equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), - equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()), - satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); - } else if (endpoint == NOT_FOUND) { - span.hasStatus(StatusData.error()); - if (Boolean.getBoolean("testLatestDeps")) { - span.hasEventsSatisfyingExactly( - event -> - event - .hasName("exception") - .hasAttributesSatisfyingExactly( - equalTo( - EXCEPTION_TYPE, - "org.springframework.web.reactive.resource.NoResourceFoundException"), - equalTo( - EXCEPTION_MESSAGE, "404 NOT_FOUND \"No static resource notFound.\""), - satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); - } else { - span.hasEventsSatisfyingExactly( - event -> - event - .hasName("exception") - .hasAttributesSatisfyingExactly( - equalTo( - EXCEPTION_TYPE, - "org.springframework.web.server.ResponseStatusException"), - equalTo(EXCEPTION_MESSAGE, "Response status 404"), - satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); - } - } - return span; - } - - @Override - protected void configure(HttpServerTestOptions options) { - super.configure(options); - // TODO (trask) it seems like in this case ideally the controller span (which ends when the - // Mono that the controller returns completes) should end before the server span (which needs - // the result of the Mono) - options.setVerifyServerSpanEndTime(false); - - options.setResponseCodeOnNonStandardHttpMethod(404); - } - - @Override - public String expectedHttpRoute(ServerEndpoint endpoint, String method) { - if (HttpConstants._OTHER.equals(method)) { - return getContextPath() + "/**"; - } - return super.expectedHttpRoute(endpoint, method); - } -} +///* +// * Copyright The OpenTelemetry Authors +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; +// +//import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +//import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; +//import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +//import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; +//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; +//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; +// +//import io.opentelemetry.api.trace.SpanKind; +//import io.opentelemetry.instrumentation.api.internal.HttpConstants; +//import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +//import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +//import io.opentelemetry.sdk.trace.data.StatusData; +// +//public abstract class HandlerSpringWebFluxServerTest extends SpringWebFluxServerTest { +// +// @Override +// protected SpanDataAssert assertHandlerSpan( +// SpanDataAssert span, String method, ServerEndpoint endpoint) { +// String handlerSpanName = ServerTestRouteFactory.class.getSimpleName() + "$$Lambda.handle"; +// if (endpoint == NOT_FOUND) { +// handlerSpanName = "ResourceWebHandler.handle"; +// } +// span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL); +// if (endpoint == EXCEPTION) { +// span.hasStatus(StatusData.error()); +// span.hasEventsSatisfyingExactly( +// event -> +// event +// .hasName("exception") +// .hasAttributesSatisfyingExactly( +// equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), +// equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()), +// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); +// } else if (endpoint == NOT_FOUND) { +// span.hasStatus(StatusData.error()); +// if (Boolean.getBoolean("testLatestDeps")) { +// span.hasEventsSatisfyingExactly( +// event -> +// event +// .hasName("exception") +// .hasAttributesSatisfyingExactly( +// equalTo( +// EXCEPTION_TYPE, +// "org.springframework.web.reactive.resource.NoResourceFoundException"), +// equalTo( +// EXCEPTION_MESSAGE, "404 NOT_FOUND \"No static resource notFound.\""), +// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); +// } else { +// span.hasEventsSatisfyingExactly( +// event -> +// event +// .hasName("exception") +// .hasAttributesSatisfyingExactly( +// equalTo( +// EXCEPTION_TYPE, +// "org.springframework.web.server.ResponseStatusException"), +// equalTo(EXCEPTION_MESSAGE, "Response status 404"), +// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); +// } +// } +// return span; +// } +// +// @Override +// protected void configure(HttpServerTestOptions options) { +// super.configure(options); +// // TODO (trask) it seems like in this case ideally the controller span (which ends when the +// // Mono that the controller returns completes) should end before the server span (which needs +// // the result of the Mono) +// options.setVerifyServerSpanEndTime(false); +// +// options.setResponseCodeOnNonStandardHttpMethod(404); +// } +// +// @Override +// public String expectedHttpRoute(ServerEndpoint endpoint, String method) { +// if (HttpConstants._OTHER.equals(method)) { +// return getContextPath() + "/**"; +// } +// return super.expectedHttpRoute(endpoint, method); +// } +//} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringThreadedSpringWebfluxTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/SpringThreadedSpringWebfluxTest.java similarity index 98% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringThreadedSpringWebfluxTest.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/SpringThreadedSpringWebfluxTest.java index 2e6e12437a0b..69ba5c9e29fe 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringThreadedSpringWebfluxTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/SpringThreadedSpringWebfluxTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0; +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server; import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; import org.springframework.boot.test.context.SpringBootTest; diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringWebfluxTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/SpringWebfluxTest.java similarity index 98% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringWebfluxTest.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/SpringWebfluxTest.java index 423bec098e50..7d25e82be658 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/SpringWebfluxTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/SpringWebfluxTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0; +package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server; import io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebfluxTest; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java index a351d549af6f..6e89b8e1234e 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; +import io.opentelemetry.instrumentation.spring.webflux.server.AbstractHandlerSpringWebFluxServerTest; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import java.time.Duration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -20,7 +21,7 @@ * map step, which follows a delay step. For exception endpoint, the exception is thrown within the * last map step. */ -class DelayedHandlerSpringWebFluxServerTest extends HandlerSpringWebFluxServerTest { +class DelayedHandlerSpringWebFluxServerTest extends AbstractHandlerSpringWebFluxServerTest { @Override protected Class getApplicationClass() { return Application.class; diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java index 3b919f970345..c83dfb2f3245 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java @@ -1,81 +1,81 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; -import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; -import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; - -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.api.internal.HttpConstants; -import io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest; -import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -import io.opentelemetry.sdk.trace.data.StatusData; - -public abstract class HandlerSpringWebFluxServerTest extends AbstractSpringWebFluxServerTest { - - @Override - protected SpanDataAssert assertHandlerSpan( - SpanDataAssert span, String method, ServerEndpoint endpoint) { - String handlerSpanName = ServerTestRouteFactory.class.getSimpleName() + "$$Lambda.handle"; - if (endpoint == NOT_FOUND) { - handlerSpanName = "ResourceWebHandler.handle"; - } - span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL); - if (endpoint == EXCEPTION) { - span.hasStatus(StatusData.error()); - span.hasEventsSatisfyingExactly( - event -> - event - .hasName("exception") - .hasAttributesSatisfyingExactly( - equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), - equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()), - satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); - } else if (endpoint == NOT_FOUND) { - span.hasStatus(StatusData.error()); - // Spring 7 uses NoResourceFoundException - span.hasEventsSatisfyingExactly( - event -> - event - .hasName("exception") - .hasAttributesSatisfyingExactly( - equalTo( - EXCEPTION_TYPE, - "org.springframework.web.reactive.resource.NoResourceFoundException"), - equalTo(EXCEPTION_MESSAGE, "404 NOT_FOUND \"No static resource notFound.\""), - satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); - } - return span; - } - - @Override - protected void configure(HttpServerTestOptions options) { - super.configure(options); - // TODO (trask) it seems like in this case ideally the controller span (which ends when the - // Mono that the controller returns completes) should end before the server span (which needs - // the result of the Mono) - options.setVerifyServerSpanEndTime(false); - - options.setHasHandlerSpan(t -> false); - - options.setResponseCodeOnNonStandardHttpMethod(404); - } - - @Override - public String expectedHttpRoute(ServerEndpoint endpoint, String method) { - if (HttpConstants._OTHER.equals(method)) { - return getContextPath() + "/**"; - } - return super.expectedHttpRoute(endpoint, method); - } -} +///* +// * Copyright The OpenTelemetry Authors +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; +// +//import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +//import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; +//import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +//import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; +//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; +//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; +// +//import io.opentelemetry.api.trace.SpanKind; +//import io.opentelemetry.instrumentation.api.internal.HttpConstants; +//import io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest; +//import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +//import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +//import io.opentelemetry.sdk.trace.data.StatusData; +// +//public abstract class HandlerSpringWebFluxServerTest extends AbstractSpringWebFluxServerTest { +// +// @Override +// protected SpanDataAssert assertHandlerSpan( +// SpanDataAssert span, String method, ServerEndpoint endpoint) { +// String handlerSpanName = ServerTestRouteFactory.class.getSimpleName() + "$$Lambda.handle"; +// if (endpoint == NOT_FOUND) { +// handlerSpanName = "ResourceWebHandler.handle"; +// } +// span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL); +// if (endpoint == EXCEPTION) { +// span.hasStatus(StatusData.error()); +// span.hasEventsSatisfyingExactly( +// event -> +// event +// .hasName("exception") +// .hasAttributesSatisfyingExactly( +// equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), +// equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()), +// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); +// } else if (endpoint == NOT_FOUND) { +// span.hasStatus(StatusData.error()); +// // Spring 7 uses NoResourceFoundException +// span.hasEventsSatisfyingExactly( +// event -> +// event +// .hasName("exception") +// .hasAttributesSatisfyingExactly( +// equalTo( +// EXCEPTION_TYPE, +// "org.springframework.web.reactive.resource.NoResourceFoundException"), +// equalTo(EXCEPTION_MESSAGE, "404 NOT_FOUND \"No static resource notFound.\""), +// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); +// } +// return span; +// } +// +// @Override +// protected void configure(HttpServerTestOptions options) { +// super.configure(options); +// // TODO (trask) it seems like in this case ideally the controller span (which ends when the +// // Mono that the controller returns completes) should end before the server span (which needs +// // the result of the Mono) +// options.setVerifyServerSpanEndTime(false); +// +// options.setHasHandlerSpan(t -> false); +// +// options.setResponseCodeOnNonStandardHttpMethod(404); +// } +// +// @Override +// public String expectedHttpRoute(ServerEndpoint endpoint, String method) { +// if (HttpConstants._OTHER.equals(method)) { +// return getContextPath() + "/**"; +// } +// return super.expectedHttpRoute(endpoint, method); +// } +//} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java index 4cf6d286e176..998c31a93830 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java @@ -6,6 +6,14 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; import io.opentelemetry.instrumentation.spring.webflux.server.AbstractImmediateHandlerSpringWebFluxServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; /** * Tests the case where {@code Mono} from a router function is already a fully @@ -14,32 +22,32 @@ */ class ImmediateHandlerSpringWebFluxServerTest extends AbstractImmediateHandlerSpringWebFluxServerTest { - // @Override - // protected Class getApplicationClass() { - // return Application.class; - // } - // - // @Configuration - // @EnableAutoConfiguration - // static class Application { - // - // @Bean - // RouterFunction router() { - // return new RouteFactory().createRoutes(); - // } - // - // @Bean - // NettyReactiveWebServerFactory nettyFactory() { - // return new NettyReactiveWebServerFactory(); - // } - // } - // - // static class RouteFactory extends ServerTestRouteFactory { - // @Override - // protected Mono wrapResponse( - // ServerEndpoint endpoint, Mono response, Runnable spanAction) { - // spanAction.run(); - // return response; - // } - // } + @Override + protected Class getApplicationClass() { + return Application.class; + } + + @Configuration + @EnableAutoConfiguration + static class Application { + + @Bean + RouterFunction router() { + return new RouteFactory().createRoutes(); + } + + @Bean + NettyReactiveWebServerFactory nettyFactory() { + return new NettyReactiveWebServerFactory(); + } + } + + static class RouteFactory extends ServerTestRouteFactory { + @Override + protected Mono wrapResponse( + ServerEndpoint endpoint, Mono response, Runnable spanAction) { + spanAction.run(); + return response; + } + } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/HandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java similarity index 73% rename from instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/HandlerSpringWebFluxServerTest.java rename to instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java index ffeb1d1aee3b..688601cc9666 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/HandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java @@ -12,6 +12,7 @@ import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.api.internal.HttpConstants; @@ -20,12 +21,13 @@ import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.trace.data.StatusData; -public abstract class HandlerSpringWebFluxServerTest extends AbstractSpringWebFluxServerTest { +public abstract class AbstractHandlerSpringWebFluxServerTest + extends AbstractSpringWebFluxServerTest { @Override protected SpanDataAssert assertHandlerSpan( SpanDataAssert span, String method, ServerEndpoint endpoint) { - String handlerSpanName = ServerTestRouteFactory.class.getSimpleName() + "$$Lambda.handle"; + String handlerSpanName = "Test" + "$$Lambda.handle"; if (endpoint == NOT_FOUND) { handlerSpanName = "ResourceWebHandler.handle"; } @@ -60,11 +62,26 @@ protected SpanDataAssert assertHandlerSpan( event .hasName("exception") .hasAttributesSatisfyingExactly( - equalTo( + satisfies( EXCEPTION_TYPE, - "org.springframework.web.server.ResponseStatusException"), - equalTo(EXCEPTION_MESSAGE, "Response status 404"), - satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); + val -> + val.satisfiesAnyOf( + // changed in spring 7+ + v -> + assertThat(v) + .isEqualTo( + "org.springframework.web.server.ResponseStatusException"), + v -> + assertThat(v) + .isEqualTo( + "org.springframework.web.reactive.resource.NoResourceFoundException"))), + satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)), + satisfies( + EXCEPTION_MESSAGE, + val -> + val.satisfiesAnyOf( + v -> assertThat(v).isNull(), + v -> assertThat(v).contains("404 NOT_FOUND"))))); } } return span; diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java index 1bc976ac8854..91e55945a82f 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java @@ -8,17 +8,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; /** * Tests the case where "controller" span is created within the route handler method scope, and the @@ -27,40 +19,40 @@ * deferred actions. For exception endpoint, the exception is thrown within route handler method * scope. */ -public class AbstractImmediateHandlerSpringWebFluxServerTest - extends HandlerSpringWebFluxServerTest { - @Override - protected Class getApplicationClass() { - return Application.class; - } - - @Configuration - @EnableAutoConfiguration - static class Application { - @Bean - RouterFunction router() { - return new RouteFactory().createRoutes(); - } +public abstract class AbstractImmediateHandlerSpringWebFluxServerTest + extends AbstractHandlerSpringWebFluxServerTest { + // @Override + // protected Class getApplicationClass() { + // return Application.class; + // } - @Bean - NettyReactiveWebServerFactory nettyFactory() { - return new NettyReactiveWebServerFactory(); - } - } + // @Configuration + // @EnableAutoConfiguration + // static class Application { + // @Bean + // RouterFunction router() { + // return new RouteFactory().createRoutes(); + // } + // + // @Bean + // NettyReactiveWebServerFactory nettyFactory() { + // return new NettyReactiveWebServerFactory(); + // } + // } - static class RouteFactory extends ServerTestRouteFactory { - - @Override - protected Mono wrapResponse( - ServerEndpoint endpoint, Mono response, Runnable spanAction) { - return controller( - endpoint, - () -> { - spanAction.run(); - return response; - }); - } - } + // static class RouteFactory extends ServerTestRouteFactory { + // + // @Override + // protected Mono wrapResponse( + // ServerEndpoint endpoint, Mono response, Runnable spanAction) { + // return controller( + // endpoint, + // () -> { + // spanAction.run(); + // return response; + // }); + // } + // } @Test void nestedPath() { diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java index de7e606f03c7..c5f7db5fba03 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java @@ -1,135 +1,135 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.webflux.server; - -import static io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest.NESTED_PATH; -import static org.springframework.web.reactive.function.server.RequestPredicates.GET; -import static org.springframework.web.reactive.function.server.RequestPredicates.path; -import static org.springframework.web.reactive.function.server.RouterFunctions.nest; -import static org.springframework.web.reactive.function.server.RouterFunctions.route; - -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerResponse; -import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder; -import reactor.core.publisher.Mono; - -public abstract class ServerTestRouteFactory { - public RouterFunction createRoutes() { - return route( - GET("/success"), - request -> { - ServerEndpoint endpoint = ServerEndpoint.SUCCESS; - - return respond(endpoint, null, null, null); - }) - .andRoute( - GET("/query"), - request -> { - ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; - - return respond(endpoint, null, request.uri().getRawQuery(), null); - }) - .andRoute( - GET("/redirect"), - request -> { - ServerEndpoint endpoint = ServerEndpoint.REDIRECT; - - return respond( - endpoint, - ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())) - .header(HttpHeaders.LOCATION, endpoint.getBody()), - "", - null); - }) - .andRoute( - GET("/error-status"), - redirect -> { - ServerEndpoint endpoint = ServerEndpoint.ERROR; - - return respond(endpoint, null, null, null); - }) - .andRoute( - GET("/exception"), - request -> { - ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; - - return respond( - endpoint, - ServerResponse.ok(), - "", - () -> { - throw new IllegalStateException(endpoint.getBody()); - }); - }) - .andRoute( - GET("/path/{id}/param"), - request -> { - ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; - - return respond(endpoint, null, request.pathVariable("id"), null); - }) - .andRoute( - GET("/child"), - request -> { - ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; - - return respond( - endpoint, - null, - null, - () -> - Span.current() - .setAttribute( - "test.request.id", Long.parseLong(request.queryParam("id").get()))); - }) - .andRoute( - GET("/captureHeaders"), - request -> { - ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; - - return respond( - endpoint, - ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())) - .header( - "X-Test-Response", - request.headers().asHttpHeaders().getFirst("X-Test-Request")), - null, - null); - }) - .andNest( - path("/nestedPath"), - nest( - path("/hello"), - route( - path("/world"), - request -> { - ServerEndpoint endpoint = NESTED_PATH; - return respond(endpoint, null, null, null); - }))); - } - - protected Mono respond( - ServerEndpoint endpoint, BodyBuilder bodyBuilder, String body, Runnable spanAction) { - if (bodyBuilder == null) { - bodyBuilder = ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())); - } - if (body == null) { - body = endpoint.getBody() != null ? endpoint.getBody() : ""; - } - if (spanAction == null) { - spanAction = () -> {}; - } - - return wrapResponse(endpoint, bodyBuilder.syncBody(body), spanAction); - } - - protected abstract Mono wrapResponse( - ServerEndpoint endpoint, Mono response, Runnable spanAction); -} +///* +// * Copyright The OpenTelemetry Authors +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package io.opentelemetry.instrumentation.spring.webflux.server; +// +//import static io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest.NESTED_PATH; +//import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +//import static org.springframework.web.reactive.function.server.RequestPredicates.path; +//import static org.springframework.web.reactive.function.server.RouterFunctions.nest; +//import static org.springframework.web.reactive.function.server.RouterFunctions.route; +// +//import io.opentelemetry.api.trace.Span; +//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +//import org.springframework.http.HttpHeaders; +//import org.springframework.http.HttpStatus; +//import org.springframework.web.reactive.function.server.RouterFunction; +//import org.springframework.web.reactive.function.server.ServerResponse; +//import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder; +//import reactor.core.publisher.Mono; +// +//public abstract class ServerTestRouteFactory { +// public RouterFunction createRoutes() { +// return route( +// GET("/success"), +// request -> { +// ServerEndpoint endpoint = ServerEndpoint.SUCCESS; +// +// return respond(endpoint, null, null, null); +// }) +// .andRoute( +// GET("/query"), +// request -> { +// ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; +// +// return respond(endpoint, null, request.uri().getRawQuery(), null); +// }) +// .andRoute( +// GET("/redirect"), +// request -> { +// ServerEndpoint endpoint = ServerEndpoint.REDIRECT; +// +// return respond( +// endpoint, +// ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())) +// .header(HttpHeaders.LOCATION, endpoint.getBody()), +// "", +// null); +// }) +// .andRoute( +// GET("/error-status"), +// redirect -> { +// ServerEndpoint endpoint = ServerEndpoint.ERROR; +// +// return respond(endpoint, null, null, null); +// }) +// .andRoute( +// GET("/exception"), +// request -> { +// ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; +// +// return respond( +// endpoint, +// ServerResponse.ok(), +// "", +// () -> { +// throw new IllegalStateException(endpoint.getBody()); +// }); +// }) +// .andRoute( +// GET("/path/{id}/param"), +// request -> { +// ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; +// +// return respond(endpoint, null, request.pathVariable("id"), null); +// }) +// .andRoute( +// GET("/child"), +// request -> { +// ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; +// +// return respond( +// endpoint, +// null, +// null, +// () -> +// Span.current() +// .setAttribute( +// "test.request.id", Long.parseLong(request.queryParam("id").get()))); +// }) +// .andRoute( +// GET("/captureHeaders"), +// request -> { +// ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; +// +// return respond( +// endpoint, +// ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())) +// .header( +// "X-Test-Response", +// request.headers().asHttpHeaders().getFirst("X-Test-Request")), +// null, +// null); +// }) +// .andNest( +// path("/nestedPath"), +// nest( +// path("/hello"), +// route( +// path("/world"), +// request -> { +// ServerEndpoint endpoint = NESTED_PATH; +// return respond(endpoint, null, null, null); +// }))); +// } +// +// protected Mono respond( +// ServerEndpoint endpoint, BodyBuilder bodyBuilder, String body, Runnable spanAction) { +// if (bodyBuilder == null) { +// bodyBuilder = ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())); +// } +// if (body == null) { +// body = endpoint.getBody() != null ? endpoint.getBody() : ""; +// } +// if (spanAction == null) { +// spanAction = () -> {}; +// } +// +// return wrapResponse(endpoint, bodyBuilder.syncBody(body), spanAction); +// } +// +// protected abstract Mono wrapResponse( +// ServerEndpoint endpoint, Mono response, Runnable spanAction); +//} diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/TestInstrumenters.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/TestInstrumenters.java index c070fca33af4..1171d098323d 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/TestInstrumenters.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/TestInstrumenters.java @@ -97,7 +97,23 @@ T runWithNonRecordingSpan(ThrowingSupplier callba private static T runWithInstrumenter( String spanName, Instrumenter instrumenter, ThrowingSupplier callback) throws E { - Context context = instrumenter.start(Context.current(), spanName); + System.out.println("=== TestInstrumenters.runWithInstrumenter() CALLED ==="); + System.out.println(" Span name: " + spanName); + System.out.println(" Thread: " + Thread.currentThread().getName()); + + Context parentContext = Context.current(); + System.out.println(" Parent context: " + parentContext); + + boolean shouldStart = instrumenter.shouldStart(parentContext, spanName); + System.out.println(" shouldStart: " + shouldStart); + + if (!shouldStart) { + System.out.println(" Instrumenter refused to start span, running callback without span"); + return callback.get(); + } + + Context context = instrumenter.start(parentContext, spanName); + System.out.println(" Started context: " + context); T result; try (Scope ignored = context.makeCurrent()) { @@ -107,6 +123,7 @@ private static T runWithInstrumenter( throw t; } instrumenter.end(context, spanName, null, null); + System.out.println(" Span ended successfully"); return result; } diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java index c6bb2218067c..405c089b7683 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java @@ -121,10 +121,19 @@ protected void configure(HttpServerTestOptions options) {} public static T controller( ServerEndpoint endpoint, ThrowingSupplier closure) throws E { + System.out.println("=== controller() CALLED ==="); + System.out.println(" Endpoint: " + endpoint); + System.out.println(" Thread: " + Thread.currentThread().getName()); + System.out.println(" Current span: " + Span.current()); + System.out.println( + " Current span context valid: " + Span.current().getSpanContext().isValid()); + assert Span.current().getSpanContext().isValid() : "Controller should have a parent span."; if (endpoint == NOT_FOUND) { + System.out.println(" Endpoint is NOT_FOUND, skipping controller span"); return closure.get(); } + System.out.println(" Calling GlobalTraceUtil.runWithSpan('controller', ...)"); return GlobalTraceUtil.runWithSpan("controller", closure); } From 39b823c5d6433f1c4a01019dfcdee193299df976 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 8 Dec 2025 13:18:10 -0500 Subject: [PATCH 5/9] cleanup --- .../base/HandlerSpringWebFluxServerTest.java | 91 ----------- ...mediateHandlerSpringWebFluxServerTest.java | 86 +++++------ .../server/base/ServerTestController.java | 138 ----------------- .../server/base/ServerTestRouteFactory.java | 9 +- .../server/base/SpringWebFluxServerTest.java | 67 -------- ...DelayedHandlerSpringWebFluxServerTest.java | 16 +- .../base/HandlerSpringWebFluxServerTest.java | 81 ---------- ...mediateHandlerSpringWebFluxServerTest.java | 8 +- .../server/base/ServerTestController.java | 144 ------------------ .../server/base/ServerTestRouteFactory.java | 7 - ...bstractHandlerSpringWebFluxServerTest.java | 4 +- .../server/ServerTestRouteFactory.java | 135 ---------------- 12 files changed, 59 insertions(+), 727 deletions(-) delete mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/HandlerSpringWebFluxServerTest.java delete mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestController.java delete mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/SpringWebFluxServerTest.java delete mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java delete mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java delete mode 100644 instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/HandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/HandlerSpringWebFluxServerTest.java deleted file mode 100644 index 3aa9d2ebf597..000000000000 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/HandlerSpringWebFluxServerTest.java +++ /dev/null @@ -1,91 +0,0 @@ -///* -// * Copyright The OpenTelemetry Authors -// * SPDX-License-Identifier: Apache-2.0 -// */ -// -//package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; -// -//import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; -//import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; -//import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -//import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; -//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; -//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; -// -//import io.opentelemetry.api.trace.SpanKind; -//import io.opentelemetry.instrumentation.api.internal.HttpConstants; -//import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -//import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -//import io.opentelemetry.sdk.trace.data.StatusData; -// -//public abstract class HandlerSpringWebFluxServerTest extends SpringWebFluxServerTest { -// -// @Override -// protected SpanDataAssert assertHandlerSpan( -// SpanDataAssert span, String method, ServerEndpoint endpoint) { -// String handlerSpanName = ServerTestRouteFactory.class.getSimpleName() + "$$Lambda.handle"; -// if (endpoint == NOT_FOUND) { -// handlerSpanName = "ResourceWebHandler.handle"; -// } -// span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL); -// if (endpoint == EXCEPTION) { -// span.hasStatus(StatusData.error()); -// span.hasEventsSatisfyingExactly( -// event -> -// event -// .hasName("exception") -// .hasAttributesSatisfyingExactly( -// equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), -// equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()), -// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); -// } else if (endpoint == NOT_FOUND) { -// span.hasStatus(StatusData.error()); -// if (Boolean.getBoolean("testLatestDeps")) { -// span.hasEventsSatisfyingExactly( -// event -> -// event -// .hasName("exception") -// .hasAttributesSatisfyingExactly( -// equalTo( -// EXCEPTION_TYPE, -// "org.springframework.web.reactive.resource.NoResourceFoundException"), -// equalTo( -// EXCEPTION_MESSAGE, "404 NOT_FOUND \"No static resource notFound.\""), -// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); -// } else { -// span.hasEventsSatisfyingExactly( -// event -> -// event -// .hasName("exception") -// .hasAttributesSatisfyingExactly( -// equalTo( -// EXCEPTION_TYPE, -// "org.springframework.web.server.ResponseStatusException"), -// equalTo(EXCEPTION_MESSAGE, "Response status 404"), -// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); -// } -// } -// return span; -// } -// -// @Override -// protected void configure(HttpServerTestOptions options) { -// super.configure(options); -// // TODO (trask) it seems like in this case ideally the controller span (which ends when the -// // Mono that the controller returns completes) should end before the server span (which needs -// // the result of the Mono) -// options.setVerifyServerSpanEndTime(false); -// -// options.setResponseCodeOnNonStandardHttpMethod(404); -// } -// -// @Override -// public String expectedHttpRoute(ServerEndpoint endpoint, String method) { -// if (HttpConstants._OTHER.equals(method)) { -// return getContextPath() + "/**"; -// } -// return super.expectedHttpRoute(endpoint, method); -// } -//} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java index a0cecb320bb4..248bb7f00038 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java @@ -6,53 +6,47 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; import io.opentelemetry.instrumentation.spring.webflux.server.AbstractImmediateHandlerSpringWebFluxServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; class ImmediateHandlerSpringWebFluxServerTest extends AbstractImmediateHandlerSpringWebFluxServerTest { - // @Override - // protected Class getApplicationClass() { - // return Application.class; - // } - // - // @Configuration - // @EnableAutoConfiguration - // static class Application { - // @Bean - // RouterFunction router() { - // return new RouteFactory().createRoutes(); - // } - // - // @Bean - // NettyReactiveWebServerFactory nettyFactory() { - // return new NettyReactiveWebServerFactory(); - // } - // } - // - // static class RouteFactory extends ServerTestRouteFactory { - // - // @Override - // protected Mono wrapResponse( - // ServerEndpoint endpoint, Mono response, Runnable spanAction) { - // return controller( - // endpoint, - // () -> { - // spanAction.run(); - // return response; - // }); - // } - // } - // - // @Test - // void nestedPath() { - // assumeTrue(Boolean.getBoolean("testLatestDeps")); - // - // String method = "GET"; - // AggregatedHttpRequest request = request(NESTED_PATH, method); - // AggregatedHttpResponse response = client.execute(request).aggregate().join(); - // assertThat(response.status().code()).isEqualTo(NESTED_PATH.getStatus()); - // assertThat(response.contentUtf8()).isEqualTo(NESTED_PATH.getBody()); - // assertResponseHasCustomizedHeaders(response, NESTED_PATH, null); - // - // assertTheTraces(1, null, null, null, method, NESTED_PATH); - // } + @Override + protected Class getApplicationClass() { + return Application.class; + } + + @Configuration + @EnableAutoConfiguration + static class Application { + @Bean + RouterFunction router() { + return new RouteFactory().createRoutes(); + } + + @Bean + NettyReactiveWebServerFactory nettyFactory() { + return new NettyReactiveWebServerFactory(); + } + } + + static class RouteFactory extends ServerTestRouteFactory { + + @Override + protected Mono wrapResponse( + ServerEndpoint endpoint, Mono response, Runnable spanAction) { + return controller( + endpoint, + () -> { + spanAction.run(); + return response; + }); + } + } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestController.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestController.java deleted file mode 100644 index 87de4d7d46e7..000000000000 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestController.java +++ /dev/null @@ -1,138 +0,0 @@ -///* -// * Copyright The OpenTelemetry Authors -// * SPDX-License-Identifier: Apache-2.0 -// */ -// -//package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; -// -//import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base.SpringWebFluxServerTest.NESTED_PATH; -// -//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -//import java.net.URI; -//import java.util.function.Supplier; -//import org.springframework.http.HttpStatus; -//import org.springframework.http.server.reactive.ServerHttpRequest; -//import org.springframework.http.server.reactive.ServerHttpResponse; -//import org.springframework.web.bind.annotation.GetMapping; -//import org.springframework.web.bind.annotation.PathVariable; -//import reactor.core.publisher.Mono; -// -//public abstract class ServerTestController { -// @GetMapping("/success") -// public Mono success(ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.SUCCESS; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return endpoint.getBody(); -// }); -// } -// -// @GetMapping("/query") -// public Mono query_param(ServerHttpRequest request, ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return request.getURI().getRawQuery(); -// }); -// } -// -// @GetMapping("/redirect") -// public Mono redirect(ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.REDIRECT; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// response.getHeaders().setLocation(URI.create(endpoint.getBody())); -// return ""; -// }); -// } -// -// @GetMapping("/error-status") -// Mono error(ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.ERROR; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return endpoint.getBody(); -// }); -// } -// -// @GetMapping("/exception") -// Mono exception() { -// ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// throw new IllegalStateException(endpoint.getBody()); -// }); -// } -// -// @GetMapping("/path/{id}/param") -// Mono path_param(ServerHttpResponse response, @PathVariable("id") String id) { -// ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return id; -// }); -// } -// -// @GetMapping("/child") -// Mono indexed_child(ServerHttpRequest request, ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// endpoint.collectSpanAttributes(it -> request.getQueryParams().getFirst(it)); -// setStatus(response, endpoint); -// return endpoint.getBody(); -// }); -// } -// -// @GetMapping("/captureHeaders") -// public Mono capture_headers(ServerHttpRequest request, ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// response -// .getHeaders() -// .set("X-Test-Response", request.getHeaders().getFirst("X-Test-Request")); -// return endpoint.getBody(); -// }); -// } -// -// @GetMapping("/nestedPath") -// public Mono nested_path(ServerHttpRequest request, ServerHttpResponse response) { -// ServerEndpoint endpoint = NESTED_PATH; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return endpoint.getBody(); -// }); -// } -// -// protected abstract Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler); -// -// private static void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { -// response.setStatusCode(HttpStatus.resolve(endpoint.getStatus())); -// } -//} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestRouteFactory.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestRouteFactory.java index 7d91a7aa48b4..dd176682c19b 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestRouteFactory.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/ServerTestRouteFactory.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; -import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base.SpringWebFluxServerTest.NESTED_PATH; +import static io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest.NESTED_PATH; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.path; import static org.springframework.web.reactive.function.server.RouterFunctions.nest; @@ -106,12 +106,7 @@ public RouterFunction createRoutes() { path("/nestedPath"), nest( path("/hello"), - route( - path("/world"), - request -> { - ServerEndpoint endpoint = NESTED_PATH; - return respond(endpoint, null, null, null); - }))); + route(path("/world"), request -> respond(NESTED_PATH, null, null, null)))); } protected Mono respond( diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/SpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/SpringWebFluxServerTest.java deleted file mode 100644 index 620737d41ce3..000000000000 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/base/SpringWebFluxServerTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.spring.webflux.v5_0.server.base; - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM; - -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; -import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; -import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import java.util.HashMap; -import java.util.Map; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.springframework.boot.SpringApplication; -import org.springframework.context.ConfigurableApplicationContext; - -public abstract class SpringWebFluxServerTest - extends AbstractHttpServerTest { - - protected static final ServerEndpoint NESTED_PATH = - new ServerEndpoint("NESTED_PATH", "nestedPath/hello/world", 200, "nested path"); - - protected abstract Class getApplicationClass(); - - @RegisterExtension - static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); - - @Override - public ConfigurableApplicationContext setupServer() { - SpringApplication app = new SpringApplication(getApplicationClass()); - Map properties = new HashMap<>(); - properties.put("server.port", port); - properties.put("server.context-path", getContextPath()); - properties.put("server.servlet.contextPath", getContextPath()); - properties.put("server.error.include-message", "always"); - app.setDefaultProperties(properties); - return app.run(); - } - - @Override - public void stopServer(ConfigurableApplicationContext configurableApplicationContext) { - configurableApplicationContext.close(); - } - - @Override - public String expectedHttpRoute(ServerEndpoint endpoint, String method) { - if (endpoint.equals(PATH_PARAM)) { - return getContextPath() + "/path/{id}/param"; - } else if (endpoint.equals(NOT_FOUND)) { - return "/**"; - } else if (endpoint.equals(NESTED_PATH)) { - return "/nestedPath/hello/world"; - } - return super.expectedHttpRoute(endpoint, method); - } - - @Override - protected void configure(HttpServerTestOptions options) { - options.setTestPathParam(true); - options.setHasHandlerSpan(unused -> true); - } -} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java index 6e89b8e1234e..a5f7014b164b 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java @@ -49,13 +49,15 @@ protected Mono wrapResponse( return response .delayElement(Duration.ofMillis(10)) .map( - original -> - controller( - endpoint, - () -> { - spanAction.run(); - return original; - })); + original -> { + System.out.println("In wrapResponse map for endpoint " + endpoint); + return controller( + endpoint, + () -> { + spanAction.run(); + return original; + }); + }); } } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java deleted file mode 100644 index c83dfb2f3245..000000000000 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/HandlerSpringWebFluxServerTest.java +++ /dev/null @@ -1,81 +0,0 @@ -///* -// * Copyright The OpenTelemetry Authors -// * SPDX-License-Identifier: Apache-2.0 -// */ -// -//package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; -// -//import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; -//import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; -//import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -//import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; -//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; -//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; -//import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; -// -//import io.opentelemetry.api.trace.SpanKind; -//import io.opentelemetry.instrumentation.api.internal.HttpConstants; -//import io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest; -//import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; -//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -//import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -//import io.opentelemetry.sdk.trace.data.StatusData; -// -//public abstract class HandlerSpringWebFluxServerTest extends AbstractSpringWebFluxServerTest { -// -// @Override -// protected SpanDataAssert assertHandlerSpan( -// SpanDataAssert span, String method, ServerEndpoint endpoint) { -// String handlerSpanName = ServerTestRouteFactory.class.getSimpleName() + "$$Lambda.handle"; -// if (endpoint == NOT_FOUND) { -// handlerSpanName = "ResourceWebHandler.handle"; -// } -// span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL); -// if (endpoint == EXCEPTION) { -// span.hasStatus(StatusData.error()); -// span.hasEventsSatisfyingExactly( -// event -> -// event -// .hasName("exception") -// .hasAttributesSatisfyingExactly( -// equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"), -// equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()), -// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); -// } else if (endpoint == NOT_FOUND) { -// span.hasStatus(StatusData.error()); -// // Spring 7 uses NoResourceFoundException -// span.hasEventsSatisfyingExactly( -// event -> -// event -// .hasName("exception") -// .hasAttributesSatisfyingExactly( -// equalTo( -// EXCEPTION_TYPE, -// "org.springframework.web.reactive.resource.NoResourceFoundException"), -// equalTo(EXCEPTION_MESSAGE, "404 NOT_FOUND \"No static resource notFound.\""), -// satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)))); -// } -// return span; -// } -// -// @Override -// protected void configure(HttpServerTestOptions options) { -// super.configure(options); -// // TODO (trask) it seems like in this case ideally the controller span (which ends when the -// // Mono that the controller returns completes) should end before the server span (which needs -// // the result of the Mono) -// options.setVerifyServerSpanEndTime(false); -// -// options.setHasHandlerSpan(t -> false); -// -// options.setResponseCodeOnNonStandardHttpMethod(404); -// } -// -// @Override -// public String expectedHttpRoute(ServerEndpoint endpoint, String method) { -// if (HttpConstants._OTHER.equals(method)) { -// return getContextPath() + "/**"; -// } -// return super.expectedHttpRoute(endpoint, method); -// } -//} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java index 998c31a93830..74cde956fd34 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ImmediateHandlerSpringWebFluxServerTest.java @@ -46,8 +46,12 @@ static class RouteFactory extends ServerTestRouteFactory { @Override protected Mono wrapResponse( ServerEndpoint endpoint, Mono response, Runnable spanAction) { - spanAction.run(); - return response; + return controller( + endpoint, + () -> { + spanAction.run(); + return response; + }); } } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java deleted file mode 100644 index 309bbafad326..000000000000 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestController.java +++ /dev/null @@ -1,144 +0,0 @@ -///* -// * Copyright The OpenTelemetry Authors -// * SPDX-License-Identifier: Apache-2.0 -// */ -// -//package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base; -// -//import static io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base.SpringWebFluxServerTest.NESTED_PATH; -// -//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -//import java.net.URI; -//import java.util.function.Supplier; -//import org.springframework.http.HttpStatus; -//import org.springframework.http.server.reactive.ServerHttpRequest; -//import org.springframework.http.server.reactive.ServerHttpResponse; -//import org.springframework.web.bind.annotation.GetMapping; -//import org.springframework.web.bind.annotation.PathVariable; -//import reactor.core.publisher.Mono; -// -//public abstract class ServerTestController { -// @GetMapping("/success") -// public Mono success(ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.SUCCESS; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return endpoint.getBody(); -// }); -// } -// -// @GetMapping("/query") -// public Mono query_param(ServerHttpRequest request, ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return request.getURI().getRawQuery(); -// }); -// } -// -// @GetMapping("/redirect") -// public Mono redirect(ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.REDIRECT; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// response.getHeaders().setLocation(URI.create(endpoint.getBody())); -// return ""; -// }); -// } -// -// @GetMapping("/error-status") -// Mono error(ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.ERROR; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return endpoint.getBody(); -// }); -// } -// -// @GetMapping("/exception") -// Mono exception() { -// ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// throw new IllegalStateException(endpoint.getBody()); -// }); -// } -// -// @GetMapping("/path/{id}/param") -// Mono path_param(ServerHttpResponse response, @PathVariable("id") String id) { -// ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return id; -// }); -// } -// -// @GetMapping("/child") -// Mono indexed_child(ServerHttpRequest request, ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// endpoint.collectSpanAttributes(it -> request.getQueryParams().getFirst(it)); -// setStatus(response, endpoint); -// return endpoint.getBody(); -// }); -// } -// -// @GetMapping("/captureHeaders") -// public Mono capture_headers(ServerHttpRequest request, ServerHttpResponse response) { -// ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// response -// .getHeaders() -// .set("X-Test-Response", request.getHeaders().getFirst("X-Test-Request")); -// return endpoint.getBody(); -// }); -// } -// -// @GetMapping("/nestedPath") -// public Mono nested_path(ServerHttpRequest request, ServerHttpResponse response) { -// ServerEndpoint endpoint = NESTED_PATH; -// -// return wrapControllerMethod( -// endpoint, -// () -> { -// setStatus(response, endpoint); -// return endpoint.getBody(); -// }); -// } -// -// protected abstract Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler); -// -// private static void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { -// response.setStatusCode(HttpStatus.resolve(endpoint.getStatus())); -// } -// -// protected T controller(ServerEndpoint endpoint, Supplier handler) { -// return endpoint.getBody() == null && endpoint != ServerEndpoint.EXCEPTION -// ? null -// : handler.get(); -// } -//} diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java index 47f8d7f21da5..a83eea47bf36 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/ServerTestRouteFactory.java @@ -13,7 +13,6 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -import java.util.function.Supplier; import org.springframework.http.HttpHeaders; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; @@ -127,10 +126,4 @@ protected Mono respond( protected abstract Mono wrapResponse( ServerEndpoint endpoint, Mono response, Runnable spanAction); - - protected T controller(ServerEndpoint endpoint, Supplier handler) { - return endpoint.getBody() == null && endpoint != ServerEndpoint.EXCEPTION - ? null - : handler.get(); - } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java index 688601cc9666..a384a165f7d1 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java @@ -27,7 +27,7 @@ public abstract class AbstractHandlerSpringWebFluxServerTest @Override protected SpanDataAssert assertHandlerSpan( SpanDataAssert span, String method, ServerEndpoint endpoint) { - String handlerSpanName = "Test" + "$$Lambda.handle"; + String handlerSpanName = "ServerTestRouteFactory" + "$$Lambda.handle"; if (endpoint == NOT_FOUND) { handlerSpanName = "ResourceWebHandler.handle"; } @@ -81,7 +81,7 @@ protected SpanDataAssert assertHandlerSpan( val -> val.satisfiesAnyOf( v -> assertThat(v).isNull(), - v -> assertThat(v).contains("404 NOT_FOUND"))))); + v -> assertThat(v).contains("404"))))); } } return span; diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java deleted file mode 100644 index c5f7db5fba03..000000000000 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestRouteFactory.java +++ /dev/null @@ -1,135 +0,0 @@ -///* -// * Copyright The OpenTelemetry Authors -// * SPDX-License-Identifier: Apache-2.0 -// */ -// -//package io.opentelemetry.instrumentation.spring.webflux.server; -// -//import static io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest.NESTED_PATH; -//import static org.springframework.web.reactive.function.server.RequestPredicates.GET; -//import static org.springframework.web.reactive.function.server.RequestPredicates.path; -//import static org.springframework.web.reactive.function.server.RouterFunctions.nest; -//import static org.springframework.web.reactive.function.server.RouterFunctions.route; -// -//import io.opentelemetry.api.trace.Span; -//import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; -//import org.springframework.http.HttpHeaders; -//import org.springframework.http.HttpStatus; -//import org.springframework.web.reactive.function.server.RouterFunction; -//import org.springframework.web.reactive.function.server.ServerResponse; -//import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder; -//import reactor.core.publisher.Mono; -// -//public abstract class ServerTestRouteFactory { -// public RouterFunction createRoutes() { -// return route( -// GET("/success"), -// request -> { -// ServerEndpoint endpoint = ServerEndpoint.SUCCESS; -// -// return respond(endpoint, null, null, null); -// }) -// .andRoute( -// GET("/query"), -// request -> { -// ServerEndpoint endpoint = ServerEndpoint.QUERY_PARAM; -// -// return respond(endpoint, null, request.uri().getRawQuery(), null); -// }) -// .andRoute( -// GET("/redirect"), -// request -> { -// ServerEndpoint endpoint = ServerEndpoint.REDIRECT; -// -// return respond( -// endpoint, -// ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())) -// .header(HttpHeaders.LOCATION, endpoint.getBody()), -// "", -// null); -// }) -// .andRoute( -// GET("/error-status"), -// redirect -> { -// ServerEndpoint endpoint = ServerEndpoint.ERROR; -// -// return respond(endpoint, null, null, null); -// }) -// .andRoute( -// GET("/exception"), -// request -> { -// ServerEndpoint endpoint = ServerEndpoint.EXCEPTION; -// -// return respond( -// endpoint, -// ServerResponse.ok(), -// "", -// () -> { -// throw new IllegalStateException(endpoint.getBody()); -// }); -// }) -// .andRoute( -// GET("/path/{id}/param"), -// request -> { -// ServerEndpoint endpoint = ServerEndpoint.PATH_PARAM; -// -// return respond(endpoint, null, request.pathVariable("id"), null); -// }) -// .andRoute( -// GET("/child"), -// request -> { -// ServerEndpoint endpoint = ServerEndpoint.INDEXED_CHILD; -// -// return respond( -// endpoint, -// null, -// null, -// () -> -// Span.current() -// .setAttribute( -// "test.request.id", Long.parseLong(request.queryParam("id").get()))); -// }) -// .andRoute( -// GET("/captureHeaders"), -// request -> { -// ServerEndpoint endpoint = ServerEndpoint.CAPTURE_HEADERS; -// -// return respond( -// endpoint, -// ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())) -// .header( -// "X-Test-Response", -// request.headers().asHttpHeaders().getFirst("X-Test-Request")), -// null, -// null); -// }) -// .andNest( -// path("/nestedPath"), -// nest( -// path("/hello"), -// route( -// path("/world"), -// request -> { -// ServerEndpoint endpoint = NESTED_PATH; -// return respond(endpoint, null, null, null); -// }))); -// } -// -// protected Mono respond( -// ServerEndpoint endpoint, BodyBuilder bodyBuilder, String body, Runnable spanAction) { -// if (bodyBuilder == null) { -// bodyBuilder = ServerResponse.status(HttpStatus.resolve(endpoint.getStatus())); -// } -// if (body == null) { -// body = endpoint.getBody() != null ? endpoint.getBody() : ""; -// } -// if (spanAction == null) { -// spanAction = () -> {}; -// } -// -// return wrapResponse(endpoint, bodyBuilder.syncBody(body), spanAction); -// } -// -// protected abstract Mono wrapResponse( -// ServerEndpoint endpoint, Mono response, Runnable spanAction); -//} From 559d89f313046c5dab0f44f42192f08ae6276578 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 8 Dec 2025 13:33:00 -0500 Subject: [PATCH 6/9] more cleanup --- .../server/HandlerAdapterInstrumentation.java | 36 +++---------------- ...DelayedHandlerSpringWebFluxServerTest.java | 16 ++++----- ...mediateHandlerSpringWebFluxServerTest.java | 32 ----------------- .../testing/TestInstrumenters.java | 19 +--------- .../junit/http/AbstractHttpServerTest.java | 9 ----- 5 files changed, 12 insertions(+), 100 deletions(-) diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/HandlerAdapterInstrumentation.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/HandlerAdapterInstrumentation.java index dbac8d9c4e61..2f3391bdbef6 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/HandlerAdapterInstrumentation.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/HandlerAdapterInstrumentation.java @@ -47,10 +47,6 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer transformer) { - // Get the class name being transformed - this requires accessing transformer internals - // For now, just log that we're transforming - System.out.println("=== HandlerAdapterInstrumentation.transform() called ==="); - System.out.println(" Applying advice to 'handle' method"); transformer.applyAdviceToMethod( isMethod() .and(isPublic()) @@ -59,7 +55,6 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(1, Object.class)) .and(takesArguments(2)), this.getClass().getName() + "$HandleAdvice"); - System.out.println("=== HandlerAdapterInstrumentation advice applied ==="); } @SuppressWarnings("unused") @@ -76,13 +71,7 @@ private AdviceScope(Context context, Scope scope) { @Nullable public static AdviceScope enter(ServerWebExchange exchange, Object handler) { - System.out.println("=== HandlerAdapter.enter() called ==="); - System.out.println( - " Handler: " + (handler != null ? handler.getClass().getName() : "null")); - System.out.println(" Thread: " + Thread.currentThread().getName()); - Context parentContext = Context.current(); - System.out.println(" Parent context: " + parentContext); // HttpRouteSource.CONTROLLER has useFirst true, and it will update http.route only once // using the last portion of the nested path. @@ -93,20 +82,13 @@ public static AdviceScope enter(ServerWebExchange exchange, Object handler) { parentContext, HttpServerRouteSource.NESTED_CONTROLLER, httpRouteGetter(), exchange); if (handler == null) { - System.out.println(" Handler is null, returning null"); return null; } - boolean shouldStart = instrumenter().shouldStart(parentContext, handler); - System.out.println(" shouldStart: " + shouldStart); - - if (!shouldStart) { - System.out.println(" instrumenter().shouldStart returned false, returning null"); + if (!instrumenter().shouldStart(parentContext, handler)) { return null; } - Context context = instrumenter().start(parentContext, handler); - System.out.println(" Started new context: " + context); return new AdviceScope(context, context.makeCurrent()); } @@ -132,13 +114,10 @@ public Mono exit( } @Nullable - @Advice.OnMethodEnter + @Advice.OnMethodEnter(suppress = Throwable.class) public static AdviceScope methodEnter( @Advice.Argument(0) ServerWebExchange exchange, @Advice.Argument(1) Object handler) { - System.out.println("=== HandlerAdapter.methodEnter() CALLED ==="); - AdviceScope scope = AdviceScope.enter(exchange, handler); - System.out.println(" Returning AdviceScope: " + scope); - return scope; + return AdviceScope.enter(exchange, handler); } @AssignReturned.ToReturned @@ -150,18 +129,11 @@ public static Mono methodExit( @Advice.Thrown Throwable throwable, @Advice.Enter @Nullable AdviceScope adviceScope) { - System.out.println("=== HandlerAdapter.methodExit() CALLED ==="); - System.out.println(" AdviceScope: " + adviceScope); - System.out.println(" Throwable: " + throwable); - if (adviceScope == null) { - System.out.println(" AdviceScope is null, returning original mono"); return mono; } - Mono result = adviceScope.exit(throwable, exchange, handler, mono); - System.out.println(" Returning wrapped mono"); - return result; + return adviceScope.exit(throwable, exchange, handler, mono); } } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java index a5f7014b164b..6e89b8e1234e 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v7_0/server/base/DelayedHandlerSpringWebFluxServerTest.java @@ -49,15 +49,13 @@ protected Mono wrapResponse( return response .delayElement(Duration.ofMillis(10)) .map( - original -> { - System.out.println("In wrapResponse map for endpoint " + endpoint); - return controller( - endpoint, - () -> { - spanAction.run(); - return original; - }); - }); + original -> + controller( + endpoint, + () -> { + spanAction.run(); + return original; + })); } } } diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java index 91e55945a82f..d7a001c28d8e 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractImmediateHandlerSpringWebFluxServerTest.java @@ -21,38 +21,6 @@ */ public abstract class AbstractImmediateHandlerSpringWebFluxServerTest extends AbstractHandlerSpringWebFluxServerTest { - // @Override - // protected Class getApplicationClass() { - // return Application.class; - // } - - // @Configuration - // @EnableAutoConfiguration - // static class Application { - // @Bean - // RouterFunction router() { - // return new RouteFactory().createRoutes(); - // } - // - // @Bean - // NettyReactiveWebServerFactory nettyFactory() { - // return new NettyReactiveWebServerFactory(); - // } - // } - - // static class RouteFactory extends ServerTestRouteFactory { - // - // @Override - // protected Mono wrapResponse( - // ServerEndpoint endpoint, Mono response, Runnable spanAction) { - // return controller( - // endpoint, - // () -> { - // spanAction.run(); - // return response; - // }); - // } - // } @Test void nestedPath() { diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/TestInstrumenters.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/TestInstrumenters.java index 1171d098323d..c070fca33af4 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/TestInstrumenters.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/TestInstrumenters.java @@ -97,23 +97,7 @@ T runWithNonRecordingSpan(ThrowingSupplier callba private static T runWithInstrumenter( String spanName, Instrumenter instrumenter, ThrowingSupplier callback) throws E { - System.out.println("=== TestInstrumenters.runWithInstrumenter() CALLED ==="); - System.out.println(" Span name: " + spanName); - System.out.println(" Thread: " + Thread.currentThread().getName()); - - Context parentContext = Context.current(); - System.out.println(" Parent context: " + parentContext); - - boolean shouldStart = instrumenter.shouldStart(parentContext, spanName); - System.out.println(" shouldStart: " + shouldStart); - - if (!shouldStart) { - System.out.println(" Instrumenter refused to start span, running callback without span"); - return callback.get(); - } - - Context context = instrumenter.start(parentContext, spanName); - System.out.println(" Started context: " + context); + Context context = instrumenter.start(Context.current(), spanName); T result; try (Scope ignored = context.makeCurrent()) { @@ -123,7 +107,6 @@ private static T runWithInstrumenter( throw t; } instrumenter.end(context, spanName, null, null); - System.out.println(" Span ended successfully"); return result; } diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java index 405c089b7683..c6bb2218067c 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java @@ -121,19 +121,10 @@ protected void configure(HttpServerTestOptions options) {} public static T controller( ServerEndpoint endpoint, ThrowingSupplier closure) throws E { - System.out.println("=== controller() CALLED ==="); - System.out.println(" Endpoint: " + endpoint); - System.out.println(" Thread: " + Thread.currentThread().getName()); - System.out.println(" Current span: " + Span.current()); - System.out.println( - " Current span context valid: " + Span.current().getSpanContext().isValid()); - assert Span.current().getSpanContext().isValid() : "Controller should have a parent span."; if (endpoint == NOT_FOUND) { - System.out.println(" Endpoint is NOT_FOUND, skipping controller span"); return closure.get(); } - System.out.println(" Calling GlobalTraceUtil.runWithSpan('controller', ...)"); return GlobalTraceUtil.runWithSpan("controller", closure); } From 9c98489e9d941d782745e891888d1740c36e7154 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 8 Dec 2025 15:03:46 -0500 Subject: [PATCH 7/9] handle issue with 5.0 testlatestdeps --- .../webflux/server/ServerTestController.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestController.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestController.java index db78715c51ab..320cde7873dd 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestController.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/ServerTestController.java @@ -8,6 +8,7 @@ import static io.opentelemetry.instrumentation.spring.webflux.server.AbstractSpringWebFluxServerTest.NESTED_PATH; import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.lang.reflect.Method; import java.net.URI; import java.util.function.Supplier; import org.springframework.http.HttpStatus; @@ -19,6 +20,27 @@ @SuppressWarnings("IdentifierName") // method names are snake_case to match endpoints public abstract class ServerTestController { + + // Spring 5.x uses setStatusCode(HttpStatus), Spring 6+ uses setStatusCode(HttpStatusCode) + private static final Method setStatusCodeMethod; + + static { + Method method; + try { + // Try Spring 6+ signature first (HttpStatusCode interface) + Class httpStatusCodeClass = Class.forName("org.springframework.http.HttpStatusCode"); + method = ServerHttpResponse.class.getMethod("setStatusCode", httpStatusCodeClass); + } catch (ClassNotFoundException | NoSuchMethodException e) { + // Fall back to Spring 5.x signature (HttpStatus enum) + try { + method = ServerHttpResponse.class.getMethod("setStatusCode", HttpStatus.class); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex); + } + } + setStatusCodeMethod = method; + } + @GetMapping("/success") public Mono success(ServerHttpResponse response) { ServerEndpoint endpoint = ServerEndpoint.SUCCESS; @@ -134,6 +156,11 @@ public Mono nested_path(ServerHttpRequest request, ServerHttpResponse re protected abstract Mono wrapControllerMethod(ServerEndpoint endpoint, Supplier handler); protected void setStatus(ServerHttpResponse response, ServerEndpoint endpoint) { - response.setStatusCode(HttpStatus.resolve(endpoint.getStatus())); + HttpStatus status = HttpStatus.resolve(endpoint.getStatus()); + try { + setStatusCodeMethod.invoke(response, status); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to set status code", e); + } } } From b84954d286909ca66201f73d898b272067cbb7b0 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 8 Dec 2025 16:22:09 -0500 Subject: [PATCH 8/9] small cleanup --- .../server/AbstractHandlerSpringWebFluxServerTest.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java index a384a165f7d1..355e0ce7bb10 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing/src/main/java/io/opentelemetry/instrumentation/spring/webflux/server/AbstractHandlerSpringWebFluxServerTest.java @@ -27,7 +27,7 @@ public abstract class AbstractHandlerSpringWebFluxServerTest @Override protected SpanDataAssert assertHandlerSpan( SpanDataAssert span, String method, ServerEndpoint endpoint) { - String handlerSpanName = "ServerTestRouteFactory" + "$$Lambda.handle"; + String handlerSpanName = "ServerTestRouteFactory$$Lambda.handle"; if (endpoint == NOT_FOUND) { handlerSpanName = "ResourceWebHandler.handle"; } @@ -76,12 +76,7 @@ protected SpanDataAssert assertHandlerSpan( .isEqualTo( "org.springframework.web.reactive.resource.NoResourceFoundException"))), satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class)), - satisfies( - EXCEPTION_MESSAGE, - val -> - val.satisfiesAnyOf( - v -> assertThat(v).isNull(), - v -> assertThat(v).contains("404"))))); + satisfies(EXCEPTION_MESSAGE, val -> val.contains("404")))); } } return span; From 6a78aca2f0c491f80962d51a4cfd5ab9b16fc8fd Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 9 Dec 2025 08:30:18 -0500 Subject: [PATCH 9/9] remove unneeded plugin and jvmArgs --- .../spring-webflux-5.0/testing-webflux7/build.gradle.kts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts index b7cd8289e23b..f0b4dc1ba9da 100644 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts +++ b/instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts @@ -1,5 +1,4 @@ plugins { - id("otel.java-conventions") id("otel.javaagent-testing") } @@ -24,8 +23,5 @@ otelJava { } tasks.withType().configureEach { - // required on jdk17 - jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") - jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") }