Extract shared tests into abstract parent
ADD
dd-java-agent/instrumentation/servlet-3/src/test/groovy/AbstractServlet3Test.groovy
+import datadog.trace.agent.test.AgentTestRunner
+import datadog.trace.agent.test.TestUtils
+import datadog.trace.agent.test.utils.OkHttpUtils
+import datadog.trace.api.DDSpanTypes
+import datadog.trace.api.DDTags
+import okhttp3.Credentials
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import org.apache.catalina.core.ApplicationFilterChain
+import spock.lang.Shared
+import spock.lang.Unroll
+
+import javax.servlet.Servlet
+
+// Need to be explicit to unroll inherited tests:
+@Unroll
+abstract class AbstractServlet3Test<CONTEXT> extends AgentTestRunner {
+
+ @Shared
+ OkHttpClient client = OkHttpUtils.clientBuilder().addNetworkInterceptor(new Interceptor() {
+ @Override
+ Response intercept(Interceptor.Chain chain) throws IOException {
+ def response = chain.proceed(chain.request())
+ TEST_WRITER.waitForTraces(1)
+ return response
+ }
+ })
+ .build()
+
+ @Shared
+ int port = TestUtils.randomOpenPort()
+ @Shared
+ String user = "user"
+ @Shared
+ String pass = "password"
+
+ abstract String getContext()
+
+ abstract void addServlet(CONTEXT context, String url, Class<Servlet> servlet)
+
+ protected void setupServlets(CONTEXT context) {
+ addServlet(context, "/sync", TestServlet3.Sync)
+ addServlet(context, "/auth/sync", TestServlet3.Sync)
+ addServlet(context, "/async", TestServlet3.Async)
+ addServlet(context, "/auth/async", TestServlet3.Async)
+ addServlet(context, "/blocking", TestServlet3.BlockingAsync)
+ addServlet(context, "/dispatch/sync", TestServlet3.DispatchSync)
+ addServlet(context, "/dispatch/async", TestServlet3.DispatchAsync)
+ addServlet(context, "/fake", TestServlet3.FakeAsync)
+ }
+
+ def "test #path servlet call (auth: #auth, distributed tracing: #distributedTracing)"() {
+ setup:
+ def requestBuilder = new Request.Builder()
+ .url("http://localhost:$port/$context/$path")
+ .get()
+ if (distributedTracing) {
+ requestBuilder.header("x-datadog-trace-id", "123")
+ requestBuilder.header("x-datadog-parent-id", "456")
+ }
+ if (auth) {
+ requestBuilder.header("Authorization", Credentials.basic(user, pass))
+ }
+ def response = client.newCall(requestBuilder.build()).execute()
+
+ expect:
+ response.body().string().trim() == expectedResponse
+
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ if (distributedTracing) {
+ traceId "123"
+ parentId "456"
+ } else {
+ parent()
+ }
+ serviceName context
+ operationName "servlet.request"
+ resourceName "GET /$context/$path"
+ spanType DDSpanTypes.WEB_SERVLET
+ errored false
+ tags {
+ "http.url" "http://localhost:$port/$context/$path"
+ "http.method" "GET"
+ "span.kind" "server"
+ "component" "java-web-servlet"
+ "span.origin.type" { it == "TestServlet3\$$origin" || it == ApplicationFilterChain.name }
+ "span.type" DDSpanTypes.WEB_SERVLET
+ "servlet.context" "/$context"
+ "http.status_code" 200
+ if (auth) {
+ "$DDTags.USER_NAME" user
+ }
+ defaultTags(distributedTracing)
+ }
+ }
+ }
+ }
+
+ where:
+ path | expectedResponse | auth | origin | distributedTracing
+ "async" | "Hello Async" | false | "Async" | false
+ "sync" | "Hello Sync" | false | "Sync" | false
+ "auth/async" | "Hello Async" | true | "Async" | false
+ "auth/sync" | "Hello Sync" | true | "Sync" | false
+ "blocking" | "Hello BlockingAsync" | false | "BlockingAsync" | false
+ "fake" | "Hello FakeAsync" | false | "FakeAsync" | false
+ "async" | "Hello Async" | false | "Async" | true
+ "sync" | "Hello Sync" | false | "Sync" | true
+ "auth/async" | "Hello Async" | true | "Async" | true
+ "auth/sync" | "Hello Sync" | true | "Sync" | true
+ "blocking" | "Hello BlockingAsync" | false | "BlockingAsync" | true
+ "fake" | "Hello FakeAsync" | false | "FakeAsync" | true
+ }
+
+ def "test dispatch #path"() {
+ setup:
+ def requestBuilder = new Request.Builder()
+ .url("http://localhost:$port/$context/dispatch/$path")
+ .get()
+ if (distributedTracing) {
+ requestBuilder.header("x-datadog-trace-id", "123")
+ requestBuilder.header("x-datadog-parent-id", "456")
+ }
+ def response = client.newCall(requestBuilder.build()).execute()
+
+ expect:
+ response.body().string().trim() == "Hello $origin"
+
+ assertTraces(2) {
+ trace(0, 1) {
+ span(0) {
+ if (distributedTracing) {
+ traceId "123"
+ parentId "456"
+ } else {
+ parent()
+ }
+ serviceName context
+ operationName "servlet.request"
+ resourceName "GET /$context/dispatch/$path"
+ spanType DDSpanTypes.WEB_SERVLET
+ errored false
+ tags {
+ "http.url" "http://localhost:$port/$context/dispatch/$path"
+ "http.method" "GET"
+ "span.kind" "server"
+ "component" "java-web-servlet"
+ "span.origin.type" { it == "TestServlet3\$Dispatch$origin" || it == ApplicationFilterChain.name }
+ "span.type" DDSpanTypes.WEB_SERVLET
+ "http.status_code" 200
+ "servlet.context" "/$context"
+ "servlet.dispatch" "/$path"
+ defaultTags(distributedTracing)
+ }
+ }
+ }
+ trace(1, 1) {
+ span(0) {
+ serviceName context
+ operationName "servlet.request"
+ resourceName "GET /$context/$path"
+ spanType DDSpanTypes.WEB_SERVLET
+ errored false
+ childOf TEST_WRITER[0][0]
+ tags {
+ "http.url" "http://localhost:$port/$context/$path"
+ "http.method" "GET"
+ "span.kind" "server"
+ "component" "java-web-servlet"
+ "span.origin.type" { it == "TestServlet3\$$origin" || it == ApplicationFilterChain.name }
+ "span.type" DDSpanTypes.WEB_SERVLET
+ "http.status_code" 200
+ "servlet.context" "/$context"
+ defaultTags(true)
+ }
+ }
+ }
+ }
+
+ where:
+ path | distributedTracing
+ "sync" | true
+ "sync" | false
+ "async" | true
+ "async" | false
+
+ origin = path.capitalize()
+ }
+
+ def "servlet instrumentation clears state after async request"() {
+ setup:
+ def request = new Request.Builder()
+ .url("http://localhost:$port/$context/$path")
+ .get()
+ .build()
+ def numTraces = 1
+ for (int i = 0; i < numTraces; ++i) {
+ client.newCall(request).execute()
+ }
+
+ expect:
+ assertTraces(dispatched ? numTraces * 2 : numTraces) {
+ for (int i = 0; (dispatched ? i + 1 : i) < TEST_WRITER.size(); i += (dispatched ? 2 : 1)) {
+ if (dispatched) {
+ trace(i, 1) {
+ span(0) {
+ operationName "servlet.request"
+ resourceName "GET /$context/dispatch/async"
+ parent()
+ }
+ }
+ }
+ trace(dispatched ? i + 1 : i, 1) {
+ span(0) {
+ operationName "servlet.request"
+ resourceName "GET /$context/async"
+ if (dispatched) {
+ childOf TEST_WRITER[i][0]
+ } else {
+ parent()
+ }
+ }
+ }
+ }
+ }
+
+ where:
+ path | dispatched
+ "async" | false
+ "dispatch/async" | true
+ }
+
+ def "test #path error servlet call"() {
+ setup:
+ def request = new Request.Builder()
+ .url("http://localhost:$port/$context/$path?error=true")
+ .get()
+ .build()
+ def response = client.newCall(request).execute()
+
+ expect:
+ response.body().string().trim() != expectedResponse
+
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ serviceName context
+ operationName "servlet.request"
+ resourceName "GET /$context/$path"
+ spanType DDSpanTypes.WEB_SERVLET
+ errored true
+ parent()
+ tags {
+ "http.url" "http://localhost:$port/$context/$path"
+ "http.method" "GET"
+ "span.kind" "server"
+ "component" "java-web-servlet"
+ "span.type" DDSpanTypes.WEB_SERVLET
+ "span.origin.type" { it == "TestServlet3\$$origin" || it == ApplicationFilterChain.name }
+ "servlet.context" "/$context"
+ "http.status_code" 500
+ errorTags(RuntimeException, "some $path error")
+ defaultTags()
+ }
+ }
+ }
+ }
+
+ where:
+ path | expectedResponse
+ //"async" | "Hello Async" // FIXME: I can't seem get the async error handler to trigger
+ "sync" | "Hello Sync"
+
+ origin = path.capitalize()
+ }
+
+ def "test #path non-throwing-error servlet call"() {
+ setup:
+ def request = new Request.Builder()
+ .url("http://localhost:$port/$context/$path?non-throwing-error=true")
+ .get()
+ .build()
+ def response = client.newCall(request).execute()
+
+ expect:
+ response.body().string().trim() != expectedResponse
+
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ serviceName context
+ operationName "servlet.request"
+ resourceName "GET /$context/$path"
+ spanType DDSpanTypes.WEB_SERVLET
+ errored true
+ parent()
+ tags {
+ "http.url" "http://localhost:$port/$context/$path"
+ "http.method" "GET"
+ "span.kind" "server"
+ "component" "java-web-servlet"
+ "span.type" DDSpanTypes.WEB_SERVLET
+ "span.origin.type" { it == "TestServlet3\$$origin" || it == ApplicationFilterChain.name }
+ "servlet.context" "/$context"
+ "http.status_code" 500
+ "error" true
+ defaultTags()
+ }
+ }
+ }
+ }
+
+ where:
+ path | expectedResponse
+ "sync" | "Hello Sync"
+
+ origin = path.capitalize()
+ }
+}

