Skip to content

Commit c1e2106

Browse files
committed
Add test to check multiple traces can be marshalled into a single payload
1 parent 66fe66c commit c1e2106

1 file changed

Lines changed: 109 additions & 0 deletions

File tree

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

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.Map;
4141
import java.util.Set;
4242
import java.util.stream.Stream;
43+
import org.junit.jupiter.api.Test;
4344
import org.junit.jupiter.params.ParameterizedTest;
4445
import org.junit.jupiter.params.provider.Arguments;
4546
import org.junit.jupiter.params.provider.MethodSource;
@@ -621,6 +622,114 @@ void testCollectTraces(String caseName, List<SpanSpec> specs) throws IOException
621622
}
622623
}
623624

625+
@Test
626+
void testCollectMultipleTraces() throws IOException {
627+
// Three independent traces — each root span gets its own auto-generated trace ID.
628+
List<DDSpan> trace1 =
629+
buildSpans(asList(span("trace1.root", "op.root", "web"), childSpan("trace1.child", 0)));
630+
List<DDSpan> trace2 = buildSpans(asList(span("trace2.root", "op.root", "db")));
631+
List<DDSpan> trace3 =
632+
buildSpans(
633+
asList(
634+
span("trace3.a", "op.a", "web"),
635+
span("trace3.b", "op.b", "web"),
636+
span("trace3.c", "op.c", "web")));
637+
638+
// Sanity: all three traces must have distinct trace IDs.
639+
DDTraceId traceId1 = trace1.get(0).getTraceId();
640+
DDTraceId traceId2 = trace2.get(0).getTraceId();
641+
DDTraceId traceId3 = trace3.get(0).getTraceId();
642+
assertNotEquals(traceId1, traceId2, "trace IDs must be distinct");
643+
assertNotEquals(traceId2, traceId3, "trace IDs must be distinct");
644+
assertNotEquals(traceId1, traceId3, "trace IDs must be distinct");
645+
646+
OtlpTraceProtoCollector.INSTANCE.addTrace(trace1);
647+
OtlpTraceProtoCollector.INSTANCE.addTrace(trace2);
648+
OtlpTraceProtoCollector.INSTANCE.addTrace(trace3);
649+
OtlpPayload payload = OtlpTraceProtoCollector.INSTANCE.collectTraces();
650+
651+
// Collect all span IDs we expect to find across all three traces.
652+
Set<Long> expectedSpanIds = new HashSet<>();
653+
Set<Long> expectedTraceIds = new HashSet<>();
654+
for (DDSpan s : trace1) {
655+
expectedSpanIds.add(s.getSpanId());
656+
expectedTraceIds.add(s.getTraceId().toLong());
657+
}
658+
for (DDSpan s : trace2) {
659+
expectedSpanIds.add(s.getSpanId());
660+
expectedTraceIds.add(s.getTraceId().toLong());
661+
}
662+
for (DDSpan s : trace3) {
663+
expectedSpanIds.add(s.getSpanId());
664+
expectedTraceIds.add(s.getTraceId().toLong());
665+
}
666+
int totalSpans = trace1.size() + trace2.size() + trace3.size(); // 6
667+
668+
ByteArrayOutputStream baos = new ByteArrayOutputStream(payload.getContentLength());
669+
payload.drain(baos::write);
670+
byte[] bytes = baos.toByteArray();
671+
assertTrue(bytes.length > 0, "multi-trace payload must be non-empty");
672+
673+
// Parse TracesData → ResourceSpans → ScopeSpans → extract span_id and trace_id per span.
674+
CodedInputStream td = CodedInputStream.newInstance(bytes);
675+
int tdTag = td.readTag();
676+
assertEquals(1, WireFormat.getTagFieldNumber(tdTag), "TracesData.resource_spans is field 1");
677+
assertEquals(WireFormat.WIRETYPE_LENGTH_DELIMITED, WireFormat.getTagWireType(tdTag));
678+
CodedInputStream rs = td.readBytes().newCodedInput();
679+
assertTrue(td.isAtEnd(), "expected exactly one ResourceSpans");
680+
681+
CodedInputStream ss = null;
682+
while (!rs.isAtEnd()) {
683+
int rsTag = rs.readTag();
684+
if (WireFormat.getTagFieldNumber(rsTag) == 2) {
685+
ss = rs.readBytes().newCodedInput();
686+
} else {
687+
rs.skipField(rsTag);
688+
}
689+
}
690+
assertNotNull(ss, "ScopeSpans must be present in ResourceSpans");
691+
692+
Set<Long> parsedSpanIds = new HashSet<>();
693+
Set<Long> parsedTraceIds = new HashSet<>();
694+
while (!ss.isAtEnd()) {
695+
int ssTag = ss.readTag();
696+
if (WireFormat.getTagFieldNumber(ssTag) == 2) {
697+
CodedInputStream sp = ss.readBytes().newCodedInput();
698+
byte[] parsedTraceId = null;
699+
byte[] parsedSpanId = null;
700+
while (!sp.isAtEnd()) {
701+
int spTag = sp.readTag();
702+
switch (WireFormat.getTagFieldNumber(spTag)) {
703+
case 1:
704+
parsedTraceId = sp.readBytes().toByteArray();
705+
break;
706+
case 2:
707+
parsedSpanId = sp.readBytes().toByteArray();
708+
break;
709+
default:
710+
sp.skipField(spTag);
711+
}
712+
}
713+
assertNotNull(parsedSpanId, "span_id must be present in every span");
714+
assertNotNull(parsedTraceId, "trace_id must be present in every span");
715+
assertEquals(16, parsedTraceId.length, "trace_id must be 16 bytes");
716+
assertEquals(8, parsedSpanId.length, "span_id must be 8 bytes");
717+
parsedSpanIds.add(readLittleEndianLong(parsedSpanId));
718+
parsedTraceIds.add(readLittleEndianLong(parsedTraceId));
719+
} else {
720+
ss.skipField(ssTag);
721+
}
722+
}
723+
724+
assertEquals(
725+
totalSpans, parsedSpanIds.size(), "all spans from all traces must appear in payload");
726+
assertEquals(expectedSpanIds, parsedSpanIds, "span IDs in payload must match those built");
727+
assertEquals(
728+
expectedTraceIds.size(),
729+
parsedTraceIds.size(),
730+
"payload must contain spans with all three distinct trace IDs");
731+
}
732+
624733
// ── span construction ─────────────────────────────────────────────────────
625734

626735
/** Builds {@link DDSpan} instances from the given specs, collecting them in order. */

0 commit comments

Comments
 (0)