diff --git a/dd-trace-ot/build.gradle.kts b/dd-trace-ot/build.gradle.kts index 7c73abef06b..a0f3f3a360c 100644 --- a/dd-trace-ot/build.gradle.kts +++ b/dd-trace-ot/build.gradle.kts @@ -66,6 +66,11 @@ dependencies { implementation(project(":dd-trace-ot:correlation-id-injection")) testImplementation(project(":dd-java-agent:testing")) + testImplementation(project(":utils:junit-utils")) + testImplementation(libs.bundles.mockito) + + add("ot31CompatibilityTestImplementation", project(":utils:junit-utils")) + add("ot33CompatibilityTestImplementation", project(":utils:junit-utils")) // Kotlin accessors not generated if not coming from plugin add("ot33CompatibilityTestImplementation", "io.opentracing:opentracing-api") { diff --git a/dd-trace-ot/correlation-id-injection/build.gradle.kts b/dd-trace-ot/correlation-id-injection/build.gradle.kts index f2613570972..a71e6cfddf7 100644 --- a/dd-trace-ot/correlation-id-injection/build.gradle.kts +++ b/dd-trace-ot/correlation-id-injection/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { testImplementation(libs.guava) testImplementation(project(":dd-trace-ot")) testImplementation(project(":dd-java-agent:testing")) + testImplementation(libs.bundles.mockito) testImplementation("org.apache.logging.log4j:log4j-core:$log4j2") testImplementation("ch.qos.logback:logback-core:$logback") } diff --git a/dd-trace-ot/correlation-id-injection/src/test/groovy/CorrelationIdInjectorTest.groovy b/dd-trace-ot/correlation-id-injection/src/test/groovy/CorrelationIdInjectorTest.groovy deleted file mode 100644 index 66969d9cc9f..00000000000 --- a/dd-trace-ot/correlation-id-injection/src/test/groovy/CorrelationIdInjectorTest.groovy +++ /dev/null @@ -1,74 +0,0 @@ -import datadog.opentracing.DDTracer -import datadog.trace.api.CorrelationIdentifier -import datadog.trace.api.GlobalTracer -import datadog.trace.test.util.DDSpecification - -abstract class CorrelationIdInjectorTest extends DDSpecification { - def logPattern = "TRACE_ID=%X{${CorrelationIdentifier.getTraceIdKey()}} SPAN_ID=%X{${CorrelationIdentifier.getSpanIdKey()}} %m" - - def "test correlation id injection"() { - setup: - def tracer = buildTracer() - def journal = buildJournal() - def logger = buildLogger() - - when: - logger.log("Event without context") - - then: - journal.nextLog() == "TRACE_ID= SPAN_ID= Event without context" - - when: - def rootSpan = tracer.buildSpan("operation1").start() - def rootScope = tracer.activateSpan(rootSpan) - logger.log("Event with root span context") - - then: - journal.nextLog() == "TRACE_ID=${CorrelationIdentifier.traceId} SPAN_ID=${CorrelationIdentifier.spanId} Event with root span context" - - when: - def childSpan = tracer.buildSpan("operation1").asChildOf(rootSpan).start() - def childScope = tracer.activateSpan(childSpan) - logger.log("Event with child span context") - - then: - journal.nextLog() == "TRACE_ID=${CorrelationIdentifier.traceId} SPAN_ID=${CorrelationIdentifier.spanId} Event with child span context" - - when: - childScope.close() - childSpan.finish() - logger.log("Event with root span context") - - then: - journal.nextLog() == "TRACE_ID=${CorrelationIdentifier.traceId} SPAN_ID=${CorrelationIdentifier.spanId} Event with root span context" - - when: - rootScope.close() - rootSpan.finish() - logger.log("Event without context") - - then: - journal.nextLog() == "TRACE_ID= SPAN_ID= Event without context" - - cleanup: - tracer.close() - } - - def buildTracer() { - DDTracer tracer = new DDTracer.DDTracerBuilder().build() - GlobalTracer.registerIfAbsent(tracer) - return tracer - } - - abstract LogJournal buildJournal() - - abstract TestLogger buildLogger() - - interface LogJournal { - String nextLog() - } - - interface TestLogger { - void log(String message) - } -} diff --git a/dd-trace-ot/correlation-id-injection/src/test/groovy/Log4j2CorrelationIdInjectorTest.groovy b/dd-trace-ot/correlation-id-injection/src/test/groovy/Log4j2CorrelationIdInjectorTest.groovy deleted file mode 100644 index 0f450f931ea..00000000000 --- a/dd-trace-ot/correlation-id-injection/src/test/groovy/Log4j2CorrelationIdInjectorTest.groovy +++ /dev/null @@ -1,64 +0,0 @@ -import org.apache.logging.log4j.Level -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.core.Appender -import org.apache.logging.log4j.core.Filter -import org.apache.logging.log4j.core.LogEvent -import org.apache.logging.log4j.core.LoggerContext -import org.apache.logging.log4j.core.appender.AbstractAppender -import org.apache.logging.log4j.core.config.Configuration -import org.apache.logging.log4j.core.config.LoggerConfig -import org.apache.logging.log4j.core.layout.PatternLayout - -class Log4j2CorrelationIdInjectorTest extends CorrelationIdInjectorTest { - @Override - LogJournal buildJournal() { - final LoggerContext context = LoggerContext.getContext(false) - final Configuration config = context.getConfiguration() - - TestAppender appender = new TestAppender(PatternLayout.newBuilder().withPattern(logPattern).build()) - appender.start() - config.addAppender(appender) - updateLoggers(appender, config) - return appender - } - - @Override - TestLogger buildLogger() { - def logger = LogManager.getLogger("TestLogger") - return { message -> logger.error(message) } - } - - private static void updateLoggers(final Appender appender, final Configuration config) { - final Level level = null - final Filter filter = null - for (final LoggerConfig loggerConfig : config.getLoggers().values()) { - loggerConfig.addAppender(appender, level, filter) - } - config.getRootLogger().addAppender(appender, level, filter) - } - - static class TestAppender extends AbstractAppender implements CorrelationIdInjectorTest.LogJournal { - List events - int read - - protected TestAppender(PatternLayout patternLayout) { - super("TestAppender", null, patternLayout, false, null) - events = [] - read = 0 - } - - @Override - void append(LogEvent event) { - def log = getLayout().toSerializable(event) - events << log - } - - @Override - String nextLog() { - if (events.size() <= read) { - return null - } - return events[read++] - } - } -} diff --git a/dd-trace-ot/correlation-id-injection/src/test/groovy/Slf4jCorrelationIdInjectorTest.groovy b/dd-trace-ot/correlation-id-injection/src/test/groovy/Slf4jCorrelationIdInjectorTest.groovy deleted file mode 100644 index 35821cf5045..00000000000 --- a/dd-trace-ot/correlation-id-injection/src/test/groovy/Slf4jCorrelationIdInjectorTest.groovy +++ /dev/null @@ -1,63 +0,0 @@ -import ch.qos.logback.classic.LoggerContext -import ch.qos.logback.classic.encoder.PatternLayoutEncoder -import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.AppenderBase -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -class Slf4jCorrelationIdInjectorTest extends CorrelationIdInjectorTest { - - def logger - - @Override - LogJournal buildJournal() { - def logCtx = (LoggerContext) LoggerFactory.getILoggerFactory() - def logger = logCtx.getLogger("TestLogger") - this.logger = logger - def testAppender = new TestAppender(logCtx) - testAppender.start() - logger.addAppender(testAppender) - return testAppender - } - - @Override - TestLogger buildLogger() { - Logger logger = LoggerFactory.getLogger("TestLogger") - return { message -> logger.error(message)} - } - - class TestAppender extends AppenderBase implements CorrelationIdInjectorTest.LogJournal { - List events - int read - PatternLayoutEncoder encoder - - TestAppender(LoggerContext logCtx) { - name = "TestAppender" - encoder = new PatternLayoutEncoder() - encoder.setContext(logCtx) - encoder.setPattern(logPattern) - events = [] - read = 0 - } - - @Override - void start() { - encoder.start() - super.start() - } - - @Override - void append(ILoggingEvent event) { - def log = this.encoder.getLayout().doLayout(event) - events << log - } - - @Override - String nextLog() { - if (events.size() <= read) { - return null - } - return events[read++] - } - } -} diff --git a/dd-trace-ot/correlation-id-injection/src/test/java/CorrelationIdInjectorTest.java b/dd-trace-ot/correlation-id-injection/src/test/java/CorrelationIdInjectorTest.java new file mode 100644 index 00000000000..d2ac33470cd --- /dev/null +++ b/dd-trace-ot/correlation-id-injection/src/test/java/CorrelationIdInjectorTest.java @@ -0,0 +1,80 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; + +import datadog.opentracing.DDTracer; +import datadog.trace.api.CorrelationIdentifier; +import datadog.trace.api.GlobalTracer; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.Scope; +import io.opentracing.Span; +import org.junit.jupiter.api.Test; + +abstract class CorrelationIdInjectorTest extends DDJavaSpecification { + + protected String logPattern = + "TRACE_ID=%X{" + + CorrelationIdentifier.getTraceIdKey() + + "} SPAN_ID=%X{" + + CorrelationIdentifier.getSpanIdKey() + + "} %m"; + + @Test + void testCorrelationIdInjection() throws Exception { + DDTracer tracer = buildTracer(); + LogJournal journal = buildJournal(); + TestLogger logger = buildLogger(); + + logger.log("Event without context"); + + assertEquals("TRACE_ID= SPAN_ID= Event without context", journal.nextLog()); + + Span rootSpan = tracer.buildSpan("operation1").start(); + Scope rootScope = tracer.activateSpan(rootSpan); + logger.log("Event with root span context"); + + assertEquals(expectedLog("Event with root span context"), journal.nextLog()); + + Span childSpan = tracer.buildSpan("operation1").asChildOf(rootSpan).start(); + Scope childScope = tracer.activateSpan(childSpan); + logger.log("Event with child span context"); + + assertEquals(expectedLog("Event with child span context"), journal.nextLog()); + + childScope.close(); + childSpan.finish(); + logger.log("Event with root span context"); + + assertEquals(expectedLog("Event with root span context"), journal.nextLog()); + + rootScope.close(); + rootSpan.finish(); + logger.log("Event without context"); + + assertEquals("TRACE_ID= SPAN_ID= Event without context", journal.nextLog()); + + tracer.close(); + } + + private static String expectedLog(String message) { + return String.format( + "TRACE_ID=%s SPAN_ID=%s %s", + CorrelationIdentifier.getTraceId(), CorrelationIdentifier.getSpanId(), message); + } + + DDTracer buildTracer() { + DDTracer tracer = new DDTracer.DDTracerBuilder().build(); + GlobalTracer.registerIfAbsent(tracer); + return tracer; + } + + abstract LogJournal buildJournal(); + + abstract TestLogger buildLogger(); + + interface LogJournal { + String nextLog(); + } + + interface TestLogger { + void log(String message); + } +} diff --git a/dd-trace-ot/correlation-id-injection/src/test/java/Log4j2CorrelationIdInjectorTest.java b/dd-trace-ot/correlation-id-injection/src/test/java/Log4j2CorrelationIdInjectorTest.java new file mode 100644 index 00000000000..df5d0ad8cd2 --- /dev/null +++ b/dd-trace-ot/correlation-id-injection/src/test/java/Log4j2CorrelationIdInjectorTest.java @@ -0,0 +1,70 @@ +import java.util.ArrayList; +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.layout.PatternLayout; + +class Log4j2CorrelationIdInjectorTest extends CorrelationIdInjectorTest { + + @Override + LogJournal buildJournal() { + LoggerContext context = LoggerContext.getContext(false); + Configuration config = context.getConfiguration(); + + TestAppender appender = + new TestAppender(PatternLayout.newBuilder().withPattern(logPattern).build()); + appender.start(); + config.addAppender(appender); + updateLoggers(appender, config); + return appender; + } + + @Override + TestLogger buildLogger() { + Logger logger = LogManager.getLogger("TestLogger"); + return message -> logger.error(message); + } + + private static void updateLoggers(Appender appender, Configuration config) { + Level level = null; + Filter filter = null; + for (LoggerConfig loggerConfig : config.getLoggers().values()) { + loggerConfig.addAppender(appender, level, filter); + } + config.getRootLogger().addAppender(appender, level, filter); + } + + static class TestAppender extends AbstractAppender + implements CorrelationIdInjectorTest.LogJournal { + List events; + int read; + + protected TestAppender(PatternLayout patternLayout) { + super("TestAppender", null, patternLayout, false, null); + events = new ArrayList<>(); + read = 0; + } + + @Override + public void append(LogEvent event) { + String log = ((PatternLayout) getLayout()).toSerializable(event); + events.add(log); + } + + @Override + public String nextLog() { + if (events.size() <= read) { + return null; + } + return events.get(read++); + } + } +} diff --git a/dd-trace-ot/correlation-id-injection/src/test/java/Slf4jCorrelationIdInjectorTest.java b/dd-trace-ot/correlation-id-injection/src/test/java/Slf4jCorrelationIdInjectorTest.java new file mode 100644 index 00000000000..2dd2f3fd490 --- /dev/null +++ b/dd-trace-ot/correlation-id-injection/src/test/java/Slf4jCorrelationIdInjectorTest.java @@ -0,0 +1,66 @@ +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class Slf4jCorrelationIdInjectorTest extends CorrelationIdInjectorTest { + + ch.qos.logback.classic.Logger logger; + + @Override + LogJournal buildJournal() { + LoggerContext logCtx = (LoggerContext) LoggerFactory.getILoggerFactory(); + ch.qos.logback.classic.Logger log = logCtx.getLogger("TestLogger"); + this.logger = log; + TestAppender testAppender = new TestAppender(logCtx); + testAppender.start(); + log.addAppender(testAppender); + return testAppender; + } + + @Override + TestLogger buildLogger() { + Logger log = LoggerFactory.getLogger("TestLogger"); + return message -> log.error(message); + } + + class TestAppender extends AppenderBase + implements CorrelationIdInjectorTest.LogJournal { + List events; + int read; + PatternLayoutEncoder encoder; + + TestAppender(LoggerContext logCtx) { + name = "TestAppender"; + encoder = new PatternLayoutEncoder(); + encoder.setContext(logCtx); + encoder.setPattern(logPattern); + events = new ArrayList<>(); + read = 0; + } + + @Override + public void start() { + encoder.start(); + super.start(); + } + + @Override + protected void append(ILoggingEvent event) { + String log = this.encoder.getLayout().doLayout(event); + events.add(log); + } + + @Override + public String nextLog() { + if (events.size() <= read) { + return null; + } + return events.get(read++); + } + } +} diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java index fffaad4d8e3..853cb72c077 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java @@ -13,6 +13,7 @@ import datadog.trace.api.interceptor.TraceInterceptor; import datadog.trace.api.internal.InternalTracer; import datadog.trace.api.internal.TraceSegment; +import datadog.trace.api.internal.VisibleForTesting; import datadog.trace.api.profiling.Profiling; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; @@ -545,6 +546,11 @@ public void close() { tracer.close(); } + @VisibleForTesting + AgentTracer.TracerAPI getInternalTracer() { + return tracer; + } + private static class TextMapSetter implements CarrierSetter { static final TextMapSetter INSTANCE = new TextMapSetter(); diff --git a/dd-trace-ot/src/main/java/datadog/opentracing/OTSpan.java b/dd-trace-ot/src/main/java/datadog/opentracing/OTSpan.java index 053dfa50eec..e13184374af 100644 --- a/dd-trace-ot/src/main/java/datadog/opentracing/OTSpan.java +++ b/dd-trace-ot/src/main/java/datadog/opentracing/OTSpan.java @@ -1,6 +1,7 @@ package datadog.opentracing; import datadog.trace.api.interceptor.MutableSpan; +import datadog.trace.api.internal.VisibleForTesting; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities; import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities; @@ -248,4 +249,9 @@ public int hashCode() { public AgentSpan asAgentSpan() { return delegate; } + + @VisibleForTesting + AgentSpan getDelegate() { + return delegate; + } } diff --git a/dd-trace-ot/src/ot31CompatibilityTest/groovy/OT31ApiTest.groovy b/dd-trace-ot/src/ot31CompatibilityTest/groovy/OT31ApiTest.groovy deleted file mode 100644 index 10966d67d84..00000000000 --- a/dd-trace-ot/src/ot31CompatibilityTest/groovy/OT31ApiTest.groovy +++ /dev/null @@ -1,162 +0,0 @@ -import datadog.opentracing.DDTracer -import datadog.trace.api.DDSpanId -import datadog.trace.api.DDTraceId -import datadog.trace.api.internal.util.LongStringUtils -import datadog.trace.common.writer.ListWriter -import datadog.trace.core.DDSpan -import datadog.trace.test.util.DDSpecification -import io.opentracing.Tracer -import io.opentracing.propagation.Format -import io.opentracing.propagation.TextMap -import spock.lang.Subject - -import static datadog.trace.agent.test.asserts.ListWriterAssert.assertTraces -import static datadog.trace.agent.test.utils.TraceUtils.basicSpan -import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP -import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP -import static datadog.trace.api.sampling.PrioritySampling.UNSET -import static datadog.trace.api.sampling.PrioritySampling.USER_DROP -import static datadog.trace.api.sampling.PrioritySampling.USER_KEEP -import static datadog.trace.api.sampling.SamplingMechanism.AGENT_RATE -import static datadog.trace.api.sampling.SamplingMechanism.DEFAULT -import static datadog.trace.api.sampling.SamplingMechanism.MANUAL - -// This test focuses on things that are different between OpenTracing API 0.31.0 and 0.32.0 -class OT31ApiTest extends DDSpecification { - def writer = new ListWriter() - - @Subject - Tracer tracer = DDTracer.builder().writer(writer).build() - - def cleanup() { - tracer?.close() - } - - def "test startActive"() { - when: - def scope = tracer.buildSpan("some name").startActive(finishSpan) - scope.close() - - then: - (scope.span().delegate as DDSpan).isFinished() == finishSpan - - where: - finishSpan << [true, false] - } - - def "test startManual"() { - when: - tracer.buildSpan("some name").startManual().finish() - - then: - assertTraces(writer, 1) { - trace(1) { - basicSpan(it, "some name") - } - } - } - - def "test scopemanager"() { - setup: - def span = tracer.buildSpan("some name").start() - def scope = tracer.scopeManager().activate(span, finishSpan) - - expect: - scope != null - tracer.scopeManager().active().span() == span - - when: "attempting to close the span this way doesn't work because we lost the 'finishSpan' reference" - tracer.scopeManager().active().close() - - then: - !(span.delegate as DDSpan).isFinished() - - when: - scope.close() - - then: - (span.delegate as DDSpan).isFinished() == finishSpan - - where: - finishSpan << [true, false] - } - - def "test inject extract"() { - setup: - def span = tracer.buildSpan("some name").start() - def context = span.context() - def textMap = [:] - def adapter = new TextMapAdapter(textMap) - - when: - context.delegate.setSamplingPriority(contextPriority, samplingMechanism) - tracer.inject(context, Format.Builtin.TEXT_MAP, adapter) - - then: - def traceId = span.delegate.context.traceId as DDTraceId - def spanId = span.delegate.context.spanId - def expectedTraceparent = "00-${traceId.toHexStringPadded(32)}" + - "-${DDSpanId.toHexStringPadded(spanId)}" + - "-" + (propagatedPriority > 0 ? "01" : "00") - def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism - def expectedTracestate = "dd=s:${propagatedPriority};p:${DDSpanId.toHexStringPadded(spanId)}" + - (propagatedPriority > 0 ? ";t.dm:-" + effectiveSamplingMechanism : "") + - ";t.tid:${traceId.toHexStringPadded(32).substring(0, 16)}" + - (contextPriority == UNSET ? ";t.ksr:1" : "") - def expectedTextMap = [ - "x-datadog-trace-id" : context.toTraceId(), - "x-datadog-parent-id" : context.toSpanId(), - "x-datadog-sampling-priority": propagatedPriority.toString(), - "traceparent" : expectedTraceparent, - "tracestate" : expectedTracestate, - ] - def datadogTags = [] - if (propagatedPriority > 0) { - datadogTags << "_dd.p.dm=-$effectiveSamplingMechanism" - } - if (traceId.toHighOrderLong() != 0) { - datadogTags << "_dd.p.tid=" + LongStringUtils.toHexStringPadded(traceId.toHighOrderLong(), 16) - } - if (contextPriority == UNSET) { - datadogTags << "_dd.p.ksr=1" - } - if (!datadogTags.empty) { - expectedTextMap.put("x-datadog-tags", datadogTags.join(',')) - } - textMap == expectedTextMap - - when: - def extract = tracer.extract(Format.Builtin.TEXT_MAP, adapter) - - then: - extract.toTraceId() == context.toTraceId() - extract.toSpanId() == context.toSpanId() - extract.delegate.samplingPriority == propagatedPriority - - where: - contextPriority | samplingMechanism | propagatedPriority - SAMPLER_DROP | DEFAULT | SAMPLER_DROP - SAMPLER_KEEP | DEFAULT | SAMPLER_KEEP - UNSET | DEFAULT | SAMPLER_KEEP - USER_KEEP | MANUAL | USER_KEEP - USER_DROP | MANUAL | USER_DROP - } - - static class TextMapAdapter implements TextMap { - private final Map map - - TextMapAdapter(Map map) { - this.map = map - } - - @Override - Iterator> iterator() { - return map.entrySet().iterator() - } - - @Override - void put(String key, String value) { - map.put(key, value) - } - } -} diff --git a/dd-trace-ot/src/ot31CompatibilityTest/java/datadog/opentracing/OT31ApiTest.java b/dd-trace-ot/src/ot31CompatibilityTest/java/datadog/opentracing/OT31ApiTest.java new file mode 100644 index 00000000000..4f85922674b --- /dev/null +++ b/dd-trace-ot/src/ot31CompatibilityTest/java/datadog/opentracing/OT31ApiTest.java @@ -0,0 +1,180 @@ +package datadog.opentracing; + +import static datadog.trace.api.sampling.PrioritySampling.UNSET; +import static datadog.trace.api.sampling.SamplingMechanism.AGENT_RATE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.api.DDSpanId; +import datadog.trace.api.DDTraceId; +import datadog.trace.api.internal.util.LongStringUtils; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.DDSpan; +import datadog.trace.core.DDSpanContext; +import datadog.trace.junit.utils.tabletest.PrioritySamplingConverter; +import datadog.trace.junit.utils.tabletest.SamplingMechanismConverter; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.Scope; +import io.opentracing.Tracer; +import io.opentracing.propagation.Format; +import io.opentracing.propagation.TextMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.tabletest.junit.TableTest; + +// This test focuses on things that are different between OpenTracing API 0.31.0 and 0.32.0 +class OT31ApiTest extends DDJavaSpecification { + + private final ListWriter writer = new ListWriter(); + private final Tracer tracer = DDTracer.builder().writer(writer).build(); + + @AfterEach + void cleanup() throws Exception { + if (tracer != null) { + ((DDTracer) tracer).close(); + } + } + + @ParameterizedTest + @TableTest({ + "scenario | finishSpan", + "finish=true | true ", + "finish=false | false " + }) + void testStartActive(String scenario, boolean finishSpan) { + Scope scope = tracer.buildSpan("some name").startActive(finishSpan); + scope.close(); + assertEquals(finishSpan, ((DDSpan) ((OTSpan) scope.span()).getDelegate()).isFinished()); + } + + @Test + void testStartManual() throws Exception { + tracer.buildSpan("some name").startManual().finish(); + + writer.waitForTraces(1); + assertEquals(1, writer.size()); + assertEquals(1, writer.get(0).size()); + assertEquals("some name", writer.get(0).get(0).getOperationName()); + } + + @ParameterizedTest + @TableTest({ + "scenario | finishSpan", + "finish=true | true ", + "finish=false | false " + }) + void testScopeManager(String scenario, boolean finishSpan) { + io.opentracing.Span span = tracer.buildSpan("some name").start(); + Scope scope = tracer.scopeManager().activate(span, finishSpan); + + assertNotNull(scope); + assertEquals(span, tracer.scopeManager().active().span()); + + // attempting to close via active() doesn't work because we lost the 'finishSpan' reference + tracer.scopeManager().active().close(); + assertTrue(!((DDSpan) ((OTSpan) span).getDelegate()).isFinished()); + + scope.close(); + assertEquals(finishSpan, ((DDSpan) ((OTSpan) span).getDelegate()).isFinished()); + } + + @ParameterizedTest + @TableTest({ + "scenario | contextPriority | samplingMechanism | propagatedPriority ", + "sampler drop | PrioritySampling.SAMPLER_DROP | SamplingMechanism.DEFAULT | PrioritySampling.SAMPLER_DROP", + "sampler keep | PrioritySampling.SAMPLER_KEEP | SamplingMechanism.DEFAULT | PrioritySampling.SAMPLER_KEEP", + "unset | PrioritySampling.UNSET | SamplingMechanism.DEFAULT | PrioritySampling.SAMPLER_KEEP", + "user keep | PrioritySampling.USER_KEEP | SamplingMechanism.MANUAL | PrioritySampling.USER_KEEP ", + "user drop | PrioritySampling.USER_DROP | SamplingMechanism.MANUAL | PrioritySampling.USER_DROP " + }) + void testInjectExtract( + String scenario, + @ConvertWith(PrioritySamplingConverter.class) int contextPriority, + @ConvertWith(SamplingMechanismConverter.class) int samplingMechanism, + @ConvertWith(PrioritySamplingConverter.class) int propagatedPriority) + throws Exception { + io.opentracing.Span span = tracer.buildSpan("some name").start(); + io.opentracing.SpanContext context = span.context(); + Map map = new HashMap<>(); + LocalTextMapAdapter adapter = new LocalTextMapAdapter(map); + + DDSpanContext ddContext = (DDSpanContext) ((OTSpanContext) context).getDelegate(); + ddContext.setSamplingPriority(contextPriority, samplingMechanism); + tracer.inject(context, Format.Builtin.TEXT_MAP, adapter); + + DDTraceId traceId = ((OTSpan) span).getDelegate().context().getTraceId(); + long spanId = ((OTSpan) span).getDelegate().context().getSpanId(); + String expectedTraceparent = + "00-" + + traceId.toHexStringPadded(32) + + "-" + + DDSpanId.toHexStringPadded(spanId) + + "-" + + (propagatedPriority > 0 ? "01" : "00"); + int effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism; + String expectedTracestate = + "dd=s:" + + propagatedPriority + + ";p:" + + DDSpanId.toHexStringPadded(spanId) + + (propagatedPriority > 0 ? ";t.dm:-" + effectiveSamplingMechanism : "") + + ";t.tid:" + + traceId.toHexStringPadded(32).substring(0, 16) + + (contextPriority == UNSET ? ";t.ksr:1" : ""); + + Map expectedTextMap = new HashMap<>(); + OTSpanContext otContext = (OTSpanContext) context; + expectedTextMap.put("x-datadog-trace-id", otContext.toTraceId()); + expectedTextMap.put("x-datadog-parent-id", otContext.toSpanId()); + expectedTextMap.put("x-datadog-sampling-priority", String.valueOf(propagatedPriority)); + expectedTextMap.put("traceparent", expectedTraceparent); + expectedTextMap.put("tracestate", expectedTracestate); + + ArrayList datadogTags = new ArrayList<>(); + if (propagatedPriority > 0) { + datadogTags.add("_dd.p.dm=-" + effectiveSamplingMechanism); + } + if (traceId.toHighOrderLong() != 0) { + datadogTags.add( + "_dd.p.tid=" + LongStringUtils.toHexStringPadded(traceId.toHighOrderLong(), 16)); + } + if (contextPriority == UNSET) { + datadogTags.add("_dd.p.ksr=1"); + } + if (!datadogTags.isEmpty()) { + expectedTextMap.put("x-datadog-tags", String.join(",", datadogTags)); + } + + assertEquals(expectedTextMap, map); + + OTSpanContext extract = (OTSpanContext) tracer.extract(Format.Builtin.TEXT_MAP, adapter); + assertEquals(otContext.toTraceId(), extract.toTraceId()); + assertEquals(otContext.toSpanId(), extract.toSpanId()); + assertEquals(propagatedPriority, extract.getDelegate().getSamplingPriority()); + } + + static class LocalTextMapAdapter implements TextMap { + private final Map map; + + LocalTextMapAdapter(Map map) { + this.map = map; + } + + @Override + public Iterator> iterator() { + return map.entrySet().iterator(); + } + + @Override + public void put(String key, String value) { + map.put(key, value); + } + } +} diff --git a/dd-trace-ot/src/ot33CompatibilityTest/groovy/OT33ApiTest.groovy b/dd-trace-ot/src/ot33CompatibilityTest/groovy/OT33ApiTest.groovy deleted file mode 100644 index 0ce61afec90..00000000000 --- a/dd-trace-ot/src/ot33CompatibilityTest/groovy/OT33ApiTest.groovy +++ /dev/null @@ -1,150 +0,0 @@ -import datadog.opentracing.DDTracer -import datadog.trace.api.DDSpanId -import datadog.trace.api.DDTraceId -import datadog.trace.api.internal.util.LongStringUtils -import datadog.trace.common.writer.ListWriter -import datadog.trace.core.DDSpan -import datadog.trace.test.util.DDSpecification -import io.opentracing.Tracer -import io.opentracing.propagation.Format -import io.opentracing.propagation.TextMap -import spock.lang.Subject - -import static datadog.trace.agent.test.asserts.ListWriterAssert.assertTraces -import static datadog.trace.agent.test.utils.TraceUtils.basicSpan -import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP -import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP -import static datadog.trace.api.sampling.PrioritySampling.UNSET -import static datadog.trace.api.sampling.PrioritySampling.USER_DROP -import static datadog.trace.api.sampling.PrioritySampling.USER_KEEP -import static datadog.trace.api.sampling.SamplingMechanism.AGENT_RATE -import static datadog.trace.api.sampling.SamplingMechanism.DEFAULT -import static datadog.trace.api.sampling.SamplingMechanism.MANUAL - -// This test focuses on things that are different between OpenTracing API 0.32.0 and 0.33.0 -class OT33ApiTest extends DDSpecification { - def writer = new ListWriter() - - @Subject - Tracer tracer = DDTracer.builder().writer(writer).build() - - def cleanup() { - tracer?.close() - } - - def "test start"() { - when: - def span = tracer.buildSpan("some name").start() - def scope = tracer.activateSpan(span) - scope.close() - - then: - (scope.span().delegate as DDSpan).isFinished() == false - assertTraces(writer, 0) {} - - when: - span.finish() - - then: - assertTraces(writer, 1) { - trace(1) { - basicSpan(it, "some name") - } - } - } - - def "test scopemanager"() { - setup: - def coreTracer = tracer.tracer - - when: - def span = tracer.buildSpan("some name").start() - def scope = tracer.scopeManager().activate(span) - - then: - tracer.activeSpan().delegate == span.delegate - coreTracer.activeSpan() == span.delegate - - cleanup: - scope.close() - span.finish() - } - - def "test inject extract"() { - setup: - def span = tracer.buildSpan("some name").start() - def context = span.context() - def textMap = [:] - def adapter = new TextMapAdapter(textMap) - - when: - context.delegate.setSamplingPriority(contextPriority, samplingMechanism) - tracer.inject(context, Format.Builtin.TEXT_MAP, adapter) - - then: - def traceId = span.delegate.context.traceId as DDTraceId - def spanId = span.delegate.context.spanId - def expectedTraceparent = "00-${traceId.toHexStringPadded(32)}" + - "-${DDSpanId.toHexStringPadded(spanId)}" + - "-" + (propagatedPriority > 0 ? "01" : "00") - def effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism - def expectedTracestate = "dd=s:${propagatedPriority};p:${DDSpanId.toHexStringPadded(spanId)}" + - (propagatedPriority > 0 ? ";t.dm:-" + effectiveSamplingMechanism : "") + - ";t.tid:${traceId.toHexStringPadded(32).substring(0, 16)}" + - (contextPriority == UNSET ? ";t.ksr:1" : "") - def expectedTextMap = [ - "x-datadog-trace-id" : context.toTraceId(), - "x-datadog-parent-id" : context.toSpanId(), - "x-datadog-sampling-priority": propagatedPriority.toString(), - "traceparent" : expectedTraceparent, - "tracestate" : expectedTracestate, - ] - def datadogTags = [] - if (propagatedPriority > 0) { - datadogTags << "_dd.p.dm=-$effectiveSamplingMechanism" - } - if (traceId.toHighOrderLong() != 0) { - datadogTags << "_dd.p.tid=" + LongStringUtils.toHexStringPadded(traceId.toHighOrderLong(), 16) - } - if (contextPriority == UNSET) { - datadogTags << "_dd.p.ksr=1" - } - if (!datadogTags.empty) { - expectedTextMap.put("x-datadog-tags", datadogTags.join(',')) - } - textMap == expectedTextMap - when: - def extract = tracer.extract(Format.Builtin.TEXT_MAP, adapter) - - then: - extract.toTraceId() == context.toTraceId() - extract.toSpanId() == context.toSpanId() - extract.delegate.samplingPriority == propagatedPriority - - where: - contextPriority | samplingMechanism | propagatedPriority | propagatedMechanism | samplingRate - SAMPLER_DROP | DEFAULT | SAMPLER_DROP | DEFAULT | null - SAMPLER_KEEP | DEFAULT | SAMPLER_KEEP | DEFAULT | null - UNSET | DEFAULT | SAMPLER_KEEP | AGENT_RATE | 1 - USER_KEEP | MANUAL | USER_KEEP | MANUAL | null - USER_DROP | MANUAL | USER_DROP | MANUAL | null - } - - static class TextMapAdapter implements TextMap { - private final Map map - - TextMapAdapter(Map map) { - this.map = map - } - - @Override - Iterator> iterator() { - return map.entrySet().iterator() - } - - @Override - void put(String key, String value) { - map.put(key, value) - } - } -} diff --git a/dd-trace-ot/src/ot33CompatibilityTest/java/datadog/opentracing/OT33ApiTest.java b/dd-trace-ot/src/ot33CompatibilityTest/java/datadog/opentracing/OT33ApiTest.java new file mode 100644 index 00000000000..da403049764 --- /dev/null +++ b/dd-trace-ot/src/ot33CompatibilityTest/java/datadog/opentracing/OT33ApiTest.java @@ -0,0 +1,165 @@ +package datadog.opentracing; + +import static datadog.trace.api.sampling.PrioritySampling.UNSET; +import static datadog.trace.api.sampling.SamplingMechanism.AGENT_RATE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import datadog.trace.api.DDSpanId; +import datadog.trace.api.DDTraceId; +import datadog.trace.api.internal.util.LongStringUtils; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.DDSpanContext; +import datadog.trace.junit.utils.tabletest.PrioritySamplingConverter; +import datadog.trace.junit.utils.tabletest.SamplingMechanismConverter; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.Scope; +import io.opentracing.Tracer; +import io.opentracing.propagation.Format; +import io.opentracing.propagation.TextMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.tabletest.junit.TableTest; + +// This test focuses on things that are different between OpenTracing API 0.32.0 and 0.33.0 +class OT33ApiTest extends DDJavaSpecification { + + ListWriter writer = new ListWriter(); + Tracer tracer = DDTracer.builder().writer(writer).build(); + + @AfterEach + void cleanup() throws Exception { + if (tracer != null) { + ((DDTracer) tracer).close(); + } + } + + @Test + void testStart() throws Exception { + io.opentracing.Span span = tracer.buildSpan("some name").start(); + Scope scope = tracer.activateSpan(span); + scope.close(); + + assertFalse(((datadog.trace.core.DDSpan) ((OTSpan) span).getDelegate()).isFinished()); + assertEquals(0, writer.size()); + + span.finish(); + + writer.waitForTraces(1); + assertEquals(1, writer.size()); + assertEquals("some name", writer.get(0).get(0).getOperationName()); + } + + @Test + void testScopeManager() { + datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI coreTracer = + ((DDTracer) tracer).getInternalTracer(); + + io.opentracing.Span span = tracer.buildSpan("some name").start(); + Scope scope = tracer.scopeManager().activate(span); + + assertEquals(span, tracer.activeSpan()); + assertEquals(((OTSpan) span).getDelegate(), coreTracer.activeSpan()); + + scope.close(); + span.finish(); + } + + @ParameterizedTest + @TableTest({ + "scenario | contextPriority | samplingMechanism | propagatedPriority ", + "sampler drop | PrioritySampling.SAMPLER_DROP | SamplingMechanism.DEFAULT | PrioritySampling.SAMPLER_DROP", + "sampler keep | PrioritySampling.SAMPLER_KEEP | SamplingMechanism.DEFAULT | PrioritySampling.SAMPLER_KEEP", + "unset | PrioritySampling.UNSET | SamplingMechanism.DEFAULT | PrioritySampling.SAMPLER_KEEP", + "user keep | PrioritySampling.USER_KEEP | SamplingMechanism.MANUAL | PrioritySampling.USER_KEEP ", + "user drop | PrioritySampling.USER_DROP | SamplingMechanism.MANUAL | PrioritySampling.USER_DROP " + }) + void testInjectExtract( + String scenario, + @ConvertWith(PrioritySamplingConverter.class) int contextPriority, + @ConvertWith(SamplingMechanismConverter.class) int samplingMechanism, + @ConvertWith(PrioritySamplingConverter.class) int propagatedPriority) + throws Exception { + io.opentracing.Span span = tracer.buildSpan("some name").start(); + io.opentracing.SpanContext context = span.context(); + Map map = new HashMap<>(); + LocalTextMapAdapter adapter = new LocalTextMapAdapter(map); + + DDSpanContext ddContext = (DDSpanContext) ((OTSpanContext) context).getDelegate(); + ddContext.setSamplingPriority(contextPriority, samplingMechanism); + tracer.inject(context, Format.Builtin.TEXT_MAP, adapter); + + DDTraceId traceId = ((OTSpan) span).getDelegate().context().getTraceId(); + long spanId = ((OTSpan) span).getDelegate().context().getSpanId(); + String expectedTraceparent = + "00-" + + traceId.toHexStringPadded(32) + + "-" + + DDSpanId.toHexStringPadded(spanId) + + "-" + + (propagatedPriority > 0 ? "01" : "00"); + int effectiveSamplingMechanism = contextPriority == UNSET ? AGENT_RATE : samplingMechanism; + String expectedTracestate = + "dd=s:" + + propagatedPriority + + ";p:" + + DDSpanId.toHexStringPadded(spanId) + + (propagatedPriority > 0 ? ";t.dm:-" + effectiveSamplingMechanism : "") + + ";t.tid:" + + traceId.toHexStringPadded(32).substring(0, 16) + + (contextPriority == UNSET ? ";t.ksr:1" : ""); + + Map expectedTextMap = new HashMap<>(); + expectedTextMap.put("x-datadog-trace-id", context.toTraceId()); + expectedTextMap.put("x-datadog-parent-id", context.toSpanId()); + expectedTextMap.put("x-datadog-sampling-priority", String.valueOf(propagatedPriority)); + expectedTextMap.put("traceparent", expectedTraceparent); + expectedTextMap.put("tracestate", expectedTracestate); + + ArrayList datadogTags = new ArrayList<>(); + if (propagatedPriority > 0) { + datadogTags.add("_dd.p.dm=-" + effectiveSamplingMechanism); + } + if (traceId.toHighOrderLong() != 0) { + datadogTags.add( + "_dd.p.tid=" + LongStringUtils.toHexStringPadded(traceId.toHighOrderLong(), 16)); + } + if (contextPriority == UNSET) { + datadogTags.add("_dd.p.ksr=1"); + } + if (!datadogTags.isEmpty()) { + expectedTextMap.put("x-datadog-tags", String.join(",", datadogTags)); + } + + assertEquals(expectedTextMap, map); + + io.opentracing.SpanContext extract = tracer.extract(Format.Builtin.TEXT_MAP, adapter); + assertEquals(context.toTraceId(), extract.toTraceId()); + assertEquals(context.toSpanId(), extract.toSpanId()); + assertEquals(propagatedPriority, ((OTSpanContext) extract).getDelegate().getSamplingPriority()); + } + + static class LocalTextMapAdapter implements TextMap { + private final Map map; + + LocalTextMapAdapter(Map map) { + this.map = map; + } + + @Override + public Iterator> iterator() { + return map.entrySet().iterator(); + } + + @Override + public void put(String key, String value) { + map.put(key, value); + } + } +} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDTracerAPITest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDTracerAPITest.groovy deleted file mode 100644 index 28a77e89cdc..00000000000 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDTracerAPITest.groovy +++ /dev/null @@ -1,33 +0,0 @@ -package datadog.opentracing - - -import datadog.trace.common.sampling.RateByServiceTraceSampler -import datadog.trace.common.writer.ListWriter -import datadog.trace.test.util.DDSpecification - -import static datadog.trace.api.ConfigDefaults.DEFAULT_SERVICE_NAME -import static datadog.trace.api.DDTags.LANGUAGE_TAG_KEY -import static datadog.trace.api.DDTags.LANGUAGE_TAG_VALUE -import static datadog.trace.api.DDTags.RUNTIME_ID_TAG - -class DDTracerAPITest extends DDSpecification { - def "verify sampler/writer constructor"() { - setup: - def writer = new ListWriter() - def sampler = new RateByServiceTraceSampler() - - when: - def tracerOT = new DDTracer(DEFAULT_SERVICE_NAME, writer, sampler) - def tracer = tracerOT.tracer - - then: - tracer.serviceName == DEFAULT_SERVICE_NAME - tracer.initialSampler == sampler - tracer.writer == writer - tracer.localRootSpanTags[RUNTIME_ID_TAG].size() > 0 // not null or empty - tracer.localRootSpanTags[LANGUAGE_TAG_KEY] == LANGUAGE_TAG_VALUE - - cleanup: - tracer.close() - } -} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDTracerTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DDTracerTest.groovy deleted file mode 100644 index 7a3ba7b377e..00000000000 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/DDTracerTest.groovy +++ /dev/null @@ -1,96 +0,0 @@ -package datadog.opentracing - -import datadog.trace.common.sampling.Sampler -import datadog.trace.common.writer.DDAgentWriter -import datadog.trace.common.writer.ListWriter -import datadog.trace.common.writer.Writer -import datadog.trace.test.util.DDSpecification - -class DDTracerTest extends DDSpecification { - - def "test tracer builder"() { - when: - def tracer = DDTracer.builder().build() - - then: - tracer != null - - cleanup: - tracer.close() - } - - def "test deprecated tracer constructor"() { - setup: - def tracers = [] - - when: - tracers << new DDTracer() - tracers << new DDTracer('serviceName') - tracers << new DDTracer('serviceName', Mock(Writer), Mock(Sampler)) - tracers << new DDTracer('serviceName', Mock(Writer), Mock(Sampler), [:]) - tracers << new DDTracer(Mock(Writer)) - - then: - tracers.each { assert it != null } - } - - def "test tracer builder with default writer"() { - when: - def tracer = DDTracer.builder().writer(DDAgentWriter.builder().build()).build() - - then: - tracer != null - - cleanup: - tracer.close() - } - - def "test access to TraceSegment"() { - when: - def tracer = DDTracer.builder().writer(DDAgentWriter.builder().build()).build() - def span = tracer.buildSpan("some name").start() - def scope = tracer.scopeManager().activate(span) - - then: - tracer != null - tracer.activeSpan().delegate == span.delegate - - when: - def seg = tracer.getTraceSegment() - - then: - seg != null - scope.close() - - cleanup: - tracer.close() - } - - def "should produce blackhole scopes"() { - setup: - def writer = new ListWriter() - def tracer = DDTracer.builder().writer(writer).build() - - when: - def span = tracer.buildSpan("some name").start() - def scope = tracer.scopeManager().activate(span) - def muteScope = tracer.muteTracing() - def blackholed = tracer.buildSpan("hidden span").start() - blackholed.finish() - muteScope.close() - def visibleSpan = tracer.buildSpan("visible span").start() - visibleSpan.finish() - scope.close() - span.finish() - - then: - writer.waitForTraces(1) - assert writer.size() == 1 - assert writer.firstTrace().size() == 2 - assert Long.toString(writer.firstTrace()[0].context().getSpanId()) == span.context().toSpanId() - assert Long.toString(writer.firstTrace()[1].context().getSpanId()) == visibleSpan.context().toSpanId() - - cleanup: - tracer.close() - } -} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/DefaultLogHandlerTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/DefaultLogHandlerTest.groovy deleted file mode 100644 index a4617c77da9..00000000000 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/DefaultLogHandlerTest.groovy +++ /dev/null @@ -1,291 +0,0 @@ -package datadog.opentracing - -import datadog.trace.api.DDTags -import datadog.trace.common.writer.ListWriter -import datadog.trace.core.CoreTracer -import datadog.trace.core.DDSpan -import datadog.trace.test.util.DDSpecification -import io.opentracing.Span -import io.opentracing.log.Fields - -class DefaultLogHandlerTest extends DDSpecification { - def writer = new ListWriter() - def tracer = CoreTracer.builder().writer(writer).build() - - def cleanup() { - tracer?.close() - } - - def "handles correctly the error passed in the fields"() { - setup: - final LogHandler underTest = new DefaultLogHandler() - final DDSpan span = tracer.buildSpan("datadog", "op name").withServiceName("foo").start() - final String errorMessage = "errorMessage" - final String differentMessage = "differentMessage" - final Throwable error = new Throwable(errorMessage) - final Map fields = new HashMap<>() - fields.put(Fields.ERROR_OBJECT, error) - fields.put(Fields.MESSAGE, differentMessage) - - when: - underTest.log(fields, span) - - then: - span.getTags().get(DDTags.ERROR_MSG) == error.getMessage() - span.getTags().get(DDTags.ERROR_TYPE) == error.getClass().getName() - } - - def "handles correctly the error passed in the fields when called with timestamp"() { - setup: - final LogHandler underTest = new DefaultLogHandler() - final DDSpan span = tracer.buildSpan("datadog", "op name").withServiceName("foo").start() - final String errorMessage = "errorMessage" - final String differentMessage = "differentMessage" - final Throwable error = new Throwable(errorMessage) - final Map fields = new HashMap<>() - fields.put(Fields.ERROR_OBJECT, error) - fields.put(Fields.MESSAGE, differentMessage) - - when: - underTest.log(System.currentTimeMillis(), fields, span) - - then: - span.getTags().get(DDTags.ERROR_MSG) == error.getMessage() - span.getTags().get(DDTags.ERROR_TYPE) == error.getClass().getName() - } - - def "handles correctly the message passed in the fields but the span is not an error"() { - setup: - final LogHandler underTest = new DefaultLogHandler() - final DDSpan span = tracer.buildSpan("datadog", "op name").withServiceName("foo").start() - final String errorMessage = "errorMessage" - final Map fields = new HashMap<>() - fields.put(Fields.MESSAGE, errorMessage) - - when: - underTest.log(fields, span) - - then: - span.getTags().get(DDTags.ERROR_MSG) is null - } - - def "handles correctly the message passed in the fields when called with timestamp but the span is not an error"() { - setup: - final LogHandler underTest = new DefaultLogHandler() - final DDSpan span = tracer.buildSpan("datadog", "op name").withServiceName("foo").start() - final String errorMessage = "errorMessage" - final Map fields = new HashMap<>() - fields.put(Fields.MESSAGE, errorMessage) - - when: - underTest.log(System.currentTimeMillis(), fields, span) - - then: - span.getTags().get(DDTags.ERROR_MSG) is null - } - - def "handles correctly the message passed in the fields when the span is error"() { - setup: - final LogHandler underTest = new DefaultLogHandler() - final DDSpan span = tracer.buildSpan("datadog", "op name").withServiceName("foo").start() - final String errorMessage = "errorMessage" - final Map fields = new HashMap<>() - span.setError(true) - fields.put(Fields.MESSAGE, errorMessage) - - when: - underTest.log(fields, span) - - then: - span.getTags().get(DDTags.ERROR_MSG) == errorMessage - } - - def "handles correctly the message passed in the fields when called with timestamp when the span is error"() { - setup: - final LogHandler underTest = new DefaultLogHandler() - final DDSpan span = tracer.buildSpan("datadog", "op name").withServiceName("foo").start() - final String errorMessage = "errorMessage" - final Map fields = new HashMap<>() - span.setError(true) - fields.put(Fields.MESSAGE, errorMessage) - - when: - underTest.log(System.currentTimeMillis(), fields, span) - - then: - span.getTags().get(DDTags.ERROR_MSG) == errorMessage - } - - def "handles correctly the message passed in the fields when the event is error"() { - setup: - final LogHandler underTest = new DefaultLogHandler() - final DDSpan span = tracer.buildSpan("datadog", "op name").withServiceName("foo").start() - final String errorMessage = "errorMessage" - final Map fields = new HashMap<>() - fields.put(Fields.EVENT, "error") - fields.put(Fields.MESSAGE, errorMessage) - - when: - underTest.log(fields, span) - - then: - span.getTags().get(DDTags.ERROR_MSG) == errorMessage - } - - def "handles correctly the message passed in the fields when called with timestampwhen the event is error"() { - setup: - final LogHandler underTest = new DefaultLogHandler() - final DDSpan span = tracer.buildSpan("datadog", "op name").withServiceName("foo").start() - final String errorMessage = "errorMessage" - final Map fields = new HashMap<>() - fields.put(Fields.EVENT, "error") - fields.put(Fields.MESSAGE, errorMessage) - - when: - underTest.log(System.currentTimeMillis(), fields, span) - - then: - span.getTags().get(DDTags.ERROR_MSG) == errorMessage - } - - def "sanity test with loghandler not set"() { - setup: - final String expectedName = "fakeName" - final String expectedLogEvent = "fakeEvent" - final timeStamp = System.currentTimeMillis() - final Map fieldsMap = new HashMap<>() - - when: - def loggingTracer = DDTracer.builder().writer(writer).build() - final Span span = loggingTracer - .buildSpan(expectedName) - .withServiceName("foo") - .start() - - span.log(expectedLogEvent) - span.log(timeStamp, expectedLogEvent) - span.log(fieldsMap) - span.log(timeStamp, fieldsMap) - - then: - noExceptionThrown() - - cleanup: - loggingTracer.close() - } - - def "sanity test when passed log handler is null"() { - setup: - final String expectedName = "fakeName" - final String expectedLogEvent = "fakeEvent" - final timeStamp = System.currentTimeMillis() - final Map fieldsMap = new HashMap<>() - - when: - def loggingTracer = DDTracer.builder().writer(writer).logHandler(null).build() - final Span span = loggingTracer - .buildSpan(expectedName) - .start() - - span.log(expectedLogEvent) - span.log(timeStamp, expectedLogEvent) - span.log(fieldsMap) - span.log(timeStamp, fieldsMap) - - then: - noExceptionThrown() - - cleanup: - loggingTracer.close() - } - - def "should delegate simple logs to logHandler"() { - setup: - def logHandler = Mock(LogHandler) - final String expectedName = "fakeName" - final String expectedLogEvent = "fakeEvent" - final timeStamp = System.currentTimeMillis() - - def loggingTracer = DDTracer.builder().writer(writer).logHandler(logHandler).build() - def span = loggingTracer - .buildSpan(expectedName) - .withServiceName("foo") - .start() - - when: - span.log(timeStamp, expectedLogEvent) - - then: - 1 * logHandler.log(timeStamp, expectedLogEvent, span.delegate) - - cleanup: - loggingTracer.close() - } - - def "should delegate simple logs with timestamp to logHandler"() { - setup: - def logHandler = Mock(LogHandler) - final String expectedName = "fakeName" - final String expectedLogEvent = "fakeEvent" - - def loggingTracer = DDTracer.builder().writer(writer).logHandler(logHandler).build() - def span = loggingTracer - .buildSpan(expectedName) - .withServiceName("foo") - .start() - - when: - span.log(expectedLogEvent) - - then: - 1 * logHandler.log(expectedLogEvent, span.delegate) - - cleanup: - loggingTracer.close() - } - - def "should delegate logs with fields to logHandler"() { - setup: - def logHandler = Mock(LogHandler) - final String expectedName = "fakeName" - final Map fieldsMap = new HashMap<>() - - def loggingTracer = DDTracer.builder().writer(writer).logHandler(logHandler).build() - def span = loggingTracer - .buildSpan(expectedName) - .withServiceName("foo") - .start() - - when: - span.log(fieldsMap) - - then: - 1 * logHandler.log(fieldsMap, span.delegate) - - cleanup: - loggingTracer.close() - } - - def "should delegate logs with fields and timestamp to logHandler"() { - setup: - def logHandler = Mock(LogHandler) - final String expectedName = "fakeName" - final Map fieldsMap = new HashMap<>() - final timeStamp = System.currentTimeMillis() - - def loggingTracer = DDTracer.builder().writer(writer).logHandler(logHandler).build() - def span = loggingTracer - .buildSpan(expectedName) - .withServiceName("foo") - .start() - - when: - span.log(timeStamp, fieldsMap) - - then: - 1 * logHandler.log(timeStamp, fieldsMap, span.delegate) - - cleanup: - loggingTracer.close() - } -} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/IterationSpansForkedTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/IterationSpansForkedTest.groovy deleted file mode 100644 index 80471a67231..00000000000 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/IterationSpansForkedTest.groovy +++ /dev/null @@ -1,212 +0,0 @@ -package datadog.opentracing - -import datadog.metrics.api.statsd.StatsDClient -import datadog.trace.bootstrap.instrumentation.api.AgentSpan -import datadog.trace.common.writer.ListWriter -import datadog.trace.core.CoreTracer -import datadog.trace.core.DDSpan -import datadog.trace.test.util.DDSpecification -import io.opentracing.ScopeManager - -class IterationSpansForkedTest extends DDSpecification { - ListWriter writer - DDTracer tracer - ScopeManager scopeManager - StatsDClient statsDClient - CoreTracer coreTracer - - def setup() { - injectSysConfig("dd.trace.scope.iteration.keep.alive", "1") - - writer = new ListWriter() - statsDClient = Mock() - tracer = DDTracer.builder().writer(writer).statsDClient(statsDClient).build() - scopeManager = tracer.scopeManager() - coreTracer = tracer.tracer - } - - def cleanup() { - coreTracer.close() - } - - def "root iteration scope lifecycle"() { - when: - coreTracer.closePrevious(true) - def span1 = coreTracer.buildSpan("datadog", "next1").start() - def scope1 = coreTracer.activateNext(span1) - - then: - writer.empty - - and: - scope1.span() == span1 - scopeManager.active().span().delegate == span1 - !spanFinished(span1) - - when: - coreTracer.closePrevious(true) - def span2 = coreTracer.buildSpan("datadog", "next2").start() - def scope2 = coreTracer.activateNext(span2) - - then: - spanFinished(span1) - writer == [[span1]] - - and: - scope2.span() == span2 - scopeManager.active().span().delegate == span2 - !spanFinished(span2) - - when: - coreTracer.closePrevious(true) - def span3 = coreTracer.buildSpan("datadog", "next3").start() - def scope3 = coreTracer.activateNext(span3) - writer.waitForTraces(2) - - then: - spanFinished(span2) - writer == [[span1], [span2]] - - and: - scope3.span() == span3 - scopeManager.active().span().delegate == span3 - !spanFinished(span3) - - when: - // 'next3' should time out & finish after 1s - writer.waitForTraces(3) - - then: - spanFinished(span3) - writer == [[span1], [span2], [span3]] - } - - def "non-root iteration scope lifecycle"() { - setup: - def span0 = coreTracer.buildSpan("datadog", "parent").start() - def scope0 = coreTracer.activateSpan(span0) - - when: - coreTracer.closePrevious(true) - def span1 = coreTracer.buildSpan("datadog", "next1").start() - def scope1 = coreTracer.activateNext(span1) - - then: - writer.empty - - and: - scope1.span() == span1 - scopeManager.active().span().delegate == span1 - !spanFinished(span1) - - when: - coreTracer.closePrevious(true) - def span2 = coreTracer.buildSpan("datadog", "next2").start() - def scope2 = coreTracer.activateNext(span2) - - then: - spanFinished(span1) - writer.empty - - and: - scope2.span() == span2 - scopeManager.active().span().delegate == span2 - !spanFinished(span2) - - when: - coreTracer.closePrevious(true) - def span3 = coreTracer.buildSpan("datadog", "next3").start() - def scope3 = coreTracer.activateNext(span3) - - then: - spanFinished(span2) - writer.empty - - and: - scope3.span() == span3 - scopeManager.active().span().delegate == span3 - !spanFinished(span3) - - // close and finish the surrounding (non-iteration) span to complete the trace - scope0.close() - span0.finish() - writer.waitForTraces(1) - - then: - spanFinished(span3) - spanFinished(span0) - sortSpansByStart() - writer == [[span0, span1, span2, span3]] - } - - def "nested iteration scope lifecycle"() { - when: - coreTracer.closePrevious(true) - def span1 = coreTracer.buildSpan("datadog", "next1").start() - def scope1 = coreTracer.activateNext(span1) - - then: - writer.empty - - and: - scope1.span() == span1 - scopeManager.active().span().delegate == span1 - !spanFinished(span1) - - when: - def span1A = coreTracer.buildSpan("datadog", "methodA").start() - def scope1A = coreTracer.activateSpan(span1A) - - and: - coreTracer.closePrevious(true) - def span1A1 = coreTracer.buildSpan("datadog", "next1A1").start() - def scope1A1 = coreTracer.activateNext(span1A1) - - then: - !spanFinished(span1) - writer.empty - - and: - scope1A1.span() == span1A1 - scopeManager.active().span().delegate == span1A1 - !spanFinished(span1A1) - - when: - coreTracer.closePrevious(true) - def span1A2 = coreTracer.buildSpan("datadog", "next1A2").start() - def scope1A2 = coreTracer.activateNext(span1A2) - - then: - spanFinished(span1A1) - writer.empty - - and: - scope1A2.span() == span1A2 - scopeManager.active().span().delegate == span1A2 - !spanFinished(span1A2) - - when: - // close and finish the intermediate (non-iteration) span - scope1A.close() - span1A.finish() - // 'next1' (and 'next1A2') should time out & finish after 1s to complete the trace - writer.waitForTraces(1) - - then: - spanFinished(span1A2) - spanFinished(span1A) - spanFinished(span1) - sortSpansByStart() - writer == [[span1, span1A, span1A1, span1A2]] - } - - boolean spanFinished(AgentSpan span) { - return ((DDSpan) span)?.isFinished() - } - - private List sortSpansByStart() { - writer.firstTrace().sort { a, b -> - return a.startTimeNano <=> b.startTimeNano - } - } -} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/OTSpanTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/OTSpanTest.groovy deleted file mode 100644 index 0b9ffa40f6a..00000000000 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/OTSpanTest.groovy +++ /dev/null @@ -1,46 +0,0 @@ -package datadog.opentracing - -import datadog.trace.api.interceptor.MutableSpan -import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities -import datadog.trace.test.util.DDSpecification -import io.opentracing.Scope -import io.opentracing.Span -import spock.lang.Shared - -class OTSpanTest extends DDSpecification { - @Shared - DDTracer tracer = DDTracer.builder().build() - - def cleanup() { - tracer?.close() - } - - def "test resource name assignment through MutableSpan casting"() { - given: - OTSpan testSpan = tracer.buildSpan("parent").withResourceName("test-resource").start() as OTSpan - OTScopeManager.OTScope testScope = tracer.activateSpan(testSpan) as OTScopeManager.OTScope - - when: - Span active = tracer.activeSpan() - Span child = tracer.buildSpan("child").asChildOf(active).start() - Scope scope = tracer.activateSpan(child) - - MutableSpan localRootSpan = ((MutableSpan) child).getLocalRootSpan() - localRootSpan.setResourceName("correct-resource") - - then: - testSpan.getResourceName() == "correct-resource" - - when: - testSpan.delegate.setResourceName("should-be-ignored", ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE) - - then: - testSpan.getResourceName() == "correct-resource" - - cleanup: - scope.close() - child.finish() - testScope.close() - testSpan.finish() - } -} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/OpenTracingAPITest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/OpenTracingAPITest.groovy deleted file mode 100644 index bc73a1b80df..00000000000 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/OpenTracingAPITest.groovy +++ /dev/null @@ -1,541 +0,0 @@ -package datadog.opentracing - -import datadog.trace.api.DDTags -import datadog.trace.api.config.TracerConfig -import datadog.trace.api.interceptor.MutableSpan -import datadog.trace.api.interceptor.TraceInterceptor -import datadog.trace.api.scopemanager.ScopeListener -import datadog.trace.bootstrap.instrumentation.api.AgentTracer -import datadog.trace.common.writer.ListWriter -import datadog.trace.context.TraceScope -import datadog.trace.test.util.DDSpecification -import io.opentracing.Scope -import io.opentracing.Span -import io.opentracing.SpanContext -import io.opentracing.propagation.Format -import io.opentracing.propagation.TextMapAdapter -import io.opentracing.tag.Tags - -import static datadog.trace.agent.test.asserts.ListWriterAssert.assertTraces - -class OpenTracingAPITest extends DDSpecification { - def writer = new ListWriter() - - def tracer = DDTracer.builder().writer(writer).build() - - def traceInterceptor = Mock(TraceInterceptor) - def scopeListener = Mock(ScopeListener) - - def setup() { - assert tracer.scopeManager().active() == null - tracer.addTraceInterceptor(traceInterceptor) - tracer.tracer.addScopeListener(scopeListener) - } - - def cleanup() { - tracer.close() - } - - def "tracer/scopeManager returns null for no active span"() { - expect: - tracer.activeSpan() == null - tracer.scopeManager().active() == null - tracer.scopeManager().activeSpan() == null - } - - def "single span"() { - when: - Scope scope - try { - scope = tracer.buildSpan("someOperation").startActive(true) - scope.span().setTag(DDTags.SERVICE_NAME, "someService") - } finally { - scope.close() - } - writer.waitForTraces(1) - - then: - 1 * traceInterceptor.onTraceComplete({ it.size() == 1 }) >> { args -> args[0] } - - assertTraces(writer, 1) { - trace(1) { - span { - serviceName "someService" - operationName "someOperation" - resourceName "someOperation" - tags { - "$DDTags.DD_INTEGRATION" "opentracing" - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - } - } - } - - def "span with builder"() { - when: - Span testSpan = tracer.buildSpan("someOperation") - .withTag(Tags.COMPONENT, "opentracing") - .withTag("someBoolean", true) - .withTag("someNumber", 1) - .withTag(DDTags.SERVICE_NAME, "someService") - .start() - - Scope scope - try { - scope = tracer.activateSpan(testSpan) - testSpan.finish() - } finally { - scope.close() - } - writer.waitForTraces(1) - - then: - 1 * traceInterceptor.onTraceComplete({ it.size() == 1 }) >> { args -> args[0] } - testSpan instanceof MutableSpan - - assertTraces(writer, 1) { - trace(1) { - span { - serviceName "someService" - operationName "someOperation" - resourceName "someOperation" - tags { - "$datadog.trace.bootstrap.instrumentation.api.Tags.COMPONENT" "opentracing" - "$DDTags.DD_INTEGRATION" "opentracing" - "someBoolean" true - "someNumber" 1 - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - } - } - } - - def "single span with manual start/finish"() { - when: - Span testSpan = tracer.buildSpan("someOperation").start() - Scope scope = tracer.activateSpan(testSpan) - - then: - 1 * scopeListener.afterScopeActivated() - testSpan instanceof MutableSpan - scope.span() instanceof MutableSpan - - when: - testSpan.setTag(DDTags.SERVICE_NAME, "someService") - testSpan.setTag(Tags.COMPONENT, "opentracing") - testSpan.setTag("someBoolean", true) - testSpan.setTag("someNumber", 1) - testSpan.setOperationName("someOtherOperation") - scope.close() - testSpan.finish() - writer.waitForTraces(1) - - then: - 1 * traceInterceptor.onTraceComplete({ it.size() == 1 }) >> { args -> args[0] } - 1 * scopeListener.afterScopeClosed() - - assertTraces(writer, 1) { - trace(1) { - span { - serviceName "someService" - operationName("someOtherOperation") - resourceName "someOtherOperation" - tags { - "$datadog.trace.bootstrap.instrumentation.api.Tags.COMPONENT" "opentracing" - "$DDTags.DD_INTEGRATION" "opentracing" - "someBoolean" true - "someNumber" 1 - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - } - } - } - - def "spans and scopes all equal"() { - when: - Span testSpan = tracer.buildSpan("someOperation").start() - Scope testScope = tracer.activateSpan(testSpan) - - Span traceActiveSpan = tracer.activeSpan() - Span scopeManagerActiveSpan = tracer.scopeManager().activeSpan() - Span scopeActiveSpan = testScope.span() - - Scope scopeManagerActiveScope = tracer.scopeManager().active() - testScope.close() - testSpan.finish() - writer.waitForTraces(1) - - then: - 1 * traceInterceptor.onTraceComplete({ it.size() == 1 }) >> { args -> args[0] } - testSpan == traceActiveSpan - testSpan.hashCode() == traceActiveSpan.hashCode() - testSpan == scopeManagerActiveSpan - testSpan.hashCode() == scopeManagerActiveSpan.hashCode() - testSpan == scopeActiveSpan - testSpan.hashCode() == scopeActiveSpan.hashCode() - testScope == scopeManagerActiveScope - testScope.hashCode() == scopeManagerActiveScope.hashCode() - } - - def "nested spans"() { - when: - Scope scope - try { - scope = tracer.buildSpan("someOperation").startActive(true) - scope.span().setTag(DDTags.SERVICE_NAME, "someService") - - Scope scope2 - try { - scope2 = tracer.buildSpan("someOperation2").startActive(true) - } finally { - scope2.close() - } - } finally { - scope.close() - } - writer.waitForTraces(1) - - then: - 1 * traceInterceptor.onTraceComplete({ it.size() == 2 }) >> { args -> args[0] } - - assertTraces(writer, 1) { - trace(2) { - span { - serviceName "someService" - operationName "someOperation" - resourceName "someOperation" - tags { - "$DDTags.DD_INTEGRATION" "opentracing" - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - span { - serviceName "someService" - operationName "someOperation2" - resourceName "someOperation2" - childOf(span(0)) - tags { - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - } - } - } - - def "span with async propagation"() { - setup: - AgentTracer.TracerAPI internalTracer = tracer.tracer - - when: - Scope scope = tracer.buildSpan("someOperation") - .withTag(DDTags.SERVICE_NAME, "someService") - .startActive(true) - internalTracer.setAsyncPropagationEnabled(false) - - then: - scope instanceof TraceScope - !internalTracer.isAsyncPropagationEnabled() - - when: - internalTracer.setAsyncPropagationEnabled(true) - TraceScope.Continuation continuation = ((TraceScope) scope).capture() - - then: - internalTracer.isAsyncPropagationEnabled() - continuation != null - - when: - continuation.cancel() - scope.close() - writer.waitForTraces(1) - - then: - 1 * traceInterceptor.onTraceComplete({ it.size() == 1 }) >> { args -> args[0] } - - assertTraces(writer, 1) { - trace(1) { - span { - serviceName "someService" - operationName "someOperation" - resourceName "someOperation" - tags { - "$DDTags.DD_INTEGRATION" "opentracing" - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - } - } - } - - def "span inherits async propagation"() { - setup: - AgentTracer.TracerAPI internalTracer = tracer.tracer - - when: - Scope outer = tracer.buildSpan("someOperation") - .withTag(DDTags.SERVICE_NAME, "someService") - .startActive(true) - internalTracer.setAsyncPropagationEnabled(false) - - then: - !internalTracer.isAsyncPropagationEnabled() - - when: - internalTracer.setAsyncPropagationEnabled(true) - Scope inner = tracer.buildSpan("otherOperation") - .withTag(DDTags.SERVICE_NAME, "otherService") - .startActive(true) - - then: - internalTracer.isAsyncPropagationEnabled() - - when: - inner.close() - outer.close() - writer.waitForTraces(1) - - then: - 1 * traceInterceptor.onTraceComplete({ it.size() == 2 }) >> { args -> args[0] } - - assertTraces(writer, 1) { - trace(2) { - span { - serviceName "someService" - operationName "someOperation" - resourceName "someOperation" - tags { - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - span { - serviceName "otherService" - operationName "otherOperation" - resourceName "otherOperation" - tags { - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - } - } - } - - def "SpanContext ids equal tracer ids"() { - when: - Span testSpan = tracer.buildSpan("someOperation") - .withServiceName("someService") - .start() - Scope scope = tracer.activateSpan(testSpan) - - then: - 1 * scopeListener.afterScopeActivated() - - testSpan.context().toSpanId() == tracer.getSpanId() - testSpan.context().toTraceId() == tracer.tracer.activeSpan().context().traceId.toString() - - when: - scope.close() - testSpan.finish() - writer.waitForTraces(1) - - then: - 1 * traceInterceptor.onTraceComplete({ it.size() == 1 }) >> { args -> args[0] } - 1 * scopeListener.afterScopeClosed() - - assertTraces(writer, 1) { - trace(1) { - span { - serviceName "someService" - operationName "someOperation" - resourceName "someOperation" - tags { - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - } - } - } - - def "closing scope when not on top"() { - when: - Span firstSpan = tracer.buildSpan("someOperation").start() - Scope firstScope = tracer.activateSpan(firstSpan) - - Span secondSpan = tracer.buildSpan("someOperation").start() - Scope secondScope = tracer.activateSpan(secondSpan) - - firstSpan.finish() - firstScope.close() - - then: - 2 * scopeListener.afterScopeActivated() - 0 * _ - - when: - secondSpan.finish() - secondScope.close() - writer.waitForTraces(1) - - then: - 2 * scopeListener.afterScopeClosed() - 1 * traceInterceptor.onTraceComplete({ it.size() == 2 }) >> { args -> args[0] } - 0 * _ - - when: - firstScope.close() - - then: - 0 * _ - } - - def "closing scope when not on top in strict mode"() { - setup: - injectSysConfig(TracerConfig.SCOPE_STRICT_MODE, "true") - DDTracer strictTracer = DDTracer.builder().writer(writer).build() - strictTracer.addTraceInterceptor(traceInterceptor) - strictTracer.tracer.addScopeListener(scopeListener) - - when: - Span firstSpan = strictTracer.buildSpan("someOperation").start() - Scope firstScope = strictTracer.activateSpan(firstSpan) - - Span secondSpan = strictTracer.buildSpan("someOperation").start() - Scope secondScope = strictTracer.activateSpan(secondSpan) - - then: - 2 * scopeListener.afterScopeActivated() - 0 * _ - - when: - firstSpan.finish() - firstScope.close() - - then: - thrown(RuntimeException) - 0 * _ - - when: - secondSpan.finish() - secondScope.close() - writer.waitForTraces(1) - - then: - 1 * scopeListener.afterScopeClosed() - 1 * traceInterceptor.onTraceComplete({ it.size() == 2 }) >> { args -> args[0] } - 1 * scopeListener.afterScopeActivated() - 0 * _ - - when: - firstScope.close() - - then: - 1 * scopeListener.afterScopeClosed() - 0 * _ - - cleanup: - strictTracer?.close() - } - - def "inject and extract context"() { - given: - def textMap = new TextMapAdapter(new HashMap()) - - when: - Span testSpan = tracer.buildSpan("clientOperation") - .withServiceName("someClientService") - .start() - Scope scope = tracer.activateSpan(testSpan) - - tracer.inject(testSpan.context(), Format.Builtin.HTTP_HEADERS, textMap) - - - SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, textMap) - Span serverSpan = tracer.buildSpan("serverOperation") - .withServiceName("someService") - .asChildOf(extractedContext) - .start() - tracer.activateSpan(serverSpan).close() - serverSpan.finish() - - scope.close() - testSpan.finish() - writer.waitForTraces(2) - - then: - 2 * traceInterceptor.onTraceComplete({ it.size() == 1 }) >> { args -> args[0] } - extractedContext.toTraceId() == testSpan.context().toTraceId() - extractedContext.toSpanId() == testSpan.context().toSpanId() - - assertTraces(writer, 2) { - trace(1) { - span { - serviceName "someClientService" - operationName "clientOperation" - resourceName "clientOperation" - tags { - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - } - trace(1) { - span { - serviceName "someService" - operationName "serverOperation" - resourceName "serverOperation" - childOf(trace(0).get(0)) - tags { - serviceNameSource "m" // service name was manually set - defaultTags(true) - } - } - } - } - } - - def "tolerate null span activation"() { - when: - try { - tracer.scopeManager().activate(null)?.close() - } catch (Exception ignored) {} - - try { - tracer.activateSpan(null)?.close() - } catch (Exception ignored) {} - - // make sure scope stack has been left in a valid state - Span testSpan = tracer.buildSpan("someOperation").withServiceName("someService").start() - Scope testScope = tracer.scopeManager().activate(testSpan) - testSpan.finish() - testScope.close() - writer.waitForTraces(1) - - then: - 1 * traceInterceptor.onTraceComplete({ it.size() == 1 }) >> { args -> args[0] } - - assertTraces(writer, 1) { - trace(1) { - span { - serviceName "someService" - operationName "someOperation" - resourceName "someOperation" - tags { - serviceNameSource "m" // service name was manually set - defaultTags() - } - } - } - } - } -} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/SpanBuilderTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/SpanBuilderTest.groovy deleted file mode 100644 index 7b4b6c73c9b..00000000000 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/SpanBuilderTest.groovy +++ /dev/null @@ -1,129 +0,0 @@ -package datadog.opentracing - -import datadog.trace.common.writer.ListWriter -import datadog.trace.test.util.DDSpecification -import io.opentracing.Span - -class SpanBuilderTest extends DDSpecification { - // TODO more io.opentracing.SpanBuilder specific tests - - def writer = new ListWriter() - def tracer = DDTracer.builder().writer(writer).build() - - def cleanup() { - tracer?.close() - } - - def "should inherit the DD parent attributes addReference CHILD_OF"() { - setup: - def expectedName = "fakeName" - def expectedParentServiceName = "fakeServiceName" - def expectedParentResourceName = "fakeResourceName" - def expectedParentType = "fakeType" - def expectedChildServiceName = "fakeServiceName-child" - def expectedChildResourceName = "fakeResourceName-child" - def expectedChildType = "fakeType-child" - def expectedBaggageItemKey = "fakeKey" - def expectedBaggageItemValue = "fakeValue" - - final Span parent = - tracer - .buildSpan(expectedName) - .withServiceName("foo") - .withResourceName(expectedParentResourceName) - .withSpanType(expectedParentType) - .start() - - parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue) - - // ServiceName and SpanType are always set by the parent if they are not present in the child - Span span = - tracer - .buildSpan(expectedName) - .withServiceName(expectedParentServiceName) - .addReference("child_of", parent.context()) - .start() - - expect: - span.delegate.getOperationName() == expectedName - span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue - span.context().delegate.getServiceName() == expectedParentServiceName - span.context().delegate.getResourceName() == expectedName - span.context().delegate.getSpanType() == null - - when: - // ServiceName and SpanType are always overwritten by the child if they are present - span = - tracer - .buildSpan(expectedName) - .withServiceName(expectedChildServiceName) - .withResourceName(expectedChildResourceName) - .withSpanType(expectedChildType) - .addReference("child_of", parent.context()) - .start() - - then: - span.delegate.getOperationName() == expectedName - span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue - span.context().delegate.getServiceName() == expectedChildServiceName - span.context().delegate.getResourceName() == expectedChildResourceName - span.context().delegate.getSpanType() == expectedChildType - } - - - def "should inherit the DD parent attributes add reference FOLLOWS_FROM"() { - setup: - def expectedName = "fakeName" - def expectedParentServiceName = "fakeServiceName" - def expectedParentResourceName = "fakeResourceName" - def expectedParentType = "fakeType" - def expectedChildServiceName = "fakeServiceName-child" - def expectedChildResourceName = "fakeResourceName-child" - def expectedChildType = "fakeType-child" - def expectedBaggageItemKey = "fakeKey" - def expectedBaggageItemValue = "fakeValue" - - final Span parent = - tracer - .buildSpan(expectedName) - .withServiceName("foo") - .withResourceName(expectedParentResourceName) - .withSpanType(expectedParentType) - .start() - - parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue) - - // ServiceName and SpanType are always set by the parent if they are not present in the child - Span span = - tracer - .buildSpan(expectedName) - .withServiceName(expectedParentServiceName) - .addReference("follows_from", parent.context()) - .start() - - expect: - span.delegate.getOperationName() == expectedName - span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue - span.context().delegate.getServiceName() == expectedParentServiceName - span.context().delegate.getResourceName() == expectedName - span.context().delegate.getSpanType() == null - - when: - // ServiceName and SpanType are always overwritten by the child if they are present - span = - tracer - .buildSpan(expectedName) - .withServiceName(expectedChildServiceName) - .withResourceName(expectedChildResourceName) - .withSpanType(expectedChildType) - .addReference("follows_from", parent.context()) - .start() - - then: - span.delegate.getOperationName() == expectedName - span.getBaggageItem(expectedBaggageItemKey) == expectedBaggageItemValue - span.context().delegate.getServiceName() == expectedChildServiceName - span.context().delegate.getResourceName() == expectedChildResourceName - span.context().delegate.getSpanType() == expectedChildType - } -} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/TypeConverterTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/TypeConverterTest.groovy deleted file mode 100644 index 6646832e1bd..00000000000 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/TypeConverterTest.groovy +++ /dev/null @@ -1,81 +0,0 @@ -package datadog.opentracing - -import datadog.trace.api.DDSpanId -import datadog.trace.api.DDTraceId -import datadog.trace.api.datastreams.NoopPathwayContext -import datadog.trace.api.sampling.PrioritySampling -import datadog.trace.core.CoreTracer -import datadog.trace.core.DDSpan -import datadog.trace.core.DDSpanContext -import datadog.trace.core.PendingTrace -import datadog.trace.core.propagation.PropagationTags -import datadog.trace.test.util.DDSpecification - -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopScope -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan -import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpanContext - -class TypeConverterTest extends DDSpecification { - TypeConverter typeConverter = new TypeConverter(new DefaultLogHandler()) - - def "should avoid the noop span wrapper allocation"() { - def noopAgentSpan = noopSpan() - expect: - typeConverter.toSpan(noopAgentSpan) is typeConverter.toSpan(noopAgentSpan) - } - - def "should avoid extra allocation for a span wrapper"() { - def context = createTestSpanContext() - def span1 = new DDSpan("test", 0, context, null) - def span2 = new DDSpan("test", 0, context, null) - expect: - // return the same wrapper for the same span - typeConverter.toSpan(span1) is typeConverter.toSpan(span1) - // return a distinct wrapper for another span - !typeConverter.toSpan(span1).is(typeConverter.toSpan(span2)) - } - - def "should avoid the noop context wrapper allocation"() { - def noopContext = noopSpanContext() - expect: - typeConverter.toSpanContext(noopContext) is typeConverter.toSpanContext(noopContext) - } - - def "should avoid the noop scope wrapper allocation"() { - def noopScope = noopScope() - expect: - typeConverter.toScope(noopScope, true) is typeConverter.toScope(noopScope, true) - typeConverter.toScope(noopScope, false) is typeConverter.toScope(noopScope, false) - // noop scopes expected to be the same despite the finishSpanOnClose flag - typeConverter.toScope(noopScope, true) is typeConverter.toScope(noopScope, false) - typeConverter.toScope(noopScope, false) is typeConverter.toScope(noopScope, true) - } - - def createTestSpanContext() { - def tracer = Stub(CoreTracer) - def trace = Stub(PendingTrace) - trace.mapServiceName(_) >> { String serviceName -> serviceName } - trace.getTracer() >> tracer - - return new DDSpanContext( - DDTraceId.ONE, - 1, - DDSpanId.ZERO, - null, - "fakeService", - "fakeOperation", - "fakeResource", - PrioritySampling.UNSET, - null, - [:], - false, - "fakeType", - 0, - trace, - null, - null, - NoopPathwayContext.INSTANCE, - false, - PropagationTags.factory().empty()) - } -} diff --git a/dd-trace-ot/src/test/groovy/datadog/opentracing/resolver/DDTracerResolverTest.groovy b/dd-trace-ot/src/test/groovy/datadog/opentracing/resolver/DDTracerResolverTest.groovy deleted file mode 100644 index 828deefaa3c..00000000000 --- a/dd-trace-ot/src/test/groovy/datadog/opentracing/resolver/DDTracerResolverTest.groovy +++ /dev/null @@ -1,34 +0,0 @@ -package datadog.opentracing.resolver - -import datadog.opentracing.DDTracer -import datadog.trace.test.util.DDSpecification -import io.opentracing.contrib.tracerresolver.TracerResolver - -import static datadog.trace.api.config.TracerConfig.TRACE_RESOLVER_ENABLED - -class DDTracerResolverTest extends DDSpecification { - - def resolver = new DDTracerResolver() - - def "test resolveTracer"() { - when: - def tracer = TracerResolver.resolveTracer() - - then: - tracer instanceof DDTracer - - cleanup: - tracer.close() - } - - def "test disable DDTracerResolver"() { - setup: - injectSysConfig(TRACE_RESOLVER_ENABLED, "false") - - when: - def tracer = resolver.resolve() - - then: - tracer == null - } -} diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/DDTracerAPITest.java b/dd-trace-ot/src/test/java/datadog/opentracing/DDTracerAPITest.java new file mode 100644 index 00000000000..a95cb2b360d --- /dev/null +++ b/dd-trace-ot/src/test/java/datadog/opentracing/DDTracerAPITest.java @@ -0,0 +1,61 @@ +package datadog.opentracing; + +import static datadog.trace.api.ConfigDefaults.DEFAULT_SERVICE_NAME; +import static datadog.trace.api.DDTags.LANGUAGE_TAG_KEY; +import static datadog.trace.api.DDTags.LANGUAGE_TAG_VALUE; +import static datadog.trace.api.DDTags.RUNTIME_ID_TAG; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.common.sampling.RateByServiceTraceSampler; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.CoreTracer; +import datadog.trace.test.util.DDJavaSpecification; +import java.lang.reflect.Field; +import org.junit.jupiter.api.Test; + +class DDTracerAPITest extends DDJavaSpecification { + + @Test + void verifySamplerWriterConstructor() throws Exception { + ListWriter writer = new ListWriter(); + RateByServiceTraceSampler sampler = new RateByServiceTraceSampler(); + DDTracer tracerOT = new DDTracer(DEFAULT_SERVICE_NAME, writer, sampler); + try { + AgentTracer.TracerAPI tracerAPI = tracerOT.getInternalTracer(); + CoreTracer tracer = (CoreTracer) tracerAPI; + + assertEquals(DEFAULT_SERVICE_NAME, getField(tracer, "serviceName")); + assertSame(sampler, getField(tracer, "initialSampler")); + assertSame(writer, getField(tracer, "writer")); + + Object localRootSpanTags = getField(tracer, "localRootSpanTags"); + assertNotNull(localRootSpanTags.toString()); + // Verify runtime-id and language tags are populated + assertTrue( + ((java.util.Map) localRootSpanTags).get(RUNTIME_ID_TAG).toString().length() > 0); + assertEquals( + LANGUAGE_TAG_VALUE, ((java.util.Map) localRootSpanTags).get(LANGUAGE_TAG_KEY)); + } finally { + tracerOT.close(); + } + } + + @SuppressWarnings("unchecked") + private static T getField(Object obj, String fieldName) throws Exception { + Class cls = obj.getClass(); + while (cls != null) { + try { + Field field = cls.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(obj); + } catch (NoSuchFieldException ignored) { + cls = cls.getSuperclass(); + } + } + throw new NoSuchFieldException("Field " + fieldName + " not found on " + obj.getClass()); + } +} diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/DDTracerTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/DDTracerTest.java new file mode 100644 index 00000000000..65f249278d1 --- /dev/null +++ b/dd-trace-ot/src/test/java/datadog/opentracing/DDTracerTest.java @@ -0,0 +1,95 @@ +package datadog.opentracing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; + +import datadog.trace.common.sampling.Sampler; +import datadog.trace.common.writer.DDAgentWriter; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.common.writer.Writer; +import datadog.trace.context.TraceScope; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.Scope; +import java.util.HashMap; +import org.junit.jupiter.api.Test; + +class DDTracerTest extends DDJavaSpecification { + + @Test + void testTracerBuilder() throws Exception { + DDTracer tracer = DDTracer.builder().build(); + assertNotNull(tracer); + tracer.close(); + } + + @Test + void testDeprecatedTracerConstructor() throws Exception { + DDTracer tracer1 = new DDTracer(); + DDTracer tracer2 = new DDTracer("serviceName"); + DDTracer tracer3 = new DDTracer("serviceName", mock(Writer.class), mock(Sampler.class)); + DDTracer tracer4 = + new DDTracer("serviceName", mock(Writer.class), mock(Sampler.class), new HashMap<>()); + DDTracer tracer5 = new DDTracer(mock(Writer.class)); + + assertNotNull(tracer1); + assertNotNull(tracer2); + assertNotNull(tracer3); + assertNotNull(tracer4); + assertNotNull(tracer5); + + tracer1.close(); + tracer2.close(); + tracer3.close(); + tracer4.close(); + tracer5.close(); + } + + @Test + void testTracerBuilderWithDefaultWriter() throws Exception { + DDTracer tracer = DDTracer.builder().writer(DDAgentWriter.builder().build()).build(); + assertNotNull(tracer); + tracer.close(); + } + + @Test + void testAccessToTraceSegment() throws Exception { + DDTracer tracer = DDTracer.builder().writer(DDAgentWriter.builder().build()).build(); + OTSpan span = (OTSpan) tracer.buildSpan("some name").start(); + try (Scope scope = tracer.scopeManager().activate(span)) { + assertNotNull(tracer); + assertEquals(span, tracer.activeSpan()); + assertNotNull(tracer.getTraceSegment()); + } + + tracer.close(); + } + + @Test + void shouldProduceBlackholeScopes() throws Exception { + ListWriter writer = new ListWriter(); + DDTracer tracer = DDTracer.builder().writer(writer).build(); + + OTSpan span = (OTSpan) tracer.buildSpan("some name").start(); + Scope scope = tracer.scopeManager().activate(span); + TraceScope muteScope = tracer.muteTracing(); + io.opentracing.Span blackholed = tracer.buildSpan("hidden span").start(); + blackholed.finish(); + muteScope.close(); + io.opentracing.Span visibleSpan = tracer.buildSpan("visible span").start(); + visibleSpan.finish(); + scope.close(); + span.finish(); + + writer.waitForTraces(1); + assertEquals(1, writer.size()); + assertEquals(2, writer.firstTrace().size()); + assertEquals( + Long.toString(writer.firstTrace().get(0).context().getSpanId()), span.context().toSpanId()); + assertEquals( + Long.toString(writer.firstTrace().get(1).context().getSpanId()), + visibleSpan.context().toSpanId()); + + tracer.close(); + } +} diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/DefaultLogHandlerTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/DefaultLogHandlerTest.java new file mode 100644 index 00000000000..6f8da607586 --- /dev/null +++ b/dd-trace-ot/src/test/java/datadog/opentracing/DefaultLogHandlerTest.java @@ -0,0 +1,263 @@ +package datadog.opentracing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import datadog.trace.api.DDTags; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.CoreTracer; +import datadog.trace.core.DDSpan; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.log.Fields; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class DefaultLogHandlerTest extends DDJavaSpecification { + + ListWriter writer = new ListWriter(); + CoreTracer tracer = CoreTracer.builder().writer(writer).build(); + + @AfterEach + void cleanup() throws Exception { + if (tracer != null) { + tracer.close(); + } + } + + @Test + void handlesCorrectlyTheErrorPassedInTheFields() { + LogHandler underTest = new DefaultLogHandler(); + DDSpan span = (DDSpan) tracer.buildSpan("datadog", "op name").withServiceName("foo").start(); + String errorMessage = "errorMessage"; + String differentMessage = "differentMessage"; + Throwable error = new Throwable(errorMessage); + Map fields = new HashMap<>(); + fields.put(Fields.ERROR_OBJECT, error); + fields.put(Fields.MESSAGE, differentMessage); + + underTest.log(fields, span); + + assertEquals(error.getMessage(), span.getTags().get(DDTags.ERROR_MSG)); + assertEquals(error.getClass().getName(), span.getTags().get(DDTags.ERROR_TYPE)); + } + + @Test + void handlesCorrectlyTheErrorPassedInTheFieldsWhenCalledWithTimestamp() { + LogHandler underTest = new DefaultLogHandler(); + DDSpan span = (DDSpan) tracer.buildSpan("datadog", "op name").withServiceName("foo").start(); + String errorMessage = "errorMessage"; + String differentMessage = "differentMessage"; + Throwable error = new Throwable(errorMessage); + Map fields = new HashMap<>(); + fields.put(Fields.ERROR_OBJECT, error); + fields.put(Fields.MESSAGE, differentMessage); + + underTest.log(System.currentTimeMillis(), fields, span); + + assertEquals(error.getMessage(), span.getTags().get(DDTags.ERROR_MSG)); + assertEquals(error.getClass().getName(), span.getTags().get(DDTags.ERROR_TYPE)); + } + + @Test + void handlesCorrectlyTheMessageInTheFieldsButSpanIsNotAnError() { + LogHandler underTest = new DefaultLogHandler(); + DDSpan span = (DDSpan) tracer.buildSpan("datadog", "op name").withServiceName("foo").start(); + String errorMessage = "errorMessage"; + Map fields = new HashMap<>(); + fields.put(Fields.MESSAGE, errorMessage); + + underTest.log(fields, span); + + assertNull(span.getTags().get(DDTags.ERROR_MSG)); + } + + @Test + void handlesCorrectlyTheMessageInTheFieldsCalledWithTimestampButSpanIsNotAnError() { + LogHandler underTest = new DefaultLogHandler(); + DDSpan span = (DDSpan) tracer.buildSpan("datadog", "op name").withServiceName("foo").start(); + String errorMessage = "errorMessage"; + Map fields = new HashMap<>(); + fields.put(Fields.MESSAGE, errorMessage); + + underTest.log(System.currentTimeMillis(), fields, span); + + assertNull(span.getTags().get(DDTags.ERROR_MSG)); + } + + @Test + void handlesCorrectlyTheMessageInTheFieldsWhenSpanIsError() { + LogHandler underTest = new DefaultLogHandler(); + DDSpan span = (DDSpan) tracer.buildSpan("datadog", "op name").withServiceName("foo").start(); + String errorMessage = "errorMessage"; + Map fields = new HashMap<>(); + span.setError(true); + fields.put(Fields.MESSAGE, errorMessage); + + underTest.log(fields, span); + + assertEquals(errorMessage, span.getTags().get(DDTags.ERROR_MSG)); + } + + @Test + void handlesCorrectlyTheMessageInTheFieldsCalledWithTimestampWhenSpanIsError() { + LogHandler underTest = new DefaultLogHandler(); + DDSpan span = (DDSpan) tracer.buildSpan("datadog", "op name").withServiceName("foo").start(); + String errorMessage = "errorMessage"; + Map fields = new HashMap<>(); + span.setError(true); + fields.put(Fields.MESSAGE, errorMessage); + + underTest.log(System.currentTimeMillis(), fields, span); + + assertEquals(errorMessage, span.getTags().get(DDTags.ERROR_MSG)); + } + + @Test + void handlesCorrectlyTheMessageInTheFieldsWhenEventIsError() { + LogHandler underTest = new DefaultLogHandler(); + DDSpan span = (DDSpan) tracer.buildSpan("datadog", "op name").withServiceName("foo").start(); + String errorMessage = "errorMessage"; + Map fields = new HashMap<>(); + fields.put(Fields.EVENT, "error"); + fields.put(Fields.MESSAGE, errorMessage); + + underTest.log(fields, span); + + assertEquals(errorMessage, span.getTags().get(DDTags.ERROR_MSG)); + } + + @Test + void handlesCorrectlyTheMessageInTheFieldsCalledWithTimestampWhenEventIsError() { + LogHandler underTest = new DefaultLogHandler(); + DDSpan span = (DDSpan) tracer.buildSpan("datadog", "op name").withServiceName("foo").start(); + String errorMessage = "errorMessage"; + Map fields = new HashMap<>(); + fields.put(Fields.EVENT, "error"); + fields.put(Fields.MESSAGE, errorMessage); + + underTest.log(System.currentTimeMillis(), fields, span); + + assertEquals(errorMessage, span.getTags().get(DDTags.ERROR_MSG)); + } + + @Test + void sanityTestWithLoghandlerNotSet() throws Exception { + String expectedName = "fakeName"; + String expectedLogEvent = "fakeEvent"; + long timeStamp = System.currentTimeMillis(); + Map fieldsMap = new HashMap<>(); + + DDTracer loggingTracer = DDTracer.builder().writer(writer).build(); + try { + io.opentracing.Span span = + loggingTracer.buildSpan(expectedName).withServiceName("foo").start(); + + span.log(expectedLogEvent); + span.log(timeStamp, expectedLogEvent); + span.log(fieldsMap); + span.log(timeStamp, fieldsMap); + // no exception thrown + } finally { + loggingTracer.close(); + } + } + + @Test + void sanityTestWhenPassedLogHandlerIsNull() throws Exception { + String expectedName = "fakeName"; + String expectedLogEvent = "fakeEvent"; + long timeStamp = System.currentTimeMillis(); + Map fieldsMap = new HashMap<>(); + + DDTracer loggingTracer = DDTracer.builder().writer(writer).logHandler(null).build(); + try { + io.opentracing.Span span = loggingTracer.buildSpan(expectedName).start(); + + span.log(expectedLogEvent); + span.log(timeStamp, expectedLogEvent); + span.log(fieldsMap); + span.log(timeStamp, fieldsMap); + // no exception thrown + } finally { + loggingTracer.close(); + } + } + + @Test + void shouldDelegateSimpleLogsToLogHandler() throws Exception { + LogHandler logHandler = mock(LogHandler.class); + String expectedName = "fakeName"; + String expectedLogEvent = "fakeEvent"; + long timeStamp = System.currentTimeMillis(); + + DDTracer loggingTracer = DDTracer.builder().writer(writer).logHandler(logHandler).build(); + try { + OTSpan span = (OTSpan) loggingTracer.buildSpan(expectedName).withServiceName("foo").start(); + + span.log(timeStamp, expectedLogEvent); + + verify(logHandler).log(timeStamp, expectedLogEvent, span.getDelegate()); + } finally { + loggingTracer.close(); + } + } + + @Test + void shouldDelegateSimpleLogsWithTimestampToLogHandler() throws Exception { + LogHandler logHandler = mock(LogHandler.class); + String expectedName = "fakeName"; + String expectedLogEvent = "fakeEvent"; + + DDTracer loggingTracer = DDTracer.builder().writer(writer).logHandler(logHandler).build(); + try { + OTSpan span = (OTSpan) loggingTracer.buildSpan(expectedName).withServiceName("foo").start(); + + span.log(expectedLogEvent); + + verify(logHandler).log(expectedLogEvent, span.getDelegate()); + } finally { + loggingTracer.close(); + } + } + + @Test + void shouldDelegateLogsWithFieldsToLogHandler() throws Exception { + LogHandler logHandler = mock(LogHandler.class); + String expectedName = "fakeName"; + Map fieldsMap = new HashMap<>(); + + DDTracer loggingTracer = DDTracer.builder().writer(writer).logHandler(logHandler).build(); + try { + OTSpan span = (OTSpan) loggingTracer.buildSpan(expectedName).withServiceName("foo").start(); + + span.log(fieldsMap); + + verify(logHandler).log(fieldsMap, span.getDelegate()); + } finally { + loggingTracer.close(); + } + } + + @Test + void shouldDelegateLogsWithFieldsAndTimestampToLogHandler() throws Exception { + LogHandler logHandler = mock(LogHandler.class); + String expectedName = "fakeName"; + Map fieldsMap = new HashMap<>(); + long timeStamp = System.currentTimeMillis(); + + DDTracer loggingTracer = DDTracer.builder().writer(writer).logHandler(logHandler).build(); + try { + OTSpan span = (OTSpan) loggingTracer.buildSpan(expectedName).withServiceName("foo").start(); + + span.log(timeStamp, fieldsMap); + + verify(logHandler).log(timeStamp, fieldsMap, span.getDelegate()); + } finally { + loggingTracer.close(); + } + } +} diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/IterationSpansForkedTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/IterationSpansForkedTest.java new file mode 100644 index 00000000000..8e16782f84b --- /dev/null +++ b/dd-trace-ot/src/test/java/datadog/opentracing/IterationSpansForkedTest.java @@ -0,0 +1,196 @@ +package datadog.opentracing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import datadog.metrics.api.statsd.StatsDClient; +import datadog.trace.bootstrap.instrumentation.api.AgentScope; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.CoreTracer; +import datadog.trace.core.DDSpan; +import datadog.trace.junit.utils.config.WithConfig; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.ScopeManager; +import java.util.Comparator; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@WithConfig(key = "trace.scope.iteration.keep.alive", value = "1") +class IterationSpansForkedTest extends DDJavaSpecification { + + ListWriter writer; + DDTracer tracer; + ScopeManager scopeManager; + StatsDClient statsDClient; + CoreTracer coreTracer; + + @BeforeEach + void setup() { + writer = new ListWriter(); + statsDClient = mock(StatsDClient.class); + tracer = DDTracer.builder().writer(writer).statsDClient(statsDClient).build(); + scopeManager = tracer.scopeManager(); + coreTracer = (CoreTracer) tracer.getInternalTracer(); + } + + @AfterEach + void cleanup() throws Exception { + coreTracer.close(); + } + + @Test + void rootIterationScopeLifecycle() throws Exception { + coreTracer.closePrevious(true); + AgentSpan span1 = coreTracer.buildSpan("datadog", "next1").start(); + AgentScope scope1 = coreTracer.activateNext(span1); + + assertTrue(writer.isEmpty()); + assertSame(span1, scope1.span()); + assertSame(span1, ((OTSpan) tracer.activeSpan()).getDelegate()); + assertFalse(spanFinished(span1)); + + coreTracer.closePrevious(true); + AgentSpan span2 = coreTracer.buildSpan("datadog", "next2").start(); + AgentScope scope2 = coreTracer.activateNext(span2); + + assertTrue(spanFinished(span1)); + assertEquals(1, writer.size()); + assertSame(span1, writer.get(0).get(0)); + assertSame(span2, scope2.span()); + assertSame(span2, ((OTSpan) tracer.activeSpan()).getDelegate()); + assertFalse(spanFinished(span2)); + + coreTracer.closePrevious(true); + AgentSpan span3 = coreTracer.buildSpan("datadog", "next3").start(); + AgentScope scope3 = coreTracer.activateNext(span3); + writer.waitForTraces(2); + + assertTrue(spanFinished(span2)); + assertEquals(2, writer.size()); + assertSame(span3, scope3.span()); + assertSame(span3, ((OTSpan) tracer.activeSpan()).getDelegate()); + assertFalse(spanFinished(span3)); + + // 'next3' should time out & finish after 1s + writer.waitForTraces(3); + + assertTrue(spanFinished(span3)); + assertEquals(3, writer.size()); + } + + @Test + void nonRootIterationScopeLifecycle() throws Exception { + AgentSpan span0 = coreTracer.buildSpan("datadog", "parent").start(); + AgentScope scope0 = coreTracer.activateSpan(span0); + + coreTracer.closePrevious(true); + AgentSpan span1 = coreTracer.buildSpan("datadog", "next1").start(); + AgentScope scope1 = coreTracer.activateNext(span1); + + assertTrue(writer.isEmpty()); + assertSame(span1, scope1.span()); + assertSame(span1, ((OTSpan) tracer.activeSpan()).getDelegate()); + assertFalse(spanFinished(span1)); + + coreTracer.closePrevious(true); + AgentSpan span2 = coreTracer.buildSpan("datadog", "next2").start(); + AgentScope scope2 = coreTracer.activateNext(span2); + + assertTrue(spanFinished(span1)); + assertTrue(writer.isEmpty()); + assertSame(span2, scope2.span()); + assertSame(span2, ((OTSpan) tracer.activeSpan()).getDelegate()); + assertFalse(spanFinished(span2)); + + coreTracer.closePrevious(true); + AgentSpan span3 = coreTracer.buildSpan("datadog", "next3").start(); + AgentScope scope3 = coreTracer.activateNext(span3); + + assertTrue(spanFinished(span2)); + assertTrue(writer.isEmpty()); + assertSame(span3, scope3.span()); + assertSame(span3, ((OTSpan) tracer.activeSpan()).getDelegate()); + assertFalse(spanFinished(span3)); + + // close and finish the surrounding (non-iteration) span to complete the trace + scope0.close(); + span0.finish(); + writer.waitForTraces(1); + + assertTrue(spanFinished(span3)); + assertTrue(spanFinished(span0)); + sortSpansByStart(); + List trace = writer.get(0); + assertEquals(4, trace.size()); + assertSame(span0, trace.get(0)); + assertSame(span1, trace.get(1)); + assertSame(span2, trace.get(2)); + assertSame(span3, trace.get(3)); + } + + @Test + void nestedIterationScopeLifecycle() throws Exception { + coreTracer.closePrevious(true); + AgentSpan span1 = coreTracer.buildSpan("datadog", "next1").start(); + AgentScope scope1 = coreTracer.activateNext(span1); + + assertTrue(writer.isEmpty()); + assertSame(span1, scope1.span()); + assertSame(span1, ((OTSpan) tracer.activeSpan()).getDelegate()); + assertFalse(spanFinished(span1)); + + AgentSpan span1A = coreTracer.buildSpan("datadog", "methodA").start(); + AgentScope scope1A = coreTracer.activateSpan(span1A); + + coreTracer.closePrevious(true); + AgentSpan span1A1 = coreTracer.buildSpan("datadog", "next1A1").start(); + AgentScope scope1A1 = coreTracer.activateNext(span1A1); + + assertFalse(spanFinished(span1)); + assertTrue(writer.isEmpty()); + assertSame(span1A1, scope1A1.span()); + assertSame(span1A1, ((OTSpan) tracer.activeSpan()).getDelegate()); + assertFalse(spanFinished(span1A1)); + + coreTracer.closePrevious(true); + AgentSpan span1A2 = coreTracer.buildSpan("datadog", "next1A2").start(); + AgentScope scope1A2 = coreTracer.activateNext(span1A2); + + assertTrue(spanFinished(span1A1)); + assertTrue(writer.isEmpty()); + assertSame(span1A2, scope1A2.span()); + assertSame(span1A2, ((OTSpan) tracer.activeSpan()).getDelegate()); + assertFalse(spanFinished(span1A2)); + + // close and finish the intermediate (non-iteration) span + scope1A.close(); + span1A.finish(); + // 'next1' (and 'next1A2') should time out & finish after 1s to complete the trace + writer.waitForTraces(1); + + assertTrue(spanFinished(span1A2)); + assertTrue(spanFinished(span1A)); + assertTrue(spanFinished(span1)); + sortSpansByStart(); + List trace = writer.get(0); + assertEquals(4, trace.size()); + assertSame(span1, trace.get(0)); + assertSame(span1A, trace.get(1)); + assertSame(span1A1, trace.get(2)); + assertSame(span1A2, trace.get(3)); + } + + private boolean spanFinished(AgentSpan span) { + return span instanceof DDSpan && ((DDSpan) span).isFinished(); + } + + private void sortSpansByStart() { + writer.firstTrace().sort(Comparator.comparingLong(DDSpan::getStartTime)); + } +} diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/OTSpanTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/OTSpanTest.java new file mode 100644 index 00000000000..de4b33fa919 --- /dev/null +++ b/dd-trace-ot/src/test/java/datadog/opentracing/OTSpanTest.java @@ -0,0 +1,55 @@ +package datadog.opentracing; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import datadog.trace.api.interceptor.MutableSpan; +import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.Scope; +import io.opentracing.Span; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class OTSpanTest extends DDJavaSpecification { + + static DDTracer tracer; + + @BeforeAll + static void setUpClass() { + tracer = DDTracer.builder().build(); + } + + @AfterAll + static void tearDownClass() throws Exception { + if (tracer != null) { + tracer.close(); + } + } + + @Test + void testResourceNameAssignmentThroughMutableSpanCasting() { + OTSpan testSpan = (OTSpan) tracer.buildSpan("parent").withResourceName("test-resource").start(); + OTScopeManager.OTScope testScope = (OTScopeManager.OTScope) tracer.activateSpan(testSpan); + + Span active = tracer.activeSpan(); + Span child = tracer.buildSpan("child").asChildOf(active).start(); + Scope scope = tracer.activateSpan(child); + + MutableSpan localRootSpan = ((MutableSpan) child).getLocalRootSpan(); + localRootSpan.setResourceName("correct-resource"); + + assertEquals("correct-resource", testSpan.getResourceName()); + + testSpan + .getDelegate() + .setResourceName("should-be-ignored", ResourceNamePriorities.HTTP_FRAMEWORK_ROUTE); + + assertEquals("correct-resource", testSpan.getResourceName()); + + scope.close(); + child.finish(); + testScope.close(); + testSpan.finish(); + } +} diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/OpenTracingAPITest.java b/dd-trace-ot/src/test/java/datadog/opentracing/OpenTracingAPITest.java new file mode 100644 index 00000000000..090b134c490 --- /dev/null +++ b/dd-trace-ot/src/test/java/datadog/opentracing/OpenTracingAPITest.java @@ -0,0 +1,479 @@ +package datadog.opentracing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import datadog.trace.api.DDTags; +import datadog.trace.api.interceptor.MutableSpan; +import datadog.trace.api.interceptor.TraceInterceptor; +import datadog.trace.api.scopemanager.ScopeListener; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.context.TraceScope; +import datadog.trace.core.DDSpan; +import datadog.trace.junit.utils.config.WithConfig; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.SpanContext; +import io.opentracing.propagation.Format; +import io.opentracing.propagation.TextMapAdapter; +import io.opentracing.tag.Tags; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class OpenTracingAPITest extends DDJavaSpecification { + + ListWriter writer = new ListWriter(); + DDTracer tracer = DDTracer.builder().writer(writer).build(); + TraceInterceptor traceInterceptor = mock(TraceInterceptor.class); + ScopeListener scopeListener = mock(ScopeListener.class); + + @BeforeEach + void setup() { + assertNull(tracer.scopeManager().active()); + tracer.addTraceInterceptor(traceInterceptor); + tracer.getInternalTracer().addScopeListener(scopeListener); + // stub onTraceComplete to pass traces through (return first argument) + when(traceInterceptor.onTraceComplete(any())).thenAnswer(inv -> inv.getArgument(0)); + // clear interactions from setup (e.g. priority() called during addTraceInterceptor) + clearInvocations(traceInterceptor, scopeListener); + } + + @AfterEach + void tearDown() throws Exception { + tracer.close(); + } + + @Test + void tracerScopeManagerReturnsNullForNoActiveSpan() { + assertNull(tracer.activeSpan()); + assertNull(tracer.scopeManager().active()); + assertNull(tracer.scopeManager().activeSpan()); + } + + @Test + void singleSpan() throws Exception { + Scope scope = null; + try { + scope = tracer.buildSpan("someOperation").startActive(true); + scope.span().setTag(DDTags.SERVICE_NAME, "someService"); + } finally { + if (scope != null) scope.close(); + } + writer.waitForTraces(1); + + verify(traceInterceptor).onTraceComplete(any()); + + assertEquals(1, writer.size()); + List trace = writer.get(0); + assertEquals(1, trace.size()); + DDSpan span = trace.get(0); + assertEquals("someService", span.getServiceName()); + assertEquals("someOperation", span.getOperationName().toString()); + assertEquals("someOperation", span.getResourceName().toString()); + assertEquals("opentracing", span.getTags().get(DDTags.DD_INTEGRATION)); + } + + @Test + void spanWithBuilder() throws Exception { + Span testSpan = + tracer + .buildSpan("someOperation") + .withTag(Tags.COMPONENT, "opentracing") + .withTag("someBoolean", true) + .withTag("someNumber", 1) + .withTag(DDTags.SERVICE_NAME, "someService") + .start(); + + Scope scope = null; + try { + scope = tracer.activateSpan(testSpan); + testSpan.finish(); + } finally { + if (scope != null) scope.close(); + } + writer.waitForTraces(1); + + verify(traceInterceptor).onTraceComplete(any()); + assertTrue(testSpan instanceof MutableSpan); + + assertEquals(1, writer.size()); + List trace = writer.get(0); + assertEquals(1, trace.size()); + DDSpan span = trace.get(0); + assertEquals("someService", span.getServiceName()); + assertEquals("someOperation", span.getOperationName().toString()); + assertEquals("someOperation", span.getResourceName().toString()); + assertEquals("opentracing", span.getTags().get(Tags.COMPONENT.getKey())); + assertEquals("opentracing", span.getTags().get(DDTags.DD_INTEGRATION)); + assertEquals(true, span.getTags().get("someBoolean")); + assertEquals(1, span.getTags().get("someNumber")); + } + + @Test + void singleSpanWithManualStartFinish() throws Exception { + Span testSpan = tracer.buildSpan("someOperation").start(); + Scope scope = tracer.activateSpan(testSpan); + + verify(scopeListener).afterScopeActivated(); + assertTrue(testSpan instanceof MutableSpan); + assertTrue(scope.span() instanceof MutableSpan); + + testSpan.setTag(DDTags.SERVICE_NAME, "someService"); + testSpan.setTag(Tags.COMPONENT, "opentracing"); + testSpan.setTag("someBoolean", true); + testSpan.setTag("someNumber", 1); + testSpan.setOperationName("someOtherOperation"); + scope.close(); + testSpan.finish(); + writer.waitForTraces(1); + + verify(traceInterceptor).onTraceComplete(any()); + verify(scopeListener).afterScopeClosed(); + + assertEquals(1, writer.size()); + List trace = writer.get(0); + assertEquals(1, trace.size()); + DDSpan span = trace.get(0); + assertEquals("someService", span.getServiceName()); + assertEquals("someOtherOperation", span.getOperationName().toString()); + assertEquals("someOtherOperation", span.getResourceName().toString()); + assertEquals("opentracing", span.getTags().get(Tags.COMPONENT.getKey())); + assertEquals("opentracing", span.getTags().get(DDTags.DD_INTEGRATION)); + assertEquals(true, span.getTags().get("someBoolean")); + assertEquals(1, span.getTags().get("someNumber")); + } + + @Test + void spansAndScopesAllEqual() throws Exception { + Span testSpan = tracer.buildSpan("someOperation").start(); + Scope testScope = tracer.activateSpan(testSpan); + + Span traceActiveSpan = tracer.activeSpan(); + Span scopeManagerActiveSpan = tracer.scopeManager().activeSpan(); + Span scopeActiveSpan = testScope.span(); + + Scope scopeManagerActiveScope = tracer.scopeManager().active(); + testScope.close(); + testSpan.finish(); + writer.waitForTraces(1); + + verify(traceInterceptor).onTraceComplete(any()); + assertEquals(testSpan, traceActiveSpan); + assertEquals(testSpan.hashCode(), traceActiveSpan.hashCode()); + assertEquals(testSpan, scopeManagerActiveSpan); + assertEquals(testSpan.hashCode(), scopeManagerActiveSpan.hashCode()); + assertEquals(testSpan, scopeActiveSpan); + assertEquals(testSpan.hashCode(), scopeActiveSpan.hashCode()); + assertEquals(testScope, scopeManagerActiveScope); + assertEquals(testScope.hashCode(), scopeManagerActiveScope.hashCode()); + } + + @Test + void nestedSpans() throws Exception { + Scope scope = null; + try { + scope = tracer.buildSpan("someOperation").startActive(true); + scope.span().setTag(DDTags.SERVICE_NAME, "someService"); + + Scope scope2 = null; + try { + scope2 = tracer.buildSpan("someOperation2").startActive(true); + } finally { + if (scope2 != null) scope2.close(); + } + } finally { + if (scope != null) scope.close(); + } + writer.waitForTraces(1); + + verify(traceInterceptor).onTraceComplete(any()); + + assertEquals(1, writer.size()); + List trace = writer.get(0); + assertEquals(2, trace.size()); + DDSpan parentSpan = trace.get(0); + DDSpan childSpan = trace.get(1); + assertEquals("someService", parentSpan.getServiceName()); + assertEquals("someOperation", parentSpan.getOperationName().toString()); + assertEquals("someOperation", parentSpan.getResourceName().toString()); + assertEquals("someService", childSpan.getServiceName()); + assertEquals("someOperation2", childSpan.getOperationName().toString()); + assertEquals("someOperation2", childSpan.getResourceName().toString()); + assertEquals(parentSpan.getSpanId(), childSpan.getParentId()); + } + + @Test + void spanWithAsyncPropagation() throws Exception { + AgentTracer.TracerAPI internalTracer = tracer.getInternalTracer(); + + Scope scope = + tracer + .buildSpan("someOperation") + .withTag(DDTags.SERVICE_NAME, "someService") + .startActive(true); + internalTracer.setAsyncPropagationEnabled(false); + + assertTrue(scope instanceof TraceScope); + assertTrue(!internalTracer.isAsyncPropagationEnabled()); + + internalTracer.setAsyncPropagationEnabled(true); + TraceScope.Continuation continuation = ((TraceScope) scope).capture(); + + assertTrue(internalTracer.isAsyncPropagationEnabled()); + assertNotNull(continuation); + + continuation.cancel(); + scope.close(); + writer.waitForTraces(1); + + verify(traceInterceptor).onTraceComplete(any()); + + assertEquals(1, writer.size()); + List trace = writer.get(0); + assertEquals(1, trace.size()); + DDSpan span = trace.get(0); + assertEquals("someService", span.getServiceName()); + assertEquals("someOperation", span.getOperationName().toString()); + assertEquals("someOperation", span.getResourceName().toString()); + } + + @Test + void spanInheritsAsyncPropagation() throws Exception { + AgentTracer.TracerAPI internalTracer = tracer.getInternalTracer(); + + Scope outer = + tracer + .buildSpan("someOperation") + .withTag(DDTags.SERVICE_NAME, "someService") + .startActive(true); + internalTracer.setAsyncPropagationEnabled(false); + + assertTrue(!internalTracer.isAsyncPropagationEnabled()); + + internalTracer.setAsyncPropagationEnabled(true); + Scope inner = + tracer + .buildSpan("otherOperation") + .withTag(DDTags.SERVICE_NAME, "otherService") + .startActive(true); + + assertTrue(internalTracer.isAsyncPropagationEnabled()); + + inner.close(); + outer.close(); + writer.waitForTraces(1); + + verify(traceInterceptor).onTraceComplete(any()); + + assertEquals(1, writer.size()); + List trace = writer.get(0); + assertEquals(2, trace.size()); + DDSpan outerSpan = trace.get(0); + DDSpan innerSpan = trace.get(1); + assertEquals("someService", outerSpan.getServiceName()); + assertEquals("someOperation", outerSpan.getOperationName().toString()); + assertEquals("someOperation", outerSpan.getResourceName().toString()); + assertEquals("otherService", innerSpan.getServiceName()); + assertEquals("otherOperation", innerSpan.getOperationName().toString()); + assertEquals("otherOperation", innerSpan.getResourceName().toString()); + } + + @Test + void spanContextIdsEqualTracerIds() throws Exception { + Span testSpan = tracer.buildSpan("someOperation").withServiceName("someService").start(); + Scope scope = tracer.activateSpan(testSpan); + + verify(scopeListener).afterScopeActivated(); + + assertEquals(testSpan.context().toSpanId(), tracer.getSpanId()); + assertEquals( + testSpan.context().toTraceId(), + tracer.getInternalTracer().activeSpan().context().getTraceId().toString()); + + scope.close(); + testSpan.finish(); + writer.waitForTraces(1); + + verify(traceInterceptor).onTraceComplete(any()); + verify(scopeListener).afterScopeClosed(); + + assertEquals(1, writer.size()); + List trace = writer.get(0); + assertEquals(1, trace.size()); + DDSpan span = trace.get(0); + assertEquals("someService", span.getServiceName()); + assertEquals("someOperation", span.getOperationName().toString()); + assertEquals("someOperation", span.getResourceName().toString()); + } + + @Test + void closingScopeWhenNotOnTop() throws Exception { + Span firstSpan = tracer.buildSpan("someOperation").start(); + Scope firstScope = tracer.activateSpan(firstSpan); + Span secondSpan = tracer.buildSpan("someOperation").start(); + Scope secondScope = tracer.activateSpan(secondSpan); + firstSpan.finish(); + firstScope.close(); + + // then: 2 * scopeListener.afterScopeActivated(), 0 * _ + verify(scopeListener, times(2)).afterScopeActivated(); + verifyNoMoreInteractions(scopeListener, traceInterceptor); + clearInvocations(scopeListener, traceInterceptor); + + secondSpan.finish(); + secondScope.close(); + writer.waitForTraces(1); + + // then: 2 * scopeListener.afterScopeClosed(), 1 * traceInterceptor.onTraceComplete(...) + verify(scopeListener, times(2)).afterScopeClosed(); + verify(traceInterceptor).onTraceComplete(any()); + assertEquals(1, writer.size()); + assertEquals(2, writer.get(0).size()); + verifyNoMoreInteractions(scopeListener, traceInterceptor); + clearInvocations(scopeListener, traceInterceptor); + + firstScope.close(); + + // then: 0 * _ + verifyNoMoreInteractions(scopeListener, traceInterceptor); + } + + @Test + @WithConfig(key = "trace.scope.strict.mode", value = "true") + void closingScopeWhenNotOnTopInStrictMode() throws Exception { + DDTracer strictTracer = DDTracer.builder().writer(writer).build(); + strictTracer.addTraceInterceptor(traceInterceptor); + strictTracer.getInternalTracer().addScopeListener(scopeListener); + // clear priority() interaction from addTraceInterceptor + clearInvocations(traceInterceptor, scopeListener); + + Span firstSpan = strictTracer.buildSpan("someOperation").start(); + Scope firstScope = strictTracer.activateSpan(firstSpan); + Span secondSpan = strictTracer.buildSpan("someOperation").start(); + Scope secondScope = strictTracer.activateSpan(secondSpan); + + // then: 2 * scopeListener.afterScopeActivated(), 0 * _ + verify(scopeListener, times(2)).afterScopeActivated(); + verifyNoMoreInteractions(scopeListener, traceInterceptor); + clearInvocations(scopeListener, traceInterceptor); + + firstSpan.finish(); + assertThrows(RuntimeException.class, firstScope::close); + + // then: thrown(RuntimeException), 0 * _ + verifyNoMoreInteractions(scopeListener, traceInterceptor); + clearInvocations(scopeListener, traceInterceptor); + + secondSpan.finish(); + secondScope.close(); + writer.waitForTraces(1); + + // then: 1 * scopeListener.afterScopeClosed(), 1 * traceInterceptor.onTraceComplete(...) + // 1 * scopeListener.afterScopeActivated() (scope restoration after strict mode exception) + verify(scopeListener).afterScopeClosed(); + verify(traceInterceptor).onTraceComplete(any()); + assertEquals(1, writer.size()); + assertEquals(2, writer.get(0).size()); + verify(scopeListener).afterScopeActivated(); + verifyNoMoreInteractions(scopeListener, traceInterceptor); + clearInvocations(scopeListener, traceInterceptor); + + firstScope.close(); + + verify(scopeListener).afterScopeClosed(); + verifyNoMoreInteractions(scopeListener, traceInterceptor); + + strictTracer.close(); + } + + @Test + void injectAndExtractContext() throws Exception { + TextMapAdapter textMap = new TextMapAdapter(new HashMap()); + + Span testSpan = + tracer.buildSpan("clientOperation").withServiceName("someClientService").start(); + Scope scope = tracer.activateSpan(testSpan); + + tracer.inject(testSpan.context(), Format.Builtin.HTTP_HEADERS, textMap); + + SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, textMap); + Span serverSpan = + tracer + .buildSpan("serverOperation") + .withServiceName("someService") + .asChildOf(extractedContext) + .start(); + tracer.activateSpan(serverSpan).close(); + serverSpan.finish(); + + scope.close(); + testSpan.finish(); + writer.waitForTraces(2); + + verify(traceInterceptor, times(2)).onTraceComplete(any()); + assertEquals(extractedContext.toTraceId(), testSpan.context().toTraceId()); + assertEquals(extractedContext.toSpanId(), testSpan.context().toSpanId()); + + assertEquals(2, writer.size()); + // sort traces by start time to get deterministic order (client started first) + List> sortedTraces = new java.util.ArrayList<>(writer); + sortedTraces.sort(Comparator.comparingLong(t -> t.get(0).getStartTime())); + DDSpan clientSpan = sortedTraces.get(0).get(0); + DDSpan serverSpanDD = sortedTraces.get(1).get(0); + assertEquals("someClientService", clientSpan.getServiceName()); + assertEquals("clientOperation", clientSpan.getOperationName().toString()); + assertEquals("clientOperation", clientSpan.getResourceName().toString()); + assertEquals("someService", serverSpanDD.getServiceName()); + assertEquals("serverOperation", serverSpanDD.getOperationName().toString()); + assertEquals("serverOperation", serverSpanDD.getResourceName().toString()); + assertEquals(clientSpan.context().getSpanId(), serverSpanDD.getParentId()); + } + + @Test + void tolerateNullSpanActivation() throws Exception { + try { + Scope s = tracer.scopeManager().activate(null); + if (s != null) s.close(); + } catch (Exception ignored) { + } + + try { + Scope s = tracer.activateSpan(null); + if (s != null) s.close(); + } catch (Exception ignored) { + } + + // make sure scope stack has been left in a valid state + Span testSpan = tracer.buildSpan("someOperation").withServiceName("someService").start(); + Scope testScope = tracer.scopeManager().activate(testSpan); + testSpan.finish(); + testScope.close(); + writer.waitForTraces(1); + + verify(traceInterceptor).onTraceComplete(any()); + + assertEquals(1, writer.size()); + List trace = writer.get(0); + assertEquals(1, trace.size()); + DDSpan span = trace.get(0); + assertEquals("someService", span.getServiceName()); + assertEquals("someOperation", span.getOperationName().toString()); + assertEquals("someOperation", span.getResourceName().toString()); + } +} diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/SpanBuilderTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/SpanBuilderTest.java new file mode 100644 index 00000000000..47800466751 --- /dev/null +++ b/dd-trace-ot/src/test/java/datadog/opentracing/SpanBuilderTest.java @@ -0,0 +1,155 @@ +package datadog.opentracing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.DDSpanContext; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.Span; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class SpanBuilderTest extends DDJavaSpecification { + // TODO more io.opentracing.SpanBuilder specific tests + + ListWriter writer = new ListWriter(); + DDTracer tracer = DDTracer.builder().writer(writer).build(); + + @AfterEach + void cleanup() throws Exception { + if (tracer != null) { + tracer.close(); + } + } + + @Test + void shouldInheritDDParentAttributesAddReferenceChildOf() { + String expectedName = "fakeName"; + String expectedParentServiceName = "fakeServiceName"; + String expectedParentResourceName = "fakeResourceName"; + String expectedParentType = "fakeType"; + String expectedChildServiceName = "fakeServiceName-child"; + String expectedChildResourceName = "fakeResourceName-child"; + String expectedChildType = "fakeType-child"; + String expectedBaggageItemKey = "fakeKey"; + String expectedBaggageItemValue = "fakeValue"; + + Span parent = + tracer + .buildSpan(expectedName) + .withServiceName("foo") + .withResourceName(expectedParentResourceName) + .withSpanType(expectedParentType) + .start(); + + parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue); + + // ServiceName and SpanType are always set by the parent if they are not present in the child + OTSpan span = + (OTSpan) + tracer + .buildSpan(expectedName) + .withServiceName(expectedParentServiceName) + .addReference("child_of", parent.context()) + .start(); + + assertEquals(expectedName, span.getDelegate().getOperationName()); + assertEquals(expectedBaggageItemValue, span.getBaggageItem(expectedBaggageItemKey)); + assertEquals( + expectedParentServiceName, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getServiceName()); + assertEquals( + expectedName, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getResourceName()); + assertNull(((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getSpanType()); + + // ServiceName and SpanType are always overwritten by the child if they are present + span = + (OTSpan) + tracer + .buildSpan(expectedName) + .withServiceName(expectedChildServiceName) + .withResourceName(expectedChildResourceName) + .withSpanType(expectedChildType) + .addReference("child_of", parent.context()) + .start(); + + assertEquals(expectedName, span.getDelegate().getOperationName()); + assertEquals(expectedBaggageItemValue, span.getBaggageItem(expectedBaggageItemKey)); + assertEquals( + expectedChildServiceName, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getServiceName()); + assertEquals( + expectedChildResourceName, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getResourceName()); + assertEquals( + expectedChildType, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getSpanType()); + } + + @Test + void shouldInheritDDParentAttributesAddReferenceFollowsFrom() { + String expectedName = "fakeName"; + String expectedParentServiceName = "fakeServiceName"; + String expectedParentResourceName = "fakeResourceName"; + String expectedParentType = "fakeType"; + String expectedChildServiceName = "fakeServiceName-child"; + String expectedChildResourceName = "fakeResourceName-child"; + String expectedChildType = "fakeType-child"; + String expectedBaggageItemKey = "fakeKey"; + String expectedBaggageItemValue = "fakeValue"; + + Span parent = + tracer + .buildSpan(expectedName) + .withServiceName("foo") + .withResourceName(expectedParentResourceName) + .withSpanType(expectedParentType) + .start(); + + parent.setBaggageItem(expectedBaggageItemKey, expectedBaggageItemValue); + + // ServiceName and SpanType are always set by the parent if they are not present in the child + OTSpan span = + (OTSpan) + tracer + .buildSpan(expectedName) + .withServiceName(expectedParentServiceName) + .addReference("follows_from", parent.context()) + .start(); + + assertEquals(expectedName, span.getDelegate().getOperationName()); + assertEquals(expectedBaggageItemValue, span.getBaggageItem(expectedBaggageItemKey)); + assertEquals( + expectedParentServiceName, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getServiceName()); + assertEquals( + expectedName, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getResourceName()); + assertNull(((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getSpanType()); + + // ServiceName and SpanType are always overwritten by the child if they are present + span = + (OTSpan) + tracer + .buildSpan(expectedName) + .withServiceName(expectedChildServiceName) + .withResourceName(expectedChildResourceName) + .withSpanType(expectedChildType) + .addReference("follows_from", parent.context()) + .start(); + + assertEquals(expectedName, span.getDelegate().getOperationName()); + assertEquals(expectedBaggageItemValue, span.getBaggageItem(expectedBaggageItemKey)); + assertEquals( + expectedChildServiceName, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getServiceName()); + assertEquals( + expectedChildResourceName, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getResourceName()); + assertEquals( + expectedChildType, + ((DDSpanContext) ((OTSpanContext) span.context()).getDelegate()).getSpanType()); + } +} diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/TypeConverterTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/TypeConverterTest.java new file mode 100644 index 00000000000..839c1470792 --- /dev/null +++ b/dd-trace-ot/src/test/java/datadog/opentracing/TypeConverterTest.java @@ -0,0 +1,64 @@ +package datadog.opentracing; + +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopScope; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan; +import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpanContext; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.common.writer.ListWriter; +import datadog.trace.core.CoreTracer; +import datadog.trace.test.util.DDJavaSpecification; +import org.junit.jupiter.api.Test; + +class TypeConverterTest extends DDJavaSpecification { + + TypeConverter typeConverter = new TypeConverter(new DefaultLogHandler()); + + @Test + void shouldAvoidTheNoopSpanWrapperAllocation() { + AgentSpan noopAgentSpan = noopSpan(); + assertSame(typeConverter.toSpan(noopAgentSpan), typeConverter.toSpan(noopAgentSpan)); + } + + @Test + void shouldAvoidExtraAllocationForASpanWrapper() throws Exception { + CoreTracer testTracer = CoreTracer.builder().writer(new ListWriter()).build(); + try { + AgentSpan span1 = testTracer.buildSpan("datadog", "test").start(); + AgentSpan span2 = testTracer.buildSpan("datadog", "test").start(); + + // return the same wrapper for the same span + assertSame(typeConverter.toSpan(span1), typeConverter.toSpan(span1)); + // return a distinct wrapper for another span + assertNotSame(typeConverter.toSpan(span1), typeConverter.toSpan(span2)); + } finally { + testTracer.close(); + } + } + + @Test + void shouldAvoidTheNoopContextWrapperAllocation() { + datadog.trace.bootstrap.instrumentation.api.AgentSpanContext noopContext = noopSpanContext(); + assertSame(typeConverter.toSpanContext(noopContext), typeConverter.toSpanContext(noopContext)); + } + + @Test + void shouldAvoidTheNoopScopeWrapperAllocation() { + datadog.trace.bootstrap.instrumentation.api.AgentScope noopScopeInstance = noopScope(); + assertSame( + typeConverter.toScope(noopScopeInstance, true), + typeConverter.toScope(noopScopeInstance, true)); + assertSame( + typeConverter.toScope(noopScopeInstance, false), + typeConverter.toScope(noopScopeInstance, false)); + // noop scopes expected to be the same despite the finishSpanOnClose flag + assertSame( + typeConverter.toScope(noopScopeInstance, true), + typeConverter.toScope(noopScopeInstance, false)); + assertSame( + typeConverter.toScope(noopScopeInstance, false), + typeConverter.toScope(noopScopeInstance, true)); + } +} diff --git a/dd-trace-ot/src/test/java/datadog/opentracing/resolver/DDTracerResolverTest.java b/dd-trace-ot/src/test/java/datadog/opentracing/resolver/DDTracerResolverTest.java new file mode 100644 index 00000000000..21ee38ba2eb --- /dev/null +++ b/dd-trace-ot/src/test/java/datadog/opentracing/resolver/DDTracerResolverTest.java @@ -0,0 +1,28 @@ +package datadog.opentracing.resolver; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; + +import datadog.opentracing.DDTracer; +import datadog.trace.junit.utils.config.WithConfig; +import datadog.trace.test.util.DDJavaSpecification; +import io.opentracing.contrib.tracerresolver.TracerResolver; +import org.junit.jupiter.api.Test; + +class DDTracerResolverTest extends DDJavaSpecification { + + DDTracerResolver resolver = new DDTracerResolver(); + + @Test + void testResolveTracer() throws Exception { + io.opentracing.Tracer tracer = TracerResolver.resolveTracer(); + assertInstanceOf(DDTracer.class, tracer); + tracer.close(); + } + + @Test + @WithConfig(key = "trace.resolver.enabled", value = "false") + void testDisableDDTracerResolver() { + assertNull(resolver.resolve()); + } +}