Merge branch 'twilio' of github.com:darylrobbins/dd-trace-java into twilio

* 'twilio' of github.com:darylrobbins/dd-trace-java:
  Updates to handle async calls, which have broken all tests
  Missed Gradle file
  WIP Twilio SDK Instrumentation

# Conflicts:
#	dd-java-agent/instrumentation/twilio/src/main/java/datadog/trace/instrumentation/twilio/TwilioClientDecorator.java
#	dd-java-agent/instrumentation/twilio/src/main/java/datadog/trace/instrumentation/twilio/TwilioInstrumentation.java
#	dd-java-agent/instrumentation/twilio/src/test/groovy/test/TwilioClientTest.groovy

Merge branch 'twilio' of github.com:darylrobbins/dd-trace-java into twilio
Improved unit testing

* 'twilio' of github.com:darylrobbins/dd-trace-java:
  Updates to handle async calls, which have broken all tests
  Missed Gradle file
  WIP Twilio SDK Instrumentation

# Conflicts:
#	dd-java-agent/instrumentation/twilio/src/main/java/datadog/trace/instrumentation/twilio/TwilioClientDecorator.java
#	dd-java-agent/instrumentation/twilio/src/main/java/datadog/trace/instrumentation/twilio/TwilioInstrumentation.java
#	dd-java-agent/instrumentation/twilio/src/test/groovy/test/TwilioClientTest.groovy

Fix sleep times and choose Java7-friendly test dependencies

Corrected test assertion
This commit is contained in:
Daryl Robbins 2019-03-22 20:32:47 -04:00 committed by Tyler Benson
commit 315ae67fd2
5 changed files with 486 additions and 20 deletions

View File

