Add tests against an actual trace agent.
MODIFY
.circleci/config.yml
.circleci/config.yml
@@ -84,6 +84,38 @@ jobs:
docker:
- image: circleci/openjdk:9-jdk
+ agent_integration_tests:
+ <<: *defaults
+ docker:
+ - image: circleci/openjdk:8-jdk
+ - image: datadog/docker-dd-agent
+ env:
+ - DD_APM_ENABLED=true
+ - DD_BIND_HOST=0.0.0.0
+ - DD_API_KEY=invalid_key_but_this_is_fine
+ steps:
+ - checkout
+
+ - restore_cache:
+ # Reset the cache approx every release
+ keys:
+ - dd-trace-java-{{ checksum "dd-trace-java.gradle" }}-{{ .Branch }}-{{ .Revision }}
+ - dd-trace-java-{{ checksum "dd-trace-java.gradle" }}-{{ .Branch }}
+ - dd-trace-java-{{ checksum "dd-trace-java.gradle" }}
+ - dd-trace-java
+
+ - run:
+ name: Run Trace Agent Tests
+ command: ./gradlew traceAgentTest --parallel --stacktrace --no-daemon
+
+ - run:
+ name: Save Artifacts to (project-root)/build
+ when: always
+ command: .circleci/save_artifacts.sh
+
+ - store_test_results:
+ path: build/test-results
+
scan_versions:
<<: *defaults
steps:
@@ -170,6 +202,13 @@ workflows:
tags:
only: /.*/
+ - agent_integration_tests:
+ requires:
+ - build
+ filters:
+ tags:
+ only: /.*/
+
- scan_versions:
requires:
- build
@@ -182,6 +221,7 @@ workflows:
- test_7
- test_8
- test_9
+ - agent_integration_tests
filters:
branches:
only: master
@@ -193,6 +233,7 @@ workflows:
- test_7
- test_8
- test_9
+ - agent_integration_tests
filters:
branches:
ignore: /.*/

