Copy servlet3 tests to servlet5
ADD
instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/AbstractServlet5Test.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
+
+import io.opentelemetry.instrumentation.test.AgentTestTrait
+import io.opentelemetry.instrumentation.test.asserts.TraceAssert
+import io.opentelemetry.instrumentation.test.base.HttpServerTest
+import jakarta.servlet.Servlet
+import okhttp3.Request
+import okhttp3.RequestBody
+
+abstract class AbstractServlet5Test<SERVER, CONTEXT> extends HttpServerTest<SERVER> 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> servlet()
+
+ abstract void addServlet(CONTEXT context, String path, Class<Servlet> servlet)
+
+ 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)
+ }
+
+ protected ServerEndpoint lastRequest
+
+ @Override
+ Request.Builder request(ServerEndpoint uri, String method, RequestBody body) {
+ lastRequest = uri
+ super.request(uri, method, body)
+ }
+
+ boolean errorEndpointUsesSendError() {
+ 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
+ }
+ }
+}

ADD
instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServlet5Test.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
+
+import jakarta.servlet.Servlet
+import jakarta.servlet.ServletException
+import jakarta.servlet.http.HttpServletRequest
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.server.handler.ErrorHandler
+import org.eclipse.jetty.servlet.ServletContextHandler
+import spock.lang.IgnoreIf
+
+abstract class JettyServlet5Test extends AbstractServlet5Test<Object, Object> {
+
+ @Override
+ boolean testNotFound() {
+ false
+ }
+
+ @Override
+ Class<?> expectedExceptionClass() {
+ ServletException
+ }
+
+ @Override
+ Object 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(Object serverObject) {
+ Server server = (Server) serverObject
+ server.stop()
+ server.destroy()
+ }
+
+ @Override
+ String getContextPath() {
+ return "/jetty-context"
+ }
+
+ @Override
+ void addServlet(Object handlerObject, String path, Class<Servlet> servlet) {
+ ServletContextHandler handler = (ServletContextHandler) handlerObject
+ handler.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)
+// }
+}
+
+@IgnoreIf({ !jvm.java11Compatible })
+class JettyServlet5TestSync extends JettyServlet5Test {
+
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Sync
+ }
+
+ @Override
+ boolean testConcurrency() {
+ return true
+ }
+}
+
+@IgnoreIf({ !jvm.java11Compatible })
+class JettyServlet5TestAsync extends JettyServlet5Test {
+
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Async
+ }
+
+ @Override
+ boolean errorEndpointUsesSendError() {
+ false
+ }
+
+ @Override
+ boolean testException() {
+ // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807
+ return false
+ }
+
+ @Override
+ boolean testConcurrency() {
+ return true
+ }
+}
+
+@IgnoreIf({ !jvm.java11Compatible })
+class JettyServlet5TestFakeAsync extends JettyServlet5Test {
+
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.FakeAsync
+ }
+
+ @Override
+ boolean testException() {
+ // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807
+ return false
+ }
+
+ @Override
+ boolean testConcurrency() {
+ return true
+ }
+}
+
+@IgnoreIf({ !jvm.java11Compatible })
+class JettyServlet5TestForward extends JettyDispatchTest {
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Sync // dispatch to sync servlet
+ }
+
+ @Override
+ protected void setupServlets(Object 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)
+ }
+}
+
+@IgnoreIf({ !jvm.java11Compatible })
+class JettyServlet5TestInclude extends JettyDispatchTest {
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Sync // dispatch to sync servlet
+ }
+
+ @Override
+ boolean testRedirect() {
+ false
+ }
+
+ @Override
+ boolean testError() {
+ false
+ }
+
+ @Override
+ protected void setupServlets(Object 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)
+ }
+}
+
+
+@IgnoreIf({ !jvm.java11Compatible })
+class JettyServlet5TestDispatchImmediate extends JettyDispatchTest {
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Sync
+ }
+
+ @Override
+ protected void setupServlets(Object context) {
+ super.setupServlets(context)
+
+ addServlet(context, "/dispatch" + SUCCESS.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + ERROR.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + REDIRECT.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch/recursive", TestServlet5.DispatchRecursive)
+ }
+
+ @Override
+ boolean testException() {
+ // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807
+ return false
+ }
+}
+
+@IgnoreIf({ !jvm.java11Compatible })
+class JettyServlet5TestDispatchAsync extends JettyDispatchTest {
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Async
+ }
+
+ @Override
+ protected void setupServlets(Object context) {
+ super.setupServlets(context)
+
+ addServlet(context, "/dispatch" + SUCCESS.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + ERROR.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + REDIRECT.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch/recursive", TestServlet5.DispatchRecursive)
+ }
+
+ @Override
+ boolean errorEndpointUsesSendError() {
+ false
+ }
+
+ @Override
+ boolean testException() {
+ // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807
+ return false
+ }
+}
+
+abstract class JettyDispatchTest extends JettyServlet5Test {
+ @Override
+ URI buildAddress() {
+ return new URI("http://localhost:$port$contextPath/dispatch/")
+ }
+}