@ -37,7 +37,7 @@ public class TwilioClientDecorator extends ClientDecorator {
return COMPONENT_NAME; return COMPONENT_NAME;
} }
/** Decorate trace based on service execution metadata */ /** Decorate trace based on service execution metadata. */
public Span onServiceExecution( public Span onServiceExecution(
final Span span, final Object serviceExecutor, final String methodName) { final Span span, final Object serviceExecutor, final String methodName) {
@ -76,13 +76,17 @@ public class TwilioClientDecorator extends ClientDecorator {
span.setTag("twilio.type", result.getClass().getCanonicalName()); span.setTag("twilio.type", result.getClass().getCanonicalName());
span.setTag("twilio.account", message.getAccountSid()); span.setTag("twilio.account", message.getAccountSid());
span.setTag("twilio.sid", message.getSid()); span.setTag("twilio.sid", message.getSid());
span.setTag("twilio.status", message.getStatus().toString()); if (message.getStatus() != null) {
span.setTag("twilio.status", message.getStatus().toString());
}
} else if (result instanceof Call) { } else if (result instanceof Call) {
final Call call = (Call) result; final Call call = (Call) result;
span.setTag("twilio.account", call.getAccountSid()); span.setTag("twilio.account", call.getAccountSid());
span.setTag("twilio.sid", call.getSid()); span.setTag("twilio.sid", call.getSid());
span.setTag("twilio.parentSid", call.getParentCallSid()); span.setTag("twilio.parentSid", call.getParentCallSid());
span.setTag("twilio.status", call.getStatus().toString()); if (call.getStatus() != null) {
span.setTag("twilio.status", call.getStatus().toString());
}
} else { } else {
// Use reflection to gather insight from other types; note that Twilio requests take close to // Use reflection to gather insight from other types; note that Twilio requests take close to
// 1 second, so the added hit from reflection here is relatively minimal in the grand scheme // 1 second, so the added hit from reflection here is relatively minimal in the grand scheme

View File

@ -64,9 +64,10 @@ public class TwilioInstrumentation extends Instrumenter.Default {
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() { public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
/* /*
We are listing out the main service calls on the Creator, Deleter, Fetcher, Reader, and Updater abstract We are listing out the main service calls on the Creator, Deleter, Fetcher, Reader, and
classes. The isDeclaredBy() matcher did not work in the unit tests and we found that there were certain Updater abstract classes. The isDeclaredBy() matcher did not work in the unit tests and
methods declared on the base class (particularly Reader), which we weren't interested in annotating. we found that there were certain methods declared on the base class (particularly Reader),
which we weren't interested in annotating.
*/ */
return singletonMap( return singletonMap(
@ -85,7 +86,7 @@ public class TwilioInstrumentation extends Instrumenter.Default {
/** Advice for instrumenting Twilio service classes. */ /** Advice for instrumenting Twilio service classes. */
public static class TwilioClientAdvice { public static class TwilioClientAdvice {
/** Method entry instrumentation */ /** Method entry instrumentation. */
@Advice.OnMethodEnter(suppress = Throwable.class) @Advice.OnMethodEnter(suppress = Throwable.class)
public static Scope methodEnter( public static Scope methodEnter(
@Advice.This final Object that, @Advice.Origin("#m") final String methodName) { @Advice.This final Object that, @Advice.Origin("#m") final String methodName) {
@ -121,7 +122,7 @@ public class TwilioInstrumentation extends Instrumenter.Default {
return scope; return scope;
} }
/** Method exit instrumentation */ /** Method exit instrumentation. */
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void methodExit( public static void methodExit(
@Advice.Enter final Scope scope, @Advice.Enter final Scope scope,
@ -137,6 +138,7 @@ public class TwilioInstrumentation extends Instrumenter.Default {
final Span span = scope.span(); final Span span = scope.span();
DECORATE.onError(span, throwable); DECORATE.onError(span, throwable);
DECORATE.beforeFinish(span);
// If we're calling an async operation, we still need to finish the span when it's // If we're calling an async operation, we still need to finish the span when it's
// complete and report the results; set an appropriate callback // complete and report the results; set an appropriate callback
@ -165,7 +167,7 @@ public class TwilioInstrumentation extends Instrumenter.Default {
*/ */
public static class SpanFinishingCallback implements FutureCallback { public static class SpanFinishingCallback implements FutureCallback {
/** Span that we should finish and annotate when the future is complete */ /** Span that we should finish and annotate when the future is complete. */
private final Span span; private final Span span;
public SpanFinishingCallback(final Span span) { public SpanFinishingCallback(final Span span) {

View File

@ -4,14 +4,22 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.twilio.Twilio import com.twilio.Twilio
import com.twilio.exception.ApiException import com.twilio.exception.ApiException
import com.twilio.http.NetworkHttpClient
import com.twilio.http.Response import com.twilio.http.Response
import com.twilio.http.TwilioRestClient import com.twilio.http.TwilioRestClient
import com.twilio.rest.api.v2010.account.Call
import com.twilio.rest.api.v2010.account.Message import com.twilio.rest.api.v2010.account.Message
import com.twilio.type.PhoneNumber import com.twilio.type.PhoneNumber
import datadog.trace.agent.test.AgentTestRunner import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.api.DDSpanTypes import datadog.trace.api.DDSpanTypes
import io.opentracing.tag.Tags import io.opentracing.tag.Tags
import io.opentracing.util.GlobalTracer import io.opentracing.util.GlobalTracer
import org.apache.http.HttpEntity
import org.apache.http.HttpStatus
import org.apache.http.StatusLine
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClientBuilder
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -46,6 +54,41 @@ class TwilioClientTest extends AgentTestRunner {
} }
""" """
final static String CALL_RESPONSE_BODY = """
{
"account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"annotation": null,
"answered_by": null,
"api_version": "2010-04-01",
"caller_name": null,
"date_created": "Tue, 31 Aug 2010 20:36:28 +0000",
"date_updated": "Tue, 31 Aug 2010 20:36:44 +0000",
"direction": "inbound",
"duration": "15",
"end_time": "Tue, 31 Aug 2010 20:36:44 +0000",
"forwarded_from": "+141586753093",
"from": "+15017122661",
"from_formatted": "(501) 712-2661",
"group_sid": null,
"parent_call_sid": null,
"phone_number_sid": "PNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"price": -0.03000,
"price_unit": "USD",
"sid": "CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"start_time": "Tue, 31 Aug 2010 20:36:29 +0000",
"status": "completed",
"subresource_uris": {
"notifications": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Notifications.json",
"recordings": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Recordings.json",
"feedback": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Feedback.json",
"feedback_summaries": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/FeedbackSummary.json"
},
"to": "+15558675310",
"to_formatted": "(555) 867-5310",
"uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json"
}
"""
final static String ERROR_RESPONSE_BODY = """ final static String ERROR_RESPONSE_BODY = """
{ {
"code": 123, "code": 123,
@ -61,7 +104,7 @@ class TwilioClientTest extends AgentTestRunner {
Twilio.init(ACCOUNT_SID, AUTH_TOKEN) Twilio.init(ACCOUNT_SID, AUTH_TOKEN)
} }
def "synchronous call"() { def "synchronous message"() {
setup: setup:
twilioRestClient.getObjectMapper() >> new ObjectMapper() twilioRestClient.getObjectMapper() >> new ObjectMapper()
@ -75,8 +118,6 @@ class TwilioClientTest extends AgentTestRunner {
"Hello world!" // SMS body "Hello world!" // SMS body
).create(twilioRestClient) ).create(twilioRestClient)
Thread.sleep(1000);
def scope = GlobalTracer.get().scopeManager().active() def scope = GlobalTracer.get().scopeManager().active()
if (scope) { if (scope) {
scope.close() scope.close()
@ -115,6 +156,384 @@ class TwilioClientTest extends AgentTestRunner {
} }
} }
def "synchronous call"() {
setup:
twilioRestClient.getObjectMapper() >> new ObjectMapper()
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(CALL_RESPONSE_BODY.getBytes()), 200)
GlobalTracer.get().buildSpan("test").startActive(true)
Call call = Call.creator(
new PhoneNumber("+15558881234"), // To number
new PhoneNumber("+15559994321"), // From number
// Read TwiML at this URL when a call connects (hold music)
new URI("http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient")
).create(twilioRestClient)
def scope = GlobalTracer.get().scopeManager().active()
if (scope) {
scope.close()
}
expect:
call.status == Call.Status.COMPLETED
assertTraces(1) {
trace(0, 2) {
span(0) {
serviceName "unnamed-java-app"
operationName "test"
resourceName "test"
errored false
parent()
}
span(1) {
serviceName "twilio-sdk"
operationName "twilio.sdk"
resourceName "api.v2010.account.CallCreator.create"
spanType DDSpanTypes.HTTP_CLIENT
errored false
tags {
"$Tags.COMPONENT.key" "twilio-sdk"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"twilio.type" "com.twilio.rest.api.v2010.account.Call"
"twilio.account" "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.sid" "CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "completed"
defaultTags()
}
}
}
}
}
def "http client"() {
setup:
HttpClientBuilder clientBuilder = Mock()
CloseableHttpClient httpClient = Mock()
CloseableHttpResponse httpResponse = Mock()
HttpEntity httpEntity = Mock()
StatusLine statusLine = Mock()
clientBuilder.build() >> httpClient
httpClient.execute(_) >> httpResponse
httpResponse.getEntity() >> httpEntity
httpResponse.getStatusLine() >> statusLine
httpEntity.getContent() >> { new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()) }
httpEntity.isRepeatable() >> true
httpEntity.getContentLength() >> MESSAGE_RESPONSE_BODY.length()
statusLine.getStatusCode() >> HttpStatus.SC_OK
NetworkHttpClient networkHttpClient = new NetworkHttpClient(clientBuilder)
TwilioRestClient realTwilioRestClient =
new TwilioRestClient.Builder("username", "password")
.accountSid(ACCOUNT_SID)
.httpClient(networkHttpClient)
.build()
GlobalTracer.get().buildSpan("test").startActive(true)
Message message = Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).create(realTwilioRestClient)
def scope = GlobalTracer.get().scopeManager().active()
if (scope) {
scope.close()
}
expect:
message.body == "Hello, World!"
assertTraces(1) {
trace(0, 3) {
span(0) {
serviceName "unnamed-java-app"
operationName "test"
resourceName "test"
errored false
parent()
}
span(1) {
serviceName "twilio-sdk"
operationName "twilio.sdk"
resourceName "api.v2010.account.MessageCreator.create"
spanType DDSpanTypes.HTTP_CLIENT
errored false
tags {
"$Tags.COMPONENT.key" "twilio-sdk"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
defaultTags()
}
}
span(2) {
serviceName "twilio-sdk"
operationName "http.request"
resourceName "POST /?/Accounts/abc/Messages.json"
spanType DDSpanTypes.HTTP_CLIENT
errored false
}
}
}
}
def "http client retry"() {
setup:
HttpClientBuilder clientBuilder = Mock()
CloseableHttpClient httpClient = Mock()
CloseableHttpResponse httpResponse1 = Mock()
CloseableHttpResponse httpResponse2 = Mock()
HttpEntity httpEntity1 = Mock()
HttpEntity httpEntity2 = Mock()
StatusLine statusLine1 = Mock()
StatusLine statusLine2 = Mock()
clientBuilder.build() >> httpClient
httpClient.execute(_) >>> [httpResponse1, httpResponse2]
// First response is an HTTP/500 error, which should drive a retry
httpResponse1.getEntity() >> httpEntity1
httpResponse1.getStatusLine() >> statusLine1
httpEntity1.getContent() >> { new ByteArrayInputStream(ERROR_RESPONSE_BODY.getBytes()) }
httpEntity1.isRepeatable() >> true
httpEntity1.getContentLength() >> ERROR_RESPONSE_BODY.length()
statusLine1.getStatusCode() >> HttpStatus.SC_INTERNAL_SERVER_ERROR
// Second response is HTTP/200 success
httpResponse2.getEntity() >> httpEntity2
httpResponse2.getStatusLine() >> statusLine2
httpEntity2.getContent() >> {
new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes())
}
httpEntity2.isRepeatable() >> true
httpEntity2.getContentLength() >> MESSAGE_RESPONSE_BODY.length()
statusLine2.getStatusCode() >> HttpStatus.SC_OK
NetworkHttpClient networkHttpClient = new NetworkHttpClient(clientBuilder)
TwilioRestClient realTwilioRestClient =
new TwilioRestClient.Builder("username", "password")
.accountSid(ACCOUNT_SID)
.httpClient(networkHttpClient)
.build()
GlobalTracer.get().buildSpan("test").startActive(true)
Message message = Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).create(realTwilioRestClient)
def scope = GlobalTracer.get().scopeManager().active()
if (scope) {
scope.close()
}
expect:
message.body == "Hello, World!"
assertTraces(1) {
trace(0, 4) {
span(0) {
serviceName "unnamed-java-app"
operationName "test"
resourceName "test"
errored false
parent()
}
span(1) {
serviceName "twilio-sdk"
operationName "twilio.sdk"
resourceName "api.v2010.account.MessageCreator.create"
spanType DDSpanTypes.HTTP_CLIENT
errored false
tags {
"$Tags.COMPONENT.key" "twilio-sdk"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
defaultTags()
}
}
span(2) {
serviceName "twilio-sdk"
operationName "http.request"
resourceName "POST /?/Accounts/abc/Messages.json"
spanType DDSpanTypes.HTTP_CLIENT
errored false
}
span(3) {
serviceName "twilio-sdk"
operationName "http.request"
resourceName "POST /?/Accounts/abc/Messages.json"
spanType DDSpanTypes.HTTP_CLIENT
errored true
}
}
}
}
def "http client retry async"() {
setup:
HttpClientBuilder clientBuilder = Mock()
CloseableHttpClient httpClient = Mock()
CloseableHttpResponse httpResponse1 = Mock()
CloseableHttpResponse httpResponse2 = Mock()
HttpEntity httpEntity1 = Mock()
HttpEntity httpEntity2 = Mock()
StatusLine statusLine1 = Mock()
StatusLine statusLine2 = Mock()
clientBuilder.build() >> httpClient
httpClient.execute(_) >>> [httpResponse1, httpResponse2]
// First response is an HTTP/500 error, which should drive a retry
httpResponse1.getEntity() >> httpEntity1
httpResponse1.getStatusLine() >> statusLine1
httpEntity1.getContent() >> { new ByteArrayInputStream(ERROR_RESPONSE_BODY.getBytes()) }
httpEntity1.isRepeatable() >> true
httpEntity1.getContentLength() >> ERROR_RESPONSE_BODY.length()
statusLine1.getStatusCode() >> HttpStatus.SC_INTERNAL_SERVER_ERROR
// Second response is HTTP/200 success
httpResponse2.getEntity() >> httpEntity2
httpResponse2.getStatusLine() >> statusLine2
httpEntity2.getContent() >> {
new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes())
}
httpEntity2.isRepeatable() >> true
httpEntity2.getContentLength() >> MESSAGE_RESPONSE_BODY.length()
statusLine2.getStatusCode() >> HttpStatus.SC_OK
NetworkHttpClient networkHttpClient = new NetworkHttpClient(clientBuilder)
TwilioRestClient realTwilioRestClient =
new TwilioRestClient.Builder("username", "password")
.accountSid(ACCOUNT_SID)
.httpClient(networkHttpClient)
.build()
GlobalTracer.get().buildSpan("test").startActive(true)
ListenableFuture<Message> future = Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).createAsync(realTwilioRestClient)
Message message
try {
message = future.get(10, TimeUnit.SECONDS)
} finally {
// Give the future callback a chance to run
Thread.sleep(1000)
def scope = GlobalTracer.get().scopeManager().active()
if (scope) {
scope.span().finish()
scope.close()
}
}
expect:
message.body == "Hello, World!"
assertTraces(1) {
trace(0, 5) {
span(0) {
serviceName "unnamed-java-app"
operationName "test"
resourceName "test"
errored false
parent()
}
span(1) {
serviceName "twilio-sdk"
operationName "twilio.sdk"
resourceName "api.v2010.account.MessageCreator.createAsync"
spanType DDSpanTypes.HTTP_CLIENT
errored false
tags {
"$Tags.COMPONENT.key" "twilio-sdk"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
defaultTags()
}
}
span(2) {
serviceName "twilio-sdk"
operationName "twilio.sdk"
resourceName "api.v2010.account.MessageCreator.create"
spanType DDSpanTypes.HTTP_CLIENT
errored false
tags {
"$Tags.COMPONENT.key" "twilio-sdk"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
defaultTags()
}
}
span(3) {
serviceName "twilio-sdk"
operationName "http.request"
resourceName "POST /?/Accounts/abc/Messages.json"
spanType DDSpanTypes.HTTP_CLIENT
errored true
}
span(4) {
serviceName "twilio-sdk"
operationName "http.request"
resourceName "POST /?/Accounts/abc/Messages.json"
spanType DDSpanTypes.HTTP_CLIENT
errored false
}
}
}
cleanup:
Twilio.getExecutorService().shutdown()
Twilio.setExecutorService(null)
Twilio.setRestClient(null)
}
def "Sync Failure"() { def "Sync Failure"() {
setup: setup:
@ -198,14 +617,17 @@ class TwilioClientTest extends AgentTestRunner {
} }
} }
} }
} }
def "asynchronous call"() { def "asynchronous call"(a) {
setup: setup:
twilioRestClient.getObjectMapper() >> new ObjectMapper() twilioRestClient.getObjectMapper() >> new ObjectMapper()
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()), 200) 1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()), 200)
when:
GlobalTracer.get().buildSpan("test").startActive(true) GlobalTracer.get().buildSpan("test").startActive(true)
ListenableFuture<Message> future = Message.creator( ListenableFuture<Message> future = Message.creator(
@ -214,14 +636,20 @@ class TwilioClientTest extends AgentTestRunner {
"Hello world!" // SMS body "Hello world!" // SMS body
).createAsync(twilioRestClient) ).createAsync(twilioRestClient)
Message message = future.get(10, TimeUnit.SECONDS) Message message
try {
def scope = GlobalTracer.get().scopeManager().active() message = future.get(10, TimeUnit.SECONDS)
if (scope) { } finally {
scope.close() // Give the future callback a chance to run
Thread.sleep(1000)
def scope = GlobalTracer.get().scopeManager().active()
if (scope) {
scope.span().finish()
scope.close()
}
} }
expect: then:
message != null message != null
message.body == "Hello, World!" message.body == "Hello, World!"
@ -269,6 +697,16 @@ class TwilioClientTest extends AgentTestRunner {
} }
} }
} }
cleanup:
Twilio.getExecutorService().shutdown()
Twilio.setExecutorService(null)
Twilio.setRestClient(null)
where:
a | _
1 | _
2 | _
} }
def "asynchronous error"() { def "asynchronous error"() {
@ -292,6 +730,7 @@ class TwilioClientTest extends AgentTestRunner {
message = future.get(10, TimeUnit.SECONDS) message = future.get(10, TimeUnit.SECONDS)
} finally { } finally {
Thread.sleep(1000)
def scope = GlobalTracer.get().scopeManager().active() def scope = GlobalTracer.get().scopeManager().active()
if (scope) { if (scope) {
scope.close() scope.close()
@ -328,6 +767,11 @@ class TwilioClientTest extends AgentTestRunner {
} }
} }
} }
cleanup:
Twilio.getExecutorService().shutdown()
Twilio.setExecutorService(null)
Twilio.setRestClient(null)
} }
} }