MODIFY
dd-trace-java.gradle
dd-trace-java.gradle
@@ -27,6 +27,8 @@ repositories {
description = 'dd-trace-java'
+task traceAgentTest {}
+
// Applied here to allow publishing of artifactory build info
apply from: "${rootDir}/gradle/publish.gradle"

MODIFY
dd-trace-ot/dd-trace-ot.gradle
dd-trace-ot/dd-trace-ot.gradle
plugins {
id "me.champeau.gradle.jmh" version "0.4.4"
+ id 'org.unbroken-dome.test-sets' version '1.4.4'
}
description = 'dd-trace-ot'
@@ -29,6 +30,10 @@ if (JavaVersion.current() != JavaVersion.VERSION_1_8) {
}
}
+testSets {
+ traceAgentTest
+}
+
dependencies {
compile project(':dd-trace-api')
compile deps.opentracing

MODIFY
dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java
dd-trace-ot/src/main/java/datadog/trace/common/writer/DDApi.java
@@ -37,11 +37,18 @@ public class DDApi {
private final RateLimiter loggingRateLimiter =
RateLimiter.create(1.0 / SECONDS_BETWEEN_ERROR_LOG);
- private final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
+ private static final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
public DDApi(final String host, final int port) {
- if (traceEndpointAvailable("http://" + host + ":" + port + TRACES_ENDPOINT_V4)
- && serviceEndpointAvailable("http://" + host + ":" + port + SERVICES_ENDPOINT_V4)) {
+ this(
+ host,
+ port,
+ traceEndpointAvailable("http://" + host + ":" + port + TRACES_ENDPOINT_V4)
+ && serviceEndpointAvailable("http://" + host + ":" + port + SERVICES_ENDPOINT_V4));
+ }
+
+ DDApi(final String host, final int port, final boolean v4EndpointsAvailable) {
+ if (v4EndpointsAvailable) {
this.tracesEndpoint = "http://" + host + ":" + port + TRACES_ENDPOINT_V4;
this.servicesEndpoint = "http://" + host + ":" + port + SERVICES_ENDPOINT_V4;
} else {
@@ -163,15 +170,16 @@ public class DDApi {
}
}
- private boolean traceEndpointAvailable(final String endpoint) {
+ private static boolean traceEndpointAvailable(final String endpoint) {
return endpointAvailable(endpoint, Collections.emptyList(), true);
}
- private boolean serviceEndpointAvailable(final String endpoint) {
+ private static boolean serviceEndpointAvailable(final String endpoint) {
return endpointAvailable(endpoint, Collections.emptyMap(), true);
}
- private boolean endpointAvailable(final String endpoint, final Object data, final boolean retry) {
+ private static boolean endpointAvailable(
+ final String endpoint, final Object data, final boolean retry) {
try {
final HttpURLConnection httpCon = getHttpURLConnection(endpoint);
@@ -192,7 +200,7 @@ public class DDApi {
return false;
}
- private HttpURLConnection getHttpURLConnection(final String endpoint) throws IOException {
+ private static HttpURLConnection getHttpURLConnection(final String endpoint) throws IOException {
final HttpURLConnection httpCon;
final URL url = new URL(endpoint);
httpCon = (HttpURLConnection) url.openConnection();

MODIFY
dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy
dd-trace-ot/src/test/groovy/datadog/trace/api/writer/DDApiTest.groovy
@@ -241,12 +241,9 @@ class DDApiTest extends Specification {
def "Api ResponseListeners see 200 responses"() {
setup:
def agentResponse = new AtomicReference<String>(null)
- ResponseListener responseListener = new ResponseListener() {
- @Override
- void onResponse(String endpoint, JsonNode responseJson) {
+ ResponseListener responseListener = { String endpoint, JsonNode responseJson ->
agentResponse.set(responseJson.toString())
}
- }
boolean servicesAvailable = true
def agent = ratpack {
handlers {

ADD
dd-trace-ot/src/traceAgentTest/groovy/DDApiIntegrationTest.groovy
+import com.fasterxml.jackson.databind.JsonNode
+import datadog.opentracing.DDSpan
+import datadog.opentracing.DDSpanContext
+import datadog.opentracing.DDTracer
+import datadog.opentracing.PendingTrace
+import datadog.trace.common.Service
+import datadog.trace.common.sampling.PrioritySampling
+import datadog.trace.common.writer.DDAgentWriter
+import datadog.trace.common.writer.DDApi
+import datadog.trace.common.writer.ListWriter
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
+
+class DDApiIntegrationTest {
+ static class DDApiIntegrationV4Test extends Specification {
+ static final WRITER = new ListWriter()
+ static final TRACER = new DDTracer(WRITER)
+ static final CONTEXT = new DDSpanContext(
+ 1L,
+ 1L,
+ 0L,
+ "fakeService",
+ "fakeOperation",
+ "fakeResource",
+ PrioritySampling.UNSET,
+ Collections.emptyMap(),
+ false,
+ "fakeType",
+ Collections.emptyMap(),
+ new PendingTrace(TRACER, 1L),
+ TRACER)
+
+ def api = new DDApi(DDAgentWriter.DEFAULT_HOSTNAME, DDAgentWriter.DEFAULT_PORT, v4())
+
+ def endpoint = new AtomicReference<String>(null)
+ def agentResponse = new AtomicReference<String>(null)
+
+ DDApi.ResponseListener responseListener = { String receivedEndpoint, JsonNode responseJson ->
+ endpoint.set(receivedEndpoint)
+ agentResponse.set(responseJson.toString())
+ }
+
+ def setup() {
+ api.addResponseListener(responseListener)
+ }
+
+ boolean v4() {
+ return true
+ }
+
+ @Unroll
+ def "Sending traces succeeds (test #test)"() {
+ expect:
+ api.sendTraces(traces)
+ if (v4()) {
+ endpoint.get() == "http://localhost:8126/v0.4/traces"
+ agentResponse.get() == '{"rate_by_service":{"service:,env:":1}}'
+ }
+
+ where:
+ traces | test
+ [] | 1
+ [[], []] | 2
+ [[new DDSpan(1, CONTEXT)]] | 3
+ [[new DDSpan(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), CONTEXT)]] | 4
+ }
+
+ def "Sending services succeeds"() {
+ expect:
+ api.sendServices(services)
+ endpoint.get() == null
+ agentResponse.get() == null
+
+ where:
+ services | _
+ [:] | _
+ ['app': new Service("name", "appName", Service.AppType.WEB)] | _
+ }
+
+ @Unroll
+ def "Sending bad trace fails (test #test)"() {
+ expect:
+ api.sendTraces(traces) == false
+
+ where:
+ traces | test
+ [""] | 1
+ ["", 123] | 2
+ [[:]] | 3
+ [new Object()] | 4
+ }
+ }
+
+ static class DDApiIntegrationV3Test extends DDApiIntegrationV4Test {
+ boolean v4() {
+ return false
+ }
+
+ def cleanup() {
+ assert endpoint.get() == null
+ assert agentResponse.get() == null
+ }
+ }
+}

Add tests against an actual trace agent.