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;
|
||||
}
|
||||
|
||||
/** Decorate trace based on service execution metadata */
|
||||
/** Decorate trace based on service execution metadata. */
|
||||
public Span onServiceExecution(
|
||||
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.account", message.getAccountSid());
|
||||
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) {
|
||||
final Call call = (Call) result;
|
||||
span.setTag("twilio.account", call.getAccountSid());
|
||||
span.setTag("twilio.sid", call.getSid());
|
||||
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 {
|
||||
// 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
|
||||
|
|
|
@ -64,9 +64,10 @@ public class TwilioInstrumentation extends Instrumenter.Default {
|
|||
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
|
||||
classes. The isDeclaredBy() matcher did not work in the unit tests and we found that there were certain
|
||||
methods declared on the base class (particularly Reader), which we weren't interested in annotating.
|
||||
We are listing out the main service calls on the Creator, Deleter, Fetcher, Reader, and
|
||||
Updater abstract classes. The isDeclaredBy() matcher did not work in the unit tests and
|
||||
we found that there were certain methods declared on the base class (particularly Reader),
|
||||
which we weren't interested in annotating.
|
||||
*/
|
||||
|
||||
return singletonMap(
|
||||
|
@ -85,7 +86,7 @@ public class TwilioInstrumentation extends Instrumenter.Default {
|
|||
/** Advice for instrumenting Twilio service classes. */
|
||||
public static class TwilioClientAdvice {
|
||||
|
||||
/** Method entry instrumentation */
|
||||
/** Method entry instrumentation. */
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static Scope methodEnter(
|
||||
@Advice.This final Object that, @Advice.Origin("#m") final String methodName) {
|
||||
|
@ -121,7 +122,7 @@ public class TwilioInstrumentation extends Instrumenter.Default {
|
|||
return scope;
|
||||
}
|
||||
|
||||
/** Method exit instrumentation */
|
||||
/** Method exit instrumentation. */
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.Enter final Scope scope,
|
||||
|
@ -137,6 +138,7 @@ public class TwilioInstrumentation extends Instrumenter.Default {
|
|||
final Span span = scope.span();
|
||||
|
||||
DECORATE.onError(span, throwable);
|
||||
DECORATE.beforeFinish(span);
|
||||
|
||||
// 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
|
||||
|
@ -165,7 +167,7 @@ public class TwilioInstrumentation extends Instrumenter.Default {
|
|||
*/
|
||||
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;
|
||||
|
||||
public SpanFinishingCallback(final Span span) {
|
||||
|
|
|
@ -4,14 +4,22 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.twilio.Twilio
|
||||
import com.twilio.exception.ApiException
|
||||
import com.twilio.http.NetworkHttpClient
|
||||
import com.twilio.http.Response
|
||||
import com.twilio.http.TwilioRestClient
|
||||
import com.twilio.rest.api.v2010.account.Call
|
||||
import com.twilio.rest.api.v2010.account.Message
|
||||
import com.twilio.type.PhoneNumber
|
||||
import datadog.trace.agent.test.AgentTestRunner
|
||||
import datadog.trace.api.DDSpanTypes
|
||||
import io.opentracing.tag.Tags
|
||||
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.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 = """
|
||||
{
|
||||
"code": 123,
|
||||
|
@ -61,7 +104,7 @@ class TwilioClientTest extends AgentTestRunner {
|
|||
Twilio.init(ACCOUNT_SID, AUTH_TOKEN)
|
||||
}
|
||||
|
||||
def "synchronous call"() {
|
||||
def "synchronous message"() {
|
||||
setup:
|
||||
twilioRestClient.getObjectMapper() >> new ObjectMapper()
|
||||
|
||||
|
@ -75,8 +118,6 @@ class TwilioClientTest extends AgentTestRunner {
|
|||
"Hello world!" // SMS body
|
||||
).create(twilioRestClient)
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
def scope = GlobalTracer.get().scopeManager().active()
|
||||
if (scope) {
|
||||
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"() {
|
||||
setup:
|
||||
|
||||
|
@ -198,14 +617,17 @@ class TwilioClientTest extends AgentTestRunner {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def "asynchronous call"() {
|
||||
def "asynchronous call"(a) {
|
||||
setup:
|
||||
twilioRestClient.getObjectMapper() >> new ObjectMapper()
|
||||
|
||||
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()), 200)
|
||||
|
||||
when:
|
||||
|
||||
GlobalTracer.get().buildSpan("test").startActive(true)
|
||||
|
||||
ListenableFuture<Message> future = Message.creator(
|
||||
|
@ -214,14 +636,20 @@ class TwilioClientTest extends AgentTestRunner {
|
|||
"Hello world!" // SMS body
|
||||
).createAsync(twilioRestClient)
|
||||
|
||||
Message message = future.get(10, TimeUnit.SECONDS)
|
||||
|
||||
def scope = GlobalTracer.get().scopeManager().active()
|
||||
if (scope) {
|
||||
scope.close()
|
||||
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:
|
||||
then:
|
||||
|
||||
message != null
|
||||
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"() {
|
||||
|
@ -292,6 +730,7 @@ class TwilioClientTest extends AgentTestRunner {
|
|||
message = future.get(10, TimeUnit.SECONDS)
|
||||
|
||||
} finally {
|
||||
Thread.sleep(1000)
|
||||
def scope = GlobalTracer.get().scopeManager().active()
|
||||
if (scope) {
|
||||
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 project(':dd-java-agent:testing')
|
||||
testCompile project(':dd-java-agent:instrumentation:apache-httpclient-4')
|
||||
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 java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
@ -84,6 +85,8 @@ public abstract class AgentTestRunner extends Specification {
|
|||
|
||||
TEST_WRITER =
|
||||
new ListWriter() {
|
||||
private static final long serialVersionUID = 705972961882897201L;
|
||||
|
||||
@Override
|
||||
public boolean add(final List<DDSpan> trace) {
|
||||
final boolean result = super.add(trace);
|
||||
|
@ -194,6 +197,14 @@ public abstract class AgentTestRunner extends Specification {
|
|||
options = "datadog.trace.agent.test.asserts.ListWriterAssert")
|
||||
@DelegatesTo(value = ListWriterAssert.class, strategy = Closure.DELEGATE_FIRST)
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -265,6 +276,8 @@ public abstract class AgentTestRunner extends Specification {
|
|||
|
||||
/** Used to signal that a transformation was intentionally aborted and is not an error. */
|
||||
public static class AbortTransformationException extends RuntimeException {
|
||||
private static final long serialVersionUID = -1849465286193994582L;
|
||||
|
||||
public AbortTransformationException() {
|
||||
super();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue