Skip to content

Commit 4adb56e

Browse files
committed
Extend test to check link tracestate, traceflags, and span origin
1 parent 8f198b7 commit 4adb56e

File tree

1 file changed

+123
-18
lines changed

1 file changed

+123
-18
lines changed

dd-trace-core/src/test/java/datadog/trace/core/otlp/trace/OtlpTraceProtoTest.java

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_PRODUCER;
77
import static datadog.trace.bootstrap.instrumentation.api.Tags.SPAN_KIND_SERVER;
88
import static java.util.Arrays.asList;
9+
import static java.util.Arrays.copyOfRange;
910
import static java.util.Collections.emptyList;
1011
import static org.junit.jupiter.api.Assertions.assertEquals;
1112
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -17,6 +18,7 @@
1718
import com.google.protobuf.CodedInputStream;
1819
import com.google.protobuf.WireFormat;
1920
import datadog.trace.api.DD128bTraceId;
21+
import datadog.trace.api.DDTraceId;
2022
import datadog.trace.api.TracePropagationStyle;
2123
import datadog.trace.api.sampling.PrioritySampling;
2224
import datadog.trace.api.sampling.SamplingMechanism;
@@ -130,6 +132,9 @@ static final class SpanSpec {
130132
*/
131133
boolean use128BitTraceId;
132134

135+
/** Trace origin carried in the extracted parent context; {@code null} = no origin. */
136+
String origin;
137+
133138
SpanSpec(
134139
String resourceName,
135140
String operationName,
@@ -173,20 +178,40 @@ SpanSpec use128BitTraceId() {
173178
this.use128BitTraceId = true;
174179
return this;
175180
}
181+
182+
/**
183+
* Sets the origin propagated via an {@link ExtractedContext} parent so that {@code
184+
* metadata.getOrigin()} is non-null and the {@code _dd.origin} attribute is written.
185+
*/
186+
SpanSpec origin(String origin) {
187+
this.origin = origin;
188+
return this;
189+
}
176190
}
177191

178-
/** Descriptor for a single span link: the index of the target span and optional attributes. */
192+
/**
193+
* Descriptor for a single span link: target span index, optional attributes, tracestate, and
194+
* flags.
195+
*/
179196
static final class LinkSpec {
180197
final int targetIndex;
181198
final SpanAttributes attributes;
199+
final String traceState;
200+
final byte traceFlags;
182201

183202
LinkSpec(int targetIndex) {
184-
this(targetIndex, SpanAttributes.EMPTY);
203+
this(targetIndex, SpanAttributes.EMPTY, "", SpanLink.DEFAULT_FLAGS);
185204
}
186205

187206
LinkSpec(int targetIndex, SpanAttributes attributes) {
207+
this(targetIndex, attributes, "", SpanLink.DEFAULT_FLAGS);
208+
}
209+
210+
LinkSpec(int targetIndex, SpanAttributes attributes, String traceState, byte traceFlags) {
188211
this.targetIndex = targetIndex;
189212
this.attributes = attributes;
213+
this.traceState = traceState;
214+
this.traceFlags = traceFlags;
190215
}
191216
}
192217

@@ -195,6 +220,13 @@ static final class LinkSpec {
195220
private static final long BASE_MICROS = 1_700_000_000_000_000L;
196221
private static final long DURATION_MICROS = 500_000L; // 500 ms
197222

223+
/**
224+
* A known 128-bit trace ID used by {@link SpanSpec#use128BitTraceId} test cases. High-order bits
225+
* are non-zero so the test can assert the proto encodes them correctly.
226+
*/
227+
static final DD128bTraceId TRACE_ID_128BIT =
228+
DD128bTraceId.from(0x0123456789abcdefL, 0xfedcba9876543210L);
229+
198230
private static SpanSpec span(String resourceName, String operationName, String spanType) {
199231
return new SpanSpec(
200232
resourceName,
@@ -351,6 +383,44 @@ private static SpanSpec linkedSpanWithAttrs(
351383
new LinkSpec(targetIndex, attributes));
352384
}
353385

386+
/** A span with one {@link SpanLink} carrying the given W3C tracestate string. */
387+
private static SpanSpec linkedSpanWithTracestate(
388+
String resourceName, int targetIndex, String traceState) {
389+
return new SpanSpec(
390+
resourceName,
391+
"op.linked",
392+
"web",
393+
null,
394+
BASE_MICROS,
395+
BASE_MICROS + DURATION_MICROS,
396+
false,
397+
null,
398+
0,
399+
null,
400+
new HashMap<>(),
401+
-1,
402+
new LinkSpec(targetIndex, SpanAttributes.EMPTY, traceState, SpanLink.DEFAULT_FLAGS));
403+
}
404+
405+
/** A span with one {@link SpanLink} carrying the given trace flags. */
406+
private static SpanSpec linkedSpanWithFlags(
407+
String resourceName, int targetIndex, byte traceFlags) {
408+
return new SpanSpec(
409+
resourceName,
410+
"op.linked",
411+
"web",
412+
null,
413+
BASE_MICROS,
414+
BASE_MICROS + DURATION_MICROS,
415+
false,
416+
null,
417+
0,
418+
null,
419+
new HashMap<>(),
420+
-1,
421+
new LinkSpec(targetIndex, SpanAttributes.EMPTY, "", traceFlags));
422+
}
423+
354424
private static Map<String, Object> tags(Object... keyValues) {
355425
Map<String, Object> map = new HashMap<>();
356426
for (int i = 0; i < keyValues.length; i += 2) {
@@ -441,6 +511,16 @@ static Stream<Arguments> cases() {
441511
"attr.linked",
442512
0,
443513
SpanAttributes.builder().put("link.source", "test").build()))),
514+
Arguments.of(
515+
"span link with tracestate — Link.trace_state field written",
516+
asList(
517+
span("anchor.op", "anchor.op", "web"),
518+
linkedSpanWithTracestate("tracestate.linked", 0, "vendor=abc;p=123"))),
519+
Arguments.of(
520+
"span link with non-default flags — extra flag bit preserved alongside SAMPLED",
521+
asList(
522+
span("anchor.op", "anchor.op", "web"),
523+
linkedSpanWithFlags("flags.linked", 0, (byte) 0x02))),
444524

445525
// ── metadata paths ────────────────────────────────────────────────────
446526
Arguments.of(
@@ -449,6 +529,9 @@ static Stream<Arguments> cases() {
449529
Arguments.of(
450530
"span with http status code — http.status_code written via setHttpStatusCode",
451531
asList(span("GET /resource", "servlet.request", "web").httpStatusCode(404))),
532+
Arguments.of(
533+
"span with origin — _dd.origin attribute written",
534+
asList(span("GET /api", "servlet.request", "web").origin("rum"))),
452535
Arguments.of(
453536
"span with 128-bit trace ID — high-order trace_id bytes non-zero",
454537
asList(span("GET /api", "servlet.request", "web").use128BitTraceId())),
@@ -538,13 +621,6 @@ void testCollectSpans(String caseName, List<SpanSpec> specs) throws IOException
538621

539622
// ── span construction ─────────────────────────────────────────────────────
540623

541-
/**
542-
* A known 128-bit trace ID used by {@link SpanSpec#use128BitTraceId} test cases. High-order bits
543-
* are non-zero so the test can assert the proto encodes them correctly.
544-
*/
545-
static final DD128bTraceId TRACE_ID_128BIT =
546-
DD128bTraceId.from(0x0123456789abcdefL, 0xfedcba9876543210L);
547-
548624
/** Builds {@link DDSpan} instances from the given specs, collecting them in order. */
549625
private static List<DDSpan> buildSpans(List<SpanSpec> specs) {
550626
List<DDSpan> spans = new ArrayList<>(specs.size());
@@ -560,6 +636,17 @@ private static List<DDSpan> buildSpans(List<SpanSpec> specs) {
560636
PropagationTags.factory().empty(),
561637
TracePropagationStyle.DATADOG);
562638
agentSpan = TRACER.startSpan("test", spec.operationName, parent128, spec.startMicros);
639+
} else if (spec.origin != null) {
640+
ExtractedContext parentWithOrigin =
641+
new ExtractedContext(
642+
DDTraceId.ONE,
643+
0L,
644+
PrioritySampling.UNSET,
645+
spec.origin,
646+
PropagationTags.factory().empty(),
647+
TracePropagationStyle.DATADOG);
648+
agentSpan =
649+
TRACER.startSpan("test", spec.operationName, parentWithOrigin, spec.startMicros);
563650
} else if (spec.parentIndex >= 0) {
564651
agentSpan =
565652
TRACER.startSpan(
@@ -606,13 +693,11 @@ private static List<DDSpan> buildSpans(List<SpanSpec> specs) {
606693

607694
for (LinkSpec link : spec.links) {
608695
agentSpan.addLink(
609-
link.attributes.isEmpty()
610-
? SpanLink.from(spans.get(link.targetIndex).context())
611-
: SpanLink.from(
612-
spans.get(link.targetIndex).context(),
613-
SpanLink.DEFAULT_FLAGS,
614-
"",
615-
link.attributes));
696+
SpanLink.from(
697+
spans.get(link.targetIndex).context(),
698+
link.traceFlags,
699+
link.traceState,
700+
link.attributes));
616701
}
617702

618703
agentSpan.finish(spec.finishMicros);
@@ -760,8 +845,7 @@ private static void verifySpan(CodedInputStream sp, DDSpan span, SpanSpec spec,
760845
assertEquals(16, parsedTraceId.length, "trace_id must be 16 bytes [" + caseName + "]");
761846
if (spec.use128BitTraceId) {
762847
// high-order bytes occupy parsedTraceId[8..15] (little-endian in the wire format)
763-
long highOrderBytes =
764-
readLittleEndianLong(java.util.Arrays.copyOfRange(parsedTraceId, 8, 16));
848+
long highOrderBytes = readLittleEndianLong(copyOfRange(parsedTraceId, 8, 16));
765849
assertNotEquals(
766850
0L,
767851
highOrderBytes,
@@ -859,6 +943,11 @@ private static void verifySpan(CodedInputStream sp, DDSpan span, SpanSpec spec,
859943
+ caseName
860944
+ "]");
861945
}
946+
if (spec.origin != null) {
947+
assertTrue(
948+
attrKeys.contains("_dd.origin"),
949+
"attributes must include '_dd.origin' when origin is set [" + caseName + "]");
950+
}
862951

863952
// ── status (field 15) ─────────────────────────────────────────────────────
864953
if (spec.error) {
@@ -892,7 +981,9 @@ private static void verifyLink(CodedInputStream link, LinkSpec linkSpec, String
892981
throws IOException {
893982
byte[] traceId = null;
894983
byte[] spanId = null;
984+
String parsedTraceState = null;
895985
Set<String> linkAttrKeys = new HashSet<>();
986+
int parsedFlags = 0;
896987
while (!link.isAtEnd()) {
897988
int tag = link.readTag();
898989
switch (WireFormat.getTagFieldNumber(tag)) {
@@ -902,9 +993,15 @@ private static void verifyLink(CodedInputStream link, LinkSpec linkSpec, String
902993
case 2:
903994
spanId = link.readBytes().toByteArray();
904995
break;
996+
case 3:
997+
parsedTraceState = link.readString();
998+
break;
905999
case 4:
9061000
linkAttrKeys.add(readKeyValueKey(link.readBytes().newCodedInput()));
9071001
break;
1002+
case 6:
1003+
parsedFlags = link.readFixed32();
1004+
break;
9081005
default:
9091006
link.skipField(tag);
9101007
}
@@ -913,6 +1010,14 @@ private static void verifyLink(CodedInputStream link, LinkSpec linkSpec, String
9131010
assertEquals(16, traceId.length, "Link.trace_id must be 16 bytes [" + caseName + "]");
9141011
assertNotNull(spanId, "Link.span_id must be present [" + caseName + "]");
9151012
assertEquals(8, spanId.length, "Link.span_id must be 8 bytes [" + caseName + "]");
1013+
if (!linkSpec.traceState.isEmpty()) {
1014+
assertEquals(
1015+
linkSpec.traceState, parsedTraceState, "Link.trace_state mismatch [" + caseName + "]");
1016+
}
1017+
// SpanLink.from() ORs in the SAMPLED_FLAG (0x01) when the target context has positive
1018+
// sampling priority, which all test anchor spans have via the default tracer sampler.
1019+
int expectedFlags = Byte.toUnsignedInt((byte) (linkSpec.traceFlags | 0x01));
1020+
assertEquals(expectedFlags, parsedFlags, "Link.flags mismatch [" + caseName + "]");
9161021
for (String expectedKey : linkSpec.attributes.asMap().keySet()) {
9171022
assertTrue(
9181023
linkAttrKeys.contains(expectedKey),

0 commit comments

Comments
 (0)