Ensure headers are never null
Null tracer version results in the request being rejected by the agent.
Also make the DDSpanBuilderTest.shouldBuildSpanTimestampInNano more reliable.
MODIFY
dd-java-agent-ittests/build.gradle
dd-java-agent-ittests/build.gradle
@@ -37,3 +37,7 @@ test {
}
test.dependsOn project(':dd-java-agent').shadowJar
+
+parent.subprojects.collect { it.tasks.withType(Test) } each {
+ test.shouldRunAfter it
+}

MODIFY
dd-trace/build.gradle
dd-trace/build.gradle
plugins {
+ id 'groovy'
id "com.github.johnrengelman.shadow" version "2.0.1"
}

MODIFY
dd-trace/src/main/java/com/datadoghq/trace/DDTracer.java
dd-trace/src/main/java/com/datadoghq/trace/DDTracer.java
@@ -22,8 +22,13 @@ import java.util.*;
*/
public class DDTracer implements io.opentracing.Tracer {
- public final static String CURRENT_VERSION = DDTracer.class.getPackage().getImplementationVersion();
public final static String JAVA_VERSION = System.getProperty("java.version", "unknown");
+ public final static String CURRENT_VERSION;
+
+ static {
+ String version = DDTracer.class.getPackage().getImplementationVersion();
+ CURRENT_VERSION = version != null ? version : "unknown";
+ }
/**
* Writer is an charge of reporting traces and spans to the desired endpoint

RENAME
dd-trace/src/test/groovy/com/datadog/trace/SpanFactory.groovy
dd-trace/src/test/groovy/com/datadoghq/trace/SpanFactory.groovy
-package com.datadog.trace
-
-import com.datadoghq.trace.DDSpan
-import com.datadoghq.trace.DDSpanContext
+package com.datadoghq.trace
class SpanFactory {
static def newSpanOf(long timestampMicro) {

MODIFY
dd-trace/src/test/groovy/com/datadoghq/trace/writer/DDApiTest.groovy
dd-trace/src/test/groovy/com/datadoghq/trace/writer/DDApiTest.groovy
package com.datadoghq.trace.writer
-import com.datadog.trace.SpanFactory
-import com.datadoghq.trace.DDSpan
+import com.datadoghq.trace.SpanFactory
+import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
+import ratpack.http.Headers
import ratpack.http.MediaType
-import spock.lang.AutoCleanup
import spock.lang.Specification
import java.util.concurrent.atomic.AtomicReference
@@ -54,11 +54,13 @@ class DDApiTest extends Specification {
def "content is sent as JSON"() {
setup:
def requestContentType = new AtomicReference<MediaType>()
+ def requestHeaders = new AtomicReference<Headers>()
def requestBody = new AtomicReference<String>()
def agent = ratpack {
handlers {
put("v0.3/traces") {
requestContentType.set(request.contentType)
+ requestHeaders.set(request.headers)
request.body.then {
requestBody.set(it.text)
response.send()
@@ -71,47 +73,47 @@ class DDApiTest extends Specification {
expect:
client.sendTraces(traces)
requestContentType.get().type == APPLICATION_JSON
- areEqual(requestBody.get(), expectedRequestBody)
+ requestHeaders.get().get("Datadog-Meta-Lang") == "java"
+ requestHeaders.get().get("Datadog-Meta-Lang-Version") == System.getProperty("java.version", "unknown");
+ requestHeaders.get().get("Datadog-Meta-Tracer-Version") == "unknown"
+ convert(requestBody.get()) == expectedRequestBody
cleanup:
agent.close()
+ // Populate thread info dynamically as it is different when run via gradle vs idea.
where:
traces | expectedRequestBody
- [] | '[]'
- [SpanFactory.newSpanOf(1L)] | '''[{
+ [] | []
+ [SpanFactory.newSpanOf(1L)] | [new TreeMap<>([
"duration":0,
"error":0,
- "meta":{"thread-name":"main","thread-id":"1"},
+ "meta":["thread-name":Thread.currentThread().getName(),"thread-id":"${Thread.currentThread().id}"],
"name":"fakeOperation",
"parent_id":0,
- "resource":"fakeResource"
+ "resource":"fakeResource",
"service":"fakeService",
"span_id":1,
"start":1000,
"trace_id":1,
- "type":"fakeType",
- }]'''
- [SpanFactory.newSpanOf(100L)] | '''[{
+ "type":"fakeType"
+ ])]
+ [SpanFactory.newSpanOf(100L)] | [new TreeMap<>([
"duration":0,
"error":0,
- "meta":{"thread-name":"main","thread-id":"1"},
+ "meta":["thread-name":Thread.currentThread().getName(),"thread-id":"${Thread.currentThread().id}"],
"name":"fakeOperation",
"parent_id":0,
- "resource":"fakeResource"
+ "resource":"fakeResource",
"service":"fakeService",
"span_id":1,
"start":100000,
"trace_id":1,
- "type":"fakeType",
- }]'''
+ "type":"fakeType"
+ ])]
}
-
- static void areEqual(String json1, String json2) {
- def tree1 = mapper.readTree json1
- def tree2 = mapper.readTree json2
-
- assert tree1.equals(tree2)
+ static List<TreeMap<String, Object>> convert(String json) {
+ return mapper.readValue(json, new TypeReference<List<TreeMap<String, Object>>>() {})
}
}

MODIFY
dd-trace/src/test/java/com/datadoghq/trace/DDSpanBuilderTest.java
dd-trace/src/test/java/com/datadoghq/trace/DDSpanBuilderTest.java
@@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -124,15 +125,14 @@ public class DDSpanBuilderTest {
assertThat(span.getStartTime()).isEqualTo(expectedTimestamp * 1000L);
// auto-timestamp in nanoseconds
- long tick = System.currentTimeMillis() * 1000 * 1000L;
+ long tick = System.currentTimeMillis();
span = tracer
.buildSpan(expectedName)
.withServiceName("foo")
.start();
- // between now and now + 100ms
- assertThat(span.getStartTime()).isBetween(tick, tick + 100 * 1000L);
-
+ // Give a range of +/- 2 millis
+ assertThat(span.getStartTime()).isBetween(MILLISECONDS.toNanos(tick - 2), MILLISECONDS.toNanos(tick + 2));
}

Ensure headers are never null

Null tracer version results in the request being rejected by the agent.

Also make the DDSpanBuilderTest.shouldBuildSpanTimestampInNano more reliable.