diff --git a/instrumentation/undertow-1.4/javaagent/build.gradle.kts b/instrumentation/undertow-1.4/javaagent/build.gradle.kts index c220eb1803..b3c268a25a 100644 --- a/instrumentation/undertow-1.4/javaagent/build.gradle.kts +++ b/instrumentation/undertow-1.4/javaagent/build.gradle.kts @@ -24,3 +24,11 @@ dependencies { tasks.withType().configureEach { jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") } + +// since 2.3.x, undertow is compiled by JDK 11 +val latestDepTest = findProperty("testLatestDeps") as Boolean +if (latestDepTest) { + otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_11) + } +} diff --git a/instrumentation/undertow-1.4/javaagent/src/test/groovy/UndertowHttp2ServerTest.groovy b/instrumentation/undertow-1.4/javaagent/src/test/groovy/UndertowHttp2ServerTest.groovy deleted file mode 100644 index 536e813940..0000000000 --- a/instrumentation/undertow-1.4/javaagent/src/test/groovy/UndertowHttp2ServerTest.groovy +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.undertow.Undertow -import io.undertow.UndertowOptions - -class UndertowHttp2ServerTest extends UndertowServerTest { - - void configureUndertow(Undertow.Builder builder) { - builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true) - } - - boolean useHttp2() { - true - } -} diff --git a/instrumentation/undertow-1.4/javaagent/src/test/groovy/UndertowServerDispatchTest.groovy b/instrumentation/undertow-1.4/javaagent/src/test/groovy/UndertowServerDispatchTest.groovy deleted file mode 100644 index 13a125c2d8..0000000000 --- a/instrumentation/undertow-1.4/javaagent/src/test/groovy/UndertowServerDispatchTest.groovy +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.common.AttributeKey -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpServerTest -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import io.opentelemetry.semconv.HttpAttributes -import io.undertow.Handlers -import io.undertow.Undertow -import io.undertow.util.Headers -import io.undertow.util.HttpString -import io.undertow.util.StatusCodes - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS - -class UndertowServerDispatchTest extends HttpServerTest implements AgentTestTrait { - - @Override - boolean verifyServerSpanEndTime() { - return false - } - - @Override - boolean testException() { - // throwing exception from dispatched task just makes the request time out - return false - } - - @Override - boolean hasResponseCustomizer(ServerEndpoint endpoint) { - true - } - - @Override - Undertow startServer(int port) { - Undertow server = Undertow.builder() - .addHttpListener(port, "localhost") - .setHandler(Handlers.path() - .addExactPath(SUCCESS.rawPath()) { exchange -> - exchange.dispatch { - controller(SUCCESS) { - exchange.getResponseSender().send(SUCCESS.body) - } - } - } - .addExactPath(QUERY_PARAM.rawPath()) { exchange -> - exchange.dispatch { - controller(QUERY_PARAM) { - exchange.getResponseSender().send(exchange.getQueryString()) - } - } - } - .addExactPath(REDIRECT.rawPath()) { exchange -> - exchange.dispatch { - controller(REDIRECT) { - exchange.setStatusCode(StatusCodes.FOUND) - exchange.getResponseHeaders().put(Headers.LOCATION, REDIRECT.body) - exchange.endExchange() - } - } - } - .addExactPath(CAPTURE_HEADERS.rawPath()) { exchange -> - exchange.dispatch { - controller(CAPTURE_HEADERS) { - exchange.setStatusCode(StatusCodes.OK) - exchange.getResponseHeaders().put(new HttpString("X-Test-Response"), exchange.getRequestHeaders().getFirst("X-Test-Request")) - exchange.getResponseSender().send(CAPTURE_HEADERS.body) - } - } - } - .addExactPath(ERROR.rawPath()) { exchange -> - exchange.dispatch { - controller(ERROR) { - exchange.setStatusCode(ERROR.status) - exchange.getResponseSender().send(ERROR.body) - } - } - } - .addExactPath(INDEXED_CHILD.rawPath()) { exchange -> - exchange.dispatch { - controller(INDEXED_CHILD) { - INDEXED_CHILD.collectSpanAttributes { name -> exchange.getQueryParameters().get(name).peekFirst() } - exchange.getResponseSender().send(INDEXED_CHILD.body) - } - } - } - ).build() - server.start() - return server - } - - @Override - void stopServer(Undertow undertow) { - undertow.stop() - } - - @Override - Set> httpAttributes(ServerEndpoint endpoint) { - def attributes = super.httpAttributes(endpoint) - attributes.remove(HttpAttributes.HTTP_ROUTE) - attributes - } -} diff --git a/instrumentation/undertow-1.4/javaagent/src/test/groovy/UndertowServerTest.groovy b/instrumentation/undertow-1.4/javaagent/src/test/groovy/UndertowServerTest.groovy deleted file mode 100644 index 7d2a43bbb4..0000000000 --- a/instrumentation/undertow-1.4/javaagent/src/test/groovy/UndertowServerTest.groovy +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.common.AttributeKey -import io.opentelemetry.api.trace.Span -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.api.trace.StatusCode -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpServerTest -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import io.opentelemetry.semconv.ServerAttributes -import io.opentelemetry.semconv.ClientAttributes -import io.opentelemetry.semconv.UserAgentAttributes -import io.opentelemetry.semconv.HttpAttributes -import io.opentelemetry.semconv.NetworkAttributes -import io.opentelemetry.semconv.UrlAttributes -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse -import io.undertow.Handlers -import io.undertow.Undertow -import io.undertow.util.Headers -import io.undertow.util.HttpString -import io.undertow.util.StatusCodes - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS - -//TODO make test which mixes handlers and servlets -class UndertowServerTest extends HttpServerTest implements AgentTestTrait { - - @Override - Undertow startServer(int port) { - Undertow.Builder builder = Undertow.builder() - .addHttpListener(port, "localhost") - .setHandler(Handlers.path() - .addExactPath(SUCCESS.rawPath()) { exchange -> - controller(SUCCESS) { - exchange.getResponseSender().send(SUCCESS.body) - } - } - .addExactPath(QUERY_PARAM.rawPath()) { exchange -> - controller(QUERY_PARAM) { - exchange.getResponseSender().send(exchange.getQueryString()) - } - } - .addExactPath(REDIRECT.rawPath()) { exchange -> - controller(REDIRECT) { - exchange.setStatusCode(StatusCodes.FOUND) - exchange.getResponseHeaders().put(Headers.LOCATION, REDIRECT.body) - exchange.endExchange() - } - } - .addExactPath(CAPTURE_HEADERS.rawPath()) { exchange -> - controller(CAPTURE_HEADERS) { - exchange.setStatusCode(StatusCodes.OK) - exchange.getResponseHeaders().put(new HttpString("X-Test-Response"), exchange.getRequestHeaders().getFirst("X-Test-Request")) - exchange.getResponseSender().send(CAPTURE_HEADERS.body) - } - } - .addExactPath(ERROR.rawPath()) { exchange -> - controller(ERROR) { - exchange.setStatusCode(ERROR.status) - exchange.getResponseSender().send(ERROR.body) - } - } - .addExactPath(EXCEPTION.rawPath()) { exchange -> - controller(EXCEPTION) { - throw new Exception(EXCEPTION.body) - } - } - .addExactPath(INDEXED_CHILD.rawPath()) { exchange -> - controller(INDEXED_CHILD) { - INDEXED_CHILD.collectSpanAttributes { name -> exchange.getQueryParameters().get(name).peekFirst() } - exchange.getResponseSender().send(INDEXED_CHILD.body) - } - } - .addExactPath("sendResponse") { exchange -> - Span.current().addEvent("before-event") - runWithSpan("sendResponse") { - exchange.setStatusCode(StatusCodes.OK) - exchange.getResponseSender().send("sendResponse") - } - // event is added only when server span has not been ended - // we need to make sure that sending response does not end server span - Span.current().addEvent("after-event") - } - .addExactPath("sendResponseWithException") { exchange -> - Span.current().addEvent("before-event") - runWithSpan("sendResponseWithException") { - exchange.setStatusCode(StatusCodes.OK) - exchange.getResponseSender().send("sendResponseWithException") - } - // event is added only when server span has not been ended - // we need to make sure that sending response does not end server span - Span.current().addEvent("after-event") - throw new Exception("exception after sending response") - } - ) - configureUndertow(builder) - Undertow server = builder.build() - server.start() - return server - } - - @Override - void stopServer(Undertow undertow) { - undertow.stop() - } - - void configureUndertow(Undertow.Builder builder) { - } - - @Override - Set> httpAttributes(ServerEndpoint endpoint) { - def attributes = super.httpAttributes(endpoint) - attributes.remove(HttpAttributes.HTTP_ROUTE) - attributes - } - - @Override - boolean hasResponseCustomizer(ServerEndpoint endpoint) { - true - } - - def "test send response"() { - setup: - def uri = address.resolve("sendResponse") - AggregatedHttpResponse response = client.get(uri.toString()).aggregate().join() - - expect: - response.status().code() == 200 - response.contentUtf8().trim() == "sendResponse" - - and: - assertTraces(1) { - trace(0, 2) { - it.span(0) { - hasNoParent() - name "GET" - kind SpanKind.SERVER - - event(0) { - eventName "before-event" - } - event(1) { - eventName "after-event" - } - - def protocolVersion = useHttp2() ? "2" : "1.1" - attributes { - "$ClientAttributes.CLIENT_ADDRESS" TEST_CLIENT_IP - "$UrlAttributes.URL_SCHEME" uri.getScheme() - "$UrlAttributes.URL_PATH" uri.getPath() - "$HttpAttributes.HTTP_REQUEST_METHOD" "GET" - "$HttpAttributes.HTTP_RESPONSE_STATUS_CODE" 200 - "$UserAgentAttributes.USER_AGENT_ORIGINAL" TEST_USER_AGENT - "$NetworkAttributes.NETWORK_PROTOCOL_VERSION" protocolVersion - "$ServerAttributes.SERVER_ADDRESS" uri.host - "$ServerAttributes.SERVER_PORT" uri.port - "$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1" - "$NetworkAttributes.NETWORK_PEER_PORT" Long - } - } - span(1) { - name "sendResponse" - kind SpanKind.INTERNAL - childOf span(0) - } - } - } - } - - def "test send response with exception"() { - setup: - def uri = address.resolve("sendResponseWithException") - AggregatedHttpResponse response = client.get(uri.toString()).aggregate().join() - - expect: - response.status().code() == 200 - response.contentUtf8().trim() == "sendResponseWithException" - - and: - assertTraces(1) { - trace(0, 2) { - it.span(0) { - hasNoParent() - name "GET" - kind SpanKind.SERVER - status StatusCode.ERROR - - event(0) { - eventName "before-event" - } - event(1) { - eventName "after-event" - } - errorEvent(Exception, "exception after sending response", 2) - - def protocolVersion = useHttp2() ? "2" : "1.1" - attributes { - "$ClientAttributes.CLIENT_ADDRESS" TEST_CLIENT_IP - "$UrlAttributes.URL_SCHEME" uri.getScheme() - "$UrlAttributes.URL_PATH" uri.getPath() - "$HttpAttributes.HTTP_REQUEST_METHOD" "GET" - "$HttpAttributes.HTTP_RESPONSE_STATUS_CODE" 200 - "$UserAgentAttributes.USER_AGENT_ORIGINAL" TEST_USER_AGENT - "$NetworkAttributes.NETWORK_PROTOCOL_VERSION" protocolVersion - "$ServerAttributes.SERVER_ADDRESS" uri.host - "$ServerAttributes.SERVER_PORT" uri.port - "$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1" - "$NetworkAttributes.NETWORK_PEER_PORT" Long - } - } - span(1) { - name "sendResponseWithException" - kind SpanKind.INTERNAL - childOf span(0) - } - } - } - } -} diff --git a/instrumentation/undertow-1.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttp2ServerTest.java b/instrumentation/undertow-1.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttp2ServerTest.java new file mode 100644 index 0000000000..c84f08596b --- /dev/null +++ b/instrumentation/undertow-1.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttp2ServerTest.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.undertow; + +import io.undertow.Undertow; +import io.undertow.UndertowOptions; + +class UndertowHttp2ServerTest extends UndertowServerTest { + + @Override + public void configureUndertow(Undertow.Builder builder) { + builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true); + } + + @Override + public boolean useHttp2() { + return true; + } +} diff --git a/instrumentation/undertow-1.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowServerDispatchTest.java b/instrumentation/undertow-1.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowServerDispatchTest.java new file mode 100644 index 0000000000..3bc1df859e --- /dev/null +++ b/instrumentation/undertow-1.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowServerDispatchTest.java @@ -0,0 +1,135 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.undertow; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; + +import com.google.common.collect.ImmutableSet; +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.semconv.NetworkAttributes; +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.StatusCodes; +import org.junit.jupiter.api.extension.RegisterExtension; + +class UndertowServerDispatchTest extends AbstractHttpServerTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); + + @Override + public Undertow setupServer() { + Undertow server = + Undertow.builder() + .addHttpListener(port, "localhost") + .setHandler( + Handlers.path() + .addExactPath( + SUCCESS.rawPath(), + exchange -> + exchange.dispatch( + k -> + controller( + SUCCESS, + () -> k.getResponseSender().send(SUCCESS.getBody())))) + .addExactPath( + QUERY_PARAM.rawPath(), + exchange -> + exchange.dispatch( + k -> + controller( + QUERY_PARAM, + () -> k.getResponseSender().send(k.getQueryString())))) + .addExactPath( + REDIRECT.rawPath(), + exchange -> + exchange.dispatch( + k -> + controller( + REDIRECT, + () -> { + k.setStatusCode(StatusCodes.FOUND); + k.getResponseHeaders() + .put(Headers.LOCATION, REDIRECT.getBody()); + k.endExchange(); + }))) + .addExactPath( + CAPTURE_HEADERS.rawPath(), + exchange -> + exchange.dispatch( + k -> + controller( + CAPTURE_HEADERS, + () -> { + k.setStatusCode(StatusCodes.OK); + k.getResponseHeaders() + .put( + new HttpString("X-Test-Response"), + exchange + .getRequestHeaders() + .getFirst("X-Test-Request")); + k.getResponseSender().send(CAPTURE_HEADERS.getBody()); + }))) + .addExactPath( + ERROR.rawPath(), + exchange -> + exchange.dispatch( + k -> + controller( + ERROR, + () -> { + exchange.setStatusCode(ERROR.getStatus()); + exchange.getResponseSender().send(ERROR.getBody()); + }))) + .addExactPath( + INDEXED_CHILD.rawPath(), + exchange -> + exchange.dispatch( + k -> + controller( + INDEXED_CHILD, + () -> { + INDEXED_CHILD.collectSpanAttributes( + name -> + exchange + .getQueryParameters() + .get(name) + .peekFirst()); + exchange + .getResponseSender() + .send(INDEXED_CHILD.getBody()); + })))) + .build(); + server.start(); + return server; + } + + @Override + public void stopServer(Undertow undertow) { + undertow.stop(); + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setVerifyServerSpanEndTime(false); + // throwing exception from dispatched task just makes the request time out + options.setTestException(false); + options.setHasResponseCustomizer(endpoint -> true); + + options.setHttpAttributes(endpoint -> ImmutableSet.of(NetworkAttributes.NETWORK_PEER_PORT)); + } +} diff --git a/instrumentation/undertow-1.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowServerTest.java b/instrumentation/undertow-1.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowServerTest.java new file mode 100644 index 0000000000..bbadcf3b5c --- /dev/null +++ b/instrumentation/undertow-1.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowServerTest.java @@ -0,0 +1,271 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.undertow; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.collect.ImmutableSet; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +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.util.ThrowingRunnable; +import io.opentelemetry.semconv.ClientAttributes; +import io.opentelemetry.semconv.ExceptionAttributes; +import io.opentelemetry.semconv.HttpAttributes; +import io.opentelemetry.semconv.NetworkAttributes; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.UrlAttributes; +import io.opentelemetry.semconv.UserAgentAttributes; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.StatusCodes; +import java.net.URI; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class UndertowServerTest extends AbstractHttpServerTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); + + @Override + public Undertow setupServer() { + Undertow.Builder builder = + Undertow.builder() + .addHttpListener(port, "localhost") + .setHandler( + Handlers.path() + .addExactPath( + SUCCESS.rawPath(), + exchange -> + controller( + SUCCESS, + () -> exchange.getResponseSender().send(SUCCESS.getBody()))) + .addExactPath( + QUERY_PARAM.rawPath(), + exchange -> + controller( + QUERY_PARAM, + () -> exchange.getResponseSender().send(exchange.getQueryString()))) + .addExactPath( + REDIRECT.rawPath(), + exchange -> + controller( + REDIRECT, + () -> { + exchange.setStatusCode(StatusCodes.FOUND); + exchange + .getResponseHeaders() + .put(Headers.LOCATION, REDIRECT.getBody()); + exchange.endExchange(); + })) + .addExactPath( + CAPTURE_HEADERS.rawPath(), + exchange -> + controller( + CAPTURE_HEADERS, + () -> { + exchange.setStatusCode(StatusCodes.OK); + exchange + .getResponseHeaders() + .put( + new HttpString("X-Test-Response"), + exchange.getRequestHeaders().getFirst("X-Test-Request")); + exchange.getResponseSender().send(CAPTURE_HEADERS.getBody()); + })) + .addExactPath( + ERROR.rawPath(), + exchange -> + controller( + ERROR, + () -> { + exchange.setStatusCode(ERROR.getStatus()); + exchange.getResponseSender().send(ERROR.getBody()); + })) + .addExactPath( + EXCEPTION.rawPath(), + exchange -> + testing.runWithSpan( + "controller", + (ThrowingRunnable) + () -> { + throw new Exception(EXCEPTION.getBody()); + })) + .addExactPath( + INDEXED_CHILD.rawPath(), + exchange -> + controller( + INDEXED_CHILD, + () -> { + INDEXED_CHILD.collectSpanAttributes( + name -> exchange.getQueryParameters().get(name).peekFirst()); + exchange.getResponseSender().send(INDEXED_CHILD.getBody()); + })) + .addExactPath( + "sendResponse", + exchange -> { + Span.current().addEvent("before-event"); + testing.runWithSpan( + "sendResponse", + () -> { + exchange.setStatusCode(StatusCodes.OK); + exchange.getResponseSender().send("sendResponse"); + }); + // event is added only when server span has not been ended + // we need to make sure that sending response does not end server span + Span.current().addEvent("after-event"); + }) + .addExactPath( + "sendResponseWithException", + exchange -> { + Span.current().addEvent("before-event"); + testing.runWithSpan( + "sendResponseWithException", + () -> { + exchange.setStatusCode(StatusCodes.OK); + exchange.getResponseSender().send("sendResponseWithException"); + }); + // event is added only when server span has not been ended + // we need to make sure that sending response does not end server span + Span.current().addEvent("after-event"); + throw new Exception("exception after sending response"); + })); + configureUndertow(builder); + Undertow server = builder.build(); + server.start(); + return server; + } + + @Override + public void stopServer(Undertow undertow) { + undertow.stop(); + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setHttpAttributes(endpoint -> ImmutableSet.of(NetworkAttributes.NETWORK_PEER_PORT)); + options.setHasResponseCustomizer(serverEndpoint -> true); + options.setUseHttp2(useHttp2()); + } + + protected void configureUndertow(Undertow.Builder builder) {} + + protected boolean useHttp2() { + return false; + } + + @DisplayName("test send response") + @Test + void testSendResponse() { + URI uri = address.resolve("sendResponse"); + AggregatedHttpResponse response = client.get(uri.toString()).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(200); + assertThat(response.contentUtf8().trim()).isEqualTo("sendResponse"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasEventsSatisfyingExactly( + event -> event.hasName("before-event"), + event -> event.hasName("after-event")) + .hasAttributesSatisfyingExactly( + equalTo(ClientAttributes.CLIENT_ADDRESS, TEST_CLIENT_IP), + equalTo(UrlAttributes.URL_SCHEME, uri.getScheme()), + equalTo(UrlAttributes.URL_PATH, uri.getPath()), + equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), + equalTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200), + equalTo(UserAgentAttributes.USER_AGENT_ORIGINAL, TEST_USER_AGENT), + equalTo( + NetworkAttributes.NETWORK_PROTOCOL_VERSION, + useHttp2() ? "2" : "1.1"), + equalTo(ServerAttributes.SERVER_ADDRESS, uri.getHost()), + equalTo(ServerAttributes.SERVER_PORT, uri.getPort()), + equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies( + NetworkAttributes.NETWORK_PEER_PORT, + k -> k.isInstanceOf(Long.class))), + span -> + span.hasName("sendResponse") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } + + @Test + @DisplayName("test send response with exception") + void testSendResponseWithException() { + URI uri = address.resolve("sendResponseWithException"); + AggregatedHttpResponse response = client.get(uri.toString()).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(200); + assertThat(response.contentUtf8().trim()).isEqualTo("sendResponseWithException"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasEventsSatisfyingExactly( + event -> event.hasName("before-event"), + event -> event.hasName("after-event"), + event -> + event + .hasName("exception") + .hasAttributesSatisfyingExactly( + equalTo( + ExceptionAttributes.EXCEPTION_TYPE, + Exception.class.getName()), + equalTo( + ExceptionAttributes.EXCEPTION_MESSAGE, + "exception after sending response"), + satisfies( + ExceptionAttributes.EXCEPTION_STACKTRACE, + val -> val.isInstanceOf(String.class)))) + .hasAttributesSatisfyingExactly( + equalTo(ClientAttributes.CLIENT_ADDRESS, TEST_CLIENT_IP), + equalTo(UrlAttributes.URL_SCHEME, uri.getScheme()), + equalTo(UrlAttributes.URL_PATH, uri.getPath()), + equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET"), + equalTo(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200), + equalTo(UserAgentAttributes.USER_AGENT_ORIGINAL, TEST_USER_AGENT), + equalTo( + NetworkAttributes.NETWORK_PROTOCOL_VERSION, + useHttp2() ? "2" : "1.1"), + equalTo(ServerAttributes.SERVER_ADDRESS, uri.getHost()), + equalTo(ServerAttributes.SERVER_PORT, uri.getPort()), + equalTo(NetworkAttributes.NETWORK_PEER_ADDRESS, "127.0.0.1"), + satisfies( + NetworkAttributes.NETWORK_PEER_PORT, + k -> k.isInstanceOf(Long.class))), + span -> + span.hasName("sendResponseWithException") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } +}