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:
commit
315ae67fd2
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue