diff options
Diffstat (limited to 'kamon-core/src/test/scala/kamon/trace')
7 files changed, 542 insertions, 117 deletions
diff --git a/kamon-core/src/test/scala/kamon/trace/B3SpanCodecSpec.scala b/kamon-core/src/test/scala/kamon/trace/B3SpanCodecSpec.scala new file mode 100644 index 00000000..e6fa283e --- /dev/null +++ b/kamon-core/src/test/scala/kamon/trace/B3SpanCodecSpec.scala @@ -0,0 +1,192 @@ +/* + * ========================================================================================= + * Copyright © 2013-2017 the kamon project <http://kamon.io/> + * + * 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 kamon.trace + +import kamon.context.{Context, TextMap} +import kamon.testkit.SpanBuilding +import kamon.trace.IdentityProvider.Identifier +import kamon.trace.SpanContext.SamplingDecision +import org.scalatest.{Matchers, OptionValues, WordSpecLike} + + +class B3SpanCodecSpec extends WordSpecLike with Matchers with OptionValues with SpanBuilding { + val extendedB3Codec = SpanCodec.B3() + + "The ExtendedB3 SpanContextCodec" should { + "return a TextMap containing the SpanContext data" in { + val context = testContext() + + val textMap = extendedB3Codec.encode(context) + textMap.get("X-B3-TraceId").value shouldBe "1234" + textMap.get("X-B3-ParentSpanId").value shouldBe "2222" + textMap.get("X-B3-SpanId").value shouldBe "4321" + textMap.get("X-B3-Sampled").value shouldBe "1" + } + + + "not inject anything if there is no Span in the Context" in { + val textMap = extendedB3Codec.encode(Context.Empty) + textMap.values shouldBe empty + } + + "extract a RemoteSpan from a TextMap when all fields are set" in { + val textMap = TextMap.Default() + textMap.put("X-B3-TraceId", "1234") + textMap.put("X-B3-ParentSpanId", "2222") + textMap.put("X-B3-SpanId", "4321") + textMap.put("X-B3-Sampled", "1") + textMap.put("X-B3-Extra-Baggage", "some=baggage;more=baggage") + + val spanContext = extendedB3Codec.decode(textMap, Context.Empty).get(Span.ContextKey).context() + spanContext.traceID.string shouldBe "1234" + spanContext.spanID.string shouldBe "4321" + spanContext.parentID.string shouldBe "2222" + spanContext.samplingDecision shouldBe SamplingDecision.Sample + } + + "decode the sampling decision based on the X-B3-Sampled header" in { + val sampledTextMap = TextMap.Default() + sampledTextMap.put("X-B3-TraceId", "1234") + sampledTextMap.put("X-B3-SpanId", "4321") + sampledTextMap.put("X-B3-Sampled", "1") + + val notSampledTextMap = TextMap.Default() + notSampledTextMap.put("X-B3-TraceId", "1234") + notSampledTextMap.put("X-B3-SpanId", "4321") + notSampledTextMap.put("X-B3-Sampled", "0") + + val noSamplingTextMap = TextMap.Default() + noSamplingTextMap.put("X-B3-TraceId", "1234") + noSamplingTextMap.put("X-B3-SpanId", "4321") + + extendedB3Codec.decode(sampledTextMap, Context.Empty) + .get(Span.ContextKey).context().samplingDecision shouldBe SamplingDecision.Sample + + extendedB3Codec.decode(notSampledTextMap, Context.Empty) + .get(Span.ContextKey).context().samplingDecision shouldBe SamplingDecision.DoNotSample + + extendedB3Codec.decode(noSamplingTextMap, Context.Empty) + .get(Span.ContextKey).context().samplingDecision shouldBe SamplingDecision.Unknown + } + + "not include the X-B3-Sampled header if the sampling decision is unknown" in { + val context = testContext() + val sampledSpanContext = context.get(Span.ContextKey).context() + val notSampledSpanContext = Context.Empty.withKey(Span.ContextKey, + Span.Remote(sampledSpanContext.copy(samplingDecision = SamplingDecision.DoNotSample))) + val unknownSamplingSpanContext = Context.Empty.withKey(Span.ContextKey, + Span.Remote(sampledSpanContext.copy(samplingDecision = SamplingDecision.Unknown))) + + extendedB3Codec.encode(context).get("X-B3-Sampled").value shouldBe("1") + extendedB3Codec.encode(notSampledSpanContext).get("X-B3-Sampled").value shouldBe("0") + extendedB3Codec.encode(unknownSamplingSpanContext).get("X-B3-Sampled") shouldBe empty + } + + "use the Debug flag to override the sampling decision, if provided." in { + val textMap = TextMap.Default() + textMap.put("X-B3-TraceId", "1234") + textMap.put("X-B3-SpanId", "4321") + textMap.put("X-B3-Sampled", "0") + textMap.put("X-B3-Flags", "1") + + val spanContext = extendedB3Codec.decode(textMap, Context.Empty).get(Span.ContextKey).context() + spanContext.samplingDecision shouldBe SamplingDecision.Sample + } + + "use the Debug flag as sampling decision when Sampled is not provided" in { + val textMap = TextMap.Default() + textMap.put("X-B3-TraceId", "1234") + textMap.put("X-B3-SpanId", "4321") + textMap.put("X-B3-Flags", "1") + + val spanContext = extendedB3Codec.decode(textMap, Context.Empty).get(Span.ContextKey).context() + spanContext.samplingDecision shouldBe SamplingDecision.Sample + } + + "extract a minimal SpanContext from a TextMap containing only the Trace ID and Span ID" in { + val textMap = TextMap.Default() + textMap.put("X-B3-TraceId", "1234") + textMap.put("X-B3-SpanId", "4321") + + val spanContext = extendedB3Codec.decode(textMap, Context.Empty).get(Span.ContextKey).context() + spanContext.traceID.string shouldBe "1234" + spanContext.spanID.string shouldBe "4321" + spanContext.parentID shouldBe IdentityProvider.NoIdentifier + spanContext.samplingDecision shouldBe SamplingDecision.Unknown + } + + "do not extract a SpanContext if Trace ID and Span ID are not provided" in { + val onlyTraceID = TextMap.Default() + onlyTraceID.put("X-B3-TraceId", "1234") + onlyTraceID.put("X-B3-Sampled", "0") + onlyTraceID.put("X-B3-Flags", "1") + + val onlySpanID = TextMap.Default() + onlySpanID.put("X-B3-SpanId", "4321") + onlySpanID.put("X-B3-Sampled", "0") + onlySpanID.put("X-B3-Flags", "1") + + val noIds = TextMap.Default() + noIds.put("X-B3-Sampled", "0") + noIds.put("X-B3-Flags", "1") + + extendedB3Codec.decode(onlyTraceID, Context.Empty).get(Span.ContextKey) shouldBe Span.Empty + extendedB3Codec.decode(onlySpanID, Context.Empty).get(Span.ContextKey) shouldBe Span.Empty + extendedB3Codec.decode(noIds, Context.Empty).get(Span.ContextKey) shouldBe Span.Empty + } + + "round trip a Span from TextMap -> Context -> TextMap" in { + val textMap = TextMap.Default() + textMap.put("X-B3-TraceId", "1234") + textMap.put("X-B3-ParentSpanId", "2222") + textMap.put("X-B3-SpanId", "4321") + textMap.put("X-B3-Sampled", "1") + + val context = extendedB3Codec.decode(textMap, Context.Empty) + val injectTextMap = extendedB3Codec.encode(context) + + textMap.values.toSeq should contain theSameElementsAs(injectTextMap.values.toSeq) + } + + /* + // TODO: Should we be supporting this use case? maybe even have the concept of Debug requests ourselves? + "internally carry the X-B3-Flags value so that it can be injected in outgoing requests" in { + val textMap = TextMap.Default() + textMap.put("X-B3-TraceId", "1234") + textMap.put("X-B3-ParentSpanId", "2222") + textMap.put("X-B3-SpanId", "4321") + textMap.put("X-B3-Sampled", "1") + textMap.put("X-B3-Flags", "1") + + val spanContext = extendedB3Codec.extract(textMap).value + val injectTextMap = extendedB3Codec.inject(spanContext) + + injectTextMap.get("X-B3-Flags").value shouldBe("1") + }*/ + } + + def testContext(): Context = { + val spanContext = createSpanContext().copy( + traceID = Identifier("1234", Array[Byte](1, 2, 3, 4)), + spanID = Identifier("4321", Array[Byte](4, 3, 2, 1)), + parentID = Identifier("2222", Array[Byte](2, 2, 2, 2)) + ) + + Context.create().withKey(Span.ContextKey, Span.Remote(spanContext)) + } + +}
\ No newline at end of file diff --git a/kamon-core/src/test/scala/kamon/trace/DefaultIdentityGeneratorSpec.scala b/kamon-core/src/test/scala/kamon/trace/DefaultIdentityGeneratorSpec.scala new file mode 100644 index 00000000..8f9af7b0 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/trace/DefaultIdentityGeneratorSpec.scala @@ -0,0 +1,52 @@ +package kamon.trace + +import kamon.trace.IdentityProvider.Identifier +import org.scalatest.{Matchers, OptionValues, WordSpecLike} +import org.scalactic.TimesOnInt._ + +class DefaultIdentityGeneratorSpec extends WordSpecLike with Matchers with OptionValues { + val idProvider = IdentityProvider.Default() + val traceGenerator = idProvider.traceIdGenerator() + val spanGenerator = idProvider.spanIdGenerator() + + validateGenerator("TraceID Generator", traceGenerator) + validateGenerator("SpanID Generator", spanGenerator) + + def validateGenerator(generatorName: String, generator: IdentityProvider.Generator) = { + s"The $generatorName" should { + "generate random longs (8 byte) identifiers" in { + 100 times { + val Identifier(string, bytes) = generator.generate() + + string.length should be(16) + bytes.length should be(8) + } + } + + "decode the string representation back into a identifier" in { + 100 times { + val identifier = generator.generate() + val decodedIdentifier = generator.from(identifier.string) + + identifier.string should equal(decodedIdentifier.string) + identifier.bytes should equal(decodedIdentifier.bytes) + } + } + + "decode the bytes representation back into a identifier" in { + 100 times { + val identifier = generator.generate() + val decodedIdentifier = generator.from(identifier.bytes) + + identifier.string should equal(decodedIdentifier.string) + identifier.bytes should equal(decodedIdentifier.bytes) + } + } + + "return IdentityProvider.NoIdentifier if the provided input cannot be decoded into a Identifier" in { + generator.from("zzzz") shouldBe(IdentityProvider.NoIdentifier) + generator.from(Array[Byte](1)) shouldBe(IdentityProvider.NoIdentifier) + } + } + } +} diff --git a/kamon-core/src/test/scala/kamon/trace/DoubleLengthTraceIdentityGeneratorSpec.scala b/kamon-core/src/test/scala/kamon/trace/DoubleLengthTraceIdentityGeneratorSpec.scala new file mode 100644 index 00000000..b22f17e1 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/trace/DoubleLengthTraceIdentityGeneratorSpec.scala @@ -0,0 +1,86 @@ +package kamon.trace + +import kamon.trace.IdentityProvider.Identifier +import org.scalactic.TimesOnInt._ +import org.scalatest.{Matchers, OptionValues, WordSpecLike} + +class DoubleLengthTraceIdentityGeneratorSpec extends WordSpecLike with Matchers with OptionValues { + val idProvider = IdentityProvider.DoubleSizeTraceID() + val traceGenerator = idProvider.traceIdGenerator() + val spanGenerator = idProvider.spanIdGenerator() + + "The DoubleSizeTraceID identity provider" when { + "generating trace identifiers" should { + "generate random longs (16 byte) identifiers" in { + 100 times { + val Identifier(string, bytes) = traceGenerator.generate() + + string.length should be(32) + bytes.length should be(16) + } + } + + "decode the string representation back into a identifier" in { + 100 times { + val identifier = traceGenerator.generate() + val decodedIdentifier = traceGenerator.from(identifier.string) + + identifier.string should equal(decodedIdentifier.string) + identifier.bytes should equal(decodedIdentifier.bytes) + } + } + + "decode the bytes representation back into a identifier" in { + 100 times { + val identifier = traceGenerator.generate() + val decodedIdentifier = traceGenerator.from(identifier.bytes) + + identifier.string should equal(decodedIdentifier.string) + identifier.bytes should equal(decodedIdentifier.bytes) + } + } + + "return IdentityProvider.NoIdentifier if the provided input cannot be decoded into a Identifier" in { + traceGenerator.from("zzzz") shouldBe (IdentityProvider.NoIdentifier) + traceGenerator.from(Array[Byte](1)) shouldBe (IdentityProvider.NoIdentifier) + } + } + + "generating span identifiers" should { + "generate random longs (8 byte) identifiers" in { + 100 times { + val Identifier(string, bytes) = spanGenerator.generate() + + string.length should be(16) + bytes.length should be(8) + } + } + + "decode the string representation back into a identifier" in { + 100 times { + val identifier = spanGenerator.generate() + val decodedIdentifier = spanGenerator.from(identifier.string) + + identifier.string should equal(decodedIdentifier.string) + identifier.bytes should equal(decodedIdentifier.bytes) + } + } + + "decode the bytes representation back into a identifier" in { + 100 times { + val identifier = spanGenerator.generate() + val decodedIdentifier = spanGenerator.from(identifier.bytes) + + identifier.string should equal(decodedIdentifier.string) + identifier.bytes should equal(decodedIdentifier.bytes) + } + } + + "return IdentityProvider.NoIdentifier if the provided input cannot be decoded into a Identifier" in { + spanGenerator.from("zzzz") shouldBe (IdentityProvider.NoIdentifier) + spanGenerator.from(Array[Byte](1)) shouldBe (IdentityProvider.NoIdentifier) + } + } + } + +} diff --git a/kamon-core/src/test/scala/kamon/trace/LocalSpanSpec.scala b/kamon-core/src/test/scala/kamon/trace/LocalSpanSpec.scala new file mode 100644 index 00000000..e24f8727 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/trace/LocalSpanSpec.scala @@ -0,0 +1,100 @@ +package kamon.trace + +import kamon.testkit.{MetricInspection, Reconfigure, TestSpanReporter} +import kamon.util.Registration +import kamon.Kamon +import kamon.trace.Span.{Annotation, TagValue} +import org.scalatest.concurrent.Eventually +import org.scalatest.{BeforeAndAfterAll, Matchers, OptionValues, WordSpec} +import org.scalatest.time.SpanSugar._ + +class LocalSpanSpec extends WordSpec with Matchers with BeforeAndAfterAll with Eventually with OptionValues + with Reconfigure with MetricInspection { + + "a real span" when { + "sampled and finished" should { + "be sent to the Span reporters" in { + Kamon.buildSpan("test-span") + .withSpanTag("test", "value") + .withStartTimestamp(100) + .start() + .finish(200) + + eventually(timeout(2 seconds)) { + val finishedSpan = reporter.nextSpan().value + finishedSpan.operationName shouldBe("test-span") + finishedSpan.startTimestampMicros shouldBe 100 + finishedSpan.endTimestampMicros shouldBe 200 + finishedSpan.tags should contain("test" -> TagValue.String("value")) + } + } + + "pass all the tags, annotations and baggage to the FinishedSpan instance when started and finished" in { + Kamon.buildSpan("full-span") + .withSpanTag("builder-string-tag", "value") + .withSpanTag("builder-boolean-tag-true", true) + .withSpanTag("builder-boolean-tag-false", false) + .withSpanTag("builder-number-tag", 42) + .withStartTimestamp(100) + .start() + .addSpanTag("span-string-tag", "value") + .addSpanTag("span-boolean-tag-true", true) + .addSpanTag("span-boolean-tag-false", false) + .addSpanTag("span-number-tag", 42) + .annotate("simple-annotation") + .annotate("regular-annotation", Map("data" -> "something")) + .annotate(4200, "custom-annotation-1", Map("custom" -> "yes-1")) + .annotate(Annotation(4201, "custom-annotation-2", Map("custom" -> "yes-2"))) + .setOperationName("fully-populated-span") + .finish(200) + + eventually(timeout(2 seconds)) { + val finishedSpan = reporter.nextSpan().value + finishedSpan.operationName shouldBe ("fully-populated-span") + finishedSpan.startTimestampMicros shouldBe 100 + finishedSpan.endTimestampMicros shouldBe 200 + finishedSpan.tags should contain allOf( + "builder-string-tag" -> TagValue.String("value"), + "builder-boolean-tag-true" -> TagValue.True, + "builder-boolean-tag-false" -> TagValue.False, + "builder-number-tag" -> TagValue.Number(42), + "span-string-tag" -> TagValue.String("value"), + "span-boolean-tag-true" -> TagValue.True, + "span-boolean-tag-false" -> TagValue.False, + "span-number-tag" -> TagValue.Number(42) + ) + + finishedSpan.annotations.length shouldBe (4) + val annotations = finishedSpan.annotations.groupBy(_.name) + annotations.keys should contain allOf( + "simple-annotation", + "regular-annotation", + "custom-annotation-1", + "custom-annotation-2" + ) + + val customAnnotationOne = annotations("custom-annotation-1").head + customAnnotationOne.timestampMicros shouldBe (4200) + customAnnotationOne.fields shouldBe (Map("custom" -> "yes-1")) + + val customAnnotationTwo = annotations("custom-annotation-2").head + customAnnotationTwo.timestampMicros shouldBe (4201) + customAnnotationTwo.fields shouldBe (Map("custom" -> "yes-2")) + } + } + } + } + + @volatile var registration: Registration = _ + val reporter = new TestSpanReporter() + + override protected def beforeAll(): Unit = { + enableFastSpanFlushing() + sampleAlways() + registration = Kamon.addReporter(reporter) + } + + override protected def afterAll(): Unit = { + registration.cancel() + } +} diff --git a/kamon-core/src/test/scala/kamon/trace/SpanContextCodecSpec.scala b/kamon-core/src/test/scala/kamon/trace/SpanContextCodecSpec.scala deleted file mode 100644 index 5fa6200d..00000000 --- a/kamon-core/src/test/scala/kamon/trace/SpanContextCodecSpec.scala +++ /dev/null @@ -1,106 +0,0 @@ -/* - * ========================================================================================= - * Copyright © 2013-2017 the kamon project <http://kamon.io/> - * - * 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 kamon.trace - -import java.util - -import io.opentracing.propagation.TextMap -import org.scalatest.{Matchers, WordSpecLike} - - -class SpanContextCodecSpec extends WordSpecLike with Matchers { - "The Span Context Codec" should { - - "supports Text Map extraction" in { - val textMap = MapTextMap() - textMap.put("TRACE_ID", "1") - textMap.put("PARENT_ID", "2") - textMap.put("SPAN_ID", "3") - textMap.put("SAMPLED", "sampled") - textMap.put("BAGGAGE_1", "awesome-baggage-1") - textMap.put("BAGGAGE_2", "awesome-baggage-2") - - val spanContext = SpanContextCodec.TextMap.extract(textMap, Sampler.never) - - spanContext.traceID should be(1) - spanContext.parentID should be(2) - spanContext.spanID should be(3) - spanContext.sampled should be(false) - spanContext.baggageMap should be(Map("1" -> "awesome-baggage-1", "2" -> "awesome-baggage-2")) - } - - "supports Text Map injection" in { - val textMap = MapTextMap() - - SpanContextCodec.TextMap.inject(new SpanContext(1, 2, 3, false, Map("MDC" -> "awesome-mdc-value")), textMap) - - textMap.map.get("TRACE_ID") should be("0000000000000001") - textMap.map.get("PARENT_ID") should be("0000000000000003") - textMap.map.get("SPAN_ID") should be("0000000000000002") - textMap.map.get("SAMPLED") should be(null) - textMap.map.get("BAGGAGE_MDC") should be("awesome-mdc-value") - } - - "supports Http Headers extraction" in { - val textMap = MapTextMap() - textMap.put("X-B3-TraceId", "1") - textMap.put("X-B3-ParentSpanId", "2") - textMap.put("X-B3-SpanId", "3") - textMap.put("X-B3-Sampled", "sampled") - textMap.put("X-B3-Baggage-1", "awesome-baggage-1") - textMap.put("X-B3-Baggage-2", "awesome-baggage-2") - - val spanContext = SpanContextCodec.ZipkinB3.extract(textMap, Sampler.never) - - spanContext.traceID should be(1) - spanContext.parentID should be(2) - spanContext.spanID should be(3) - spanContext.sampled should be(false) - spanContext.baggageMap should be(Map("1" -> "awesome-baggage-1", "2" -> "awesome-baggage-2")) - } - - "supports Http Headers injection" in { - val textMap = MapTextMap() - - SpanContextCodec.ZipkinB3.inject(new SpanContext(1, 2, 3, false, Map("MDC" -> "awesome-mdc-value")), textMap) - - textMap.map.get("X-B3-TraceId") should be("0000000000000001") - textMap.map.get("X-B3-ParentSpanId") should be("0000000000000003") - textMap.map.get("X-B3-SpanId") should be("0000000000000002") - textMap.map.get("X-B3-Sampled") should be(null) - textMap.map.get("X-B3-Baggage-MDC") should be("awesome-mdc-value") - } - } -} - -class MapTextMap extends TextMap { - val map = new util.HashMap[String, String]() - - override def iterator: util.Iterator[util.Map.Entry[String, String]] = - map.entrySet.iterator - - override def put(key: String, value: String): Unit = { - map.put(key, value) - } -} - -object MapTextMap { - def apply(): MapTextMap = new MapTextMap() -} - - - diff --git a/kamon-core/src/test/scala/kamon/trace/SpanMetrics.scala b/kamon-core/src/test/scala/kamon/trace/SpanMetrics.scala index a4ce9882..9ecffb24 100644 --- a/kamon-core/src/test/scala/kamon/trace/SpanMetrics.scala +++ b/kamon-core/src/test/scala/kamon/trace/SpanMetrics.scala @@ -8,7 +8,7 @@ import org.scalatest.{Matchers, WordSpecLike} class SpanMetrics extends WordSpecLike with Matchers { import SpanMetricsTestHelper._ - val errorTag = "error" -> Span.BooleanTagTrueValue + val errorTag = "error" -> "true" val histogramMetric: HistogramMetric = Kamon.histogram("span.elapsed-time") "Span Metrics" should { @@ -16,14 +16,14 @@ class SpanMetrics extends WordSpecLike with Matchers { val operation = "span-success" val operationTag = "operation" -> operation - val span = buildSpan(operation).startManual() - span.finish() - + buildSpan(operation) + .start() + .finish() val histogram = histogramMetric.refine(operationTag) histogram.distribution().count === 1 - val errorHistogram = histogramMetric.refine(operationTag, errorTag).distribution() + val errorHistogram = histogramMetric.refine(Map(operationTag, errorTag)).distribution() errorHistogram.count === 0 } @@ -32,9 +32,10 @@ class SpanMetrics extends WordSpecLike with Matchers { val operation = "span-failure" val operationTag = "operation" -> operation - val span = buildSpan(operation).startManual() - span.setTag("error", Span.BooleanTagTrueValue) - span.finish() + buildSpan(operation) + .start() + .addSpanTag("error", true) + .finish() val histogram = histogramMetric.refine(operationTag) histogram.distribution().count === 0 @@ -57,9 +58,6 @@ object SpanMetricsTestHelper { case h: HdrHistogram => h.snapshot(resetState).distribution } } - - - } diff --git a/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala new file mode 100644 index 00000000..fb5bb313 --- /dev/null +++ b/kamon-core/src/test/scala/kamon/trace/TracerSpec.scala @@ -0,0 +1,103 @@ +package kamon.trace + +import com.typesafe.config.ConfigFactory +import kamon.Kamon +import kamon.context.Context +import kamon.testkit.{SpanBuilding, SpanInspector} +import kamon.trace.Span.TagValue +import org.scalatest.{Matchers, OptionValues, WordSpec} + +class TracerSpec extends WordSpec with Matchers with SpanBuilding with OptionValues { + + "the Kamon tracer" should { + "construct a minimal Span that only has a operation name" in { + val span = tracer.buildSpan("myOperation").start() + val spanData = inspect(span) + + spanData.operationName() shouldBe "myOperation" + spanData.metricTags() shouldBe empty + spanData.spanTags() shouldBe empty + } + + "pass the operation name and tags to started Span" in { + val span = tracer.buildSpan("myOperation") + .withMetricTag("metric-tag", "value") + .withMetricTag("metric-tag", "value") + .withSpanTag("hello", "world") + .withSpanTag("kamon", "rulez") + .withSpanTag("number", 123) + .withSpanTag("boolean", true) + .start() + + val spanData = inspect(span) + spanData.operationName() shouldBe "myOperation" + spanData.metricTags() should contain only ( + ("metric-tag" -> "value")) + + spanData.spanTags() should contain allOf( + ("hello" -> TagValue.String("world")), + ("kamon" -> TagValue.String("rulez")), + ("number" -> TagValue.Number(123)), + ("boolean" -> TagValue.True)) + } + + "not have any parent Span if there is ActiveSpan and no parent was explicitly given" in { + val span = tracer.buildSpan("myOperation").start() + val spanData = inspect(span) + spanData.context().parentID shouldBe IdentityProvider.NoIdentifier + } + + + "automatically take the Span from the current Context as parent" in { + val parent = tracer.buildSpan("myOperation").start() + val child = Kamon.withContext(Context.create(Span.ContextKey, parent)) { + tracer.buildSpan("childOperation").asChildOf(parent).start() + } + + val parentData = inspect(parent) + val childData = inspect(child) + parentData.context().spanID shouldBe childData.context().parentID + } + + "ignore the currently active span as parent if explicitly requested" in { + val parent = tracer.buildSpan("myOperation").start() + val child = Kamon.withContext(Context.create(Span.ContextKey, parent)) { + tracer.buildSpan("childOperation").ignoreActiveSpan().start() + } + + val childData = inspect(child) + childData.context().parentID shouldBe IdentityProvider.NoIdentifier + } + + "allow overriding the start timestamp for a Span" in { + val span = tracer.buildSpan("myOperation").withStartTimestamp(100).start() + val spanData = inspect(span) + spanData.startTimestamp() shouldBe 100 + } + + "preserve the same Span and Parent identifier when creating a Span with a remote parent if join-remote-parents-with-same-span-id is enabled" in { + val previousConfig = Kamon.config() + + Kamon.reconfigure { + ConfigFactory.parseString("kamon.trace.join-remote-parents-with-same-span-id = yes") + .withFallback(Kamon.config()) + } + + val remoteParent = Span.Remote(createSpanContext()) + val childData = inspect(tracer.buildSpan("local").asChildOf(remoteParent).start()) + + childData.context().traceID shouldBe remoteParent.context.traceID + childData.context().parentID shouldBe remoteParent.context.parentID + childData.context().spanID shouldBe remoteParent.context.spanID + + Kamon.reconfigure(previousConfig) + } + + } + + val tracer: Tracer = Kamon + + def inspect(span: Span): SpanInspector = + SpanInspector(span) + +} |