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); + } + } + +}