diff --git a/opentracing-api/src/main/java/io/opentracing/References.java b/opentracing-api/src/main/java/io/opentracing/References.java index 56419588..57cf26e9 100644 --- a/opentracing-api/src/main/java/io/opentracing/References.java +++ b/opentracing-api/src/main/java/io/opentracing/References.java @@ -32,4 +32,9 @@ private References(){} * See http://opentracing.io/spec/#causal-span-references for more information about FOLLOWS_FROM references */ public static final String FOLLOWS_FROM = "follows_from"; + + /** + * The self reference class can be used to construct a {@link Span} instance with a specific {@link SpanContext}. + */ + public static final String SELF = "self"; } diff --git a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java index 40a2597a..9e988f43 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java @@ -285,27 +285,43 @@ public int hashCode() { this.references = new ArrayList<>(refs); } MockContext parent = findPreferredParentRef(this.references); + MockContext self = findFirstReference(this.references, References.SELF); if (parent == null) { // We're a root Span. - this.context = new MockContext(nextId(), nextId(), new HashMap()); + this.context = new MockContext(self != null ? self.traceId : nextId(), self != null ? self.spanId : nextId(), + self != null ? self.baggage : new HashMap()); this.parentId = 0; } else { // We're a child Span. - this.context = new MockContext(parent.traceId, nextId(), mergeBaggages(this.references)); + this.context = new MockContext(self != null ? self.traceId : parent.traceId, self != null ? self.spanId : nextId(), + mergeBaggages(this.references)); this.parentId = parent.spanId; } } private static MockContext findPreferredParentRef(List references) { - if(references.isEmpty()) { - return null; + MockContext firstChildOf = findFirstReference(references, References.CHILD_OF); + if (firstChildOf == null) { + // return first non self reference + // self references can never be a parent reference + for (Reference reference : references) { + if (!References.SELF.equals(reference.getReferenceType())) { + return reference.getContext(); + } + } + } else { + return firstChildOf; } + return null; + } + + private static MockContext findFirstReference(List references, String referenceToFind) { for (Reference reference : references) { - if (References.CHILD_OF.equals(reference.getReferenceType())) { + if (referenceToFind.equals(reference.getReferenceType())) { return reference.getContext(); } } - return references.get(0).getContext(); + return null; } private static Map mergeBaggages(List references) { diff --git a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java index 13132f46..bb88790f 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java @@ -112,10 +112,12 @@ public MockSpan.MockContext extract(Format format, C carrier) { } }; - Propagator TEXT_MAP = new Propagator() { - public static final String SPAN_ID_KEY = "spanid"; - public static final String TRACE_ID_KEY = "traceid"; - public static final String BAGGAGE_KEY_PREFIX = "baggage-"; + Propagator TEXT_MAP = new TextMapPropagator(); + + class TextMapPropagator implements Propagator { + static final String SPAN_ID_KEY = "spanid"; + static final String TRACE_ID_KEY = "traceid"; + static final String BAGGAGE_KEY_PREFIX = "baggage-"; @Override public void inject(MockSpan.MockContext ctx, Format format, C carrier) { @@ -159,7 +161,7 @@ public MockSpan.MockContext extract(Format format, C carrier) { return null; } - }; + } } @Override diff --git a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java index 2209837a..74974746 100644 --- a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java +++ b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java @@ -13,6 +13,9 @@ */ package io.opentracing.mock; +import static io.opentracing.mock.MockTracer.Propagator.TextMapPropagator.BAGGAGE_KEY_PREFIX; +import static io.opentracing.mock.MockTracer.Propagator.TextMapPropagator.SPAN_ID_KEY; +import static io.opentracing.mock.MockTracer.Propagator.TextMapPropagator.TRACE_ID_KEY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -201,7 +204,7 @@ public void testTextMapPropagatorHttpHeaders() { Assert.assertEquals(finishedSpans.get(0).context().traceId(), finishedSpans.get(1).context().traceId()); Assert.assertEquals(finishedSpans.get(0).context().spanId(), finishedSpans.get(1).parentId()); } - + @Test public void testActiveSpan() { MockTracer mockTracer = new MockTracer(); @@ -319,4 +322,54 @@ public void testChildOfWithNullParentDoesNotThrowException() { Span span = tracer.buildSpan("foo").asChildOf(parent).start(); span.finish(); } + + @Test + public void testSelfReferenceWithParent() { + MockTracer mockTracer = new MockTracer(MockTracer.Propagator.TEXT_MAP); + + final HashMap selfIds = new HashMap<>(); + selfIds.put(TRACE_ID_KEY, "40"); + selfIds.put(SPAN_ID_KEY, "42"); + selfIds.put(BAGGAGE_KEY_PREFIX + "foo", "bar"); + final HashMap parentSpanId = new HashMap<>(); + parentSpanId.put(TRACE_ID_KEY, "40"); + parentSpanId.put(SPAN_ID_KEY, "41"); + + mockTracer.buildSpan("foo") + .addReference(References.SELF, mockTracer.extract(Format.Builtin.TEXT_MAP, new TextMapExtractAdapter(selfIds))) + .addReference(References.CHILD_OF, mockTracer.extract(Format.Builtin.TEXT_MAP, new TextMapExtractAdapter(parentSpanId))) + .startManual() + .finish(); + + assertEquals(1, mockTracer.finishedSpans().size()); + assertEquals(40, mockTracer.finishedSpans().get(0).context().traceId()); + assertEquals(42, mockTracer.finishedSpans().get(0).context().spanId()); + final Map.Entry firstBaggageItem = mockTracer.finishedSpans().get(0).context().baggageItems().iterator().next(); + assertEquals("foo", firstBaggageItem.getKey()); + assertEquals("bar", firstBaggageItem.getValue()); + assertEquals(41, mockTracer.finishedSpans().get(0).parentId()); + } + + @Test + public void testSelfReferenceWithoutParent() { + MockTracer mockTracer = new MockTracer(MockTracer.Propagator.TEXT_MAP); + + final HashMap selfIds = new HashMap<>(); + selfIds.put(TRACE_ID_KEY, "40"); + selfIds.put(SPAN_ID_KEY, "42"); + selfIds.put(BAGGAGE_KEY_PREFIX + "foo", "bar"); + + mockTracer.buildSpan("foo") + .addReference(References.SELF, mockTracer.extract(Format.Builtin.TEXT_MAP, new TextMapExtractAdapter(selfIds))) + .startManual() + .finish(); + + assertEquals(1, mockTracer.finishedSpans().size()); + assertEquals(40, mockTracer.finishedSpans().get(0).context().traceId()); + assertEquals(42, mockTracer.finishedSpans().get(0).context().spanId()); + final Map.Entry firstBaggageItem = mockTracer.finishedSpans().get(0).context().baggageItems().iterator().next(); + assertEquals("foo", firstBaggageItem.getKey()); + assertEquals("bar", firstBaggageItem.getValue()); + assertEquals(0, mockTracer.finishedSpans().get(0).parentId()); + } }