View File

@ -20,6 +20,9 @@ dependencies {
testCompile group: 'com.twilio.sdk', name: 'twilio', version: '7.36.2' testCompile group: 'com.twilio.sdk', name: 'twilio', version: '7.36.2'
testCompile project(':dd-java-agent:testing') testCompile project(':dd-java-agent:testing')
testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4')
testCompile project(':dd-java-agent:instrumentation:java-concurrent') testCompile project(':dd-java-agent:instrumentation:java-concurrent')
testCompile group: 'org.objenesis', name: 'objenesis', version: '3.0.1' testCompile group: 'org.objenesis', name: 'objenesis', version: '2.6' // Last version to support Java7
testCompile group: 'nl.jqno.equalsverifier', name: 'equalsverifier', version: '2.5.2' // Last version to support Java7
} }

View File

@ -19,6 +19,7 @@ import groovy.transform.stc.SimpleType;
import io.opentracing.Tracer; import io.opentracing.Tracer;
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.Set; import java.util.Set;
@ -84,6 +85,8 @@ public abstract class AgentTestRunner extends Specification {
TEST_WRITER = TEST_WRITER =
new ListWriter() { new ListWriter() {
private static final long serialVersionUID = 705972961882897201L;
@Override @Override
public boolean add(final List<DDSpan> trace) { public boolean add(final List<DDSpan> trace) {
final boolean result = super.add(trace); final boolean result = super.add(trace);
@ -194,6 +197,14 @@ public abstract class AgentTestRunner extends Specification {
options = "datadog.trace.agent.test.asserts.ListWriterAssert") options = "datadog.trace.agent.test.asserts.ListWriterAssert")
@DelegatesTo(value = ListWriterAssert.class, strategy = Closure.DELEGATE_FIRST) @DelegatesTo(value = ListWriterAssert.class, strategy = Closure.DELEGATE_FIRST)
final Closure spec) { final Closure spec) {
final Iterator<List<DDSpan>> iterator = TEST_WRITER.iterator();
while (iterator.hasNext()) {
final List<DDSpan> next = iterator.next();
final Iterator<DDSpan> iterator1 = next.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
}
ListWriterAssert.assertTraces(TEST_WRITER, size, spec); ListWriterAssert.assertTraces(TEST_WRITER, size, spec);
} }
@ -265,6 +276,8 @@ public abstract class AgentTestRunner extends Specification {
/** Used to signal that a transformation was intentionally aborted and is not an error. */ /** Used to signal that a transformation was intentionally aborted and is not an error. */
public static class AbortTransformationException extends RuntimeException { public static class AbortTransformationException extends RuntimeException {
private static final long serialVersionUID = -1849465286193994582L;
public AbortTransformationException() { public AbortTransformationException() {
super(); super();
} }