Starting to get tests building
This commit is contained in:
parent
8f876c1b7d
commit
73122ea72c
|
@ -0,0 +1 @@
|
|||
logs/
|
|
@ -0,0 +1,47 @@
|
|||
ext {
|
||||
minJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||
// Play doesn't work with Java 9+ until 2.6.12
|
||||
maxJavaVersionForTests = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
muzzle {
|
||||
pass {
|
||||
group = 'com.typesafe.play'
|
||||
module = 'play_2.11'
|
||||
versions = '[2.3.0,2.4)'
|
||||
assertInverse = true
|
||||
}
|
||||
fail {
|
||||
group = 'com.typesafe.play'
|
||||
module = 'play_2.12'
|
||||
versions = '[,]'
|
||||
}
|
||||
fail {
|
||||
group = 'com.typesafe.play'
|
||||
module = 'play_2.13'
|
||||
versions = '[,]'
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/gradle/java.gradle"
|
||||
apply from: "${rootDir}/gradle/test-with-scala.gradle"
|
||||
|
||||
apply plugin: 'org.unbroken-dome.test-sets'
|
||||
|
||||
testSets {
|
||||
latestDepTest {
|
||||
dirName = 'test'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
main_java8Compile group: 'com.typesafe.play', name: 'play_2.11', version: '2.3.9'
|
||||
|
||||
testCompile group: 'com.typesafe.play', name: 'play-java_2.11', version: '2.3.9'
|
||||
testCompile group: 'com.typesafe.play', name: 'play-java-ws_2.11', version: '2.3.9'
|
||||
testCompile(group: 'com.typesafe.play', name: 'play-test_2.11', version: '2.3.9')
|
||||
|
||||
latestDepTestCompile group: 'com.typesafe.play', name: 'play-java_2.11', version: '2.3.+'
|
||||
latestDepTestCompile group: 'com.typesafe.play', name: 'play-java-ws_2.11', version: '2.3.+'
|
||||
latestDepTestCompile(group: 'com.typesafe.play', name: 'play-test_2.11', version: '2.3.+')
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package datadog.trace.instrumentation.play24;
|
||||
|
||||
import static datadog.trace.agent.tooling.ClassLoaderMatcher.hasClassesNamed;
|
||||
import static datadog.trace.agent.tooling.bytebuddy.matcher.DDElementMatchers.implementsInterface;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.returns;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import java.util.Map;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public final class PlayInstrumentation extends Instrumenter.Default {
|
||||
|
||||
public PlayInstrumentation() {
|
||||
super("play");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderMatcher() {
|
||||
// Optimization for expensive typeMatcher.
|
||||
return hasClassesNamed("play.api.mvc.Action");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return implementsInterface(named("play.api.mvc.Action"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] helperClassNames() {
|
||||
return new String[] {
|
||||
packageName + ".PlayHttpServerDecorator",
|
||||
packageName + ".RequestCompleteCallback",
|
||||
packageName + ".PlayHeaders",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
return singletonMap(
|
||||
named("apply")
|
||||
.and(takesArgument(0, named("play.api.mvc.Request")))
|
||||
.and(returns(named("scala.concurrent.Future"))),
|
||||
packageName + ".PlayAdvice");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package datadog.trace.instrumentation.play24;
|
||||
|
||||
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
|
||||
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan;
|
||||
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.propagate;
|
||||
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
|
||||
import static datadog.trace.instrumentation.play24.PlayHeaders.GETTER;
|
||||
import static datadog.trace.instrumentation.play24.PlayHttpServerDecorator.DECORATE;
|
||||
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentSpan.Context;
|
||||
import datadog.trace.bootstrap.instrumentation.api.Tags;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import play.api.mvc.Action;
|
||||
import play.api.mvc.Request;
|
||||
import play.api.mvc.Result;
|
||||
import scala.concurrent.Future;
|
||||
|
||||
public class PlayAdvice {
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static AgentScope onEnter(@Advice.Argument(0) final Request req) {
|
||||
final AgentSpan span;
|
||||
if (activeSpan() == null) {
|
||||
final Context extractedContext = propagate().extract(req.headers(), GETTER);
|
||||
span = startSpan("play.request", extractedContext);
|
||||
} else {
|
||||
// An upstream framework (e.g. akka-http, netty) has already started the span.
|
||||
// Do not extract the context.
|
||||
span = startSpan("play.request");
|
||||
}
|
||||
DECORATE.afterStart(span);
|
||||
DECORATE.onConnection(span, req);
|
||||
|
||||
final AgentScope scope = activateSpan(span, false);
|
||||
scope.setAsyncPropagation(true);
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void stopTraceOnResponse(
|
||||
@Advice.Enter final AgentScope playControllerScope,
|
||||
@Advice.This final Object thisAction,
|
||||
@Advice.Thrown final Throwable throwable,
|
||||
@Advice.Argument(0) final Request req,
|
||||
@Advice.Return(readOnly = false) final Future<Result> responseFuture) {
|
||||
final AgentSpan playControllerSpan = playControllerScope.span();
|
||||
|
||||
// Call onRequest on return after tags are populated.
|
||||
DECORATE.onRequest(playControllerSpan, req);
|
||||
|
||||
if (throwable == null) {
|
||||
responseFuture.onComplete(
|
||||
new RequestCompleteCallback(playControllerSpan),
|
||||
((Action) thisAction).executionContext());
|
||||
} else {
|
||||
DECORATE.onError(playControllerSpan, throwable);
|
||||
playControllerSpan.setTag(Tags.HTTP_STATUS, 500);
|
||||
DECORATE.beforeFinish(playControllerSpan);
|
||||
playControllerSpan.finish();
|
||||
}
|
||||
playControllerScope.close();
|
||||
|
||||
final AgentSpan rootSpan = activeSpan();
|
||||
// set the resource name on the upstream akka/netty span
|
||||
DECORATE.onRequest(rootSpan, req);
|
||||
}
|
||||
|
||||
// Unused method for muzzle to allow only 2.4-2.5
|
||||
public static void muzzleCheck() {
|
||||
play.libs.Akka.system();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package datadog.trace.instrumentation.play24;
|
||||
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentPropagation;
|
||||
import play.api.mvc.Headers;
|
||||
import scala.Option;
|
||||
import scala.collection.JavaConversions;
|
||||
|
||||
public class PlayHeaders implements AgentPropagation.Getter<Headers> {
|
||||
|
||||
public static final PlayHeaders GETTER = new PlayHeaders();
|
||||
|
||||
@Override
|
||||
public Iterable<String> keys(final Headers headers) {
|
||||
return JavaConversions.asJavaIterable(headers.keys());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(final Headers headers, final String key) {
|
||||
final Option<String> option = headers.get(key);
|
||||
if (option.isDefined()) {
|
||||
return option.get();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package datadog.trace.instrumentation.play24;
|
||||
|
||||
import datadog.trace.api.DDTags;
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||
import datadog.trace.bootstrap.instrumentation.api.Tags;
|
||||
import datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import play.api.mvc.Request;
|
||||
import play.api.mvc.Result;
|
||||
import scala.Option;
|
||||
|
||||
@Slf4j
|
||||
public class PlayHttpServerDecorator extends HttpServerDecorator<Request, Request, Result> {
|
||||
public static final PlayHttpServerDecorator DECORATE = new PlayHttpServerDecorator();
|
||||
|
||||
@Override
|
||||
protected String[] instrumentationNames() {
|
||||
return new String[] {"play"};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String component() {
|
||||
return "play-action";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String method(final Request httpRequest) {
|
||||
return httpRequest.method();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URI url(final Request request) throws URISyntaxException {
|
||||
return new URI((request.secure() ? "https://" : "http://") + request.host() + request.uri());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String peerHostIP(final Request request) {
|
||||
return request.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer peerPort(final Request request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer status(final Result httpResponse) {
|
||||
return httpResponse.header().status();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentSpan onRequest(final AgentSpan span, final Request request) {
|
||||
super.onRequest(span, request);
|
||||
if (request != null) {
|
||||
// more about routes here:
|
||||
// https://github.com/playframework/playframework/blob/master/documentation/manual/releases/release26/migration26/Migration26.md#router-tags-are-now-attributes
|
||||
final Option pathOption = request.tags().get("ROUTE_PATTERN");
|
||||
if (!pathOption.isEmpty()) {
|
||||
final String path = (String) pathOption.get();
|
||||
span.setTag(DDTags.RESOURCE_NAME, request.method() + " " + path);
|
||||
}
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentSpan onError(final AgentSpan span, Throwable throwable) {
|
||||
span.setTag(Tags.HTTP_STATUS, 500);
|
||||
if (throwable != null
|
||||
// This can be moved to instanceof check when using Java 8.
|
||||
&& throwable.getClass().getName().equals("java.util.concurrent.CompletionException")
|
||||
&& throwable.getCause() != null) {
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
while ((throwable instanceof InvocationTargetException
|
||||
|| throwable instanceof UndeclaredThrowableException)
|
||||
&& throwable.getCause() != null) {
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
return super.onError(span, throwable);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package datadog.trace.instrumentation.play24;
|
||||
|
||||
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeScope;
|
||||
import static datadog.trace.instrumentation.play24.PlayHttpServerDecorator.DECORATE;
|
||||
|
||||
import datadog.trace.context.TraceScope;
|
||||
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import play.api.mvc.Result;
|
||||
import scala.util.Try;
|
||||
|
||||
@Slf4j
|
||||
public class RequestCompleteCallback extends scala.runtime.AbstractFunction1<Try<Result>, Object> {
|
||||
private final AgentSpan span;
|
||||
|
||||
public RequestCompleteCallback(final AgentSpan span) {
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(final Try<Result> result) {
|
||||
try {
|
||||
if (result.isFailure()) {
|
||||
DECORATE.onError(span, result.failed().get());
|
||||
} else {
|
||||
DECORATE.onResponse(span, result.get());
|
||||
}
|
||||
DECORATE.beforeFinish(span);
|
||||
final TraceScope scope = activeScope();
|
||||
if (scope != null) {
|
||||
scope.setAsyncPropagation(false);
|
||||
}
|
||||
} catch (final Throwable t) {
|
||||
log.debug("error in play instrumentation", t);
|
||||
} finally {
|
||||
span.finish();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package client
|
||||
|
||||
import datadog.trace.agent.test.base.HttpClientTest
|
||||
import play.libs.ws.WS
|
||||
import spock.lang.AutoCleanup
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Subject
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
// Play 2.6+ uses a separately versioned client that shades the underlying dependency
|
||||
// This means our built in instrumentation won't work.
|
||||
class PlayWSClientTest extends HttpClientTest {
|
||||
@Subject
|
||||
@Shared
|
||||
@AutoCleanup
|
||||
def client = WS.client()
|
||||
|
||||
@Override
|
||||
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||
def request = client.url(uri.toString())
|
||||
headers.entrySet().each {
|
||||
request.setHeader(it.key, it.value)
|
||||
}
|
||||
|
||||
def status = request.execute(method).map({
|
||||
callback?.call()
|
||||
it
|
||||
}).map({
|
||||
it.status
|
||||
})
|
||||
return status.get(1, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
@Override
|
||||
String component() {
|
||||
return "" // NettyHttpClientDecorator.DECORATE.component()
|
||||
}
|
||||
|
||||
@Override
|
||||
String expectedOperationName() {
|
||||
return "netty.client.request"
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean testRedirects() {
|
||||
false
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean testConnectionFailure() {
|
||||
false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package server;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import datadog.trace.agent.test.base.HttpServerTestAdvice;
|
||||
import datadog.trace.agent.tooling.Instrumenter;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
|
||||
@AutoService(Instrumenter.class)
|
||||
public class NettyServerTestInstrumentation implements Instrumenter {
|
||||
|
||||
@Override
|
||||
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
|
||||
return agentBuilder
|
||||
.type(named("io.netty.handler.codec.ByteToMessageDecoder"))
|
||||
.transform(
|
||||
new AgentBuilder.Transformer.ForAdvice()
|
||||
.advice(
|
||||
named("channelRead"), HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package server
|
||||
|
||||
class PlayAsyncServerTest { //extends PlayServerTest {
|
||||
// @Override
|
||||
// Server startServer(int port) {
|
||||
// def router =
|
||||
// new RoutingDsl()
|
||||
// .GET(SUCCESS.getPath()).routeAsync({
|
||||
// CompletableFuture.supplyAsync({
|
||||
// controller(SUCCESS) {
|
||||
// Results.status(SUCCESS.getStatus(), SUCCESS.getBody())
|
||||
// }
|
||||
// }, HttpExecution.defaultContext())
|
||||
// } as Supplier)
|
||||
// .GET(QUERY_PARAM.getPath()).routeAsync({
|
||||
// CompletableFuture.supplyAsync({
|
||||
// controller(QUERY_PARAM) {
|
||||
// Results.status(QUERY_PARAM.getStatus(), QUERY_PARAM.getBody())
|
||||
// }
|
||||
// }, HttpExecution.defaultContext())
|
||||
// } as Supplier)
|
||||
// .GET(REDIRECT.getPath()).routeAsync({
|
||||
// CompletableFuture.supplyAsync({
|
||||
// controller(REDIRECT) {
|
||||
// Results.found(REDIRECT.getBody())
|
||||
// }
|
||||
// }, HttpExecution.defaultContext())
|
||||
// } as Supplier)
|
||||
// .GET(ERROR.getPath()).routeAsync({
|
||||
// CompletableFuture.supplyAsync({
|
||||
// controller(ERROR) {
|
||||
// Results.status(ERROR.getStatus(), ERROR.getBody())
|
||||
// }
|
||||
// }, HttpExecution.defaultContext())
|
||||
// } as Supplier)
|
||||
// .GET(EXCEPTION.getPath()).routeAsync({
|
||||
// CompletableFuture.supplyAsync({
|
||||
// controller(EXCEPTION) {
|
||||
// throw new Exception(EXCEPTION.getBody())
|
||||
// }
|
||||
// }, HttpExecution.defaultContext())
|
||||
// } as Supplier)
|
||||
//
|
||||
// return Server.forRouter(router.build(), port)
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package server
|
||||
|
||||
import datadog.opentracing.DDSpan
|
||||
import datadog.trace.agent.test.asserts.TraceAssert
|
||||
import datadog.trace.agent.test.base.HttpServerTest
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import datadog.trace.api.DDTags
|
||||
import datadog.trace.bootstrap.instrumentation.api.Tags
|
||||
import play.api.test.TestServer
|
||||
|
||||
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.*
|
||||
|
||||
class PlayServerTest extends HttpServerTest<TestServer> {
|
||||
@Override
|
||||
TestServer startServer(int port) {
|
||||
def server = SyncServer.server(port)
|
||||
server.start()
|
||||
return server
|
||||
}
|
||||
|
||||
@Override
|
||||
void stopServer(TestServer server) {
|
||||
server.stop()
|
||||
}
|
||||
|
||||
@Override
|
||||
String component() {
|
||||
return ""
|
||||
}
|
||||
|
||||
@Override
|
||||
String expectedOperationName() {
|
||||
return "netty.request"
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean hasHandlerSpan() {
|
||||
true
|
||||
}
|
||||
|
||||
@Override
|
||||
// Return the handler span's name
|
||||
String reorderHandlerSpan() {
|
||||
"play.request"
|
||||
}
|
||||
|
||||
boolean testExceptionBody() {
|
||||
// I can't figure out how to set a proper exception handler to customize the response body.
|
||||
false
|
||||
}
|
||||
|
||||
@Override
|
||||
void handlerSpan(TraceAssert trace, int index, Object parent, ServerEndpoint endpoint = SUCCESS) {
|
||||
trace.span(index) {
|
||||
serviceName expectedServiceName()
|
||||
operationName "play.request"
|
||||
spanType DDSpanTypes.HTTP_SERVER
|
||||
errored endpoint == ERROR || endpoint == EXCEPTION
|
||||
childOf(parent as DDSpan)
|
||||
tags {
|
||||
"$Tags.COMPONENT" ""
|
||||
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
|
||||
"$Tags.PEER_HOST_IPV4" { it == null || it == "127.0.0.1" } // Optional
|
||||
"$Tags.HTTP_URL" String
|
||||
"$Tags.HTTP_METHOD" String
|
||||
"$Tags.HTTP_STATUS" Integer
|
||||
if (endpoint == ERROR) {
|
||||
"$Tags.ERROR" true
|
||||
} else if (endpoint == EXCEPTION) {
|
||||
errorTags(Exception, EXCEPTION.body)
|
||||
}
|
||||
if (endpoint.query) {
|
||||
"$DDTags.HTTP_QUERY" endpoint.query
|
||||
}
|
||||
defaultTags()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package server
|
||||
|
||||
object AsyncServer {
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package server
|
||||
|
||||
import datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint._
|
||||
import play.api.mvc.{Action, Handler, Results}
|
||||
import play.api.test.{FakeApplication, TestServer}
|
||||
|
||||
object SyncServer {
|
||||
val routes: PartialFunction[(String, String), Handler] = {
|
||||
case ("GET", "/success") => Action { request => Results.Status(SUCCESS.getStatus) }
|
||||
}
|
||||
|
||||
def server(port: Int): TestServer = {
|
||||
TestServer(port, FakeApplication(withRoutes = routes))
|
||||
}
|
||||
}
|
|
@ -116,6 +116,7 @@ include ':dd-java-agent:instrumentation:netty-3.8'
|
|||
include ':dd-java-agent:instrumentation:netty-4.0'
|
||||
include ':dd-java-agent:instrumentation:netty-4.1'
|
||||
include ':dd-java-agent:instrumentation:okhttp-3'
|
||||
include ':dd-java-agent:instrumentation:play-2.3'
|
||||
include ':dd-java-agent:instrumentation:play-2.4'
|
||||
include ':dd-java-agent:instrumentation:play-2.6'
|
||||
include ':dd-java-agent:instrumentation:play-ws'
|
||||
|
|
Loading…
Reference in New Issue