diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/test/groovy/HttpServletResponseTest.groovy b/instrumentation/servlet/servlet-2.2/javaagent/src/test/groovy/HttpServletResponseTest.groovy deleted file mode 100644 index 82e2fef4e27c..000000000000 --- a/instrumentation/servlet/servlet-2.2/javaagent/src/test/groovy/HttpServletResponseTest.groovy +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes - -import javax.servlet.http.HttpServlet -import spock.lang.Subject - -import javax.servlet.ServletOutputStream -import javax.servlet.ServletRequest -import javax.servlet.ServletResponse -import javax.servlet.http.Cookie -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -import static io.opentelemetry.api.trace.StatusCode.ERROR -import static java.util.Collections.emptyEnumeration - -class HttpServletResponseTest extends AgentInstrumentationSpecification { - - @Subject - def response = new TestResponse() - def request = Mock(HttpServletRequest) { - getMethod() >> "GET" - getProtocol() >> "TEST" - getHeaderNames() >> emptyEnumeration() - getAttributeNames() >> emptyEnumeration() - } - - def setup() { - def servlet = new HttpServlet() {} - // We need to call service so HttpServletAdvice can link the request to the response. - servlet.service((ServletRequest) request, (ServletResponse) response) - clearExportedData() - } - - def "test send no-parent"() { - when: - response.sendError(0) - response.sendError(0, "") - response.sendRedirect("") - - then: - assertTraces(0) {} - } - - def "test send with parent"() { - when: - runWithSpan("parent") { - response.sendError(0) - response.sendError(0, "") - response.sendRedirect("") - } - - then: - assertTraces(1) { - trace(0, 4) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - span(1) { - name "TestResponse.sendError" - childOf span(0) - attributes { - "$CodeIncubatingAttributes.CODE_NAMESPACE" TestResponse.name - "$CodeIncubatingAttributes.CODE_FUNCTION" "sendError" - } - } - span(2) { - name "TestResponse.sendError" - childOf span(0) - attributes { - "$CodeIncubatingAttributes.CODE_NAMESPACE" TestResponse.name - "$CodeIncubatingAttributes.CODE_FUNCTION" "sendError" - } - } - span(3) { - name "TestResponse.sendRedirect" - childOf span(0) - attributes { - "$CodeIncubatingAttributes.CODE_NAMESPACE" TestResponse.name - "$CodeIncubatingAttributes.CODE_FUNCTION" "sendRedirect" - } - } - } - } - } - - def "test send with exception"() { - setup: - def ex = new Exception("some error") - def response = new TestResponse() { - @Override - void sendRedirect(String s) { - throw ex - } - } - def servlet = new HttpServlet() {} - // We need to call service so HttpServletAdvice can link the request to the response. - servlet.service((ServletRequest) request, (ServletResponse) response) - clearExportedData() - - when: - runWithSpan("parent") { - response.sendRedirect("") - } - - then: - def th = thrown(Exception) - th == ex - - assertTraces(1) { - trace(0, 2) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - status ERROR - errorEvent(ex.class, ex.message) - } - span(1) { - name 'HttpServletResponseTest$2.sendRedirect' - childOf span(0) - status ERROR - errorEvent(ex.class, ex.message) - } - } - } - } - - static class TestResponse implements HttpServletResponse { - - @Override - void addCookie(Cookie cookie) { - - } - - @Override - boolean containsHeader(String s) { - return false - } - - @Override - String encodeURL(String s) { - return null - } - - @Override - String encodeRedirectURL(String s) { - return null - } - - @Override - String encodeUrl(String s) { - return null - } - - @Override - String encodeRedirectUrl(String s) { - return null - } - - @Override - void sendError(int i, String s) throws IOException { - - } - - @Override - void sendError(int i) throws IOException { - - } - - @Override - void sendRedirect(String s) throws IOException { - - } - - @Override - void setDateHeader(String s, long l) { - - } - - @Override - void addDateHeader(String s, long l) { - - } - - @Override - void setHeader(String s, String s1) { - - } - - @Override - void addHeader(String s, String s1) { - - } - - @Override - void setIntHeader(String s, int i) { - - } - - @Override - void addIntHeader(String s, int i) { - - } - - @Override - void setStatus(int i) { - - } - - @Override - void setStatus(int i, String s) { - - } - - @Override - String getCharacterEncoding() { - return null - } - - @Override - String getContentType() { - return null - } - - @Override - ServletOutputStream getOutputStream() throws IOException { - return null - } - - @Override - PrintWriter getWriter() throws IOException { - return null - } - - @Override - void setCharacterEncoding(String charset) { - - } - - @Override - void setContentLength(int i) { - - } - - @Override - void setContentType(String s) { - - } - - @Override - void setBufferSize(int i) { - - } - - @Override - int getBufferSize() { - return 0 - } - - @Override - void flushBuffer() throws IOException { - - } - - @Override - void resetBuffer() { - - } - - @Override - boolean isCommitted() { - return false - } - - @Override - void reset() { - - } - - @Override - void setLocale(Locale locale) { - - } - - @Override - Locale getLocale() { - return null - } - } -} diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/test/groovy/JettyServlet2Test.groovy b/instrumentation/servlet/servlet-2.2/javaagent/src/test/groovy/JettyServlet2Test.groovy deleted file mode 100644 index ea904600b175..000000000000 --- a/instrumentation/servlet/servlet-2.2/javaagent/src/test/groovy/JettyServlet2Test.groovy +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.common.AttributeKey -import io.opentelemetry.instrumentation.api.internal.HttpConstants -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.instrumentation.test.base.HttpServerTest -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import io.opentelemetry.sdk.trace.data.SpanData -import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes -import org.eclipse.jetty.server.Response -import org.eclipse.jetty.server.Server -import org.eclipse.jetty.server.handler.ErrorHandler -import org.eclipse.jetty.servlet.ServletContextHandler - -import javax.annotation.Nullable -import javax.servlet.http.HttpServletRequest - -import static io.opentelemetry.api.trace.SpanKind.INTERNAL -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED -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.NOT_FOUND -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM -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 JettyServlet2Test extends HttpServerTest implements AgentTestTrait { - - private static final CONTEXT = "ctx" - - @Override - Server startServer(int port) { - def jettyServer = new Server(port) - jettyServer.connectors.each { - it.setHost('localhost') - } - ServletContextHandler servletContext = new ServletContextHandler(null, "/$CONTEXT") - servletContext.errorHandler = new ErrorHandler() { - protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) throws IOException { - Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception") - writer.write(th ? th.message : message) - } - } - - // FIXME: Add tests for security/authentication. -// ConstraintSecurityHandler security = setupAuthentication(jettyServer) -// servletContext.setSecurityHandler(security) - - servletContext.addServlet(TestServlet2.Sync, SUCCESS.path) - servletContext.addServlet(TestServlet2.Sync, QUERY_PARAM.path) - servletContext.addServlet(TestServlet2.Sync, REDIRECT.path) - servletContext.addServlet(TestServlet2.Sync, ERROR.path) - servletContext.addServlet(TestServlet2.Sync, EXCEPTION.path) - servletContext.addServlet(TestServlet2.Sync, AUTH_REQUIRED.path) - servletContext.addServlet(TestServlet2.Sync, INDEXED_CHILD.path) - - jettyServer.setHandler(servletContext) - jettyServer.start() - - return jettyServer - } - - @Override - void stopServer(Server server) { - server.stop() - server.destroy() - } - - @Override - URI buildAddress() { - return new URI("http://localhost:$port/$CONTEXT/") - } - - @Override - Set> httpAttributes(ServerEndpoint endpoint) { - [] as Set - } - - @Override - String expectedServerSpanName(ServerEndpoint endpoint, String method, @Nullable String route) { - if (method == HttpConstants._OTHER) { - return "HTTP " + endpoint.resolvePath(address).path - } - switch (endpoint) { - case NOT_FOUND: - return method - case PATH_PARAM: - return method + " " + getContextPath() + "/path/:id/param" - default: - return method + " " + endpoint.resolvePath(address).path - } - } - - @Override - boolean testNotFound() { - false - } - - // servlet 2 does not expose a way to retrieve response headers - @Override - boolean testCapturedHttpHeaders() { - false - } - - @Override - boolean hasResponseSpan(ServerEndpoint endpoint) { - endpoint == REDIRECT || endpoint == ERROR - } - - @Override - boolean hasResponseCustomizer(ServerEndpoint endpoint) { - true - } - - @Override - void responseSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { - def responseMethod = endpoint == REDIRECT ? "sendRedirect" : "sendError" - trace.span(index) { - name "Response.$responseMethod" - kind INTERNAL - childOf((SpanData) parent) - attributes { - "$CodeIncubatingAttributes.CODE_NAMESPACE" Response.name - "$CodeIncubatingAttributes.CODE_FUNCTION" responseMethod - } - } - } - - /** - * Setup simple authentication for tests - *

- * requests to {@code /auth/*} need login 'user' and password 'password' - *

- * For details @see http://www.eclipse.org/jetty/documentation/9.3.x/embedded-examples.html - * - * @param jettyServer server to attach login service - * @return SecurityHandler that can be assigned to servlet - */ -// private ConstraintSecurityHandler setupAuthentication(Server jettyServer) { -// ConstraintSecurityHandler security = new ConstraintSecurityHandler() -// -// Constraint constraint = new Constraint() -// constraint.setName("auth") -// constraint.setAuthenticate(true) -// constraint.setRoles("role") -// -// ConstraintMapping mapping = new ConstraintMapping() -// mapping.setPathSpec("/auth/*") -// mapping.setConstraint(constraint) -// -// security.setConstraintMappings(mapping) -// security.setAuthenticator(new BasicAuthenticator()) -// -// LoginService loginService = new HashLoginService("TestRealm", -// "src/test/resources/realm.properties") -// security.setLoginService(loginService) -// jettyServer.addBean(loginService) -// -// security -// } -} diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/test/groovy/TestServlet2.groovy b/instrumentation/servlet/servlet-2.2/javaagent/src/test/groovy/TestServlet2.groovy deleted file mode 100644 index 3031280e8bc2..000000000000 --- a/instrumentation/servlet/servlet-2.2/javaagent/src/test/groovy/TestServlet2.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.instrumentation.test.base.HttpServerTest -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import javax.servlet.http.HttpServlet -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -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 - -class TestServlet2 { - - static class Sync extends HttpServlet { - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) { - req.getRequestDispatcher() - ServerEndpoint endpoint = ServerEndpoint.forPath(req.servletPath) - HttpServerTest.controller(endpoint) { - resp.contentType = "text/plain" - switch (endpoint) { - case SUCCESS: - resp.status = endpoint.status - resp.writer.print(endpoint.body) - break - case QUERY_PARAM: - resp.status = endpoint.status - resp.writer.print(req.queryString) - break - case REDIRECT: - resp.sendRedirect(endpoint.body) - break - case ERROR: - resp.sendError(endpoint.status, endpoint.body) - break - case EXCEPTION: - throw new Exception(endpoint.body) - case INDEXED_CHILD: - INDEXED_CHILD.collectSpanAttributes { name -> req.getParameter(name) } - resp.status = endpoint.status - resp.writer.print(endpoint.body) - break - } - } - } - } -} diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/HttpServletResponseTest.java b/instrumentation/servlet/servlet-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/HttpServletResponseTest.java new file mode 100644 index 000000000000..0bf2090b6e17 --- /dev/null +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/HttpServletResponseTest.java @@ -0,0 +1,266 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; + +import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.Locale; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class HttpServletResponseTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private final TestResponse response = new TestResponse(); + private final HttpServletRequest request = mock(HttpServletRequest.class); + + @BeforeEach + void setUp() throws ServletException, IOException { + when(request.getProtocol()).thenReturn("HTTP/1.1"); + when(request.getMethod()).thenReturn("GET"); + when(request.getHeaderNames()).thenReturn(Collections.emptyEnumeration()); + when(request.getAttributeNames()).thenReturn(Collections.emptyEnumeration()); + + HttpServlet servlet = new HttpServlet() {}; + // We need to call service so HttpServletAdvice can link the request to the response. + servlet.service(request, response); + testing.clearData(); + } + + @Test + void testSendNoParent() { + response.sendError(0); + response.sendError(0, ""); + response.sendRedirect(""); + + assertThat(testing.spans()).isEmpty(); + } + + @Test + void testSendWithParent() { + runWithSpan( + "parent", + () -> { + response.sendError(0); + response.sendError(0, ""); + response.sendRedirect(""); + }); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("TestResponse.sendError") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + CodeIncubatingAttributes.CODE_NAMESPACE, + TestResponse.class.getName()), + equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "sendError")), + span -> + span.hasName("TestResponse.sendError") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + CodeIncubatingAttributes.CODE_NAMESPACE, + TestResponse.class.getName()), + equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "sendError")), + span -> + span.hasName("TestResponse.sendRedirect") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + CodeIncubatingAttributes.CODE_NAMESPACE, + TestResponse.class.getName()), + equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "sendRedirect")))); + } + + @Test + void testSendWithException() throws ServletException, IOException { + RuntimeException ex = new RuntimeException("some error"); + TestResponse response = + new TestResponse() { + @Override + public void sendRedirect(String s) { + throw ex; + } + }; + HttpServlet servlet = new HttpServlet() {}; + // We need to call service so HttpServletAdvice can link the request to the response. + servlet.service(request, response); + testing.clearData(); + + assertThatCode(() -> runWithSpan("parent", () -> response.sendRedirect(""))) + .isInstanceOf(RuntimeException.class) + .hasMessage("some error"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("parent") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasStatus(StatusData.error()) + .hasException(ex), + span -> + span.hasName("HttpServletResponseTest$2.sendRedirect") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasStatus(StatusData.error()) + .hasException(ex))); + } + + /** Tests deprecated methods */ + @SuppressWarnings("deprecation") + public static class TestResponse implements HttpServletResponse { + @Override + public void addCookie(Cookie cookie) {} + + @Override + public boolean containsHeader(String s) { + return false; + } + + @Override + public String encodeURL(String s) { + return null; + } + + @Override + public String encodeRedirectURL(String s) { + return null; + } + + @Override + public String encodeUrl(String s) { + return null; + } + + @Override + public String encodeRedirectUrl(String s) { + return null; + } + + @Override + public void sendError(int i, String s) {} + + @Override + public void sendError(int i) {} + + @Override + public void sendRedirect(String s) {} + + @Override + public void setDateHeader(String s, long l) {} + + @Override + public void addDateHeader(String s, long l) {} + + @Override + public void setHeader(String s, String s1) {} + + @Override + public void addHeader(String s, String s1) {} + + @Override + public void setIntHeader(String s, int i) {} + + @Override + public void addIntHeader(String s, int i) {} + + @Override + public void setStatus(int i) {} + + @Override + public void setStatus(int i, String s) {} + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletOutputStream getOutputStream() { + return null; + } + + @Override + public PrintWriter getWriter() { + return null; + } + + @Override + public void setCharacterEncoding(String charset) {} + + @Override + public void setContentLength(int i) {} + + @Override + public void setContentType(String s) {} + + @Override + public void setBufferSize(int i) {} + + @Override + public int getBufferSize() { + return 0; + } + + @Override + public void flushBuffer() {} + + @Override + public void resetBuffer() {} + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public void reset() {} + + @Override + public void setLocale(Locale locale) {} + + @Override + public Locale getLocale() { + return null; + } + } +} diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/JettyServlet2Test.java b/instrumentation/servlet/servlet-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/JettyServlet2Test.java new file mode 100644 index 000000000000..07ac818be6ed --- /dev/null +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/JettyServlet2Test.java @@ -0,0 +1,124 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; + +import static io.opentelemetry.api.trace.SpanKind.INTERNAL; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +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.NOT_FOUND; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM; +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.semconv.incubating.CodeIncubatingAttributes.CODE_FUNCTION; +import static io.opentelemetry.semconv.incubating.CodeIncubatingAttributes.CODE_NAMESPACE; + +import io.opentelemetry.instrumentation.api.internal.HttpConstants; +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 io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import java.io.IOException; +import java.io.Writer; +import java.util.HashSet; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.junit.jupiter.api.extension.RegisterExtension; + +class JettyServlet2Test extends AbstractHttpServerTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); + + private static final String CONTEXT = "ctx"; + + @Override + protected Server setupServer() throws Exception { + Server jettyServer = new Server(port); + for (Connector connector : jettyServer.getConnectors()) { + connector.setHost("localhost"); + } + ServletContextHandler servletContext = new ServletContextHandler(null, "/" + CONTEXT); + servletContext.setErrorHandler( + new ErrorHandler() { + @Override + protected void handleErrorPage( + HttpServletRequest request, Writer writer, int code, String message) + throws IOException { + Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception"); + writer.write(th != null ? th.getMessage() : message); + } + }); + + servletContext.addServlet(TestServlet2.Sync.class, SUCCESS.getPath()); + servletContext.addServlet(TestServlet2.Sync.class, QUERY_PARAM.getPath()); + servletContext.addServlet(TestServlet2.Sync.class, REDIRECT.getPath()); + servletContext.addServlet(TestServlet2.Sync.class, ERROR.getPath()); + servletContext.addServlet(TestServlet2.Sync.class, EXCEPTION.getPath()); + servletContext.addServlet(TestServlet2.Sync.class, AUTH_REQUIRED.getPath()); + servletContext.addServlet(TestServlet2.Sync.class, INDEXED_CHILD.getPath()); + + jettyServer.setHandler(servletContext); + jettyServer.start(); + + return jettyServer; + } + + @Override + public void stopServer(Server server) throws Exception { + server.stop(); + server.destroy(); + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setContextPath("/" + CONTEXT); + options.setHttpAttributes(e -> new HashSet<>()); + options.setTestNotFound(false); + // servlet 2 does not expose a way to retrieve response headers + options.setTestCaptureHttpHeaders(false); + options.setHasResponseSpan(e -> e.equals(REDIRECT) || e.equals(ERROR)); + options.setHasResponseCustomizer(e -> true); + } + + @Override + public String expectedServerSpanName( + ServerEndpoint endpoint, String method, @Nullable String route) { + if (method.equals(HttpConstants._OTHER)) { + return "HTTP " + getContextPath() + endpoint.getPath(); + } + + if (NOT_FOUND.equals(endpoint)) { + return method; + } else if (PATH_PARAM.equals(endpoint)) { + return method + " " + getContextPath() + "/path/:id/param"; + } else { + return method + " " + getContextPath() + endpoint.getPath(); + } + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, String method, ServerEndpoint endpoint) { + String responseMethod = endpoint.equals(REDIRECT) ? "sendRedirect" : "sendError"; + return span.hasName("Response." + responseMethod) + .hasKind(INTERNAL) + .hasAttributesSatisfyingExactly( + equalTo(CODE_NAMESPACE, Response.class.getName()), + equalTo(CODE_FUNCTION, responseMethod)); + } +} diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/TestServlet2.java b/instrumentation/servlet/servlet-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/TestServlet2.java new file mode 100644 index 000000000000..8890b0dd91b4 --- /dev/null +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/TestServlet2.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; + +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 io.opentelemetry.instrumentation.test.base.HttpServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class TestServlet2 { + + private TestServlet2() {} + + public static class Sync extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + req.getRequestDispatcher(null); + ServerEndpoint endpoint = ServerEndpoint.forPath(req.getServletPath()); + HttpServerTest.controller( + endpoint, + () -> { + resp.setContentType("text/plain"); + if (SUCCESS.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (QUERY_PARAM.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(req.getQueryString()); + } else if (REDIRECT.equals(endpoint)) { + resp.sendRedirect(endpoint.getBody()); + } else if (ERROR.equals(endpoint)) { + resp.sendError(endpoint.getStatus(), endpoint.getBody()); + } else if (EXCEPTION.equals(endpoint)) { + throw new Exception(endpoint.getBody()); + } else if (INDEXED_CHILD.equals(endpoint)) { + INDEXED_CHILD.collectSpanAttributes(req::getParameter); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } + return null; + }); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3MappingTest.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3MappingTest.groovy deleted file mode 100644 index 0349aefbf742..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3MappingTest.groovy +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.base.HttpServerTestTrait -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse -import spock.lang.Unroll - -import javax.servlet.Servlet -import javax.servlet.ServletException -import javax.servlet.http.HttpServlet -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -import static io.opentelemetry.api.trace.StatusCode.ERROR - -abstract class AbstractServlet3MappingTest extends AgentInstrumentationSpecification implements HttpServerTestTrait { - - def setupSpec() { - setupServer() - } - - def cleanupSpec() { - cleanupServer() - } - - abstract void addServlet(CONTEXT context, String path, Class servlet) - - protected void setupServlets(CONTEXT context) { - addServlet(context, "/prefix/*", TestServlet) - addServlet(context, "*.suffix", TestServlet) - } - - static class TestServlet extends HttpServlet { - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - response.getWriter().write("Ok") - } - } - - @Unroll - def "test path #path"() { - setup: - AggregatedHttpResponse response = client.get(address.resolve(path).toString()).aggregate().join() - - expect: - response.status().code() == success ? 200 : 404 - - and: - def spanCount = success ? 1 : 2 - assertTraces(1) { - trace(0, spanCount) { - span(0) { - name "GET " + getContextPath() + route - kind SpanKind.SERVER - if (!success && response.status().code() >= 500) { - status ERROR - } - } - if (!success) { - span(1) { - } - } - } - } - - where: - path | route | success - 'prefix' | '/prefix/*' | true - 'prefix/' | '/prefix/*' | true - 'prefix/a' | '/prefix/*' | true - 'prefixa' | '/*' | false - 'a.suffix' | '/*.suffix' | true - '.suffix' | '/*.suffix' | true - 'suffix' | '/*' | false - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy deleted file mode 100644 index 7f990d7118ca..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/AbstractServlet3Test.groovy +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.instrumentation.api.internal.HttpConstants -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.instrumentation.test.base.HttpServerTest -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder -import io.opentelemetry.semconv.HttpAttributes - -import javax.servlet.Servlet - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS -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.NOT_FOUND -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 - -abstract class AbstractServlet3Test extends HttpServerTest implements AgentTestTrait { - @Override - URI buildAddress() { - return new URI("http://localhost:$port$contextPath/") - } - - // FIXME: Add authentication tests back in... -// @Shared -// protected String user = "user" -// @Shared -// protected String pass = "password" - - abstract Class servlet() - - abstract void addServlet(CONTEXT context, String path, Class servlet) - - public static final ServerEndpoint HTML_PRINT_WRITER = - new ServerEndpoint("HTML_PRINT_WRITER", "htmlPrintWriter", - 200, - "\n" - + "\n" - + "\n" - + " \n" - + " Title\n" - + "\n" - + "\n" - + "

test works

\n" - + "\n" - + "") - public static final ServerEndpoint HTML_SERVLET_OUTPUT_STREAM = - new ServerEndpoint("HTML_SERVLET_OUTPUT_STREAM", "htmlServletOutputStream", - 200, - "\n" - + "\n" - + "\n" - + " \n" - + " Title\n" - + "\n" - + "\n" - + "

test works

\n" - + "\n" - + "") - protected void setupServlets(CONTEXT context) { - def servlet = servlet() - - addServlet(context, SUCCESS.path, servlet) - addServlet(context, QUERY_PARAM.path, servlet) - addServlet(context, ERROR.path, servlet) - addServlet(context, EXCEPTION.path, servlet) - addServlet(context, REDIRECT.path, servlet) - addServlet(context, AUTH_REQUIRED.path, servlet) - addServlet(context, INDEXED_CHILD.path, servlet) - addServlet(context, CAPTURE_HEADERS.path, servlet) - addServlet(context, CAPTURE_PARAMETERS.path, servlet) - addServlet(context, HTML_PRINT_WRITER.path, servlet) - addServlet(context, HTML_SERVLET_OUTPUT_STREAM.path, servlet) - } - - @Override - String expectedHttpRoute(ServerEndpoint endpoint, String method) { - // no need to compute route if we're not expecting it - if (!httpAttributes(endpoint).contains(HttpAttributes.HTTP_ROUTE)) { - return null - } - if (method == HttpConstants._OTHER) { - return endpoint.resolvePath(address).path - } - switch (endpoint) { - case NOT_FOUND: - return getContextPath() + "/*" - default: - return super.expectedHttpRoute(endpoint, method) - } - } - - @Override - boolean testCapturedRequestParameters() { - true - } - - boolean errorEndpointUsesSendError() { - true - } - - @Override - boolean hasResponseCustomizer(ServerEndpoint endpoint) { - true - } - - @Override - boolean hasResponseSpan(ServerEndpoint endpoint) { - endpoint == REDIRECT || (endpoint == ERROR && errorEndpointUsesSendError()) - } - - @Override - void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) { - switch (endpoint) { - case REDIRECT: - redirectSpan(trace, index, parent) - break - case ERROR: - sendErrorSpan(trace, index, parent) - break - } - } - - def "snippet injection with ServletOutputStream"() { - setup: - ExperimentalSnippetHolder.setSnippet("\n ") - def request = request(HTML_SERVLET_OUTPUT_STREAM, "GET") - def response = client.execute(request).aggregate().join() - - expect: - response.status().code() == HTML_SERVLET_OUTPUT_STREAM.status - String result = "\n" + - "\n" + - "\n" + - " \n" + - " \n" + - " Title\n" + - "\n" + - "\n" + - "

test works

\n" + - "\n" + - "" - response.contentUtf8() == result - response.headers().contentLength() == result.length() - - cleanup: - ExperimentalSnippetHolder.setSnippet("") - - def expectedRoute = expectedHttpRoute(HTML_SERVLET_OUTPUT_STREAM, "GET") - assertTraces(1) { - trace(0, 2) { - span(0) { - name "GET" + (expectedRoute != null ? " " + expectedRoute : "") - kind SpanKind.SERVER - hasNoParent() - } - span(1) { - name "controller" - kind SpanKind.INTERNAL - childOf span(0) - } - } - } - } - - def "snippet injection with PrintWriter"() { - setup: - ExperimentalSnippetHolder.setSnippet("\n ") - def request = request(HTML_PRINT_WRITER, "GET") - def response = client.execute(request).aggregate().join() - - expect: - response.status().code() == HTML_PRINT_WRITER.status - String result = "\n" + - "\n" + - "\n" + - " \n" + - " \n" + - " Title\n" + - "\n" + - "\n" + - "

test works

\n" + - "\n" + - "" - - response.contentUtf8() == result - response.headers().contentLength() == result.length() - - cleanup: - ExperimentalSnippetHolder.setSnippet("") - - def expectedRoute = expectedHttpRoute(HTML_PRINT_WRITER, "GET") - assertTraces(1) { - trace(0, 2) { - span(0) { - name "GET" + (expectedRoute != null ? " " + expectedRoute : "") - kind SpanKind.SERVER - hasNoParent() - } - span(1) { - name "controller" - kind SpanKind.INTERNAL - childOf span(0) - } - } - } - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/HttpServletResponseTest.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/HttpServletResponseTest.groovy deleted file mode 100644 index bb5f494a35e7..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/HttpServletResponseTest.groovy +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes - -import javax.servlet.http.HttpServlet -import spock.lang.Subject - -import javax.servlet.ServletOutputStream -import javax.servlet.ServletRequest -import javax.servlet.ServletResponse -import javax.servlet.http.Cookie -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -import static io.opentelemetry.api.trace.StatusCode.ERROR -import static java.util.Collections.emptyEnumeration - -class HttpServletResponseTest extends AgentInstrumentationSpecification { - - @Subject - def response = new TestResponse() - def request = Mock(HttpServletRequest) { - getMethod() >> "GET" - getProtocol() >> "TEST" - getHeaderNames() >> emptyEnumeration() - getAttributeNames() >> emptyEnumeration() - } - - def setup() { - def servlet = new HttpServlet() {} - // We need to call service so HttpServletAdvice can link the request to the response. - servlet.service((ServletRequest) request, (ServletResponse) response) - clearExportedData() - } - - def "test send no-parent"() { - when: - response.sendError(0) - response.sendError(0, "") - response.sendRedirect("") - - then: - assertTraces(0) {} - } - - def "test send with parent"() { - when: - runWithSpan("parent") { - response.sendError(0) - response.sendError(0, "") - response.sendRedirect("") - } - - then: - assertTraces(1) { - trace(0, 4) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - } - span(1) { - name "TestResponse.sendError" - childOf span(0) - attributes { - "$CodeIncubatingAttributes.CODE_NAMESPACE" TestResponse.name - "$CodeIncubatingAttributes.CODE_FUNCTION" "sendError" - } - } - span(2) { - name "TestResponse.sendError" - childOf span(0) - attributes { - "$CodeIncubatingAttributes.CODE_NAMESPACE" TestResponse.name - "$CodeIncubatingAttributes.CODE_FUNCTION" "sendError" - } - } - span(3) { - name "TestResponse.sendRedirect" - childOf span(0) - attributes { - "$CodeIncubatingAttributes.CODE_NAMESPACE" TestResponse.name - "$CodeIncubatingAttributes.CODE_FUNCTION" "sendRedirect" - } - } - } - } - } - - def "test send with exception"() { - setup: - def ex = new Exception("some error") - def response = new TestResponse() { - @Override - void sendRedirect(String s) { - throw ex - } - } - def servlet = new HttpServlet() {} - // We need to call service so HttpServletAdvice can link the request to the response. - servlet.service((ServletRequest) request, (ServletResponse) response) - clearExportedData() - - when: - runWithSpan("parent") { - response.sendRedirect("") - } - - then: - def th = thrown(Exception) - th == ex - - assertTraces(1) { - trace(0, 2) { - span(0) { - name "parent" - kind SpanKind.INTERNAL - hasNoParent() - status ERROR - errorEvent(ex.class, ex.message) - } - span(1) { - name 'HttpServletResponseTest$2.sendRedirect' - childOf span(0) - status ERROR - errorEvent(ex.class, ex.message) - } - } - } - } - - static class TestResponse implements HttpServletResponse { - - @Override - void addCookie(Cookie cookie) { - - } - - @Override - boolean containsHeader(String s) { - return false - } - - @Override - String encodeURL(String s) { - return null - } - - @Override - String encodeRedirectURL(String s) { - return null - } - - @Override - String encodeUrl(String s) { - return null - } - - @Override - String encodeRedirectUrl(String s) { - return null - } - - @Override - void sendError(int i, String s) throws IOException { - - } - - @Override - void sendError(int i) throws IOException { - - } - - @Override - void sendRedirect(String s) throws IOException { - - } - - @Override - void setDateHeader(String s, long l) { - - } - - @Override - void addDateHeader(String s, long l) { - - } - - @Override - void setHeader(String s, String s1) { - - } - - @Override - void addHeader(String s, String s1) { - - } - - @Override - void setIntHeader(String s, int i) { - - } - - @Override - void addIntHeader(String s, int i) { - - } - - @Override - void setStatus(int i) { - - } - - @Override - void setStatus(int i, String s) { - - } - - @Override - int getStatus() { - return 0 - } - - @Override - String getHeader(String s) { - return null - } - - @Override - Collection getHeaders(String s) { - return null - } - - @Override - Collection getHeaderNames() { - return null - } - - @Override - String getCharacterEncoding() { - return null - } - - @Override - String getContentType() { - return null - } - - @Override - ServletOutputStream getOutputStream() throws IOException { - return null - } - - @Override - PrintWriter getWriter() throws IOException { - return null - } - - @Override - void setCharacterEncoding(String charset) { - - } - - @Override - void setContentLength(int i) { - - } - - @Override - void setContentLengthLong(long l) { - - } - - @Override - void setContentType(String s) { - - } - - @Override - void setBufferSize(int i) { - - } - - @Override - int getBufferSize() { - return 0 - } - - @Override - void flushBuffer() throws IOException { - - } - - @Override - void resetBuffer() { - - } - - @Override - boolean isCommitted() { - return false - } - - @Override - void reset() { - - } - - @Override - void setLocale(Locale locale) { - - } - - @Override - Locale getLocale() { - return null - } - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3MappingTest.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3MappingTest.groovy deleted file mode 100644 index dd338121e9d9..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3MappingTest.groovy +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import org.eclipse.jetty.server.Server -import org.eclipse.jetty.servlet.ServletContextHandler - -import javax.servlet.Servlet -import javax.servlet.ServletException -import javax.servlet.http.HttpServlet -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -class JettyServlet3MappingTest extends AbstractServlet3MappingTest { - - @Override - Server startServer(int port) { - Server server = new Server(port) - ServletContextHandler handler = new ServletContextHandler(null, contextPath) - setupServlets(handler) - server.setHandler(handler) - server.start() - return server - } - - @Override - void stopServer(Server server) { - server.stop() - server.destroy() - } - - @Override - protected void setupServlets(ServletContextHandler handler) { - super.setupServlets(handler) - - addServlet(handler, "/", DefaultServlet) - } - - @Override - void addServlet(ServletContextHandler handler, String path, Class servlet) { - handler.addServlet(servlet, path) - } - - static class DefaultServlet extends HttpServlet { - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - response.sendError(404) - } - } - - @Override - String getContextPath() { - "/jetty-context" - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy deleted file mode 100644 index f2f93e4c2ef9..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServlet3Test.groovy +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import org.eclipse.jetty.server.Server -import org.eclipse.jetty.server.handler.ErrorHandler -import org.eclipse.jetty.servlet.ServletContextHandler - -import javax.servlet.Servlet -import javax.servlet.ServletException -import javax.servlet.http.HttpServletRequest - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS -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 - -abstract class JettyServlet3Test extends AbstractServlet3Test { - - private static final boolean IS_BEFORE_94 = isBefore94() - - static isBefore94() { - def version = Server.getVersion().split("\\.") - def major = Integer.parseInt(version[0]) - def minor = Integer.parseInt(version[1]) - return major < 9 || (major == 9 && minor < 4) - } - - @Override - boolean testNotFound() { - false - } - - @Override - Throwable expectedException() { - new ServletException(EXCEPTION.body) - } - - boolean isAsyncTest() { - false - } - - @Override - boolean hasResponseSpan(ServerEndpoint endpoint) { - return (IS_BEFORE_94 && endpoint == EXCEPTION && !isAsyncTest()) || super.hasResponseSpan(endpoint) - } - - @Override - void responseSpan(TraceAssert trace, int index, Object controllerSpan, Object handlerSpan, String method, ServerEndpoint endpoint) { - if (IS_BEFORE_94 && endpoint == EXCEPTION) { - sendErrorSpan(trace, index, handlerSpan) - } - super.responseSpan(trace, index, controllerSpan, handlerSpan, method, endpoint) - } - - @Override - Server startServer(int port) { - def jettyServer = new Server(port) - jettyServer.connectors.each { - it.setHost('localhost') - } - - ServletContextHandler servletContext = new ServletContextHandler(null, contextPath) - servletContext.errorHandler = new ErrorHandler() { - protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) throws IOException { - Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception") - writer.write(th ? th.message : message) - } - } -// setupAuthentication(jettyServer, servletContext) - setupServlets(servletContext) - jettyServer.setHandler(servletContext) - - jettyServer.start() - - return jettyServer - } - - @Override - void stopServer(Server server) { - server.stop() - server.destroy() - } - - @Override - String getContextPath() { - return "/jetty-context" - } - - @Override - void addServlet(ServletContextHandler servletContext, String path, Class servlet) { - servletContext.addServlet(servlet, path) - } - - // FIXME: Add authentication tests back in... -// static setupAuthentication(Server jettyServer, ServletContextHandler servletContext) { -// ConstraintSecurityHandler authConfig = new ConstraintSecurityHandler() -// -// Constraint constraint = new Constraint() -// constraint.setName("auth") -// constraint.setAuthenticate(true) -// constraint.setRoles("role") -// -// ConstraintMapping mapping = new ConstraintMapping() -// mapping.setPathSpec("/auth/*") -// mapping.setConstraint(constraint) -// -// authConfig.setConstraintMappings(mapping) -// authConfig.setAuthenticator(new BasicAuthenticator()) -// -// LoginService loginService = new HashLoginService("TestRealm", -// "src/test/resources/realm.properties") -// authConfig.setLoginService(loginService) -// jettyServer.addBean(loginService) -// -// servletContext.setSecurityHandler(authConfig) -// } -} - -class JettyServlet3TestSync extends JettyServlet3Test { - - @Override - Class servlet() { - TestServlet3.Sync - } -} - -class JettyServlet3TestAsync extends JettyServlet3Test { - - @Override - Class servlet() { - TestServlet3.Async - } - - @Override - boolean errorEndpointUsesSendError() { - false - } - - @Override - boolean isAsyncTest() { - true - } -} - -class JettyServlet3TestFakeAsync extends JettyServlet3Test { - - @Override - Class servlet() { - TestServlet3.FakeAsync - } -} - -class JettyServlet3TestForward extends JettyDispatchTest { - @Override - Class servlet() { - TestServlet3.Sync // dispatch to sync servlet - } - - @Override - protected void setupServlets(ServletContextHandler context) { - super.setupServlets(context) - - addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Forward) - } -} - -class JettyServlet3TestInclude extends JettyDispatchTest { - @Override - Class servlet() { - TestServlet3.Sync // dispatch to sync servlet - } - - @Override - boolean testRedirect() { - false - } - - @Override - boolean testCapturedHttpHeaders() { - false - } - - @Override - boolean testError() { - false - } - - @Override - protected void setupServlets(ServletContextHandler context) { - super.setupServlets(context) - - addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Include) - } -} - - -class JettyServlet3TestDispatchImmediate extends JettyDispatchTest { - @Override - Class servlet() { - TestServlet3.Sync - } - - @Override - boolean isAsyncTest() { - true - } - - @Override - protected void setupServlets(ServletContextHandler context) { - super.setupServlets(context) - addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + REDIRECT.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) - } -} - -class JettyServlet3TestDispatchAsync extends JettyDispatchTest { - @Override - Class servlet() { - TestServlet3.Async - } - - @Override - boolean isAsyncTest() { - true - } - - @Override - protected void setupServlets(ServletContextHandler context) { - super.setupServlets(context) - addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + REDIRECT.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) - } - - @Override - boolean errorEndpointUsesSendError() { - false - } -} - -abstract class JettyDispatchTest extends JettyServlet3Test { - @Override - URI buildAddress() { - return new URI("http://localhost:$port$contextPath/dispatch/") - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy deleted file mode 100644 index f681852649f4..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.common.AttributeKey -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import io.opentelemetry.semconv.HttpAttributes -import org.eclipse.jetty.server.Server -import org.eclipse.jetty.server.handler.ErrorHandler -import org.eclipse.jetty.servlet.ServletHandler - -import javax.servlet.Servlet -import javax.servlet.ServletException -import javax.servlet.http.HttpServletRequest - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION - -class JettyServletHandlerTest extends AbstractServlet3Test { - - private static final boolean IS_BEFORE_94 = isBefore94() - - static isBefore94() { - def version = Server.getVersion().split("\\.") - def major = Integer.parseInt(version[0]) - def minor = Integer.parseInt(version[1]) - return major < 9 || (major == 9 && minor < 4) - } - - @Override - boolean hasResponseSpan(ServerEndpoint endpoint) { - return (IS_BEFORE_94 && endpoint == EXCEPTION) || super.hasResponseSpan(endpoint) - } - - @Override - void responseSpan(TraceAssert trace, int index, Object controllerSpan, Object handlerSpan, String method, ServerEndpoint endpoint) { - if (IS_BEFORE_94 && endpoint == EXCEPTION) { - sendErrorSpan(trace, index, handlerSpan) - } - super.responseSpan(trace, index, controllerSpan, handlerSpan, method, endpoint) - } - - @Override - Set> httpAttributes(ServerEndpoint endpoint) { - def attributes = super.httpAttributes(endpoint) - attributes.remove(HttpAttributes.HTTP_ROUTE) - attributes - } - - @Override - Server startServer(int port) { - Server server = new Server(port) - ServletHandler handler = new ServletHandler() - server.setHandler(handler) - setupServlets(handler) - server.addBean(new ErrorHandler() { - protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) throws IOException { - Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception") - writer.write(th ? th.message : message) - } - }) - server.start() - return server - } - - @Override - void addServlet(ServletHandler servletHandler, String path, Class servlet) { - servletHandler.addServletWithMapping(servlet, path) - } - - @Override - void stopServer(Server server) { - server.stop() - server.destroy() - } - - @Override - String getContextPath() { - "" - } - - @Override - Class servlet() { - TestServlet3.Sync - } - - @Override - boolean testNotFound() { - false - } - - @Override - Throwable expectedException() { - new ServletException(EXCEPTION.body) - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy deleted file mode 100644 index 9048c157c27f..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TestServlet3.groovy +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.instrumentation.test.base.HttpServerTest -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint - -import javax.servlet.RequestDispatcher -import javax.servlet.ServletException -import javax.servlet.annotation.WebServlet -import javax.servlet.http.HttpServlet -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse -import java.util.concurrent.CountDownLatch - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS -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 - -class TestServlet3 { - - @WebServlet - static class Sync extends HttpServlet { - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) { - String servletPath = req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH) - if (servletPath == null) { - servletPath = req.servletPath - } - ServerEndpoint endpoint = ServerEndpoint.forPath(servletPath) - HttpServerTest.controller(endpoint) { - resp.contentType = "text/plain" - switch (endpoint) { - case SUCCESS: - resp.status = endpoint.status - resp.writer.print(endpoint.body) - break - case INDEXED_CHILD: - resp.status = endpoint.status - endpoint.collectSpanAttributes { req.getParameter(it) } - break - case QUERY_PARAM: - resp.status = endpoint.status - resp.writer.print(req.queryString) - break - case REDIRECT: - resp.sendRedirect(endpoint.body) - break - case CAPTURE_HEADERS: - resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request")) - resp.status = endpoint.status - resp.writer.print(endpoint.body) - break - case CAPTURE_PARAMETERS: - req.setCharacterEncoding("UTF8") - def value = req.getParameter("test-parameter") - if (value != "test value õäöü") { - throw new ServletException("request parameter does not have expected value " + value) - } - resp.status = endpoint.status - resp.writer.print(endpoint.body) - break - case ERROR: - resp.sendError(endpoint.status, endpoint.body) - break - case EXCEPTION: - throw new ServletException(endpoint.body) - case AbstractServlet3Test.HTML_PRINT_WRITER: - resp.contentType = "text/html" - resp.status = endpoint.status - resp.setContentLengthLong(endpoint.body.length()) - resp.writer.print(endpoint.body) - break - case AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM: - resp.contentType = "text/html" - resp.status = endpoint.status - resp.setContentLength(endpoint.body.length()) - byte[] body = endpoint.body.getBytes() - resp.getOutputStream().write(body, 0, body.length) - break - } - } - } - } - - @WebServlet(asyncSupported = true) - static class Async extends HttpServlet { - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) { - ServerEndpoint endpoint = ServerEndpoint.forPath(req.servletPath) - def latch = new CountDownLatch(1) - def context = req.startAsync() - if (endpoint == EXCEPTION) { - context.setTimeout(5000) - } - context.start { - try { - HttpServerTest.controller(endpoint) { - resp.contentType = "text/plain" - switch (endpoint) { - case SUCCESS: - resp.status = endpoint.status - resp.writer.print(endpoint.body) - context.complete() - break - case INDEXED_CHILD: - endpoint.collectSpanAttributes { req.getParameter(it) } - resp.status = endpoint.status - context.complete() - break - case QUERY_PARAM: - resp.status = endpoint.status - resp.writer.print(req.queryString) - context.complete() - break - case REDIRECT: - resp.sendRedirect(endpoint.body) - context.complete() - break - case CAPTURE_HEADERS: - resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request")) - resp.status = endpoint.status - resp.writer.print(endpoint.body) - context.complete() - break - case CAPTURE_PARAMETERS: - req.setCharacterEncoding("UTF8") - def value = req.getParameter("test-parameter") - if (value != "test value õäöü") { - throw new ServletException("request parameter does not have expected value " + value) - } - resp.status = endpoint.status - resp.writer.print(endpoint.body) - context.complete() - break - case ERROR: - resp.status = endpoint.status - resp.writer.print(endpoint.body) -// resp.sendError(endpoint.status, endpoint.body) - context.complete() - break - case EXCEPTION: - resp.status = endpoint.status - def writer = resp.writer - writer.print(endpoint.body) - if (req.getClass().getName().contains("catalina")) { - // on tomcat close the writer to ensure response is sent immediately, otherwise - // there is a chance that tomcat resets the connection before the response is sent - writer.close() - } - throw new ServletException(endpoint.body) - break - case AbstractServlet3Test.HTML_PRINT_WRITER: - resp.contentType = "text/html" - resp.status = endpoint.status - resp.setContentLength(endpoint.body.length()) - resp.writer.print(endpoint.body) - context.complete() - break - case AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM: - resp.contentType = "text/html" - resp.status = endpoint.status - resp.getOutputStream().print(endpoint.body) - context.complete() - break - } - } - } finally { - latch.countDown() - } - } - latch.await() - } - } - - @WebServlet(asyncSupported = true) - static class FakeAsync extends HttpServlet { - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) { - def context = req.startAsync() - try { - ServerEndpoint endpoint = ServerEndpoint.forPath(req.servletPath) - HttpServerTest.controller(endpoint) { - resp.contentType = "text/plain" - switch (endpoint) { - case SUCCESS: - resp.status = endpoint.status - resp.writer.print(endpoint.body) - break - case INDEXED_CHILD: - endpoint.collectSpanAttributes { req.getParameter(it) } - resp.status = endpoint.status - break - case QUERY_PARAM: - resp.status = endpoint.status - resp.writer.print(req.queryString) - break - case REDIRECT: - resp.sendRedirect(endpoint.body) - break - case CAPTURE_HEADERS: - resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request")) - resp.status = endpoint.status - resp.writer.print(endpoint.body) - break - case CAPTURE_PARAMETERS: - req.setCharacterEncoding("UTF8") - def value = req.getParameter("test-parameter") - if (value != "test value õäöü") { - throw new ServletException("request parameter does not have expected value " + value) - } - resp.status = endpoint.status - resp.writer.print(endpoint.body) - break - case ERROR: - resp.sendError(endpoint.status, endpoint.body) - break - case EXCEPTION: - resp.status = endpoint.status - resp.writer.print(endpoint.body) - throw new ServletException(endpoint.body) - case AbstractServlet3Test.HTML_PRINT_WRITER: - // intentionally testing setting status before contentType here to cover that case somewhere - resp.status = endpoint.status - resp.contentType = "text/html" - resp.writer.print(endpoint.body) - break - case AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM: - resp.contentType = "text/html" - resp.status = endpoint.status - resp.getOutputStream().print(endpoint.body) - break - } - } - } finally { - context.complete() - } - } - } - - @WebServlet(asyncSupported = true) - static class DispatchImmediate extends HttpServlet { - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) { - def target = req.servletPath.replace("/dispatch", "") - if (req.queryString != null) { - target += "?" + req.queryString - } - req.startAsync().dispatch(target) - } - } - - @WebServlet(asyncSupported = true) - static class DispatchAsync extends HttpServlet { - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) { - def target = req.servletPath.replace("/dispatch", "") - if (req.queryString != null) { - target += "?" + req.queryString - } - def context = req.startAsync() - context.start { - context.dispatch(target) - } - } - } - - // TODO: Add tests for this! - @WebServlet(asyncSupported = true) - static class DispatchRecursive extends HttpServlet { - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) { - if (req.servletPath.equals("/recursive")) { - resp.writer.print("Hello Recursive") - return - } - def depth = Integer.parseInt(req.getParameter("depth")) - if (depth > 0) { - req.startAsync().dispatch("/dispatch/recursive?depth=" + (depth - 1)) - } else { - req.startAsync().dispatch("/recursive") - } - } - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3FilterMappingTest.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3FilterMappingTest.groovy deleted file mode 100644 index b40142c784ee..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3FilterMappingTest.groovy +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import org.apache.catalina.Context -import org.apache.catalina.startup.Tomcat -import org.apache.tomcat.util.descriptor.web.FilterDef -import org.apache.tomcat.util.descriptor.web.FilterMap - -import javax.servlet.Filter -import javax.servlet.FilterChain -import javax.servlet.FilterConfig -import javax.servlet.ServletException -import javax.servlet.ServletRequest -import javax.servlet.ServletResponse -import javax.servlet.http.HttpServlet -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -abstract class TomcatServlet3FilterMappingTest extends TomcatServlet3MappingTest { - - void addFilter(Context servletContext, String path, Class filter) { - String name = UUID.randomUUID() - FilterDef filterDef = new FilterDef() - filterDef.setFilter(filter.newInstance()) - filterDef.setFilterName(name) - servletContext.addFilterDef(filterDef) - FilterMap filterMap = new FilterMap() - filterMap.setFilterName(name) - filterMap.addURLPattern(path) - servletContext.addFilterMap(filterMap) - } - - void addFilterWithServletName(Context servletContext, String servletName, Class filter) { - String name = UUID.randomUUID() - FilterDef filterDef = new FilterDef() - filterDef.setFilter(filter.newInstance()) - filterDef.setFilterName(name) - servletContext.addFilterDef(filterDef) - FilterMap filterMap = new FilterMap() - filterMap.setFilterName(name) - filterMap.addServletName(servletName) - servletContext.addFilterMap(filterMap) - } - - static class TestFilter implements Filter { - @Override - void init(FilterConfig filterConfig) throws ServletException { - } - - @Override - void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - if (servletRequest.getAttribute("firstFilterCalled") != null) { - servletRequest.setAttribute("testFilterCalled", Boolean.TRUE) - filterChain.doFilter(servletRequest, servletResponse) - } else { - throw new IllegalStateException("First filter should have been called.") - } - } - - @Override - void destroy() { - } - } - - static class FirstFilter implements Filter { - @Override - void init(FilterConfig filterConfig) throws ServletException { - } - - @Override - void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - servletRequest.setAttribute("firstFilterCalled", Boolean.TRUE) - filterChain.doFilter(servletRequest, servletResponse) - } - - @Override - void destroy() { - } - } - - static class LastFilter implements Filter { - - @Override - void init(FilterConfig filterConfig) throws ServletException { - } - - @Override - void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - if (servletRequest.getAttribute("testFilterCalled") != null) { - HttpServletResponse response = (HttpServletResponse) servletResponse - response.getWriter().write("Ok") - response.setStatus(HttpServletResponse.SC_OK) - } else { - filterChain.doFilter(servletRequest, servletResponse) - } - } - - @Override - void destroy() { - } - } - - static class DefaultServlet extends HttpServlet { - protected void service(HttpServletRequest req, HttpServletResponse resp) { - throw new IllegalStateException("Servlet should not have been called, filter should have handled the request.") - } - } -} - -class TomcatServlet3FilterUrlPatternMappingTest extends TomcatServlet3FilterMappingTest { - @Override - protected void setupServlets(Context context) { - addFilter(context, "/*", FirstFilter) - addFilter(context, "/prefix/*", TestFilter) - addFilter(context, "*.suffix", TestFilter) - addFilter(context, "/*", LastFilter) - } -} - -class TomcatServlet3FilterServletNameMappingTest extends TomcatServlet3FilterMappingTest { - @Override - protected void setupServlets(Context context) { - Tomcat.addServlet(context, "prefix-servlet", new DefaultServlet()) - context.addServletMappingDecoded("/prefix/*", "prefix-servlet") - Tomcat.addServlet(context, "suffix-servlet", new DefaultServlet()) - context.addServletMappingDecoded("*.suffix", "suffix-servlet") - - addFilter(context, "/*", FirstFilter) - addFilterWithServletName(context, "prefix-servlet", TestFilter) - addFilterWithServletName(context, "suffix-servlet", TestFilter) - addFilterWithServletName(context, "prefix-servlet", LastFilter) - addFilterWithServletName(context, "suffix-servlet", LastFilter) - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3MappingTest.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3MappingTest.groovy deleted file mode 100644 index 829edd344a5a..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3MappingTest.groovy +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import org.apache.catalina.Context -import org.apache.catalina.startup.Tomcat -import org.apache.tomcat.JarScanFilter -import org.apache.tomcat.JarScanType - -import javax.servlet.Servlet -import java.nio.file.Files - -class TomcatServlet3MappingTest extends AbstractServlet3MappingTest { - - @Override - Tomcat startServer(int port) { - def tomcatServer = new Tomcat() - - def baseDir = Files.createTempDirectory("tomcat").toFile() - baseDir.deleteOnExit() - tomcatServer.setBaseDir(baseDir.getAbsolutePath()) - - tomcatServer.setPort(port) - tomcatServer.getConnector().enableLookups = true // get localhost instead of 127.0.0.1 - - File applicationDir = new File(baseDir, "/webapps/ROOT") - if (!applicationDir.exists()) { - applicationDir.mkdirs() - applicationDir.deleteOnExit() - } - Context servletContext = tomcatServer.addWebapp(contextPath, applicationDir.getAbsolutePath()) - // Speed up startup by disabling jar scanning: - servletContext.getJarScanner().setJarScanFilter(new JarScanFilter() { - @Override - boolean check(JarScanType jarScanType, String jarName) { - return false - } - }) - - setupServlets(servletContext) - - tomcatServer.start() - - return tomcatServer - } - - @Override - void stopServer(Tomcat server) { - server.stop() - server.destroy() - } - - @Override - void addServlet(Context servletContext, String path, Class servlet) { - String name = UUID.randomUUID() - Tomcat.addServlet(servletContext, name, servlet.newInstance()) - servletContext.addServletMappingDecoded(path, name) - } - - @Override - String getContextPath() { - return "/tomcat-context" - } -} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy b/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy deleted file mode 100644 index d902559869fe..000000000000 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/groovy/TomcatServlet3Test.groovy +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse -import org.apache.catalina.AccessLog -import org.apache.catalina.Context -import org.apache.catalina.connector.Request -import org.apache.catalina.connector.Response -import org.apache.catalina.core.StandardHost -import org.apache.catalina.startup.Tomcat -import org.apache.catalina.valves.ErrorReportValve -import org.apache.catalina.valves.ValveBase -import org.apache.tomcat.JarScanFilter -import org.apache.tomcat.JarScanType -import spock.lang.Shared -import spock.lang.Unroll - -import javax.servlet.Servlet -import javax.servlet.ServletException -import java.nio.file.Files -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS -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.NOT_FOUND -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 org.junit.jupiter.api.Assumptions.assumeTrue - -@Unroll -abstract class TomcatServlet3Test extends AbstractServlet3Test { - - static final ServerEndpoint ACCESS_LOG_SUCCESS = new ServerEndpoint("ACCESS_LOG_SUCCESS", - "success?access-log=true", SUCCESS.status, SUCCESS.body, false) - static final ServerEndpoint ACCESS_LOG_ERROR = new ServerEndpoint("ACCESS_LOG_ERROR", - "error-status?access-log=true", ERROR.status, ERROR.body, false) - - @Override - Throwable expectedException() { - new ServletException(EXCEPTION.body) - } - - @Override - boolean hasResponseSpan(ServerEndpoint endpoint) { - endpoint == NOT_FOUND || super.hasResponseSpan(endpoint) - } - - @Override - void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) { - switch (endpoint) { - case NOT_FOUND: - sendErrorSpan(trace, index, parent) - break - } - super.responseSpan(trace, index, parent, method, endpoint) - } - - @Shared - def accessLogValue = new TestAccessLogValve() - - @Override - Tomcat startServer(int port) { - def tomcatServer = new Tomcat() - - def baseDir = Files.createTempDirectory("tomcat").toFile() - baseDir.deleteOnExit() - tomcatServer.setBaseDir(baseDir.getAbsolutePath()) - - tomcatServer.setPort(port) - tomcatServer.getConnector().enableLookups = true // get localhost instead of 127.0.0.1 - - File applicationDir = new File(baseDir, "/webapps/ROOT") - if (!applicationDir.exists()) { - applicationDir.mkdirs() - applicationDir.deleteOnExit() - } - Context servletContext = tomcatServer.addWebapp(contextPath, applicationDir.getAbsolutePath()) - // Speed up startup by disabling jar scanning: - servletContext.getJarScanner().setJarScanFilter(new JarScanFilter() { - @Override - boolean check(JarScanType jarScanType, String jarName) { - return false - } - }) - -// setupAuthentication(tomcatServer, servletContext) - setupServlets(servletContext) - - (tomcatServer.host as StandardHost).errorReportValveClass = ErrorHandlerValve.name - (tomcatServer.host as StandardHost).getPipeline().addValve(accessLogValue) - - tomcatServer.start() - - return tomcatServer - } - - def setup() { - accessLogValue.loggedIds.clear() - } - - @Override - void stopServer(Tomcat server) { - server.stop() - server.destroy() - } - - @Override - String getContextPath() { - return "/tomcat-context" - } - - @Override - void addServlet(Context servletContext, String path, Class servlet) { - String name = UUID.randomUUID() - Tomcat.addServlet(servletContext, name, servlet.newInstance()) - servletContext.addServletMappingDecoded(path, name) - } - - def "access log has ids for #count requests"() { - given: - def request = request(ACCESS_LOG_SUCCESS, method) - - when: - List responses = (1..count).collect { - return client.execute(request).aggregate().join() - } - - then: - responses.each { response -> - assert response.status().code() == ACCESS_LOG_SUCCESS.status - assert response.contentUtf8() == ACCESS_LOG_SUCCESS.body - } - - and: - assertTraces(count) { - accessLogValue.waitForLoggedIds(count) - assert accessLogValue.loggedIds.size() == count - def loggedTraces = accessLogValue.loggedIds*.first - def loggedSpans = accessLogValue.loggedIds*.second - - (0..count - 1).each { - trace(it, 2) { - serverSpan(it, 0, null, null, "GET", ACCESS_LOG_SUCCESS) - controllerSpan(it, 1, span(0)) - } - - assert loggedTraces.contains(traces[it][0].traceId) - assert loggedSpans.contains(traces[it][0].spanId) - } - } - - where: - method = "GET" - count << [1, 4] // make multiple requests. - } - - def "access log has ids for error request"() { - setup: - assumeTrue(testError()) - - def request = request(ACCESS_LOG_ERROR, method) - def response = client.execute(request).aggregate().join() - - expect: - response.status().code() == ACCESS_LOG_ERROR.status - response.contentUtf8() == ACCESS_LOG_ERROR.body - - and: - def spanCount = 2 - if (errorEndpointUsesSendError()) { - spanCount++ - } - assertTraces(1) { - trace(0, spanCount) { - serverSpan(it, 0, null, null, method, ACCESS_LOG_ERROR) - def spanIndex = 1 - controllerSpan(it, spanIndex, span(spanIndex - 1)) - spanIndex++ - if (errorEndpointUsesSendError()) { - sendErrorSpan(it, spanIndex, span(spanIndex - 1)) - spanIndex++ - } - } - - accessLogValue.waitForLoggedIds(1) - def (String traceId, String spanId) = accessLogValue.loggedIds[0] - assert traces[0][0].traceId == traceId - assert traces[0][0].spanId == spanId - } - - where: - method = "GET" - } - - // FIXME: Add authentication tests back in... -// private setupAuthentication(Tomcat server, Context servletContext) { -// // Login Config -// LoginConfig authConfig = new LoginConfig() -// authConfig.setAuthMethod("BASIC") -// -// // adding constraint with role "test" -// SecurityConstraint constraint = new SecurityConstraint() -// constraint.addAuthRole("role") -// -// // add constraint to a collection with pattern /second -// SecurityCollection collection = new SecurityCollection() -// collection.addPattern("/auth/*") -// constraint.addCollection(collection) -// -// servletContext.setLoginConfig(authConfig) -// // does the context need a auth role too? -// servletContext.addSecurityRole("role") -// servletContext.addConstraint(constraint) -// -// // add tomcat users to realm -// MemoryRealm realm = new MemoryRealm() { -// protected void startInternal() { -// credentialHandler = new MessageDigestCredentialHandler() -// setState(LifecycleState.STARTING) -// } -// } -// realm.addUser(user, pass, "role") -// server.getEngine().setRealm(realm) -// -// servletContext.setLoginConfig(authConfig) -// } -} - -class ErrorHandlerValve extends ErrorReportValve { - @Override - protected void report(Request request, Response response, Throwable t) { - if (response.getStatus() < 400 || response.getContentWritten() > 0 || !response.isError()) { - return - } - try { - response.writer.print(t ? t.cause.message : response.message) - } catch (IOException e) { - e.printStackTrace() - } - } -} - -class TestAccessLogValve extends ValveBase implements AccessLog { - final List> loggedIds = [] - - TestAccessLogValve() { - super(true) - } - - void log(Request request, Response response, long time) { - if (request.getParameter("access-log") == null) { - return - } - - synchronized (loggedIds) { - loggedIds.add(new Tuple2(request.getAttribute("trace_id"), - request.getAttribute("span_id"))) - loggedIds.notifyAll() - } - } - - void waitForLoggedIds(int expected) { - def timeout = TimeUnit.SECONDS.toMillis(20) - def startTime = System.currentTimeMillis() - def endTime = startTime + timeout - def toWait = timeout - synchronized (loggedIds) { - while (loggedIds.size() < expected && toWait > 0) { - loggedIds.wait(toWait) - toWait = endTime - System.currentTimeMillis() - } - if (toWait <= 0) { - throw new TimeoutException("Timeout waiting for " + expected + " access log ids, got " + loggedIds.size()) - } - } - } - - @Override - void setRequestAttributesEnabled(boolean requestAttributesEnabled) { - } - - @Override - boolean getRequestAttributesEnabled() { - return false - } - - @Override - void invoke(Request request, Response response) throws IOException, ServletException { - getNext().invoke(request, response) - } -} - -class TomcatServlet3TestSync extends TomcatServlet3Test { - - @Override - Class servlet() { - TestServlet3.Sync - } -} - -class TomcatServlet3TestAsync extends TomcatServlet3Test { - - @Override - Class servlet() { - TestServlet3.Async - } - - @Override - boolean errorEndpointUsesSendError() { - false - } -} - -class TomcatServlet3TestFakeAsync extends TomcatServlet3Test { - - @Override - Class servlet() { - TestServlet3.FakeAsync - } -} - -class TomcatServlet3TestForward extends TomcatDispatchTest { - @Override - Class servlet() { - TestServlet3.Sync // dispatch to sync servlet - } - - @Override - boolean testNotFound() { - false - } - - @Override - protected void setupServlets(Context context) { - super.setupServlets(context) - - addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Forward) - addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Forward) - } -} - -class TomcatServlet3TestInclude extends TomcatDispatchTest { - @Override - Class servlet() { - TestServlet3.Sync // dispatch to sync servlet - } - - @Override - boolean testNotFound() { - false - } - - @Override - boolean testRedirect() { - false - } - - @Override - boolean testCapturedHttpHeaders() { - false - } - - @Override - boolean testError() { - false - } - - @Override - protected void setupServlets(Context context) { - super.setupServlets(context) - - addServlet(context, "/dispatch" + SUCCESS.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + QUERY_PARAM.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + REDIRECT.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + ERROR.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + EXCEPTION.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + AUTH_REQUIRED.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + INDEXED_CHILD.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, RequestDispatcherServlet.Include) - addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, RequestDispatcherServlet.Include) - } -} - -class TomcatServlet3TestDispatchImmediate extends TomcatDispatchTest { - @Override - Class servlet() { - TestServlet3.Sync - } - - @Override - boolean testNotFound() { - false - } - - @Override - protected void setupServlets(Context context) { - super.setupServlets(context) - - addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + REDIRECT.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchImmediate) - addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) - } -} - -class TomcatServlet3TestDispatchAsync extends TomcatDispatchTest { - @Override - Class servlet() { - TestServlet3.Async - } - - @Override - protected void setupServlets(Context context) { - super.setupServlets(context) - - addServlet(context, "/dispatch" + SUCCESS.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + ERROR.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + REDIRECT.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + CAPTURE_HEADERS.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + CAPTURE_PARAMETERS.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + HTML_PRINT_WRITER.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.path, TestServlet3.DispatchAsync) - addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive) - } - - @Override - boolean errorEndpointUsesSendError() { - false - } -} - -abstract class TomcatDispatchTest extends TomcatServlet3Test { - @Override - URI buildAddress() { - return new URI("http://localhost:$port$contextPath/dispatch/") - } -} diff --git a/instrumentation/servlet/servlet-3.0/testing/build.gradle.kts b/instrumentation/servlet/servlet-3.0/testing/build.gradle.kts new file mode 100644 index 000000000000..012f2f49ca0d --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + id("otel.javaagent-testing") +} + +dependencies { + testImplementation(project(":instrumentation:servlet:servlet-3.0:javaagent")) + + compileOnly("javax.servlet:javax.servlet-api:3.0.1") + + testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent")) + testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap")) + + testLibrary("org.eclipse.jetty:jetty-server:8.0.0.v20110901") + testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901") + testLibrary("org.apache.tomcat.embed:tomcat-embed-core:8.0.41") + testLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:8.0.41") + + latestDepTestLibrary("org.eclipse.jetty:jetty-server:10.+") // see servlet-5.0 module + latestDepTestLibrary("org.eclipse.jetty:jetty-servlet:10.+") // see servlet-5.0 module + + latestDepTestLibrary("org.apache.tomcat.embed:tomcat-embed-core:9.+") // see servlet-5.0 module + latestDepTestLibrary("org.apache.tomcat.embed:tomcat-embed-jasper:9.+") // see servlet-5.0 module +} + +tasks { + withType().configureEach { + jvmArgs("-Dotel.instrumentation.servlet.experimental.capture-request-parameters=test-parameter") + // required on jdk17 + jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + } +} + +// Servlet 3.0 in latest Jetty versions requires Java 11 +// However, projects that depend on this module are still be using Java 8 in testLatestDeps mode +// Therefore, we need a separate project for servlet 3.0 tests +val latestDepTest = findProperty("testLatestDeps") as Boolean + +if (latestDepTest) { + otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_11) + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java new file mode 100644 index 000000000000..882ec82e6c39 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/AbstractServlet3Test.java @@ -0,0 +1,226 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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.NOT_FOUND; +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.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.api.internal.HttpConstants; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import javax.servlet.Servlet; +import org.junit.jupiter.api.Test; + +public abstract class AbstractServlet3Test extends AbstractHttpServerTest { + + public static final ServerEndpoint HTML_PRINT_WRITER = + new ServerEndpoint( + "HTML_PRINT_WRITER", + "htmlPrintWriter", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""); + public static final ServerEndpoint HTML_SERVLET_OUTPUT_STREAM = + new ServerEndpoint( + "HTML_SERVLET_OUTPUT_STREAM", + "htmlServletOutputStream", + 200, + "\n" + + "\n" + + "\n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""); + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestCaptureRequestParameters(true); + options.setHasResponseCustomizer(e -> true); + options.setHasResponseSpan(this::hasResponseSpan); + } + + protected boolean hasResponseSpan(ServerEndpoint endpoint) { + return endpoint.equals(REDIRECT) || (endpoint.equals(ERROR) && errorEndpointUsesSendError()); + } + + public abstract Class servlet(); + + public abstract void addServlet(CONTEXT context, String path, Class servlet) + throws Exception; + + protected void setupServlets(CONTEXT context) throws Exception { + Class servlet = servlet(); + + addServlet(context, SUCCESS.getPath(), servlet); + addServlet(context, QUERY_PARAM.getPath(), servlet); + addServlet(context, ERROR.getPath(), servlet); + addServlet(context, EXCEPTION.getPath(), servlet); + addServlet(context, REDIRECT.getPath(), servlet); + addServlet(context, AUTH_REQUIRED.getPath(), servlet); + addServlet(context, INDEXED_CHILD.getPath(), servlet); + addServlet(context, CAPTURE_HEADERS.getPath(), servlet); + addServlet(context, CAPTURE_PARAMETERS.getPath(), servlet); + addServlet(context, HTML_PRINT_WRITER.getPath(), servlet); + addServlet(context, HTML_SERVLET_OUTPUT_STREAM.getPath(), servlet); + } + + @Override + public String expectedHttpRoute(ServerEndpoint endpoint, String method) { + // no need to compute route if we're not expecting it + if (!hasHttpRouteAttribute(endpoint)) { + return null; + } + + if (method.equals(HttpConstants._OTHER)) { + return getContextPath() + endpoint.getPath(); + } + + if (NOT_FOUND.equals(endpoint)) { + return getContextPath() + "/*"; + } else { + return super.expectedHttpRoute(endpoint, method); + } + } + + public boolean errorEndpointUsesSendError() { + return true; + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, SpanData parentSpan, String method, ServerEndpoint endpoint) { + switch (endpoint.name()) { + case "REDIRECT": + SpanDataAssert spanDataAssert = + span.satisfies(s -> assertThat(s.getName()).matches(".*\\.sendRedirect")) + .hasKind(SpanKind.INTERNAL); + if (assertParentOnRedirect()) { + return spanDataAssert.hasParent(parentSpan); + } + return spanDataAssert; + case "ERROR": + return span.satisfies(s -> assertThat(s.getName()).matches(".*\\.sendError")) + .hasKind(SpanKind.INTERNAL) + .hasParent(parentSpan); + default: + break; + } + return span; + } + + protected boolean assertParentOnRedirect() { + return true; + } + + @Test + void snippetInjectionWithServletOutputStream() { + ExperimentalSnippetHolder.setSnippet( + "\n "); + AggregatedHttpRequest request = request(HTML_SERVLET_OUTPUT_STREAM, "GET"); + AggregatedHttpResponse response = client.execute(request).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(HTML_SERVLET_OUTPUT_STREAM.getStatus()); + String result = + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""; + assertThat(response.contentUtf8()).isEqualTo(result); + assertThat(response.headers().contentLength()).isEqualTo(result.length()); + + ExperimentalSnippetHolder.setSnippet(""); + + String expectedRoute = expectedHttpRoute(HTML_SERVLET_OUTPUT_STREAM, "GET"); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET" + (expectedRoute != null ? " " + expectedRoute : "")) + .hasKind(SpanKind.SERVER) + .hasNoParent(), + span -> + span.hasName("controller") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } + + @Test + void snippetInjectionWithPrintWriter() { + ExperimentalSnippetHolder.setSnippet("\n "); + AggregatedHttpRequest request = request(HTML_PRINT_WRITER, "GET"); + AggregatedHttpResponse response = client.execute(request).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(HTML_PRINT_WRITER.getStatus()); + String result = + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Title\n" + + "\n" + + "\n" + + "

test works

\n" + + "\n" + + ""; + + assertThat(response.contentUtf8()).isEqualTo(result); + assertThat(response.headers().contentLength()).isEqualTo(result.length()); + + ExperimentalSnippetHolder.setSnippet(""); + + String expectedRoute = expectedHttpRoute(HTML_PRINT_WRITER, "GET"); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET" + (expectedRoute != null ? " " + expectedRoute : "")) + .hasKind(SpanKind.SERVER) + .hasNoParent(), + span -> + span.hasName("controller") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/HttpServletResponseTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/HttpServletResponseTest.java new file mode 100644 index 000000000000..a71c4a239768 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/HttpServletResponseTest.java @@ -0,0 +1,296 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0; + +import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Collections; +import java.util.Locale; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class HttpServletResponseTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + private final TestResponse response = new TestResponse(); + private final HttpServletRequest request = mock(HttpServletRequest.class); + + @BeforeEach + void setUp() throws ServletException, IOException { + when(request.getMethod()).thenReturn("GET"); + when(request.getProtocol()).thenReturn("TEST"); + when(request.getHeaderNames()).thenReturn(Collections.emptyEnumeration()); + when(request.getAttributeNames()).thenReturn(Collections.emptyEnumeration()); + + HttpServlet servlet = new HttpServlet() {}; + // We need to call service so HttpServletAdvice can link the request to the response. + servlet.service(request, response); + testing.clearData(); + } + + @Test + void testSendNoParent() throws IOException { + response.sendError(0); + response.sendError(0, ""); + response.sendRedirect(""); + + assertThat(testing.spans()).isEmpty(); + } + + @Test + void testSendWithParent() throws IOException { + runWithSpan( + "parent", + () -> { + response.sendError(0); + response.sendError(0, ""); + response.sendRedirect(""); + }); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("TestResponse.sendError") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + CodeIncubatingAttributes.CODE_NAMESPACE, + TestResponse.class.getName()), + equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "sendError")), + span -> + span.hasName("TestResponse.sendError") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + CodeIncubatingAttributes.CODE_NAMESPACE, + TestResponse.class.getName()), + equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "sendError")), + span -> + span.hasName("TestResponse.sendRedirect") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + CodeIncubatingAttributes.CODE_NAMESPACE, + TestResponse.class.getName()), + equalTo(CodeIncubatingAttributes.CODE_FUNCTION, "sendRedirect")))); + } + + @Test + void testSendWithException() throws ServletException, IOException { + TestResponse response = + new TestResponse() { + @Override + public void sendRedirect(String s) { + throw new RuntimeException("some error"); + } + }; + HttpServlet servlet = new HttpServlet() {}; + // We need to call service so HttpServletAdvice can link the request to the response. + servlet.service(request, response); + testing.clearData(); + + assertThatCode(() -> runWithSpan("parent", () -> response.sendRedirect(""))) + .isInstanceOf(RuntimeException.class) + .hasMessage("some error"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("parent") + .hasKind(SpanKind.INTERNAL) + .hasNoParent() + .hasStatus(StatusData.error()) + .hasException(new RuntimeException("some error")), + span -> + span.hasName("HttpServletResponseTest$2.sendRedirect") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasStatus(StatusData.error()) + .hasException(new RuntimeException("some error")))); + } + + /** Tests deprecated methods */ + public static class TestResponse implements HttpServletResponse { + @Override + public void addCookie(Cookie cookie) {} + + @Override + public boolean containsHeader(String s) { + return false; + } + + @Override + public String encodeURL(String s) { + return null; + } + + @Override + public String encodeRedirectURL(String s) { + return null; + } + + // test deprecated methods + @SuppressWarnings("deprecation") + @Override + public String encodeUrl(String s) { + return null; + } + + // test deprecated methods + @SuppressWarnings("deprecation") + @Override + public String encodeRedirectUrl(String s) { + return null; + } + + @Override + public void sendError(int i, String s) {} + + @Override + public void sendError(int i) {} + + @Override + public void sendRedirect(String s) throws IOException {} + + @Override + public void setDateHeader(String s, long l) {} + + @Override + public void addDateHeader(String s, long l) {} + + @Override + public void setHeader(String s, String s1) {} + + @Override + public void addHeader(String s, String s1) {} + + @Override + public void setIntHeader(String s, int i) {} + + @Override + public void addIntHeader(String s, int i) {} + + @Override + public void setStatus(int i) {} + + // test deprecated methods + @SuppressWarnings("deprecation") + @Override + public void setStatus(int i, String s) {} + + @Override + public int getStatus() { + return 0; + } + + @Override + public String getHeader(String s) { + return null; + } + + @SuppressWarnings("ReturnsNullCollection") + @Override + public Collection getHeaders(String s) { + return null; + } + + @SuppressWarnings("ReturnsNullCollection") + @Override + public Collection getHeaderNames() { + return null; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletOutputStream getOutputStream() { + return null; + } + + @Override + public PrintWriter getWriter() { + return null; + } + + @Override + public void setCharacterEncoding(String charset) {} + + @Override + public void setContentLength(int i) {} + + @Override + public void setContentLengthLong(long l) {} + + @Override + public void setContentType(String s) {} + + @Override + public void setBufferSize(int i) {} + + @Override + public int getBufferSize() { + return 0; + } + + @Override + public void flushBuffer() {} + + @Override + public void resetBuffer() {} + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public void reset() {} + + @Override + public void setLocale(Locale locale) {} + + @Override + public Locale getLocale() { + return null; + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java new file mode 100644 index 000000000000..64f2fe68f1ad --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3AsyncTest.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; + +public class JettyServlet3AsyncTest extends JettyServlet3Test { + + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } + + @Override + public boolean isAsyncTest() { + return true; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java new file mode 100644 index 000000000000..4db579066e5e --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3FakeAsyncTest.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; + +public class JettyServlet3FakeAsyncTest extends JettyServlet3Test { + @Override + public Class servlet() { + return TestServlet3.FakeAsync.class; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java new file mode 100644 index 000000000000..9052044c42b5 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3SyncTest.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; + +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; + +public class JettyServlet3SyncTest extends JettyServlet3Test { + @Override + public Class servlet() { + return TestServlet3.Sync.class; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java new file mode 100644 index 000000000000..f6679c940f8c --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServlet3Test.java @@ -0,0 +1,116 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +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 io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.IOException; +import java.io.Writer; +import java.net.InetSocketAddress; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.junit.jupiter.api.extension.RegisterExtension; + +public abstract class JettyServlet3Test + extends AbstractServlet3Test { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + static final boolean IS_BEFORE_94 = isBefore94(); + + public static boolean isBefore94() { + String[] version = Server.getVersion().split("\\."); + int major = Integer.parseInt(version[0]); + int minor = Integer.parseInt(version[1]); + return major < 9 || (major == 9 && minor < 4); + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestNotFound(false); + options.setExpectedException(new ServletException(EXCEPTION.getBody())); + options.setContextPath("/jetty-context"); + options.setVerifyServerSpanEndTime(!isAsyncTest()); + } + + @Override + protected boolean hasResponseSpan(ServerEndpoint endpoint) { + return (IS_BEFORE_94 && endpoint == EXCEPTION && !isAsyncTest()) + || super.hasResponseSpan(endpoint); + } + + public boolean isAsyncTest() { + return false; + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, + SpanData controllerSpan, + SpanData handlerSpan, + String method, + ServerEndpoint endpoint) { + if (IS_BEFORE_94 && endpoint.equals(EXCEPTION)) { + span.satisfies(it -> assertThat(it.getName()).matches(".*\\.sendError")) + .hasKind(SpanKind.INTERNAL) + .hasParent(handlerSpan); + } + + return super.assertResponseSpan(span, controllerSpan, handlerSpan, method, endpoint); + } + + @Override + protected Server setupServer() throws Exception { + Server jettyServer = new Server(new InetSocketAddress("localhost", port)); + + ServletContextHandler servletContext = new ServletContextHandler(null, getContextPath()); + servletContext.setErrorHandler( + new ErrorHandler() { + @Override + protected void handleErrorPage( + HttpServletRequest request, Writer writer, int code, String message) + throws IOException { + Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception"); + writer.write(th != null ? th.getMessage() : message); + } + }); + setupServlets(servletContext); + jettyServer.setHandler(servletContext); + + jettyServer.start(); + + return jettyServer; + } + + @Override + public void stopServer(Server server) throws Exception { + server.stop(); + server.destroy(); + } + + @Override + public void addServlet( + ServletContextHandler servletContext, String path, Class servlet) + throws Exception { + servletContext.addServlet(servlet, path); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServletHandlerTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServletHandlerTest.java new file mode 100644 index 000000000000..2f8d5591c66c --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/JettyServletHandlerTest.java @@ -0,0 +1,115 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +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 io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.semconv.HttpAttributes; +import java.io.IOException; +import java.io.Writer; +import java.util.HashSet; +import java.util.Set; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.servlet.ServletHandler; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class JettyServletHandlerTest extends AbstractServlet3Test { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setContextPath(""); + options.setTestNotFound(false); + options.setExpectedException(new ServletException(EXCEPTION.getBody())); + options.setHttpAttributes( + serverEndpoint -> { + Set> attributes = + new HashSet<>(HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES); + attributes.remove(HttpAttributes.HTTP_ROUTE); + return attributes; + }); + } + + @Override + public boolean hasResponseSpan(ServerEndpoint endpoint) { + return (JettyServlet3Test.IS_BEFORE_94 && endpoint.equals(EXCEPTION)) + || super.hasResponseSpan(endpoint); + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, + SpanData controllerSpan, + SpanData handlerSpan, + String method, + ServerEndpoint endpoint) { + + if (JettyServlet3Test.IS_BEFORE_94 && endpoint.equals(EXCEPTION)) { + span.satisfies(it -> assertThat(it.getName()).matches(".*\\.sendError")) + .hasKind(SpanKind.INTERNAL) + .hasParent(handlerSpan); + } + + return super.assertResponseSpan(span, controllerSpan, handlerSpan, method, endpoint); + } + + @Override + protected Server setupServer() throws Exception { + Server server = new Server(port); + ServletHandler handler = new ServletHandler(); + server.setHandler(handler); + setupServlets(handler); + server.addBean( + new ErrorHandler() { + @Override + protected void handleErrorPage( + HttpServletRequest request, Writer writer, int code, String message) + throws IOException { + Throwable th = (Throwable) request.getAttribute("javax.servlet.error.exception"); + writer.write(th != null ? th.getMessage() : message); + } + }); + server.start(); + return server; + } + + @Override + public void addServlet( + ServletHandler servletHandler, String path, Class servlet) + throws Exception { + servletHandler.addServletWithMapping(servlet, path); + } + + @Override + public void stopServer(Server server) throws Exception { + server.stop(); + server.destroy(); + } + + @Override + public Class servlet() { + return TestServlet3.Sync.class; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java new file mode 100644 index 000000000000..d55549190cb7 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyDispatchTest.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.JettyServlet3Test; + +public abstract class JettyDispatchTest extends JettyServlet3Test { + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setContextPath(getContextPath() + "/dispatch"); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java new file mode 100644 index 000000000000..bfa2548a1080 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchAsyncTest.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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 io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; +import org.eclipse.jetty.servlet.ServletContextHandler; + +public class JettyServlet3DispatchAsyncTest extends JettyDispatchTest { + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + public boolean isAsyncTest() { + return true; + } + + @Override + protected void setupServlets(ServletContextHandler context) throws Exception { + super.setupServlets(context); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + SUCCESS.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + QUERY_PARAM.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + ERROR.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + AUTH_REQUIRED.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + CAPTURE_HEADERS.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, "/dispatch" + CAPTURE_PARAMETERS.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + INDEXED_CHILD.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive.class); + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java new file mode 100644 index 000000000000..1255096e526e --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3DispatchImmediateTest.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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 io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; +import org.eclipse.jetty.servlet.ServletContextHandler; + +public class JettyServlet3DispatchImmediateTest extends JettyDispatchTest { + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + public boolean isAsyncTest() { + return true; + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } + + @Override + protected void setupServlets(ServletContextHandler context) throws Exception { + super.setupServlets(context); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + SUCCESS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + QUERY_PARAM.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + ERROR.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + CAPTURE_HEADERS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + CAPTURE_PARAMETERS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java new file mode 100644 index 000000000000..f526519127c6 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3ForwardTest.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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 io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.RequestDispatcherServlet; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; +import org.eclipse.jetty.servlet.ServletContextHandler; + +public class JettyServlet3ForwardTest extends JettyDispatchTest { + @Override + public Class servlet() { + return TestServlet3.Sync.class; // dispatch to sync servlet + } + + @Override + protected void setupServlets(ServletContextHandler context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + QUERY_PARAM.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + ERROR.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + CAPTURE_HEADERS.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, + "/dispatch" + CAPTURE_PARAMETERS.getPath(), + RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), RequestDispatcherServlet.Forward.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java new file mode 100644 index 000000000000..d30b5e8fd636 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/jetty/dispatch/JettyServlet3IncludeTest.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.jetty.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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 io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.RequestDispatcherServlet; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; +import org.eclipse.jetty.servlet.ServletContextHandler; + +public class JettyServlet3IncludeTest extends JettyDispatchTest { + @Override + public Class servlet() { + return TestServlet3.Sync.class; // dispatch to sync servlet + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestRedirect(false); + options.setTestCaptureHttpHeaders(false); + options.setTestError(false); + } + + @Override + protected void setupServlets(ServletContextHandler context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + QUERY_PARAM.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + ERROR.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, + "/dispatch" + CAPTURE_PARAMETERS.getPath(), + RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), RequestDispatcherServlet.Include.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/AbstractServlet3MappingTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/AbstractServlet3MappingTest.java new file mode 100644 index 000000000000..15860e9d026f --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/AbstractServlet3MappingTest.java @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.mapping; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import javax.servlet.Servlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +abstract class AbstractServlet3MappingTest + extends AbstractHttpServerUsingTest { + + @RegisterExtension + private static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @BeforeAll + void setup() { + startServer(); + } + + @AfterAll + void cleanup() { + cleanupServer(); + } + + public abstract void addServlet(CONTEXT context, String path, Class servlet) + throws Exception; + + protected void setupServlets(CONTEXT context) throws Exception { + addServlet(context, "/prefix/*", TestServlet.class); + addServlet(context, "*.suffix", TestServlet.class); + } + + @ParameterizedTest + @CsvSource({ + "prefix, /prefix/*, true", + "prefix/, /prefix/*, true", + "prefix/a, /prefix/*, true", + "prefixa, /*, false", + "a.suffix, /*.suffix, true", + ".suffix, /*.suffix, true", + "suffix, /*, false", + }) + void testPath(String path, String route, boolean success) { + + AggregatedHttpResponse response = + client.get(address.resolve(path).toString()).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(success ? 200 : 404); + + testing.waitAndAssertTraces( + trace -> { + List> assertions = new ArrayList<>(); + assertions.add( + span -> + span.hasName("GET " + getContextPath() + route) + .hasKind(SpanKind.SERVER) + .hasStatus( + !success && response.status().code() >= 500 + ? StatusData.error() + : StatusData.unset())); + if (!success) { + assertions.add(span -> {}); + } + + trace.hasSpansSatisfyingExactly(assertions); + }); + } + + public static class TestServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + response.getWriter().write("Ok"); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/JettyServlet3MappingTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/JettyServlet3MappingTest.java new file mode 100644 index 000000000000..5cfc4b2ecbed --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/JettyServlet3MappingTest.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.mapping; + +import java.io.IOException; +import javax.servlet.Servlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; + +class JettyServlet3MappingTest extends AbstractServlet3MappingTest { + + @Override + protected Server setupServer() throws Exception { + Server server = new Server(port); + ServletContextHandler handler = new ServletContextHandler(null, getContextPath()); + setupServlets(handler); + server.setHandler(handler); + server.start(); + return server; + } + + @Override + public void stopServer(Server server) throws Exception { + server.stop(); + server.destroy(); + } + + @Override + protected void setupServlets(ServletContextHandler handler) throws Exception { + super.setupServlets(handler); + + addServlet(handler, "/", DefaultServlet.class); + } + + @Override + public void addServlet( + ServletContextHandler servletContextHandler, String path, Class servlet) { + servletContextHandler.addServlet(servlet, path); + } + + @Override + public String getContextPath() { + return "/jetty-context"; + } + + public static class DefaultServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + response.sendError(404); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3FilterMappingTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3FilterMappingTest.java new file mode 100644 index 000000000000..75b8969f533b --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3FilterMappingTest.java @@ -0,0 +1,114 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.mapping; + +import java.io.IOException; +import java.util.UUID; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.catalina.Context; +import org.apache.tomcat.util.descriptor.web.FilterDef; +import org.apache.tomcat.util.descriptor.web.FilterMap; + +abstract class TomcatServlet3FilterMappingTest extends TomcatServlet3MappingTest { + public void addFilter(Context servletContext, String path, Class filter) + throws Exception { + String name = UUID.randomUUID().toString(); + FilterDef filterDef = new FilterDef(); + filterDef.setFilter(filter.getConstructor().newInstance()); + filterDef.setFilterName(name); + servletContext.addFilterDef(filterDef); + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName(name); + filterMap.addURLPattern(path); + servletContext.addFilterMap(filterMap); + } + + public void addFilterWithServletName( + Context servletContext, String servletName, Class filter) throws Exception { + String name = UUID.randomUUID().toString(); + FilterDef filterDef = new FilterDef(); + filterDef.setFilter(filter.getConstructor().newInstance()); + filterDef.setFilterName(name); + servletContext.addFilterDef(filterDef); + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName(name); + filterMap.addServletName(servletName); + servletContext.addFilterMap(filterMap); + } + + public static class TestFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) {} + + @Override + public void doFilter( + ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + if (servletRequest.getAttribute("firstFilterCalled") != null) { + servletRequest.setAttribute("testFilterCalled", Boolean.TRUE); + filterChain.doFilter(servletRequest, servletResponse); + } else { + throw new IllegalStateException("First filter should have been called."); + } + } + + @Override + public void destroy() {} + } + + public static class FirstFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) {} + + @Override + public void doFilter( + ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + servletRequest.setAttribute("firstFilterCalled", Boolean.TRUE); + filterChain.doFilter(servletRequest, servletResponse); + } + + @Override + public void destroy() {} + } + + public static class LastFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) {} + + @Override + public void doFilter( + ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + if (servletRequest.getAttribute("testFilterCalled") != null) { + HttpServletResponse response = (HttpServletResponse) servletResponse; + response.getWriter().write("Ok"); + response.setStatus(HttpServletResponse.SC_OK); + } else { + filterChain.doFilter(servletRequest, servletResponse); + } + } + + @Override + public void destroy() {} + } + + public static class DefaultServlet extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + throw new IllegalStateException( + "Servlet should not have been called, filter should have handled the request."); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3FilterServletNameMappingTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3FilterServletNameMappingTest.java new file mode 100644 index 000000000000..fc66fb4f69df --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3FilterServletNameMappingTest.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.mapping; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; + +class TomcatServlet3FilterServletNameMappingTest extends TomcatServlet3FilterMappingTest { + @Override + protected void setupServlets(Context context) throws Exception { + Tomcat.addServlet(context, "prefix-servlet", new DefaultServlet()); + context.addServletMappingDecoded("/prefix/*", "prefix-servlet"); + Tomcat.addServlet(context, "suffix-servlet", new DefaultServlet()); + context.addServletMappingDecoded("*.suffix", "suffix-servlet"); + + addFilter(context, "/*", FirstFilter.class); + addFilterWithServletName(context, "prefix-servlet", TestFilter.class); + addFilterWithServletName(context, "suffix-servlet", TestFilter.class); + addFilterWithServletName(context, "prefix-servlet", LastFilter.class); + addFilterWithServletName(context, "suffix-servlet", LastFilter.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3FilterUrlPatternMappingTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3FilterUrlPatternMappingTest.java new file mode 100644 index 000000000000..1ede84cb6337 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3FilterUrlPatternMappingTest.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.mapping; + +import org.apache.catalina.Context; + +class TomcatServlet3FilterUrlPatternMappingTest extends TomcatServlet3FilterMappingTest { + @Override + protected void setupServlets(Context context) throws Exception { + addFilter(context, "/*", FirstFilter.class); + addFilter(context, "/prefix/*", TestFilter.class); + addFilter(context, "*.suffix", TestFilter.class); + addFilter(context, "/*", LastFilter.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3MappingTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3MappingTest.java new file mode 100644 index 000000000000..400877d8a1ea --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/mapping/TomcatServlet3MappingTest.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.mapping; + +import java.io.File; +import java.nio.file.Files; +import java.util.UUID; +import javax.servlet.Servlet; +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.startup.Tomcat; + +class TomcatServlet3MappingTest extends AbstractServlet3MappingTest { + @Override + protected Tomcat setupServer() throws Exception { + Tomcat tomcatServer = new Tomcat(); + + File baseDir = Files.createTempDirectory("tomcat").toFile(); + baseDir.deleteOnExit(); + tomcatServer.setBaseDir(baseDir.getAbsolutePath()); + + tomcatServer.setPort(port); + tomcatServer.getConnector().setEnableLookups(true); // get localhost instead of 127.0.0.1 + + File applicationDir = new File(baseDir, "/webapps/ROOT"); + if (!applicationDir.exists()) { + applicationDir.mkdirs(); + applicationDir.deleteOnExit(); + } + + Context servletContext = + tomcatServer.addWebapp(getContextPath(), applicationDir.getAbsolutePath()); + // Speed up startup by disabling jar scanning: + servletContext.getJarScanner().setJarScanFilter((jarScanType, jarName) -> false); + + setupServlets(servletContext); + + tomcatServer.start(); + return tomcatServer; + } + + @Override + public void stopServer(Tomcat server) throws LifecycleException { + server.stop(); + server.destroy(); + } + + @Override + public void addServlet(Context context, String path, Class servlet) + throws Exception { + String name = UUID.randomUUID().toString(); + Tomcat.addServlet(context, name, servlet.getConstructor().newInstance()); + context.addServletMappingDecoded(path, name); + } + + @Override + public String getContextPath() { + return "/tomcat-context"; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java new file mode 100644 index 000000000000..e65e08c1e6e5 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/ErrorHandlerValve.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import java.io.IOException; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.valves.ErrorReportValve; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ErrorHandlerValve extends ErrorReportValve { + + private static final Logger logger = LoggerFactory.getLogger(ErrorHandlerValve.class); + + @Override + protected void report(Request request, Response response, Throwable t) { + if (response.getStatus() < 400 || response.getContentWritten() > 0 || !response.isError()) { + return; + } + + try { + response.getWriter().print(t != null ? t.getCause().getMessage() : response.getMessage()); + } catch (IOException e) { + logger.error("Failed to write error response", e); + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/RequestDispatcherServlet.java similarity index 93% rename from instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java rename to instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/RequestDispatcherServlet.java index 63391a170868..27a1973d28b8 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/test/java/RequestDispatcherServlet.java +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/RequestDispatcherServlet.java @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; @@ -13,9 +15,6 @@ import javax.servlet.http.HttpServletResponse; public class RequestDispatcherServlet { - /* There's something about the getRequestDispatcher call that breaks horribly when these classes - * are written in groovy. - */ @WebServlet(asyncSupported = true) public static class Forward extends HttpServlet { diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java new file mode 100644 index 000000000000..176a19c7df80 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestAccessLogValve.java @@ -0,0 +1,81 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import java.io.IOException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.ServletException; +import org.apache.catalina.AccessLog; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.valves.ValveBase; + +class TestAccessLogValve extends ValveBase implements AccessLog { + + public final List> getLoggedIds() { + return loggedIds; + } + + private final List> loggedIds = new ArrayList<>(); + + public TestAccessLogValve() { + super(true); + } + + @Override + public void log(Request request, Response response, long time) { + if (request.getParameter("access-log") == null) { + return; + } + + synchronized (loggedIds) { + loggedIds.add( + new AbstractMap.SimpleEntry<>( + request.getAttribute("trace_id").toString(), + request.getAttribute("span_id").toString())); + loggedIds.notifyAll(); + } + } + + public void waitForLoggedIds(int expected) { + long timeout = TimeUnit.SECONDS.toMillis(20); + long startTime = System.currentTimeMillis(); + long endTime = startTime + timeout; + long toWait = timeout; + synchronized (loggedIds) { + while (loggedIds.size() < expected && toWait > 0) { + try { + loggedIds.wait(toWait); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + toWait = endTime - System.currentTimeMillis(); + } + + if (toWait <= 0) { + throw new RuntimeException( + "Timeout waiting for " + expected + " access log ids, got " + loggedIds.size()); + } + } + } + + @Override + public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {} + + @Override + public boolean getRequestAttributesEnabled() { + return false; + } + + @Override + public void invoke(Request request, Response response) throws IOException, ServletException { + getNext().invoke(request, response); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestServlet3.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestServlet3.java new file mode 100644 index 000000000000..a2d2065752dd --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TestServlet3.java @@ -0,0 +1,289 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_PRINT_WRITER; +import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test.HTML_SERVLET_OUTPUT_STREAM; + +import io.opentelemetry.instrumentation.test.base.HttpServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import javax.servlet.AsyncContext; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class TestServlet3 { + + private TestServlet3() {} + + @WebServlet + public static class Sync extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + String servletPath = (String) req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + if (servletPath == null) { + servletPath = req.getServletPath(); + } + + ServerEndpoint endpoint = ServerEndpoint.forPath(servletPath); + HttpServerTest.controller( + endpoint, + () -> { + resp.setContentType("text/plain"); + if (SUCCESS.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (INDEXED_CHILD.equals(endpoint)) { + endpoint.collectSpanAttributes(req::getParameter); + resp.setStatus(endpoint.getStatus()); + } else if (QUERY_PARAM.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(req.getQueryString()); + } else if (REDIRECT.equals(endpoint)) { + resp.sendRedirect(endpoint.getBody()); + } else if (CAPTURE_HEADERS.equals(endpoint)) { + resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request")); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (CAPTURE_PARAMETERS.equals(endpoint)) { + req.setCharacterEncoding("UTF8"); + String value = req.getParameter("test-parameter"); + if (!value.equals("test value õäöü")) { + throw new ServletException( + "request parameter does not have expected value " + value); + } + + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (ERROR.equals(endpoint)) { + resp.sendError(endpoint.getStatus(), endpoint.getBody()); + } else if (EXCEPTION.equals(endpoint)) { + throw new ServletException(endpoint.getBody()); + } else if (HTML_PRINT_WRITER.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.setContentLength(endpoint.getBody().length()); + resp.getWriter().print(endpoint.getBody()); + } else if (HTML_SERVLET_OUTPUT_STREAM.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.setContentLength(endpoint.getBody().length()); + byte[] body = endpoint.getBody().getBytes(StandardCharsets.UTF_8); + resp.getOutputStream().write(body, 0, body.length); + } + return null; + }); + } + } + + @WebServlet(asyncSupported = true) + public static class Async extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + ServerEndpoint endpoint = ServerEndpoint.forPath(req.getServletPath()); + CountDownLatch latch = new CountDownLatch(1); + AsyncContext context = req.startAsync(); + if (endpoint.equals(EXCEPTION)) { + context.setTimeout(5000); + } + + context.start( + () -> { + try { + HttpServerTest.controller( + endpoint, + () -> { + resp.setContentType("text/plain"); + if (SUCCESS.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (INDEXED_CHILD.equals(endpoint)) { + endpoint.collectSpanAttributes(req::getParameter); + resp.setStatus(endpoint.getStatus()); + context.complete(); + } else if (QUERY_PARAM.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(req.getQueryString()); + context.complete(); + } else if (REDIRECT.equals(endpoint)) { + resp.sendRedirect(endpoint.getBody()); + context.complete(); + } else if (CAPTURE_HEADERS.equals(endpoint)) { + resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request")); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (CAPTURE_PARAMETERS.equals(endpoint)) { + req.setCharacterEncoding("UTF8"); + String value = req.getParameter("test-parameter"); + if (!value.equals("test value õäöü")) { + throw new ServletException( + "request parameter does not have expected value " + value); + } + + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (ERROR.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (EXCEPTION.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + PrintWriter writer = resp.getWriter(); + writer.print(endpoint.getBody()); + if (req.getClass().getName().contains("catalina")) { + // on tomcat close the writer to ensure response is sent immediately, + // otherwise there is a chance that tomcat resets the connection before the + // response is sent + writer.close(); + } + throw new ServletException(endpoint.getBody()); + } else if (HTML_PRINT_WRITER.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.setContentLength(endpoint.getBody().length()); + resp.getWriter().print(endpoint.getBody()); + context.complete(); + } else if (HTML_SERVLET_OUTPUT_STREAM.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.getOutputStream().print(endpoint.getBody()); + context.complete(); + } + return null; + }); + } finally { + latch.countDown(); + } + }); + } + } + + @WebServlet(asyncSupported = true) + public static class FakeAsync extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + AsyncContext context = req.startAsync(); + try { + ServerEndpoint endpoint = ServerEndpoint.forPath(req.getServletPath()); + + HttpServerTest.controller( + endpoint, + () -> { + resp.setContentType("text/plain"); + if (SUCCESS.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (INDEXED_CHILD.equals(endpoint)) { + endpoint.collectSpanAttributes(req::getParameter); + resp.setStatus(endpoint.getStatus()); + } else if (QUERY_PARAM.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(req.getQueryString()); + } else if (REDIRECT.equals(endpoint)) { + resp.sendRedirect(endpoint.getBody()); + } else if (CAPTURE_HEADERS.equals(endpoint)) { + resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request")); + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (CAPTURE_PARAMETERS.equals(endpoint)) { + req.setCharacterEncoding("UTF8"); + String value = req.getParameter("test-parameter"); + if (!value.equals("test value õäöü")) { + throw new ServletException( + "request parameter does not have expected value " + value); + } + + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + } else if (ERROR.equals(endpoint)) { + resp.sendError(endpoint.getStatus(), endpoint.getBody()); + } else if (EXCEPTION.equals(endpoint)) { + resp.setStatus(endpoint.getStatus()); + resp.getWriter().print(endpoint.getBody()); + throw new ServletException(endpoint.getBody()); + } else if (HTML_PRINT_WRITER.equals(endpoint)) { + // intentionally testing setting status before contentType here to cover that case + // somewhere + resp.setStatus(endpoint.getStatus()); + resp.setContentType("text/html"); + resp.getWriter().print(endpoint.getBody()); + } else if (HTML_SERVLET_OUTPUT_STREAM.equals(endpoint)) { + resp.setContentType("text/html"); + resp.setStatus(endpoint.getStatus()); + resp.getOutputStream().print(endpoint.getBody()); + } + return null; + }); + } finally { + context.complete(); + } + } + } + + @WebServlet(asyncSupported = true) + public static class DispatchImmediate extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + String target = req.getServletPath().replace("/dispatch", ""); + if (req.getQueryString() != null) { + target += "?" + req.getQueryString(); + } + + req.startAsync().dispatch(target); + } + } + + @WebServlet(asyncSupported = true) + public static class DispatchAsync extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) { + AsyncContext context = req.startAsync(); + context.start( + () -> { + String target = req.getServletPath().replace("/dispatch", ""); + if (req.getQueryString() != null) { + target += "?" + req.getQueryString(); + } + context.dispatch(target); + }); + } + } + + @WebServlet(asyncSupported = true) + public static class DispatchRecursive extends HttpServlet { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException { + if (req.getServletPath().equals("/recursive")) { + resp.getWriter().print("Hello Recursive"); + } + + int depth = Integer.parseInt(req.getParameter("depth")); + if (depth > 0) { + req.startAsync().dispatch("/dispatch/recursive?depth=" + (depth - 1)); + } else { + req.startAsync().dispatch("/recursive"); + } + } + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java new file mode 100644 index 000000000000..3786326b7f61 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3AsyncTest.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import javax.servlet.Servlet; +import org.junit.jupiter.api.extension.RegisterExtension; + +class TomcatServlet3AsyncTest extends TomcatServlet3Test { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java new file mode 100644 index 000000000000..de245fc837ab --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3FakeAsyncTest.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import javax.servlet.Servlet; +import org.junit.jupiter.api.extension.RegisterExtension; + +class TomcatServlet3FakeAsyncTest extends TomcatServlet3Test { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + public Class servlet() { + return TestServlet3.FakeAsync.class; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java new file mode 100644 index 000000000000..b15b04d4153c --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3SyncTest.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import javax.servlet.Servlet; +import org.junit.jupiter.api.extension.RegisterExtension; + +class TomcatServlet3SyncTest extends TomcatServlet3Test { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + public Class servlet() { + return TestServlet3.Sync.class; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java new file mode 100644 index 000000000000..13baeb0777c8 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/TomcatServlet3Test.java @@ -0,0 +1,221 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat; + +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.NOT_FOUND; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS; +import static org.assertj.core.api.Assertions.assertThat; + +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.javaagent.instrumentation.servlet.v3_0.AbstractServlet3Test; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.core.StandardHost; +import org.apache.catalina.startup.Tomcat; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public abstract class TomcatServlet3Test extends AbstractServlet3Test { + + private static final ServerEndpoint ACCESS_LOG_SUCCESS = + new ServerEndpoint( + "ACCESS_LOG_SUCCESS", + "success?access-log=true", + SUCCESS.getStatus(), + SUCCESS.getBody(), + false); + private static final ServerEndpoint ACCESS_LOG_ERROR = + new ServerEndpoint( + "ACCESS_LOG_ERROR", + "error-status?access-log=true", + ERROR.getStatus(), + ERROR.getBody(), + false); + private final TestAccessLogValve accessLogValue = new TestAccessLogValve(); + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setExpectedException(new ServletException(EXCEPTION.getBody())); + options.setContextPath("/tomcat-context"); + options.setTestError(testError()); + } + + public boolean testError() { + return false; + } + + @Override + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, SpanData parentSpan, String method, ServerEndpoint endpoint) { + if (NOT_FOUND.equals(endpoint)) { + span.satisfies(s -> assertThat(s.getName()).matches(".*\\.sendError")) + .hasKind(SpanKind.INTERNAL) + .hasParent(parentSpan); + } + return super.assertResponseSpan(span, parentSpan, method, endpoint); + } + + @Override + protected boolean hasResponseSpan(ServerEndpoint endpoint) { + return endpoint == NOT_FOUND || super.hasResponseSpan(endpoint); + } + + @Override + protected Tomcat setupServer() throws Exception { + Tomcat tomcatServer = new Tomcat(); + + File baseDir = Files.createTempDirectory("tomcat").toFile(); + baseDir.deleteOnExit(); + tomcatServer.setBaseDir(baseDir.getAbsolutePath()); + + tomcatServer.setPort(port); + tomcatServer.getConnector().setEnableLookups(true); // get localhost instead of 127.0.0.1 + + File applicationDir = new File(baseDir, "/webapps/ROOT"); + if (!applicationDir.exists()) { + applicationDir.mkdirs(); + applicationDir.deleteOnExit(); + } + + Context servletContext = + tomcatServer.addWebapp(getContextPath(), applicationDir.getAbsolutePath()); + // Speed up startup by disabling jar scanning: + servletContext.getJarScanner().setJarScanFilter((jarScanType, jarName) -> false); + + setupServlets(servletContext); + + ((StandardHost) tomcatServer.getHost()) + .setErrorReportValveClass(ErrorHandlerValve.class.getName()); + tomcatServer.getHost().getPipeline().addValve(accessLogValue); + + tomcatServer.start(); + + return tomcatServer; + } + + @BeforeEach + void setUp() { + accessLogValue.getLoggedIds().clear(); + testing().clearAllExportedData(); + } + + @Override + public void stopServer(Tomcat server) throws LifecycleException { + server.stop(); + server.destroy(); + } + + @Override + public void addServlet(Context servletContext, String path, Class servlet) + throws Exception { + String name = UUID.randomUUID().toString(); + Tomcat.addServlet(servletContext, name, servlet.getConstructor().newInstance()); + servletContext.addServletMappingDecoded(path, name); + } + + @ParameterizedTest + @CsvSource({"1", "4"}) + void accessLogHasIdsForCountRequests(int count) { + AggregatedHttpRequest request = request(ACCESS_LOG_SUCCESS, "GET"); + + IntStream.range(0, count) + .mapToObj(i -> client.execute(request).aggregate().join()) + .forEach( + response -> { + assertThat(response.status().code()).isEqualTo(ACCESS_LOG_SUCCESS.getStatus()); + assertThat(response.contentUtf8()).isEqualTo(ACCESS_LOG_SUCCESS.getBody()); + }); + + accessLogValue.waitForLoggedIds(count); + assertThat(accessLogValue.getLoggedIds().size()).isEqualTo(count); + List loggedTraces = + accessLogValue.getLoggedIds().stream().map(Map.Entry::getKey).collect(Collectors.toList()); + List loggedSpans = + accessLogValue.getLoggedIds().stream() + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + + testing() + .waitAndAssertTraces( + IntStream.range(0, count) + .mapToObj( + i -> + (Consumer) + trace -> { + trace.hasSpansSatisfyingExactly( + span -> + assertServerSpan( + span, "GET", ACCESS_LOG_SUCCESS, SUCCESS.getStatus()), + span -> assertControllerSpan(span, null)); + SpanData span = trace.getSpan(0); + assertThat(loggedTraces).contains(span.getTraceId()); + assertThat(loggedSpans).contains(span.getSpanId()); + }) + .collect(Collectors.toList())); + } + + @Test + void accessLogHasIdsForErrorRequest() { + Assumptions.assumeTrue(testError()); + + AggregatedHttpRequest request = request(ACCESS_LOG_ERROR, "GET"); + AggregatedHttpResponse response = client.execute(request).aggregate().join(); + + assertThat(response.status().code()).isEqualTo(ACCESS_LOG_ERROR.getStatus()); + assertThat(response.contentUtf8()).isEqualTo(ACCESS_LOG_ERROR.getBody()); + + List> spanDataAsserts = new ArrayList<>(); + spanDataAsserts.add( + (span, trace) -> assertServerSpan(span, "GET", ACCESS_LOG_ERROR, ERROR.getStatus())); + spanDataAsserts.add((span, trace) -> assertControllerSpan(span, null)); + if (errorEndpointUsesSendError()) { + spanDataAsserts.add( + (span, trace) -> + span.satisfies(s -> assertThat(s.getName()).matches(".*\\.sendError")) + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(1))); + } + + accessLogValue.waitForLoggedIds(1); + testing() + .waitAndAssertTraces( + trace -> { + trace.hasSpansSatisfyingExactly( + spanDataAsserts.stream() + .map(e -> (Consumer) span -> e.accept(span, trace)) + .collect(Collectors.toList())); + SpanData span = trace.getSpan(0); + Map.Entry entry = accessLogValue.getLoggedIds().get(0); + assertThat(entry.getKey()).isEqualTo(span.getTraceId()); + assertThat(entry.getValue()).isEqualTo(span.getSpanId()); + }); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java new file mode 100644 index 000000000000..bbcf1374133c --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatDispatchTest.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TomcatServlet3Test; + +abstract class TomcatDispatchTest extends TomcatServlet3Test { + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setContextPath(getContextPath() + "/dispatch"); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java new file mode 100644 index 000000000000..ec0be95b5b83 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchAsyncTest.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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 io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; +import org.apache.catalina.Context; +import org.junit.jupiter.api.extension.RegisterExtension; + +class TomcatServlet3DispatchAsyncTest extends TomcatDispatchTest { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setVerifyServerSpanEndTime(false); + } + + @Override + public Class servlet() { + return TestServlet3.Async.class; + } + + @Override + protected void setupServlets(Context context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + QUERY_PARAM.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + ERROR.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + AUTH_REQUIRED.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + CAPTURE_HEADERS.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, "/dispatch" + CAPTURE_PARAMETERS.getPath(), TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch" + INDEXED_CHILD.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), TestServlet3.DispatchAsync.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + TestServlet3.DispatchAsync.class); + addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive.class); + } + + @Override + public boolean errorEndpointUsesSendError() { + return false; + } + + @Override + protected boolean assertParentOnRedirect() { + return false; + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java new file mode 100644 index 000000000000..b4600339542f --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3DispatchImmediateTest.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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 io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; +import org.apache.catalina.Context; +import org.junit.jupiter.api.extension.RegisterExtension; + +class TomcatServlet3DispatchImmediateTest extends TomcatDispatchTest { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + public Class servlet() { + return TestServlet3.Sync.class; + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestNotFound(false); + } + + @Override + protected void setupServlets(Context context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + QUERY_PARAM.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + ERROR.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + CAPTURE_HEADERS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + CAPTURE_PARAMETERS.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), TestServlet3.DispatchImmediate.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + TestServlet3.DispatchImmediate.class); + addServlet(context, "/dispatch/recursive", TestServlet3.DispatchRecursive.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java new file mode 100644 index 000000000000..d3b5dc869fe9 --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3ForwardTest.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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 io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.RequestDispatcherServlet; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; +import org.apache.catalina.Context; +import org.junit.jupiter.api.extension.RegisterExtension; + +class TomcatServlet3ForwardTest extends TomcatDispatchTest { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + public Class servlet() { + return TestServlet3.Sync.class; // dispatch to sync servlet + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestNotFound(false); + } + + @Override + protected void setupServlets(Context context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + QUERY_PARAM.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + ERROR.getPath(), RequestDispatcherServlet.Forward.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + CAPTURE_HEADERS.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, + "/dispatch" + CAPTURE_PARAMETERS.getPath(), + RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), RequestDispatcherServlet.Forward.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + RequestDispatcherServlet.Forward.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java new file mode 100644 index 000000000000..d8ac0758f79d --- /dev/null +++ b/instrumentation/servlet/servlet-3.0/testing/src/test/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/tomcat/dispatch/TomcatServlet3IncludeTest.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.dispatch; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS; +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 io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.RequestDispatcherServlet; +import io.opentelemetry.javaagent.instrumentation.servlet.v3_0.tomcat.TestServlet3; +import javax.servlet.Servlet; +import org.apache.catalina.Context; +import org.junit.jupiter.api.extension.RegisterExtension; + +class TomcatServlet3IncludeTest extends TomcatDispatchTest { + + @RegisterExtension + protected static final InstrumentationExtension testing = + HttpServerInstrumentationExtension.forAgent(); + + @Override + public Class servlet() { + return TestServlet3.Sync.class; // dispatch to sync servlet + } + + @Override + protected void configure(HttpServerTestOptions options) { + super.configure(options); + options.setTestNotFound(false); + options.setTestRedirect(false); + options.setTestCaptureHttpHeaders(false); + options.setTestError(false); + } + + @Override + protected void setupServlets(Context context) throws Exception { + super.setupServlets(context); + + addServlet(context, "/dispatch" + SUCCESS.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + QUERY_PARAM.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + REDIRECT.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + ERROR.getPath(), RequestDispatcherServlet.Include.class); + addServlet(context, "/dispatch" + EXCEPTION.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + AUTH_REQUIRED.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, + "/dispatch" + CAPTURE_PARAMETERS.getPath(), + RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + INDEXED_CHILD.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, "/dispatch" + HTML_PRINT_WRITER.getPath(), RequestDispatcherServlet.Include.class); + addServlet( + context, + "/dispatch" + HTML_SERVLET_OUTPUT_STREAM.getPath(), + RequestDispatcherServlet.Include.class); + } +} diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/test/resources/realm.properties b/instrumentation/servlet/servlet-3.0/testing/src/test/resources/realm.properties similarity index 100% rename from instrumentation/servlet/servlet-3.0/javaagent/src/test/resources/realm.properties rename to instrumentation/servlet/servlet-3.0/testing/src/test/resources/realm.properties diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy index 51375320b93e..ecd73205b8b8 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy @@ -96,7 +96,6 @@ abstract class TomcatServlet5Test extends AbstractServlet5Test } }) -// setupAuthentication(tomcatServer, servletContext) setupServlets(servletContext) (tomcatServer.host as StandardHost).errorReportValveClass = ErrorHandlerValve.name diff --git a/instrumentation/tapestry-5.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/tapestry/TapestryTest.java b/instrumentation/tapestry-5.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/tapestry/TapestryTest.java index 05d4d0209ee5..bb83079938d5 100644 --- a/instrumentation/tapestry-5.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/tapestry/TapestryTest.java +++ b/instrumentation/tapestry-5.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/tapestry/TapestryTest.java @@ -14,7 +14,7 @@ import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.testing.internal.armeria.client.WebClient; import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; -import org.eclipse.jetty.server.Connector; +import java.net.InetSocketAddress; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.webapp.WebAppContext; @@ -36,14 +36,10 @@ class TapestryTest extends AbstractHttpServerUsingTest { protected Server setupServer() throws Exception { WebAppContext webAppContext = new WebAppContext(); webAppContext.setContextPath(getContextPath()); - Server jettyServer = new Server(port); + Server jettyServer = new Server(new InetSocketAddress("localhost", port)); // set up test application webAppContext.setBaseResource(Resource.newResource("src/test/webapp")); - for (Connector connector : jettyServer.getConnectors()) { - connector.setHost("localhost"); - } - jettyServer.setHandler(webAppContext); jettyServer.start(); diff --git a/settings.gradle.kts b/settings.gradle.kts index b5a17c333e59..43162e3d47a3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -542,6 +542,7 @@ include(":instrumentation:scala-fork-join-2.8:javaagent") include(":instrumentation:servlet:servlet-2.2:javaagent") include(":instrumentation:servlet:servlet-3.0:javaagent") include(":instrumentation:servlet:servlet-3.0:javaagent-unit-tests") +include(":instrumentation:servlet:servlet-3.0:testing") include(":instrumentation:servlet:servlet-5.0:javaagent") include(":instrumentation:servlet:servlet-5.0:javaagent-unit-tests") include(":instrumentation:servlet:servlet-5.0:jetty12-testing") 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 74c9e6c67af2..6f9bb4c077d3 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 @@ -665,7 +665,9 @@ protected void assertTheTraces( if (options.hasResponseSpan.test(endpoint)) { int parentIndex = spanAssertions.size() - 1; spanAssertions.add( - span -> assertResponseSpan(span, trace.getSpan(parentIndex), method, endpoint)); + span -> + assertResponseSpan( + span, trace.getSpan(parentIndex), trace.getSpan(0), method, endpoint)); } if (options.hasErrorPageSpans.test(endpoint)) { @@ -706,6 +708,16 @@ protected SpanDataAssert assertHandlerSpan( "assertHandlerSpan not implemented in " + getClass().getName()); } + @CanIgnoreReturnValue + protected SpanDataAssert assertResponseSpan( + SpanDataAssert span, + SpanData controllerSpan, + SpanData handlerSpan, + String method, + ServerEndpoint endpoint) { + return assertResponseSpan(span, controllerSpan, method, endpoint); + } + @CanIgnoreReturnValue protected SpanDataAssert assertResponseSpan( SpanDataAssert span, SpanData parentSpan, String method, ServerEndpoint endpoint) { @@ -851,9 +863,13 @@ public String expectedServerSpanName( endpoint, method, route); } + public final boolean hasHttpRouteAttribute(ServerEndpoint endpoint) { + return options.httpAttributes.apply(endpoint).contains(HttpAttributes.HTTP_ROUTE); + } + public String expectedHttpRoute(ServerEndpoint endpoint, String method) { // no need to compute route if we're not expecting it - if (!options.httpAttributes.apply(endpoint).contains(HttpAttributes.HTTP_ROUTE)) { + if (!hasHttpRouteAttribute(endpoint)) { return null; }