MODIFY
dd-java-agent/instrumentation/servlet-3/src/test/groovy/JettyServlet3Test.groovy
dd-java-agent/instrumentation/servlet-3/src/test/groovy/JettyServlet3Test.groovy
-import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.TestUtils
-import datadog.trace.agent.test.utils.OkHttpUtils
-import datadog.trace.api.DDSpanTypes
-import datadog.trace.api.DDTags
-import okhttp3.Credentials
-import okhttp3.Interceptor
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.Response
import org.eclipse.jetty.security.ConstraintMapping
import org.eclipse.jetty.security.ConstraintSecurityHandler
import org.eclipse.jetty.security.HashLoginService
@@ -18,43 +9,22 @@ import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.util.security.Constraint
import spock.lang.Shared
-class JettyServlet3Test extends AgentTestRunner {
+import javax.servlet.Servlet
- OkHttpClient client = OkHttpUtils.clientBuilder().addNetworkInterceptor(new Interceptor() {
- @Override
- Response intercept(Interceptor.Chain chain) throws IOException {
- def response = chain.proceed(chain.request())
- TEST_WRITER.waitForTraces(1)
- return response
- }
- })
- .build()
+class JettyServlet3Test extends AbstractServlet3Test<ServletContextHandler> {
- @Shared
- int port
@Shared
private Server jettyServer
- @Shared
- private ServletContextHandler servletContext
def setupSpec() {
port = TestUtils.randomOpenPort()
jettyServer = new Server(port)
- servletContext = new ServletContextHandler()
-
- ConstraintSecurityHandler security = setupAuthentication(jettyServer)
-
- servletContext.setSecurityHandler(security)
- servletContext.addServlet(TestServlet3.Sync, "/sync")
- servletContext.addServlet(TestServlet3.Sync, "/auth/sync")
- servletContext.addServlet(TestServlet3.Async, "/async")
- servletContext.addServlet(TestServlet3.Async, "/auth/async")
- servletContext.addServlet(TestServlet3.BlockingAsync, "/blocking")
- servletContext.addServlet(TestServlet3.DispatchSync, "/dispatch/sync")
- servletContext.addServlet(TestServlet3.DispatchAsync, "/dispatch/async")
- servletContext.addServlet(TestServlet3.FakeAsync, "/fake")
+ ServletContextHandler servletContext = new ServletContextHandler(null, "/$context")
+ setupAuthentication(jettyServer, servletContext)
+ setupServlets(servletContext)
jettyServer.setHandler(servletContext)
+
jettyServer.start()
System.out.println(
@@ -66,278 +36,18 @@ class JettyServlet3Test extends AgentTestRunner {
jettyServer.destroy()
}
- def "test #path servlet call (auth: #auth, distributed tracing: #distributedTracing)"() {
- setup:
- def requestBuilder = new Request.Builder()
- .url("http://localhost:$port/$path")
- .get()
- if (distributedTracing) {
- requestBuilder.header("x-datadog-trace-id", "123")
- requestBuilder.header("x-datadog-parent-id", "456")
- }
- if (auth) {
- requestBuilder.header("Authorization", Credentials.basic("user", "password"))
- }
- def response = client.newCall(requestBuilder.build()).execute()
-
- expect:
- response.body().string().trim() == expectedResponse
-
- assertTraces(1) {
- trace(0, 1) {
- span(0) {
- if (distributedTracing) {
- traceId "123"
- parentId "456"
- } else {
- parent()
- }
- serviceName "unnamed-java-app"
- operationName "servlet.request"
- resourceName "GET /$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored false
- tags {
- "http.url" "http://localhost:$port/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.origin.type" "TestServlet3\$$origin"
- "span.type" DDSpanTypes.WEB_SERVLET
- "http.status_code" 200
- if (auth) {
- "$DDTags.USER_NAME" "user"
- }
- defaultTags(distributedTracing)
- }
- }
- }
- }
-
- where:
- path | expectedResponse | auth | origin | distributedTracing
- "async" | "Hello Async" | false | "Async" | false
- "sync" | "Hello Sync" | false | "Sync" | false
- "auth/async" | "Hello Async" | true | "Async" | false
- "auth/sync" | "Hello Sync" | true | "Sync" | false
- "blocking" | "Hello BlockingAsync" | false | "BlockingAsync" | false
- "fake" | "Hello FakeAsync" | false | "FakeAsync" | false
- "async" | "Hello Async" | false | "Async" | true
- "sync" | "Hello Sync" | false | "Sync" | true
- "auth/async" | "Hello Async" | true | "Async" | true
- "auth/sync" | "Hello Sync" | true | "Sync" | true
- "blocking" | "Hello BlockingAsync" | false | "BlockingAsync" | true
- "fake" | "Hello FakeAsync" | false | "FakeAsync" | true
- }
-
- def "test dispatch #path"() {
- setup:
- def requestBuilder = new Request.Builder()
- .url("http://localhost:$port/dispatch/$path")
- .get()
- if (distributedTracing) {
- requestBuilder.header("x-datadog-trace-id", "123")
- requestBuilder.header("x-datadog-parent-id", "456")
- }
- def response = client.newCall(requestBuilder.build()).execute()
-
- expect:
- response.body().string().trim() == "Hello $type"
-
- assertTraces(2) {
- trace(0, 1) {
- span(0) {
- if (distributedTracing) {
- traceId "123"
- parentId "456"
- } else {
- parent()
- }
- serviceName "unnamed-java-app"
- operationName "servlet.request"
- resourceName "GET /dispatch/$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored false
- tags {
- "http.url" "http://localhost:$port/dispatch/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.origin.type" "TestServlet3\$Dispatch$type"
- "span.type" DDSpanTypes.WEB_SERVLET
- "http.status_code" 200
- "servlet.dispatch" "/$path"
- defaultTags(distributedTracing)
- }
- }
- }
- trace(1, 1) {
- span(0) {
- serviceName "unnamed-java-app"
- operationName "servlet.request"
- resourceName "GET /$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored false
- tags {
- "http.url" "http://localhost:$port/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.origin.type" "TestServlet3\$$type"
- "span.type" DDSpanTypes.WEB_SERVLET
- "http.status_code" 200
- defaultTags(true)
- }
- }
- }
- }
-
- where:
- path | distributedTracing
- "sync" | true
- "sync" | false
- "async" | true
- "async" | false
-
- type = path.capitalize()
- }
-
- def "servlet instrumentation clears state after async request"() {
- setup:
- def request = new Request.Builder()
- .url("http://localhost:$port/$path")
- .get()
- .build()
- def numTraces = 10
- for (int i = 0; i < numTraces; ++i) {
- client.newCall(request).execute()
- }
-
- expect:
- assertTraces(dispatched ? numTraces * 2 : numTraces) {
- for (int i = 0; (dispatched ? i + 1 : i) < TEST_WRITER.size(); i += (dispatched ? 2 : 1)) {
- if (dispatched) {
- trace(i, 1) {
- span(0) {
- operationName "servlet.request"
- resourceName "GET /dispatch/async"
- parent()
- }
- }
- }
- trace(dispatched ? i + 1 : i, 1) {
- span(0) {
- operationName "servlet.request"
- resourceName "GET /async"
- if (dispatched) {
- childOf TEST_WRITER[i][0]
- } else {
- parent()
- }
- }
- }
- }
- }
-
- where:
- path | dispatched
- "async" | false
- "dispatch/async" | true
- }
-
- def "test #path error servlet call"() {
- setup:
- def request = new Request.Builder()
- .url("http://localhost:$port/$path?error=true")
- .get()
- .build()
- def response = client.newCall(request).execute()
-
- expect:
- response.body().string().trim() != expectedResponse
-
- assertTraces(1) {
- trace(0, 1) {
- span(0) {
- serviceName "unnamed-java-app"
- operationName "servlet.request"
- resourceName "GET /$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored true
- parent()
- tags {
- "http.url" "http://localhost:$port/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.type" DDSpanTypes.WEB_SERVLET
- "span.origin.type" "TestServlet3\$Sync"
- "http.status_code" 500
- errorTags(RuntimeException, "some $path error")
- defaultTags()
- }
- }
- }
- }
-
- where:
- path | expectedResponse
- //"async" | "Hello Async" // FIXME: I can't seem get the async error handler to trigger
- "sync" | "Hello Sync"
- }
-
- def "test #path non-throwing-error servlet call"() {
- setup:
- def request = new Request.Builder()
- .url("http://localhost:$port/$path?non-throwing-error=true")
- .get()
- .build()
- def response = client.newCall(request).execute()
-
- expect:
- response.body().string().trim() != expectedResponse
-
- assertTraces(1) {
- trace(0, 1) {
- span(0) {
- serviceName "unnamed-java-app"
- operationName "servlet.request"
- resourceName "GET /$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored true
- parent()
- tags {
- "http.url" "http://localhost:$port/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.type" DDSpanTypes.WEB_SERVLET
- "span.origin.type" "TestServlet3\$Sync"
- "http.status_code" 500
- "error" true
- defaultTags()
- }
- }
- }
+ @Override
+ String getContext() {
+ return "jetty-context"
}
- where:
- path | expectedResponse
- "sync" | "Hello Sync"
+ @Override
+ void addServlet(ServletContextHandler servletContext, String url, Class<Servlet> servlet) {
+ servletContext.addServlet(servlet, url)
}
- /**
- * Setup simple authentication for tests
- * <p>
- * requests to {@code /auth/*} need login 'user' and password 'password'
- * <p>
- * For details @see <a href="http://www.eclipse.org/jetty/documentation/9.3.x/embedded-examples.html">http://www.eclipse.org/jetty/documentation/9.3.x/embedded-examples.html</a>
- *
- * @param jettyServer server to attach login service
- * @return SecurityHandler that can be assigned to servlet
- */
- private ConstraintSecurityHandler setupAuthentication(Server jettyServer) {
- ConstraintSecurityHandler security = new ConstraintSecurityHandler()
+ static setupAuthentication(Server jettyServer, ServletContextHandler servletContext) {
+ ConstraintSecurityHandler authConfig = new ConstraintSecurityHandler()
Constraint constraint = new Constraint()
constraint.setName("auth")
@@ -348,14 +58,14 @@ class JettyServlet3Test extends AgentTestRunner {
mapping.setPathSpec("/auth/*")
mapping.setConstraint(constraint)
- security.setConstraintMappings(mapping)
- security.setAuthenticator(new BasicAuthenticator())
+ authConfig.setConstraintMappings(mapping)
+ authConfig.setAuthenticator(new BasicAuthenticator())
LoginService loginService = new HashLoginService("TestRealm",
"src/test/resources/realm.properties")
- security.setLoginService(loginService)
+ authConfig.setLoginService(loginService)
jettyServer.addBean(loginService)
- security
+ servletContext.setSecurityHandler(authConfig)
}
}

MODIFY
dd-java-agent/instrumentation/servlet-3/src/test/groovy/TomcatServlet3Test.groovy
dd-java-agent/instrumentation/servlet-3/src/test/groovy/TomcatServlet3Test.groovy
import com.google.common.io.Files
-import datadog.trace.agent.test.AgentTestRunner
-import datadog.trace.agent.test.TestUtils
-import datadog.trace.agent.test.utils.OkHttpUtils
-import datadog.trace.api.DDSpanTypes
-import okhttp3.OkHttpClient
-import okhttp3.Request
import org.apache.catalina.Context
-import org.apache.catalina.core.ApplicationFilterChain
+import org.apache.catalina.realm.MemoryRealm
import org.apache.catalina.startup.Tomcat
import org.apache.tomcat.JarScanFilter
import org.apache.tomcat.JarScanType
+import org.apache.tomcat.util.descriptor.web.LoginConfig
+import org.apache.tomcat.util.descriptor.web.SecurityCollection
+import org.apache.tomcat.util.descriptor.web.SecurityConstraint
import spock.lang.Shared
-class TomcatServlet3Test extends AgentTestRunner {
+import javax.servlet.Servlet
- OkHttpClient client = OkHttpUtils.client()
+class TomcatServlet3Test extends AbstractServlet3Test<Context> {
- @Shared
- int port
@Shared
Tomcat tomcatServer
@Shared
- Context appContext
+ def baseDir = Files.createTempDir()
def setupSpec() {
- port = TestUtils.randomOpenPort()
tomcatServer = new Tomcat()
tomcatServer.setPort(port)
tomcatServer.getConnector()
- def baseDir = Files.createTempDir()
baseDir.deleteOnExit()
tomcatServer.setBaseDir(baseDir.getAbsolutePath())
final File applicationDir = new File(baseDir, "/webapps/ROOT")
if (!applicationDir.exists()) {
applicationDir.mkdirs()
+ applicationDir.deleteOnExit()
}
- appContext = tomcatServer.addWebapp("/my-context", applicationDir.getAbsolutePath())
+ Context servletContext = tomcatServer.addWebapp("/$context", applicationDir.getAbsolutePath())
// Speed up startup by disabling jar scanning:
- appContext.getJarScanner().setJarScanFilter(new JarScanFilter() {
+ servletContext.getJarScanner().setJarScanFilter(new JarScanFilter() {
@Override
boolean check(JarScanType jarScanType, String jarName) {
return false
}
})
- Tomcat.addServlet(appContext, "syncServlet", new TestServlet3.Sync())
- appContext.addServletMappingDecoded("/sync", "syncServlet")
-
- Tomcat.addServlet(appContext, "asyncServlet", new TestServlet3.Async())
- appContext.addServletMappingDecoded("/async", "asyncServlet")
-
- Tomcat.addServlet(appContext, "blockingServlet", new TestServlet3.BlockingAsync())
- appContext.addServletMappingDecoded("/blocking", "blockingServlet")
-
- Tomcat.addServlet(appContext, "dispatchServlet", new TestServlet3.DispatchSync())
- appContext.addServletMappingDecoded("/dispatch/sync", "dispatchServlet")
-
- Tomcat.addServlet(appContext, "dispatchAsyncServlet", new TestServlet3.DispatchAsync())
- appContext.addServletMappingDecoded("/dispatch/async", "dispatchAsyncServlet")
-
- Tomcat.addServlet(appContext, "fakeServlet", new TestServlet3.FakeAsync())
- appContext.addServletMappingDecoded("/fake", "fakeServlet")
+ setupAuthentication(tomcatServer, servletContext)
+ setupServlets(servletContext)
tomcatServer.start()
System.out.println(
@@ -74,259 +53,44 @@ class TomcatServlet3Test extends AgentTestRunner {
tomcatServer.destroy()
}
- def "test #path servlet call (auth: #auth, distributed tracing: #distributedTracing)"() {
- setup:
- def requestBuilder = new Request.Builder()
- .url("http://localhost:$port/my-context/$path")
- .get()
- if (distributedTracing) {
- requestBuilder.header("x-datadog-trace-id", "123")
- requestBuilder.header("x-datadog-parent-id", "456")
- }
- def response = client.newCall(requestBuilder.build()).execute()
-
- expect:
- response.body().string().trim() == expectedResponse
-
- assertTraces(1) {
- trace(0, 1) {
- span(0) {
- if (distributedTracing) {
- traceId "123"
- parentId "456"
- } else {
- parent()
- }
- serviceName "my-context"
- operationName "servlet.request"
- resourceName "GET /my-context/$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored false
- tags {
- "http.url" "http://localhost:$port/my-context/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.type" DDSpanTypes.WEB_SERVLET
- "span.origin.type" ApplicationFilterChain.name
- "servlet.context" "/my-context"
- "http.status_code" 200
- defaultTags(distributedTracing)
- }
- }
- }
- }
-
- where:
- path | expectedResponse | origin | distributedTracing
- "async" | "Hello Async" | "Async" | false
- "sync" | "Hello Sync" | "Sync" | false
- "blocking" | "Hello BlockingAsync" | "BlockingAsync" | false
- "fake" | "Hello FakeAsync" | "FakeAsync" | false
- "async" | "Hello Async" | "Async" | true
- "sync" | "Hello Sync" | "Sync" | true
- "blocking" | "Hello BlockingAsync" | "BlockingAsync" | true
- "fake" | "Hello FakeAsync" | "FakeAsync" | true
- }
-
- def "test dispatch #path"() {
- setup:
- def requestBuilder = new Request.Builder()
- .url("http://localhost:$port/my-context/dispatch/$path")
- .get()
- if (distributedTracing) {
- requestBuilder.header("x-datadog-trace-id", "123")
- requestBuilder.header("x-datadog-parent-id", "456")
- }
- def response = client.newCall(requestBuilder.build()).execute()
-
- expect:
- response.body().string().trim() == "Hello $type"
-
- assertTraces(2) {
- trace(0, 1) {
- span(0) {
- if (distributedTracing) {
- traceId "123"
- parentId "456"
- } else {
- parent()
- }
- serviceName "my-context"
- operationName "servlet.request"
- resourceName "GET /my-context/dispatch/$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored false
- tags {
- "http.url" "http://localhost:$port/my-context/dispatch/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.origin.type" "org.apache.catalina.core.ApplicationFilterChain"
- "span.type" DDSpanTypes.WEB_SERVLET
- "http.status_code" 200
- "servlet.context" "/my-context"
- "servlet.dispatch" "/$path"
- defaultTags(distributedTracing)
- }
- }
- }
- trace(1, 1) {
- span(0) {
- serviceName "my-context"
- operationName "servlet.request"
- resourceName "GET /my-context/$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored false
- childOf TEST_WRITER[0][0]
- tags {
- "http.url" "http://localhost:$port/my-context/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.origin.type" "org.apache.catalina.core.ApplicationFilterChain"
- "span.type" DDSpanTypes.WEB_SERVLET
- "http.status_code" 200
- "servlet.context" "/my-context"
- defaultTags(true)
- }
- }
- }
- }
-
- where:
- path | distributedTracing
- "sync" | true
- "sync" | false
- "async" | true
- "async" | false
-
- type = path.capitalize()
- }
-
- def "servlet instrumentation clears state after async request"() {
- setup:
- def request = new Request.Builder()
- .url("http://localhost:$port/my-context/$path")
- .get()
- .build()
- def numTraces = 10
- for (int i = 0; i < numTraces; ++i) {
- client.newCall(request).execute()
- }
-
- expect:
- assertTraces(dispatched ? numTraces * 2 : numTraces) {
- for (int i = 0; (dispatched ? i + 1 : i) < TEST_WRITER.size(); i += (dispatched ? 2 : 1)) {
- if (dispatched) {
- trace(i, 1) {
- span(0) {
- operationName "servlet.request"
- resourceName "GET /my-context/dispatch/async"
- parent()
- }
- }
- }
- trace(dispatched ? i + 1 : i, 1) {
- span(0) {
- operationName "servlet.request"
- resourceName "GET /my-context/async"
- if (dispatched) {
- childOf TEST_WRITER[i][0]
- } else {
- parent()
- }
- }
- }
- }
- }
-
- where:
- path | dispatched
- "async" | false
- "dispatch/async" | true
- }
-
- def "test #path error servlet call"() {
- setup:
- def request = new Request.Builder()
- .url("http://localhost:$port/my-context/$path?error=true")
- .get()
- .build()
- def response = client.newCall(request).execute()
-
- expect:
- response.body().string().trim() != expectedResponse
-
- assertTraces(1) {
- trace(0, 1) {
- span(0) {
- serviceName "my-context"
- operationName "servlet.request"
- resourceName "GET /my-context/$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored true
- parent()
- tags {
- "http.url" "http://localhost:$port/my-context/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.type" DDSpanTypes.WEB_SERVLET
- "span.origin.type" ApplicationFilterChain.name
- "servlet.context" "/my-context"
- "http.status_code" 500
- errorTags(RuntimeException, "some $path error")
- defaultTags()
- }
- }
- }
- }
-
- where:
- path | expectedResponse
- //"async" | "Hello Async" // FIXME: I can't seem get the async error handler to trigger
- "sync" | "Hello Sync"
- }
-
- def "test #path non-throwing-error servlet call"() {
- setup:
- def request = new Request.Builder()
- .url("http://localhost:$port/my-context/$path?non-throwing-error=true")
- .get()
- .build()
- def response = client.newCall(request).execute()
-
- expect:
- response.body().string().trim() != expectedResponse
-
- assertTraces(1) {
- trace(0, 1) {
- span(0) {
- serviceName "my-context"
- operationName "servlet.request"
- resourceName "GET /my-context/$path"
- spanType DDSpanTypes.WEB_SERVLET
- errored true
- parent()
- tags {
- "http.url" "http://localhost:$port/my-context/$path"
- "http.method" "GET"
- "span.kind" "server"
- "component" "java-web-servlet"
- "span.type" DDSpanTypes.WEB_SERVLET
- "span.origin.type" ApplicationFilterChain.name
- "servlet.context" "/my-context"
- "http.status_code" 500
- "error" true
- defaultTags()
- }
- }
- }
+ @Override
+ String getContext() {
+ return "tomcat-context"
}
- where:
- path | expectedResponse
- "sync" | "Hello Sync"
+ @Override
+ void addServlet(Context servletContext, String url, Class<Servlet> servlet) {
+ String name = UUID.randomUUID()
+ Tomcat.addServlet(servletContext, name, servlet.newInstance())
+ servletContext.addServletMappingDecoded(url, name)
+ }
+
+ static 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
+ URL uri = getClass().getResource("/tomcat-users.xml");
+ assert uri
+ MemoryRealm realm = new MemoryRealm();
+ realm.setPathname(uri.toString());
+ server.getEngine().setRealm(realm);
+
+ servletContext.setLoginConfig(authConfig)
}
}

ADD
dd-java-agent/instrumentation/servlet-3/src/test/resources/tomcat-users.xml
+<tomcat-users>
+ <role rolename="role"/>
+ <user name="user" password="password" roles="role"/>
+</tomcat-users>

Extract shared tests into abstract parent