diff --git a/opentracing-api/src/main/java/io/opentracing/propagation/Format.java b/opentracing-api/src/main/java/io/opentracing/propagation/Format.java
index 0eec37bf..dd63aba5 100644
--- a/opentracing-api/src/main/java/io/opentracing/propagation/Format.java
+++ b/opentracing-api/src/main/java/io/opentracing/propagation/Format.java
@@ -27,7 +27,7 @@
*
* Tracer tracer = ...
* io.opentracing.propagation.HttpHeaders httpCarrier = new AnHttpHeaderCarrier(httpRequest);
- * SpanContext spanCtx = tracer.extract(Format.Builtin.HTTP_HEADERS, httpHeaderReader);
+ * SpanContext spanCtx = tracer.extract(Format.Builtin.HTTP_HEADERS, httpCarrier);
*
*
* @see Tracer#inject(SpanContext, Format, Object)
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..ce637381 100644
--- a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java
+++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java
@@ -13,11 +13,13 @@
*/
package io.opentracing.mock;
+import io.opentracing.References;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import io.opentracing.Span;
@@ -42,6 +44,7 @@ public final class MockSpan implements Span {
private final Map tags;
private final List logEntries = new ArrayList<>();
private String operationName;
+ private final List references;
private final List errors = new ArrayList<>();
@@ -57,11 +60,10 @@ public MockSpan setOperationName(String operationName) {
}
/**
- * TODO: Support multiple parents in this API.
- *
- * @return the spanId of the Span's parent context, or 0 if no such parent exists.
+ * @return the spanId of the Span's first {@value References#CHILD_OF} reference, or the first reference of any type, or 0 if no reference exists.
*
* @see MockContext#spanId()
+ * @see MockSpan#references()
*/
public long parentId() {
return parentId;
@@ -97,6 +99,10 @@ public List generatedErrors() {
return new ArrayList<>(errors);
}
+ public List references() {
+ return new ArrayList<>(references);
+ }
+
@Override
public synchronized MockContext context() {
return this.context;
@@ -232,7 +238,39 @@ public long timestampMicros() {
}
}
- MockSpan(MockTracer tracer, String operationName, long startMicros, Map initialTags, MockContext parent) {
+ public static final class Reference {
+ private final MockContext context;
+ private final String referenceType;
+
+ public Reference(MockContext context, String referenceType) {
+ this.context = context;
+ this.referenceType = referenceType;
+ }
+
+ public MockContext getContext() {
+ return context;
+ }
+
+ public String getReferenceType() {
+ return referenceType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Reference reference = (Reference) o;
+ return Objects.equals(context, reference.context) &&
+ Objects.equals(referenceType, reference.referenceType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(context, referenceType);
+ }
+ }
+
+ MockSpan(MockTracer tracer, String operationName, long startMicros, Map initialTags, List refs) {
this.mockTracer = tracer;
this.operationName = operationName;
this.startMicros = startMicros;
@@ -241,17 +279,45 @@ public long timestampMicros() {
} else {
this.tags = new HashMap<>(initialTags);
}
+ if(refs == null) {
+ this.references = Collections.emptyList();
+ } else {
+ this.references = new ArrayList<>(refs);
+ }
+ MockContext parent = findPreferredParentRef(this.references);
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.context = new MockContext(parent.traceId, nextId(), mergeBaggages(this.references));
this.parentId = parent.spanId;
}
}
+ private static MockContext findPreferredParentRef(List references) {
+ if(references.isEmpty()) {
+ return null;
+ }
+ for (Reference reference : references) {
+ if (References.CHILD_OF.equals(reference.getReferenceType())) {
+ return reference.getContext();
+ }
+ }
+ return references.get(0).getContext();
+ }
+
+ private static Map mergeBaggages(List references) {
+ Map baggage = new HashMap<>();
+ for(Reference ref : references) {
+ if(ref.getContext().baggage != null) {
+ baggage.putAll(ref.getContext().baggage);
+ }
+ }
+ return baggage;
+ }
+
static long nextId() {
return nextId.addAndGet(1);
}
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 326a219c..111f8a62 100644
--- a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java
+++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java
@@ -13,6 +13,17 @@
*/
package io.opentracing.mock;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
import io.opentracing.Scope;
import io.opentracing.ScopeManager;
import io.opentracing.Span;
@@ -25,17 +36,6 @@
import io.opentracing.propagation.TextMap;
import io.opentracing.util.ThreadLocalScopeManager;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-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.
*
@@ -45,16 +45,16 @@
* The MockTracerTest has simple usage examples.
*/
public class MockTracer implements Tracer {
- private List finishedSpans = new ArrayList<>();
+ private final List finishedSpans = new ArrayList<>();
private final Propagator propagator;
- private ScopeManager scopeManager;
+ private final ScopeManager scopeManager;
public MockTracer() {
- this(new ThreadLocalScopeManager(), Propagator.PRINTER);
+ this(new ThreadLocalScopeManager(), Propagator.TEXT_MAP);
}
public MockTracer(ScopeManager scopeManager) {
- this(scopeManager, Propagator.PRINTER);
+ this(scopeManager, Propagator.TEXT_MAP);
}
public MockTracer(ScopeManager scopeManager, Propagator propagator) {
@@ -254,6 +254,15 @@ public SpanBuilder buildSpan(String operationName) {
return new SpanBuilder(operationName);
}
+ private SpanContext activeSpanContext() {
+ Scope handle = this.scopeManager.active();
+ if (handle == null) {
+ return null;
+ }
+
+ return handle.span().context();
+ }
+
@Override
public void inject(SpanContext spanContext, Format format, C carrier) {
this.propagator.inject((MockSpan.MockContext)spanContext, format, carrier);
@@ -278,7 +287,7 @@ synchronized void appendFinishedSpan(MockSpan mockSpan) {
public final class SpanBuilder implements Tracer.SpanBuilder {
private final String operationName;
private long startMicros;
- private MockSpan.MockContext firstParent;
+ private List references = new ArrayList<>();
private boolean ignoringActiveSpan;
private Map initialTags = new HashMap<>();
@@ -293,6 +302,9 @@ public SpanBuilder asChildOf(SpanContext parent) {
@Override
public SpanBuilder asChildOf(Span parent) {
+ if (parent == null) {
+ return this;
+ }
return addReference(References.CHILD_OF, parent.context());
}
@@ -304,9 +316,8 @@ public SpanBuilder ignoreActiveSpan() {
@Override
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;
+ if (referencedContext != null) {
+ this.references.add(new MockSpan.Reference((MockSpan.MockContext) referencedContext, referenceType));
}
return this;
}
@@ -350,13 +361,11 @@ public MockSpan startManual() {
if (this.startMicros == 0) {
this.startMicros = MockSpan.nowMicros();
}
- if (firstParent == null && !ignoringActiveSpan) {
- Scope activeScope = scopeManager().active();
- if (activeScope != null) {
- firstParent = (MockSpan.MockContext) activeScope.span().context();
- }
+ SpanContext activeSpanContext = activeSpanContext();
+ if(references.isEmpty() && !ignoringActiveSpan && activeSpanContext != null) {
+ references.add(new MockSpan.Reference((MockSpan.MockContext) activeSpanContext, References.CHILD_OF));
}
- return new MockSpan(MockTracer.this, operationName, startMicros, initialTags, firstParent);
+ return new MockSpan(MockTracer.this, operationName, startMicros, initialTags, references);
}
}
}
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 20e11a35..4709df90 100644
--- a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java
+++ b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java
@@ -14,8 +14,19 @@
package io.opentracing.mock;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import io.opentracing.References;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.SpanContext;
@@ -24,13 +35,6 @@
import io.opentracing.propagation.Format;
import io.opentracing.propagation.TextMapExtractAdapter;
import io.opentracing.propagation.TextMapInjectAdapter;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.junit.Assert;
-import org.junit.Test;
public class MockTracerTest {
@Test
@@ -262,4 +266,93 @@ public void testReset() {
mockTracer.reset();
assertEquals(0, mockTracer.finishedSpans().size());
}
+
+ @Test
+ public void testFollowFromReference() {
+ MockTracer tracer = new MockTracer(MockTracer.Propagator.TEXT_MAP);
+ final MockSpan precedent = tracer.buildSpan("precedent").startManual();
+
+ final MockSpan followingSpan = tracer.buildSpan("follows")
+ .addReference(References.FOLLOWS_FROM, precedent.context())
+ .startManual();
+
+ assertEquals(precedent.context().spanId(), followingSpan.parentId());
+ assertEquals(1, followingSpan.references().size());
+
+ final MockSpan.Reference followsFromRef = followingSpan.references().get(0);
+
+ assertEquals(new MockSpan.Reference(precedent.context(), References.FOLLOWS_FROM), followsFromRef);
+ }
+
+ @Test
+ public void testMultiReferences() {
+ MockTracer tracer = new MockTracer(MockTracer.Propagator.TEXT_MAP);
+ final MockSpan parent = tracer.buildSpan("parent").startManual();
+ final MockSpan precedent = tracer.buildSpan("precedent").startManual();
+
+ final MockSpan followingSpan = tracer.buildSpan("follows")
+ .addReference(References.FOLLOWS_FROM, precedent.context())
+ .asChildOf(parent.context())
+ .startManual();
+
+ assertEquals(parent.context().spanId(), followingSpan.parentId());
+ assertEquals(2, followingSpan.references().size());
+
+ final MockSpan.Reference followsFromRef = followingSpan.references().get(0);
+ final MockSpan.Reference parentRef = followingSpan.references().get(1);
+
+ assertEquals(new MockSpan.Reference(precedent.context(), References.FOLLOWS_FROM), followsFromRef);
+ assertEquals(new MockSpan.Reference(parent.context(), References.CHILD_OF), parentRef);
+ }
+
+ @Test
+ public void testMultiReferencesBaggage() {
+ MockTracer tracer = new MockTracer(MockTracer.Propagator.TEXT_MAP);
+ final MockSpan parent = tracer.buildSpan("parent").startManual();
+ parent.setBaggageItem("parent", "foo");
+ final MockSpan precedent = tracer.buildSpan("precedent").startManual();
+ precedent.setBaggageItem("precedent", "bar");
+
+ final MockSpan followingSpan = tracer.buildSpan("follows")
+ .addReference(References.FOLLOWS_FROM, precedent.context())
+ .asChildOf(parent.context())
+ .startManual();
+
+ assertEquals("foo", followingSpan.getBaggageItem("parent"));
+ assertEquals("bar", followingSpan.getBaggageItem("precedent"));
+ }
+
+ @Test
+ public void testNonStandardReference() {
+ MockTracer tracer = new MockTracer(MockTracer.Propagator.TEXT_MAP);
+ final MockSpan parent = tracer.buildSpan("parent").startManual();
+
+ final MockSpan nextSpan = tracer.buildSpan("follows")
+ .addReference("a_reference", parent.context())
+ .startManual();
+
+ assertEquals(parent.context().spanId(), nextSpan.parentId());
+ assertEquals(1, nextSpan.references().size());
+ assertEquals(nextSpan.references().get(0),
+ new MockSpan.Reference(parent.context(), "a_reference"));
+ }
+
+ @Test
+ public void testDefaultConstructor() {
+ MockTracer mockTracer = new MockTracer();
+ Scope activeSpan = mockTracer.buildSpan("foo").startActive(true);
+ assertEquals(activeSpan, mockTracer.scopeManager().active());
+
+ Map propag = new HashMap<>();
+ mockTracer.inject(activeSpan.span().context(), Format.Builtin.TEXT_MAP, new TextMapInjectAdapter(propag));
+ assertFalse(propag.isEmpty());
+ }
+
+ @Test
+ public void testChildOfWithNullParentDoesNotThrowException() {
+ MockTracer tracer = new MockTracer();
+ final Span parent = null;
+ Span span = tracer.buildSpan("foo").asChildOf(parent).start();
+ span.finish();
+ }
}
diff --git a/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpanBuilder.java b/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpanBuilder.java
index 3971b141..78cebf63 100644
--- a/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpanBuilder.java
+++ b/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpanBuilder.java
@@ -22,7 +22,7 @@
import java.util.Map;
public interface NoopSpanBuilder extends Tracer.SpanBuilder, NoopSpanContext {
- static final NoopSpanBuilder INSTANCE = new NoopSpanBuilderImpl();
+ NoopSpanBuilder INSTANCE = new NoopSpanBuilderImpl();
}
final class NoopSpanBuilderImpl implements NoopSpanBuilder {
@@ -82,7 +82,7 @@ public Span startManual() {
@Override
public Iterable> baggageItems() {
- return Collections.EMPTY_MAP.entrySet();
+ return Collections.emptyMap().entrySet();
}
@Override
diff --git a/opentracing-util/pom.xml b/opentracing-util/pom.xml
index 846fc8b8..3ece08ea 100644
--- a/opentracing-util/pom.xml
+++ b/opentracing-util/pom.xml
@@ -42,4 +42,24 @@
opentracing-noop
+
+
+
+
+ maven-jar-plugin
+
+
+
+ test-jar
+
+
+
+ **/*TestUtil.*
+
+
+
+
+
+
+
diff --git a/opentracing-util/src/test/java/io/opentracing/util/GlobalTracerTest.java b/opentracing-util/src/test/java/io/opentracing/util/GlobalTracerTest.java
index 9fea040e..36e136b5 100644
--- a/opentracing-util/src/test/java/io/opentracing/util/GlobalTracerTest.java
+++ b/opentracing-util/src/test/java/io/opentracing/util/GlobalTracerTest.java
@@ -29,9 +29,7 @@
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.noop.NoopSpanBuilder;
-import io.opentracing.noop.NoopTracerFactory;
import io.opentracing.propagation.Format;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
@@ -45,21 +43,10 @@
public class GlobalTracerTest {
- private static void _setGlobal(Tracer tracer) {
- try {
- Field globalTracerField = GlobalTracer.class.getDeclaredField("tracer");
- globalTracerField.setAccessible(true);
- globalTracerField.set(null, tracer);
- globalTracerField.setAccessible(false);
- } catch (Exception e) {
- throw new RuntimeException("Error reflecting globalTracer: " + e.getMessage(), e);
- }
- }
-
@Before
@After
public void clearGlobalTracer() {
- _setGlobal(NoopTracerFactory.create());
+ GlobalTracerTestUtil.resetGlobalTracer();
}
@Test
diff --git a/opentracing-util/src/test/java/io/opentracing/util/GlobalTracerTestUtil.java b/opentracing-util/src/test/java/io/opentracing/util/GlobalTracerTestUtil.java
new file mode 100644
index 00000000..13080e3f
--- /dev/null
+++ b/opentracing-util/src/test/java/io/opentracing/util/GlobalTracerTestUtil.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-2017 The OpenTracing Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package io.opentracing.util;
+
+import io.opentracing.Tracer;
+import io.opentracing.noop.NoopTracerFactory;
+
+import java.lang.reflect.Field;
+
+/**
+ * Utility class for manipulating the {@link GlobalTracer} when testing.
+ *
+ * The {@linkplain GlobalTracer} has register-once semantics, but for testing purposes it is useful to
+ * manipulate the globaltracer to guarantee certain preconditions.
+ *
+ * The {@code GlobalTracerTestUtil} can be included in your own code by adding the following dependency:
+ *
+ * <dependency>
+ * <groupId>io.opentracing</groupId>
+ * <artifactId>opentracing-util</artifactId>
+ * <version>version</version>
+ * <type>test-jar</type>
+ * <scope>test</scope>
+ * </dependency>
+ *
+ */
+public class GlobalTracerTestUtil {
+
+ private GlobalTracerTestUtil() {
+ throw new UnsupportedOperationException("Cannot instantiate static test utility class.");
+ }
+
+ /**
+ * Resets the {@link GlobalTracer} to its initial, unregistered state.
+ */
+ public static void resetGlobalTracer() {
+ setGlobalTracerUnconditionally(NoopTracerFactory.create());
+ }
+
+ /**
+ * Unconditionally sets the {@link GlobalTracer} to the specified {@link Tracer tracer} instance.
+ *
+ * @param tracer The tracer to become the GlobalTracer's delegate.
+ */
+ public static void setGlobalTracerUnconditionally(Tracer tracer) {
+ try {
+ Field globalTracerField = GlobalTracer.class.getDeclaredField("tracer");
+ globalTracerField.setAccessible(true);
+ globalTracerField.set(null, tracer);
+ globalTracerField.setAccessible(false);
+ } catch (Exception e) {
+ throw new IllegalStateException("Error reflecting GlobalTracer.tracer: " + e.getMessage(), e);
+ }
+ }
+
+}