Parcourir la source

Convert tomcat 10 tests from groovy to java (#11420)

Jay DeLuca il y a 9 mois
Parent
commit
98d0c3d39b

+ 8 - 0
instrumentation/tomcat/tomcat-10.0/javaagent/build.gradle.kts

@@ -31,3 +31,11 @@ tasks {
     jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
   }
 }
+
+// Tomcat 10 uses deprecation annotation methods `forRemoval()` and `since()`
+// in jakarta.servlet.http.HttpServlet that don't work with Java 8
+if (findProperty("testLatestDeps") as Boolean) {
+  otelJava {
+    minJavaVersionSupported.set(JavaVersion.VERSION_11)
+  }
+}

+ 0 - 83
instrumentation/tomcat/tomcat-10.0/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/AsyncServlet.groovy

@@ -1,83 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0
-
-import io.opentelemetry.instrumentation.test.base.HttpServerTest
-import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
-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.CountDownLatch
-
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT
-import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
-
-@WebServlet(asyncSupported = true)
-class AsyncServlet 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)
-              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 ERROR:
-              resp.status = endpoint.status
-              resp.writer.print(endpoint.body)
-              break
-            case EXCEPTION:
-              resp.status = endpoint.status
-              def writer = resp.writer
-              writer.print(endpoint.body)
-              writer.close()
-              throw new ServletException(endpoint.body)
-          }
-        }
-      } finally {
-        // complete at the end so the server span will end after the controller span
-        if (endpoint != EXCEPTION) {
-          context.complete()
-        }
-        latch.countDown()
-      }
-    }
-    latch.await()
-  }
-}

+ 0 - 143
instrumentation/tomcat/tomcat-10.0/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/TomcatAsyncTest.groovy

@@ -1,143 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0
-
-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 jakarta.servlet.Servlet
-import jakarta.servlet.ServletException
-import org.apache.catalina.Context
-import org.apache.catalina.startup.Tomcat
-import org.apache.tomcat.JarScanFilter
-import org.apache.tomcat.JarScanType
-import spock.lang.Unroll
-
-import java.nio.file.Files
-
-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.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
-
-@Unroll
-class TomcatAsyncTest extends HttpServerTest<Tomcat> implements AgentTestTrait {
-
-  @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
-  String getContextPath() {
-    return "/tomcat-context"
-  }
-
-  @Override
-  String getMetricsInstrumentationName() {
-    // with async requests the span is started in one instrumentation (server instrumentation)
-    // but ended from another (servlet instrumentation)
-    "io.opentelemetry.servlet-5.0"
-  }
-
-  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, CAPTURE_HEADERS.path, servlet)
-    addServlet(context, INDEXED_CHILD.path, servlet)
-  }
-
-  void addServlet(Context servletContext, String path, Class<Servlet> servlet) {
-    String name = UUID.randomUUID()
-    Tomcat.addServlet(servletContext, name, servlet.newInstance())
-    servletContext.addServletMappingDecoded(path, name)
-  }
-
-  Class<Servlet> servlet() {
-    AsyncServlet
-  }
-
-  @Override
-  String expectedHttpRoute(ServerEndpoint endpoint, String method) {
-    if (method == HttpConstants._OTHER) {
-      return getContextPath() + endpoint.path
-    }
-    switch (endpoint) {
-      case NOT_FOUND:
-        return getContextPath() + "/*"
-      default:
-        return super.expectedHttpRoute(endpoint, method)
-    }
-  }
-
-  @Override
-  Throwable expectedException() {
-    new ServletException(EXCEPTION.body)
-  }
-
-  @Override
-  boolean hasResponseSpan(ServerEndpoint endpoint) {
-    endpoint == NOT_FOUND || endpoint == REDIRECT
-  }
-
-  @Override
-  void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) {
-    switch (endpoint) {
-      case REDIRECT:
-        redirectSpan(trace, index, parent)
-        break
-      case NOT_FOUND:
-        sendErrorSpan(trace, index, parent)
-        break
-    }
-  }
-}

