diff --git a/opentracing-api/src/main/java/io/opentracing/References.java b/opentracing-api/src/main/java/io/opentracing/References.java index 89f87b65..ba509342 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 859513aa..518b8d29 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java @@ -18,7 +18,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; import io.opentracing.Span; import io.opentracing.SpanContext; @@ -30,12 +29,8 @@ * @see MockTracer#finishedSpans() */ public final class MockSpan implements Span { - // A simple-as-possible (consecutive for repeatability) id generator. - private static AtomicLong nextId = new AtomicLong(0); - private final MockTracer mockTracer; private MockContext context; - private final long parentId; // 0 if there's no parent. private final long startMicros; private boolean finished; private long finishMicros; @@ -64,7 +59,7 @@ public MockSpan setOperationName(String operationName) { * @see MockContext#spanId() */ public long parentId() { - return parentId; + return context().parentId(); } public long startMicros() { return startMicros; @@ -180,6 +175,12 @@ public static final class MockContext implements SpanContext { private final long traceId; private final Map baggage; private final long spanId; + /** + * 0 if there's no parent. + * + * TODO: Support multiple parents in this API. + */ + private final long parentId; /** * A package-protected constructor to create a new MockContext. This should only be called by MockSpan and/or @@ -189,7 +190,8 @@ public static final class MockContext implements SpanContext { * * @see MockContext#withBaggageItem(String, String) */ - public MockContext(long traceId, long spanId, Map baggage) { + MockContext(long parentId, long traceId, long spanId, Map baggage) { + this.parentId = parentId; this.baggage = baggage; this.traceId = traceId; this.spanId = spanId; @@ -198,6 +200,8 @@ public MockContext(long traceId, long spanId, Map baggage) { public String getBaggageItem(String key) { return this.baggage.get(key); } public long traceId() { return traceId; } public long spanId() { return spanId; } + public long parentId() { return parentId; } + Map baggage() { return baggage; } /** * Create and return a new (immutable) MockContext with the added baggage item. @@ -205,7 +209,7 @@ public MockContext(long traceId, long spanId, Map baggage) { public MockContext withBaggageItem(String key, String val) { Map newBaggage = new HashMap<>(this.baggage); newBaggage.put(key, val); - return new MockContext(this.traceId, this.spanId, newBaggage); + return new MockContext(this.parentId, this.traceId, this.spanId, newBaggage); } @Override @@ -232,7 +236,7 @@ public long timestampMicros() { } } - MockSpan(MockTracer tracer, String operationName, long startMicros, Map initialTags, MockContext parent) { + MockSpan(MockTracer tracer, String operationName, long startMicros, Map initialTags, MockContext context) { this.mockTracer = tracer; this.operationName = operationName; this.startMicros = startMicros; @@ -241,19 +245,7 @@ public long timestampMicros() { } else { this.tags = new HashMap<>(initialTags); } - if (parent == null) { - // We're a root Span. - this.context = new MockContext(nextId(), nextId(), new HashMap()); - this.parentId = 0; - } else { - // We're a child Span. - this.context = new MockContext(parent.traceId, nextId(), parent.baggage); - this.parentId = parent.spanId; - } - } - - static long nextId() { - return nextId.addAndGet(1); + this.context = context; } static long nowMicros() { @@ -273,7 +265,7 @@ public String toString() { return "{" + "traceId:" + context.traceId() + ", spanId:" + context.spanId() + - ", parentId:" + parentId + + ", parentId:" + context.parentId() + ", operationName:\"" + operationName + "\"}"; } } 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 b0546f46..5fa7791b 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java @@ -13,22 +13,23 @@ */ package io.opentracing.mock; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import io.opentracing.References; import io.opentracing.Scope; import io.opentracing.ScopeManager; import io.opentracing.Span; -import io.opentracing.noop.NoopScopeManager; -import io.opentracing.References; import io.opentracing.SpanContext; import io.opentracing.Tracer; +import io.opentracing.noop.NoopScopeManager; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMap; import io.opentracing.util.ThreadLocalScopeManager; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - /** * MockTracer makes it easy to test the semantics of OpenTracing instrumentation. * @@ -38,6 +39,9 @@ * The MockTracerTest has simple usage examples. */ public class MockTracer implements Tracer { + // A simple-as-possible (consecutive for repeatability) id generator. + private static AtomicLong nextId = new AtomicLong(0); + private List finishedSpans = new ArrayList<>(); private final Propagator propagator; private ScopeManager scopeManager; @@ -115,6 +119,7 @@ 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 PARENT_ID_KEY = "parentid"; public static final String BAGGAGE_KEY_PREFIX = "baggage-"; @Override @@ -126,6 +131,9 @@ public void inject(MockSpan.MockContext ctx, Format format, C carrier) { } textMap.put(SPAN_ID_KEY, String.valueOf(ctx.spanId())); textMap.put(TRACE_ID_KEY, String.valueOf(ctx.traceId())); + if (ctx.traceId() != 0) { + textMap.put(PARENT_ID_KEY, String.valueOf(ctx.traceId())); + } } else { throw new IllegalArgumentException("Unknown carrier"); } @@ -135,6 +143,7 @@ public void inject(MockSpan.MockContext ctx, Format format, C carrier) { public MockSpan.MockContext extract(Format format, C carrier) { Long traceId = null; Long spanId = null; + long parentId = 0; Map baggage = new HashMap<>(); if (carrier instanceof TextMap) { @@ -144,6 +153,8 @@ public MockSpan.MockContext extract(Format format, C carrier) { traceId = Long.valueOf(entry.getValue()); } else if (SPAN_ID_KEY.equals(entry.getKey())) { spanId = Long.valueOf(entry.getValue()); + } else if (PARENT_ID_KEY.equals(entry.getKey())) { + parentId = Long.valueOf(entry.getValue()); } else if (entry.getKey().startsWith(BAGGAGE_KEY_PREFIX)){ String key = entry.getKey().substring((BAGGAGE_KEY_PREFIX.length())); baggage.put(key, entry.getValue()); @@ -154,7 +165,7 @@ public MockSpan.MockContext extract(Format format, C carrier) { } if (traceId != null && spanId != null) { - return new MockSpan.MockContext(traceId, spanId, baggage); + return new MockSpan.MockContext(parentId, traceId, spanId, baggage); } return null; @@ -193,6 +204,7 @@ public final class SpanBuilder implements Tracer.SpanBuilder { private MockSpan.MockContext firstParent; private boolean ignoringActiveSpan; private Map initialTags = new HashMap<>(); + private MockSpan.MockContext selfContext; SpanBuilder(String operationName) { this.operationName = operationName; @@ -218,7 +230,10 @@ public SpanBuilder ignoreActiveSpan() { public SpanBuilder addReference(String referenceType, SpanContext referencedContext) { if (firstParent == null && ( referenceType.equals(References.CHILD_OF) || referenceType.equals(References.FOLLOWS_FROM))) { - this.firstParent = (MockSpan.MockContext)referencedContext; + this.firstParent = (MockSpan.MockContext) referencedContext; + } + if (referenceType.equals(References.SELF)) { + this.selfContext = (MockSpan.MockContext) referencedContext; } return this; } @@ -273,7 +288,22 @@ public MockSpan startManual() { firstParent = (MockSpan.MockContext) activeScope.span().context(); } } - return new MockSpan(MockTracer.this, operationName, startMicros, initialTags, firstParent); + final MockSpan.MockContext context; + if (selfContext != null) { + // the context has been explicitly provided + context = selfContext; + } else if (firstParent == null) { + // We're a root Span. + context = new MockSpan.MockContext(0, nextId(), nextId(), new HashMap()); + } else { + // We're a child Span. + context = new MockSpan.MockContext(firstParent.spanId(), firstParent.traceId(), nextId(), firstParent.baggage()); + } + return new MockSpan(MockTracer.this, operationName, startMicros, initialTags, context); } } + + static long nextId() { + return nextId.addAndGet(1); + } } 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 42df1bac..dc511005 100644 --- a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java +++ b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java @@ -13,20 +13,23 @@ */ package io.opentracing.mock; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import io.opentracing.References; import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMapExtractAdapter; import io.opentracing.propagation.TextMapInjectAdapter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.Assert; -import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; public class MockTracerTest { @Test @@ -209,4 +212,23 @@ public void testReset() { mockTracer.reset(); assertEquals(0, mockTracer.finishedSpans().size()); } + + @Test + public void testSelfReference() throws Exception { + MockTracer mockTracer = new MockTracer(MockTracer.Propagator.TEXT_MAP); + + final HashMap map = new HashMap<>(); + map.put("traceid", "42"); + map.put("spanid", "42"); + + mockTracer.buildSpan("foo") + .addReference(References.SELF, mockTracer.extract(Format.Builtin.TEXT_MAP, new TextMapExtractAdapter(map))) + .startManual() + .finish(); + + assertEquals(1, mockTracer.finishedSpans().size()); + assertEquals(42, mockTracer.finishedSpans().get(0).context().traceId()); + assertEquals(42, mockTracer.finishedSpans().get(0).context().spanId()); + assertEquals(0, mockTracer.finishedSpans().get(0).context().parentId()); + } }