ADD
instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import jakarta.servlet.Servlet
+import jakarta.servlet.ServletException
+import jakarta.servlet.http.HttpServletRequest
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.server.handler.ErrorHandler
+import org.eclipse.jetty.servlet.ServletHandler
+import spock.lang.IgnoreIf
+
+@IgnoreIf({ !jvm.java11Compatible })
+class JettyServletHandlerTest extends AbstractServlet5Test<Object, Object> {
+
+ @Override
+ String expectedServerSpanName(ServerEndpoint endpoint) {
+ "HTTP GET"
+ }
+
+ @Override
+ Object 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(Object handlerObject, String path, Class<Servlet> servlet) {
+ ServletHandler servletHandler = (ServletHandler) handlerObject
+ servletHandler.addServletWithMapping(servlet, path)
+ }
+
+ @Override
+ void stopServer(Object serverObject) {
+ Server server = (Server) serverObject
+ server.stop()
+ server.destroy()
+ }
+
+ @Override
+ String getContextPath() {
+ ""
+ }
+
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Sync
+ }
+
+ @Override
+ boolean testNotFound() {
+ false
+ }
+
+ @Override
+ Class<?> expectedExceptionClass() {
+ ServletException
+ }
+}

ADD
instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TestServlet5.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
+
+import io.opentelemetry.api.trace.Span
+import io.opentelemetry.instrumentation.test.base.HttpServerTest
+import jakarta.servlet.RequestDispatcher
+import jakarta.servlet.ServletException
+import jakarta.servlet.annotation.WebServlet
+import jakarta.servlet.http.HttpServlet
+import jakarta.servlet.http.HttpServletRequest
+import jakarta.servlet.http.HttpServletResponse
+import java.util.concurrent.Phaser
+
+class TestServlet5 {
+
+ @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
+ }
+ HttpServerTest.ServerEndpoint endpoint = HttpServerTest.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
+ Span.current().setAttribute("test.request.id", req.getParameter("id") as long)
+ 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 ServletException(endpoint.body)
+ }
+ }
+ }
+ }
+
+ @WebServlet(asyncSupported = true)
+ static class Async extends HttpServlet {
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) {
+ HttpServerTest.ServerEndpoint endpoint = HttpServerTest.ServerEndpoint.forPath(req.servletPath)
+ def phaser = new Phaser(2)
+ def context = req.startAsync()
+ context.start {
+ try {
+ phaser.arriveAndAwaitAdvance()
+ 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:
+ Span.current().setAttribute("test.request.id", req.getParameter("id") as long)
+ 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 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
+ resp.writer.print(endpoint.body)
+ context.complete()
+ throw new Exception(endpoint.body)
+ }
+ }
+ } finally {
+ phaser.arriveAndDeregister()
+ }
+ }
+ phaser.arriveAndAwaitAdvance()
+ phaser.arriveAndAwaitAdvance()
+ }
+ }
+
+ @WebServlet(asyncSupported = true)
+ static class FakeAsync extends HttpServlet {
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) {
+ def context = req.startAsync()
+ try {
+ HttpServerTest.ServerEndpoint endpoint = HttpServerTest.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:
+ Span.current().setAttribute("test.request.id", req.getParameter("id") as long)
+ 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 ERROR:
+ resp.sendError(endpoint.status, endpoint.body)
+ break
+ case EXCEPTION:
+ throw new Exception(endpoint.body)
+ }
+ }
+ } 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", "")
+ 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", "")
+ 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 == "/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")
+ }
+ }
+ }
+}