+ 0 - 128
instrumentation/tomcat/tomcat-10.0/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/TomcatHandlerTest.groovy

@@ -1,128 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0
-
-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 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 static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_ERROR
-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.LOGIN
-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 TomcatHandlerTest extends HttpServerTest<Tomcat> implements AgentTestTrait {
-
-  private static final List<ServerEndpoint> serverEndpointsList = Arrays.asList(SUCCESS, REDIRECT, ERROR, EXCEPTION, NOT_FOUND, CAPTURE_HEADERS, CAPTURE_PARAMETERS, QUERY_PARAM, PATH_PARAM, AUTH_REQUIRED, LOGIN, AUTH_ERROR, INDEXED_CHILD)
-
-  def "Tomcat starts"() {
-    expect:
-    getServer() != null
-  }
-
-  @Override
-  String getContextPath() {
-    return "/app"
-  }
-
-  @Override
-  boolean hasResponseCustomizer(ServerEndpoint endpoint) {
-    true
-  }
-
-  @Override
-  boolean testCapturedRequestParameters() {
-    true
-  }
-
-  @Override
-  String expectedHttpRoute(ServerEndpoint endpoint, String method) {
-    if (method == HttpConstants._OTHER) {
-      return getContextPath() + endpoint.path
-    }
-    return super.expectedHttpRoute(endpoint, method)
-  }
-
-  @Override
-  Tomcat startServer(int port) {
-    Tomcat tomcat = new Tomcat()
-    tomcat.setBaseDir(File.createTempDir().absolutePath)
-    tomcat.setPort(port)
-    tomcat.getConnector()
-
-    Context ctx = tomcat.addContext(getContextPath(), new File(".").getAbsolutePath())
-
-    Tomcat.addServlet(ctx, "testServlet", new TestServlet())
-
-    // Mapping servlet to /* will result in all requests have a name of just a context.
-    serverEndpointsList.stream()
-      .filter { it != NOT_FOUND }
-      .forEach {
-        ctx.addServletMappingDecoded(it.path, "testServlet")
-      }
-
-    (tomcat.host as StandardHost).errorReportValveClass = ErrorHandlerValve.name
-
-    tomcat.start()
-
-    return tomcat
-  }
-
-  @Override
-  void stopServer(Tomcat tomcat) {
-    tomcat.getServer().stop()
-  }
-
-  @Override
-  boolean hasResponseSpan(ServerEndpoint endpoint) {
-    endpoint == REDIRECT || endpoint == ERROR || endpoint == NOT_FOUND
-  }
-
-  @Override
-  void responseSpan(TraceAssert trace, int index, Object parent, String method, ServerEndpoint endpoint) {
-    switch (endpoint) {
-      case REDIRECT:
-        redirectSpan(trace, index, parent)
-        break
-      case ERROR:
-      case NOT_FOUND:
-        sendErrorSpan(trace, index, parent)
-        break
-    }
-  }
-}
-
-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 ignored) {
-      // Ignore exception when writing exception message to response fails on IO - same as is done
-      // by the superclass itself and by other built-in ErrorReportValve implementations.
-    }
-  }
-}

+ 82 - 0
instrumentation/tomcat/tomcat-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/AsyncServlet.java

