effectful-tracing-0.1.0.0: test/Effectful/Tracing/Propagation/CompositeSpec.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module : Effectful.Tracing.Propagation.CompositeSpec
-- Description : Tests for combining propagators.
--
-- The combinators are exercised end to end: 'injectContextAll' and
-- 'injectBaggageAll' through the in-memory interpreter (where an active span and
-- ambient baggage exist), and 'extractContextFirst' \/ 'extractBaggageAll' with
-- crafted header vectors. The order-sensitive cases pin down the contract that
-- matters for a composite: inject writes every format, extract takes the first
-- matching context but merges all baggage, and the @OTEL_PROPAGATORS@ token
-- lookups resolve the standard names.
module Effectful.Tracing.Propagation.CompositeSpec
( tests
) where
import Data.ByteString (ByteString)
import Effectful (Eff, IOE, runEff, runPureEff)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, testCase, (@?=))
import Effectful.Tracing (Tracer, withSpan)
import Effectful.Tracing.Baggage
( lookupBaggageValue
, runBaggage
, withBaggageEntry
)
import Effectful.Tracing.Internal.Ids (traceIdToHex)
import Effectful.Tracing.Internal.Types (SpanContext (..))
import Effectful.Tracing.Interpreter.InMemory
( newCapturedSpans
, runTracerInMemory
)
import Effectful.Tracing.Propagation (traceparentHeader)
import Effectful.Tracing.Propagation.B3 (b3Header)
import Effectful.Tracing.Propagation.Baggage (baggageHeader)
import Effectful.Tracing.Propagation.Composite
( baggageByToken
, baggageName
, b3Multi
, b3Single
, extractBaggageAll
, extractContextFirst
, injectBaggageAll
, injectContextAll
, jaegerBaggage
, jaegerTraceContext
, traceContextByToken
, traceContextName
, w3cBaggage
, w3cTraceContext
)
tests :: TestTree
tests =
testGroup
"Propagation.Composite"
[ testGroup "injectContextAll" injectContextTests
, testGroup "extractContextFirst" extractContextTests
, testGroup "injectBaggageAll" injectBaggageTests
, testGroup "extractBaggageAll" extractBaggageTests
, testGroup "token lookup" tokenTests
]
-- A pair of distinct trace ids so the "first wins" extract test can tell which
-- propagator produced the context.
traceIdA :: ByteString
traceIdA = "4bf92f3577b34da6a3ce929d0e0e4736"
traceIdB :: ByteString
traceIdB = "0af7651916cd43dd8448eb211c80319c"
spanIdA :: ByteString
spanIdA = "00f067aa0ba902b7"
injectContextTests :: [TestTree]
injectContextTests =
[ testCase "writes every configured format for the active span" $ do
headers <- run (withSpan "outbound" (injectContextAll [w3cTraceContext, b3Single]))
let names = map fst headers
assertBool "traceparent present" (traceparentHeader `elem` names)
assertBool "b3 present" (b3Header `elem` names)
, testCase "an empty propagator list emits no headers" $ do
headers <- run (withSpan "outbound" (injectContextAll []))
headers @?= []
, testCase "emits no headers when there is no active span" $ do
headers <- run (injectContextAll [w3cTraceContext, b3Single])
headers @?= []
]
extractContextTests :: [TestTree]
extractContextTests =
[ testCase "takes the first propagator that parses" $ do
-- Only a b3 header is present; W3C is tried first and misses, B3 matches.
let headers = [(b3Header, traceIdB <> "-" <> spanIdA <> "-1")]
context = extractContextFirst [w3cTraceContext, b3Single] headers
fmap (traceIdToHex . spanContextTraceId) context @?= Just "0af7651916cd43dd8448eb211c80319c"
, testCase "order decides the winner when several formats are present" $ do
-- traceparent carries traceIdA, b3 carries traceIdB; W3C is listed first.
let headers =
[ (traceparentHeader, "00-" <> traceIdA <> "-" <> spanIdA <> "-01")
, (b3Header, traceIdB <> "-" <> spanIdA <> "-1")
]
context = extractContextFirst [w3cTraceContext, b3Single] headers
fmap (traceIdToHex . spanContextTraceId) context @?= Just "4bf92f3577b34da6a3ce929d0e0e4736"
, testCase "reordering the list reverses the winner" $ do
let headers =
[ (traceparentHeader, "00-" <> traceIdA <> "-" <> spanIdA <> "-01")
, (b3Header, traceIdB <> "-" <> spanIdA <> "-1")
]
context = extractContextFirst [b3Single, w3cTraceContext] headers
fmap (traceIdToHex . spanContextTraceId) context @?= Just "0af7651916cd43dd8448eb211c80319c"
, testCase "an empty propagator list never matches" $
extractContextFirst [] [(b3Header, traceIdB <> "-" <> spanIdA <> "-1")] @?= Nothing
, testCase "no matching headers yield Nothing" $
extractContextFirst [w3cTraceContext, b3Single, jaegerTraceContext] [] @?= Nothing
]
injectBaggageTests :: [TestTree]
injectBaggageTests =
[ testCase "writes every configured baggage format" $ do
let headers =
runPureEff (runBaggage (withBaggageEntry "tenant" "acme" (injectBaggageAll [w3cBaggage, jaegerBaggage])))
names = map fst headers
assertBool "W3C baggage header present" (baggageHeader `elem` names)
assertBool "uberctx- header present" ("uberctx-tenant" `elem` names)
, testCase "an empty propagator list emits no headers" $ do
let headers = runPureEff (runBaggage (withBaggageEntry "tenant" "acme" (injectBaggageAll [])))
headers @?= []
]
extractBaggageTests :: [TestTree]
extractBaggageTests =
[ testCase "merges entries from every format" $ do
let headers =
[ (baggageHeader, "fromw3c=1")
, ("uberctx-fromjaeger", "2")
]
merged = extractBaggageAll [w3cBaggage, jaegerBaggage] headers
lookupBaggageValue "fromw3c" merged @?= Just "1"
lookupBaggageValue "fromjaeger" merged @?= Just "2"
, testCase "a later propagator wins on a shared key" $ do
let headers =
[ (baggageHeader, "shared=w3c")
, ("uberctx-shared", "jaeger")
]
merged = extractBaggageAll [w3cBaggage, jaegerBaggage] headers
lookupBaggageValue "shared" merged @?= Just "jaeger"
]
tokenTests :: [TestTree]
tokenTests =
[ testCase "trace-context tokens resolve to their propagators" $ do
fmap traceContextName (traceContextByToken "tracecontext") @?= Just "tracecontext"
fmap traceContextName (traceContextByToken "b3") @?= Just "b3"
fmap traceContextName (traceContextByToken "b3multi") @?= Just "b3multi"
fmap traceContextName (traceContextByToken "jaeger") @?= Just "jaeger"
, testCase "an unknown or baggage-only token has no trace-context side" $ do
fmap traceContextName (traceContextByToken "baggage") @?= Nothing
fmap traceContextName (traceContextByToken "nonsense") @?= Nothing
, testCase "baggage tokens resolve to their propagators" $ do
fmap baggageName (baggageByToken "baggage") @?= Just "baggage"
fmap baggageName (baggageByToken "jaeger") @?= Just "jaeger"
, testCase "a trace-context-only token has no baggage side" $
fmap baggageName (baggageByToken "b3") @?= Nothing
, testCase "the standard values carry their expected tokens" $ do
traceContextName w3cTraceContext @?= "tracecontext"
traceContextName b3Single @?= "b3"
traceContextName b3Multi @?= "b3multi"
traceContextName jaegerTraceContext @?= "jaeger"
baggageName w3cBaggage @?= "baggage"
baggageName jaegerBaggage @?= "jaeger"
]
-- | Run a 'Tracer' computation through the in-memory interpreter, discarding the
-- captured spans and returning the computation's result.
run :: Eff '[Tracer, IOE] a -> IO a
run action = runEff $ do
captured <- newCapturedSpans
runTracerInMemory captured action