ADD
instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.AUTH_REQUIRED
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT
+import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS
+import static org.junit.Assume.assumeTrue
+
+import io.opentelemetry.instrumentation.test.asserts.TraceAssert
+import jakarta.servlet.Servlet
+import jakarta.servlet.ServletException
+import java.nio.file.Files
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+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
+
+@Unroll
+abstract class TomcatServlet5Test extends AbstractServlet5Test<Tomcat, Context> {
+
+ @Override
+ Class<?> expectedExceptionClass() {
+ ServletException
+ }
+
+ @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> 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(SUCCESS, method, body).build()
+
+ when:
+ List<okhttp3.Response> responses = (1..count).collect {
+ return client.newCall(request).execute()
+ }
+
+ then:
+ responses.each { response ->
+ assert response.code() == SUCCESS.status
+ assert response.body().string() == 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", SUCCESS.body.length())
+ controllerSpan(it, 1, span(0))
+ }
+
+ assert loggedTraces.contains(traces[it][0].traceId)
+ assert loggedSpans.contains(traces[it][0].spanId)
+ }
+ }
+
+ where:
+ method = "GET"
+ body = null
+ count << [1, 4] // make multiple requests.
+ }
+
+ def "access log has ids for error request"() {
+ setup:
+ assumeTrue(testError())
+ def request = request(ERROR, method, body).build()
+ def response = client.newCall(request).execute()
+
+ expect:
+ response.code() == ERROR.status
+ response.body().string() == ERROR.body
+
+ and:
+ def spanCount = 2
+ if (errorEndpointUsesSendError()) {
+ spanCount++
+ }
+ assertTraces(1) {
+ trace(0, spanCount) {
+ serverSpan(it, 0, null, null, method, response.body().contentLength(), 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"
+ body = null
+ }
+
+ // 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.setErrorReported()) {
+ return
+ }
+ try {
+ response.writer.print(t ? t.cause.message : response.message)
+ } catch (IOException e) {
+ e.printStackTrace()
+ }
+ }
+}
+
+class TestAccessLogValve extends ValveBase implements AccessLog {
+ final List<Tuple2<String, String>> loggedIds = []
+
+ TestAccessLogValve() {
+ super(true)
+ }
+
+ void log(Request request, Response response, long time) {
+ 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 TomcatServlet5TestSync extends TomcatServlet5Test {
+
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Sync
+ }
+}
+
+class TomcatServlet5TestAsync extends TomcatServlet5Test {
+
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Async
+ }
+
+ @Override
+ boolean errorEndpointUsesSendError() {
+ false
+ }
+
+ @Override
+ boolean testException() {
+ // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807
+ return false
+ }
+
+ @Override
+ boolean testConcurrency() {
+ return true
+ }
+}
+
+class TomcatServlet5TestFakeAsync extends TomcatServlet5Test {
+
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.FakeAsync
+ }
+
+ @Override
+ boolean testException() {
+ // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807
+ return false
+ }
+
+ @Override
+ boolean testConcurrency() {
+ return true
+ }
+}
+
+class TomcatServlet5TestForward extends TomcatDispatchTest {
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.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)
+ }
+}
+
+class TomcatServlet5TestInclude extends TomcatDispatchTest {
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Sync // dispatch to sync servlet
+ }
+
+ @Override
+ boolean testNotFound() {
+ false
+ }
+
+ @Override
+ boolean testRedirect() {
+ 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)
+ }
+}
+
+class TomcatServlet5TestDispatchImmediate extends TomcatDispatchTest {
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Sync
+ }
+
+ @Override
+ boolean testNotFound() {
+ false
+ }
+
+ @Override
+ protected void setupServlets(Context context) {
+ super.setupServlets(context)
+
+ addServlet(context, "/dispatch" + SUCCESS.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + ERROR.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + REDIRECT.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet5.DispatchImmediate)
+ addServlet(context, "/dispatch/recursive", TestServlet5.DispatchRecursive)
+ }
+}
+
+class TomcatServlet5TestDispatchAsync extends TomcatDispatchTest {
+ @Override
+ Class<Servlet> servlet() {
+ TestServlet5.Async
+ }
+
+ @Override
+ protected void setupServlets(Context context) {
+ super.setupServlets(context)
+
+ addServlet(context, "/dispatch" + SUCCESS.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + QUERY_PARAM.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + ERROR.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + EXCEPTION.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + REDIRECT.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch" + AUTH_REQUIRED.path, TestServlet5.DispatchAsync)
+ addServlet(context, "/dispatch/recursive", TestServlet5.DispatchRecursive)
+ }
+
+ @Override
+ boolean errorEndpointUsesSendError() {
+ false
+ }
+
+ @Override
+ boolean testException() {
+ // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/807
+ return false
+ }
+}
+
+abstract class TomcatDispatchTest extends TomcatServlet5Test {
+ @Override
+ URI buildAddress() {
+ return new URI("http://localhost:$port$contextPath/dispatch/")
+ }
+}

ADD
instrumentation/servlet/servlet-5.0/javaagent/src/test/java/RequestDispatcherServlet.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+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 {
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ String target = req.getServletPath().replace("/dispatch", "");
+ ServletContext context = getServletContext();
+ RequestDispatcher dispatcher = context.getRequestDispatcher(target);
+ dispatcher.forward(req, resp);
+ }
+ }
+
+ @WebServlet(asyncSupported = true)
+ public static class Include extends HttpServlet {
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ String target = req.getServletPath().replace("/dispatch", "");
+ ServletContext context = getServletContext();
+ RequestDispatcher dispatcher = context.getRequestDispatcher(target);
+ dispatcher.include(req, resp);
+ }
+ }
+}

ADD
instrumentation/servlet/servlet-5.0/javaagent/src/test/resources/realm.properties
+user:password,role

Copy servlet3 tests to servlet5 (#2918)