@@ -0,0 +1,82 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0;
+
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS;
+
+import io.opentelemetry.instrumentation.test.base.HttpServerTest;
+import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
+import jakarta.servlet.AsyncContext;
+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.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+
+@WebServlet(asyncSupported = true)
+class AsyncServlet 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 == EXCEPTION) {
+      context.setTimeout(5000);
+    }
+    context.start(
+        () -> {
+          try {
+            HttpServerTest.controller(
+                endpoint,
+                () -> {
+                  resp.setContentType("text/plain");
+                  if (endpoint.equals(SUCCESS) || endpoint.equals(ERROR)) {
+                    resp.setStatus(endpoint.getStatus());
+                    resp.getWriter().print(endpoint.getBody());
+                  } else if (endpoint.equals(INDEXED_CHILD)) {
+                    endpoint.collectSpanAttributes(x -> req.getParameter(x));
+                    resp.setStatus(endpoint.getStatus());
+                  } else if (endpoint.equals(QUERY_PARAM)) {
+                    resp.setStatus(endpoint.getStatus());
+                    resp.getWriter().print(req.getQueryString());
+                  } else if (endpoint.equals(REDIRECT)) {
+                    resp.sendRedirect(endpoint.getBody());
+                  } else if (endpoint.equals(CAPTURE_HEADERS)) {
+                    resp.setHeader("X-Test-Response", req.getHeader("X-Test-Request"));
+                    resp.setStatus(endpoint.getStatus());
+                    resp.getWriter().print(endpoint.getBody());
+                  } else if (endpoint.equals(EXCEPTION)) {
+                    resp.setStatus(endpoint.getStatus());
+                    PrintWriter writer = resp.getWriter();
+                    writer.print(endpoint.getBody());
+                    writer.close();
+                    throw new ServletException(endpoint.getBody());
+                  }
+                  return null;
+                });
+          } finally {
+            // complete at the end so the server span will end after the controller span
+            if (endpoint != EXCEPTION) {
+              context.complete();
+            }
+            latch.countDown();
+          }
+        });
+    try {
+      latch.await();
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}

+ 26 - 0
instrumentation/tomcat/tomcat-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/ErrorHandlerValve.java

@@ -0,0 +1,26 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0;
+
+import java.io.IOException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ErrorReportValve;
+
+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.getWriter().print(t != null ? t.getCause().getMessage() : response.getMessage());
+    } catch (IOException ignored) {
+      // Ignore exception when writing exception message to response fails on IO - same as is done
+      // by the superclass itself and by other built-in ErrorReportValve implementations.
+    }
+  }
+}

+ 1 - 1
instrumentation/tomcat/tomcat-10.0/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/TestServlet.java → instrumentation/tomcat/tomcat-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/TestServlet.java

@@ -13,7 +13,7 @@ import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import java.io.IOException;
 
