/* * ========================================================================================= * Copyright © 2013-2018 the kamon project * * 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, HttpPropagation} import kamon.testkit.SpanBuilding import kamon.trace.IdentityProvider.Identifier import kamon.trace.SpanContext.SamplingDecision import org.scalatest.{Matchers, OptionValues, WordSpecLike} import scala.collection.mutable class B3SingleSpanPropagationSpec extends WordSpecLike with Matchers with OptionValues with SpanBuilding { val b3SinglePropagation = SpanPropagation.B3Single() "The ExtendedB3 SpanContextCodec" should { "return a TextMap containing the SpanContext data" in { val headersMap = mutable.Map.empty[String, String] b3SinglePropagation.write(testContext(), headerWriterFromMap(headersMap)) headersMap.get("B3").value shouldBe "1234-4321-1-2222" } "do not include the X-B3-ParentSpanId if there is no parent" in { val headersMap = mutable.Map.empty[String, String] b3SinglePropagation.write(testContextWithoutParent(), headerWriterFromMap(headersMap)) headersMap.get("B3").value shouldBe "1234-4321-1" } "not inject anything if there is no Span in the Context" in { val headersMap = mutable.Map.empty[String, String] b3SinglePropagation.write(Context.Empty, headerWriterFromMap(headersMap)) headersMap.values shouldBe empty } "extract a RemoteSpan from a TextMap when all fields are set" in { val headersMap = Map("B3" -> "1234-4321-1-2222") val spanContext = b3SinglePropagation.read(headerReaderFromMap(headersMap), 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 sampledHeadersMap = Map("B3" -> "1234-4321-1") val notSampledHeadersMap = Map("B3" -> "1234-4321-0") val noSamplingHeadersMap = Map("B3" -> "1234-4321") b3SinglePropagation.read(headerReaderFromMap(sampledHeadersMap), Context.Empty) .get(Span.ContextKey).context().samplingDecision shouldBe SamplingDecision.Sample b3SinglePropagation.read(headerReaderFromMap(notSampledHeadersMap), Context.Empty) .get(Span.ContextKey).context().samplingDecision shouldBe SamplingDecision.DoNotSample b3SinglePropagation.read(headerReaderFromMap(noSamplingHeadersMap), 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))) val headersMap = mutable.Map.empty[String, String] b3SinglePropagation.write(context, headerWriterFromMap(headersMap)) headersMap.get("B3").value shouldBe "1234-4321-1-2222" headersMap.clear() b3SinglePropagation.write(notSampledSpanContext, headerWriterFromMap(headersMap)) headersMap.get("B3").value shouldBe "1234-4321-0-2222" headersMap.clear() b3SinglePropagation.write(unknownSamplingSpanContext,headerWriterFromMap(headersMap)) headersMap.get("B3").value shouldBe "1234-4321-2222" headersMap.clear() } "use the Debug flag to override the sampling decision, if provided." in { val headers = Map("B3" -> "1234-4321-d-2222") val spanContext = b3SinglePropagation.read(headerReaderFromMap(headers), 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 headers = Map("B3" -> "1234-4321-d") val spanContext = b3SinglePropagation.read(headerReaderFromMap(headers), 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 headers = Map("B3" -> "1234-4321") val spanContext = b3SinglePropagation.read(headerReaderFromMap(headers), 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 = Map("B3" -> "1234--0") val onlySpanID = Map("B3" -> "-4321-d") val noIds = Map("B3" -> "--0") b3SinglePropagation.read(headerReaderFromMap(onlyTraceID), Context.Empty).get(Span.ContextKey) shouldBe Span.Empty b3SinglePropagation.read(headerReaderFromMap(onlySpanID), Context.Empty).get(Span.ContextKey) shouldBe Span.Empty b3SinglePropagation.read(headerReaderFromMap(noIds), Context.Empty).get(Span.ContextKey) shouldBe Span.Empty } "round trip a Span from TextMap -> Context -> TextMap" in { val headers = Map("B3" -> "1234-4312-1-2222") val writenHeaders = mutable.Map.empty[String, String] val context = b3SinglePropagation.read(headerReaderFromMap(headers), Context.Empty) b3SinglePropagation.write(context, headerWriterFromMap(writenHeaders)) writenHeaders should contain theSameElementsAs headers } } def headerReaderFromMap(map: Map[String, String]): HttpPropagation.HeaderReader = new HttpPropagation.HeaderReader { override def read(header: String): Option[String] = { if(map.get("fail").nonEmpty) sys.error("failing on purpose") map.get(header) } override def readAll(): Map[String, String] = map } def headerWriterFromMap(map: mutable.Map[String, String]): HttpPropagation.HeaderWriter = new HttpPropagation.HeaderWriter { override def write(header: String, value: String): Unit = map.put(header, value) } 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.of(Span.ContextKey, Span.Remote(spanContext)) } def testContextWithoutParent(): Context = { val spanContext = createSpanContext().copy( traceID = Identifier("1234", Array[Byte](1, 2, 3, 4)), spanID = Identifier("4321", Array[Byte](4, 3, 2, 1)), parentID = IdentityProvider.NoIdentifier ) Context.of(Span.ContextKey, Span.Remote(spanContext)) } }