-public class TestServlet extends HttpServlet {
+class TestServlet extends HttpServlet {
 
   @Override
   protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {

+ 133 - 0
instrumentation/tomcat/tomcat-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/TomcatAsyncTest.java

@@ -0,0 +1,133 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.tomcat.v10_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.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.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.SpanKind;
+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 jakarta.servlet.Servlet;
+import jakarta.servlet.ServletException;
+import java.io.File;
+import java.nio.file.Files;
+import java.util.UUID;
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.startup.Tomcat;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class TomcatAsyncTest extends AbstractHttpServerTest<Tomcat> {
+
+  @RegisterExtension
+  static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
+
+  @Override
+  public 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;
+  }
+
+  protected void setupServlets(Context context) throws Exception {
+    Class<? extends Servlet> 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, CAPTURE_HEADERS.getPath(), servlet);
+    addServlet(context, INDEXED_CHILD.getPath(), servlet);
+  }
+
+  void addServlet(Context servletContext, String path, Class<? extends Servlet> servlet)
+      throws Exception {
+    String name = UUID.randomUUID().toString();
+    Tomcat.addServlet(servletContext, name, servlet.getDeclaredConstructor().newInstance());
+    servletContext.addServletMappingDecoded(path, name);
+  }
+
+  Class<? extends Servlet> servlet() {
+    return AsyncServlet.class;
+  }
+
+  @Override
+  public void stopServer(Tomcat server) throws LifecycleException {
+    server.stop();
+    server.destroy();
+  }
+
+  @Override
+  protected void configure(HttpServerTestOptions options) {
+    options.setContextPath("/tomcat-context");
+
+    options.setExpectedHttpRoute(
+        (ServerEndpoint endpoint, String method) -> {
+          if (method.equals(HttpConstants._OTHER)) {
+            return getContextPath() + endpoint.getPath();
+          }
+          if (endpoint.equals(NOT_FOUND)) {
+            return getContextPath() + "/*";
+          }
+          return super.expectedHttpRoute(endpoint, method);
+        });
+
+    options.setExpectedException(new ServletException(EXCEPTION.getBody()));
+
+    options.setHasResponseSpan(endpoint -> endpoint == NOT_FOUND || endpoint == REDIRECT);
+
+    // with async requests the span is started in one instrumentation (server instrumentation)
+    // but ended from another (servlet instrumentation)
+    options.setMetricsInstrumentationName(() -> "io.opentelemetry.servlet-5.0");
+  }
+
+  @Override
+  protected SpanDataAssert assertResponseSpan(
+      SpanDataAssert span, String method, ServerEndpoint endpoint) {
+    if (endpoint.equals(REDIRECT)) {
+      span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect"));
+    } else if (endpoint.equals(NOT_FOUND)) {
+      span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendError"));
+    }
+    span.hasKind(SpanKind.INTERNAL).hasAttributesSatisfying(Attributes::isEmpty);
+    return span;
+  }
+}

+ 126 - 0
instrumentation/tomcat/tomcat-10.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/tomcat/v10_0/TomcatHandlerTest.java

@@ -0,0 +1,126 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0;
+
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_ERROR;
+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.LOGIN;
+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 java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.SpanKind;
+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.File;
+import java.nio.file.Files;
+import java.util.List;
+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.extension.RegisterExtension;
+
+class TomcatHandlerTest extends AbstractHttpServerTest<Tomcat> {
+
+  @RegisterExtension
+  static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
+
+  private static final List<ServerEndpoint> serverEndpointsList =
+      asList(
+          SUCCESS,
+          REDIRECT,
+          ERROR,
+          EXCEPTION,
+          NOT_FOUND,
+          CAPTURE_HEADERS,
+          CAPTURE_PARAMETERS,
+          QUERY_PARAM,
+          PATH_PARAM,
+          AUTH_REQUIRED,
+          LOGIN,
+          AUTH_ERROR,
+          INDEXED_CHILD);
+
+  @Override
+  public 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();
+
+    Context servletContext =
+        tomcatServer.addContext(getContextPath(), new File(".").getAbsolutePath());
+
+    Tomcat.addServlet(servletContext, "testServlet", new TestServlet());
+
+    // Mapping servlet to /* will result in all requests have a name of just a context.
+    serverEndpointsList.stream()
+        .filter(endpoint -> !endpoint.equals(NOT_FOUND))
+        .forEach(
+            endpoint -> servletContext.addServletMappingDecoded(endpoint.getPath(), "testServlet"));
+
+    StandardHost host = (StandardHost) tomcatServer.getHost();
+    host.setErrorReportValveClass(ErrorHandlerValve.class.getName());
+
+    tomcatServer.start();
+    return tomcatServer;
+  }
+
+  @Override
+  public void stopServer(Tomcat server) throws LifecycleException {
+    server.stop();
+    server.destroy();
+  }
+
+  @Override
+  protected void configure(HttpServerTestOptions options) {
+    options.setContextPath("/app");
+    options.setHasResponseCustomizer(serverEndpoint -> true);
+    options.setTestCaptureRequestParameters(true);
+    options.setTestErrorBody(false);
+
+    options.setHasResponseSpan(
+        endpoint -> endpoint == REDIRECT || endpoint == ERROR || endpoint == NOT_FOUND);
+
+    options.setExpectedHttpRoute(
+        (ServerEndpoint endpoint, String method) -> {
+          if (method.equals(HttpConstants._OTHER)) {
+            return getContextPath() + endpoint.getPath();
+          }
+          return super.expectedHttpRoute(endpoint, method);
+        });
+  }
+
+  @Override
+  protected SpanDataAssert assertResponseSpan(
+      SpanDataAssert span, String method, ServerEndpoint endpoint) {
+    if (endpoint.equals(REDIRECT)) {
+      span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect"));
+    } else if (endpoint.equals(NOT_FOUND)) {
+      span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendError"));
+    }
+    span.hasKind(SpanKind.INTERNAL).hasAttributesSatisfying(Attributes::isEmpty);
+    return span;
+  }
+}