effectful-tracing-0.1.0.0: test/Effectful/Tracing/EnvConfigSpec.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module : Effectful.Tracing.EnvConfigSpec
-- Description : Tests for reading OTel configuration from the environment.
--
-- The parse is pure (a lookup function), so every case is exercised with a stub
-- environment built from an association list. The four readers each get their
-- own group: service-name resolution and its fallback into resource attributes,
-- resource-attribute decoding, propagator-token resolution (including the
-- defaults, @none@, and a token that feeds both lists), and the sampler name and
-- ratio handling.
module Effectful.Tracing.EnvConfigSpec
( tests
) where
import Data.Text (Text)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))
import Effectful.Tracing.Attribute (Attribute (Attribute), AttributeValue (AttrText))
import Effectful.Tracing.EnvConfig
( EnvConfig (baggagePropagators, resourceAttributes, serviceName, traceContextPropagators, tracesSampler)
, defaultEnvConfig
, parseEnvConfig
, parsePropagators
, parseSampler
, parseServiceName
)
import Effectful.Tracing.Propagation.Composite (baggageName, traceContextName)
import Effectful.Tracing.Sampler (samplerName)
-- | Build a lookup function from an association list of variable name to value.
stubEnv :: [(Text, Text)] -> (Text -> Maybe Text)
stubEnv entries name = lookup name entries
tests :: TestTree
tests =
testGroup
"EnvConfig"
[ testGroup "serviceName" serviceNameTests
, testGroup "resourceAttributes" resourceAttributeTests
, testGroup "propagators" propagatorTests
, testGroup "sampler" samplerTests
, testGroup "defaults" defaultTests
]
serviceNameTests :: [TestTree]
serviceNameTests =
[ testCase "reads OTEL_SERVICE_NAME" $
parseServiceName (stubEnv [("OTEL_SERVICE_NAME", "checkout")]) @?= Just "checkout"
, testCase "trims surrounding whitespace" $
parseServiceName (stubEnv [("OTEL_SERVICE_NAME", " checkout ")]) @?= Just "checkout"
, testCase "an empty value is treated as unset" $
parseServiceName (stubEnv [("OTEL_SERVICE_NAME", " ")]) @?= Nothing
, testCase "falls back to service.name in OTEL_RESOURCE_ATTRIBUTES" $
parseServiceName (stubEnv [("OTEL_RESOURCE_ATTRIBUTES", "service.name=billing,team=core")])
@?= Just "billing"
, testCase "OTEL_SERVICE_NAME wins over the resource attribute" $
parseServiceName
( stubEnv
[ ("OTEL_SERVICE_NAME", "checkout")
, ("OTEL_RESOURCE_ATTRIBUTES", "service.name=billing")
]
)
@?= Just "checkout"
, testCase "absent everywhere is Nothing" $
parseServiceName (stubEnv []) @?= Nothing
]
resourceAttributeTests :: [TestTree]
resourceAttributeTests =
[ testCase "decodes comma-separated key=value pairs" $
resourceAttributes (parseEnvConfig (stubEnv [("OTEL_RESOURCE_ATTRIBUTES", "team=core,region=us-east-1")]))
@?= [Attribute "region" (AttrText "us-east-1"), Attribute "team" (AttrText "core")]
, testCase "percent-decodes values" $
resourceAttributes (parseEnvConfig (stubEnv [("OTEL_RESOURCE_ATTRIBUTES", "note=a%20b")]))
@?= [Attribute "note" (AttrText "a b")]
, testCase "absent yields no attributes" $
resourceAttributes (parseEnvConfig (stubEnv [])) @?= []
]
propagatorTests :: [TestTree]
propagatorTests =
[ testCase "unset defaults to tracecontext + baggage" $ do
let (traces, baggages) = parsePropagators (stubEnv [])
map traceContextName traces @?= ["tracecontext"]
map baggageName baggages @?= ["baggage"]
, testCase "splits and resolves a token list in order" $ do
let (traces, baggages) = parsePropagators (stubEnv [("OTEL_PROPAGATORS", "b3, tracecontext, baggage")])
map traceContextName traces @?= ["b3", "tracecontext"]
map baggageName baggages @?= ["baggage"]
, testCase "jaeger contributes to both lists" $ do
let (traces, baggages) = parsePropagators (stubEnv [("OTEL_PROPAGATORS", "jaeger")])
map traceContextName traces @?= ["jaeger"]
map baggageName baggages @?= ["jaeger"]
, testCase "none disables all propagators" $ do
let (traces, baggages) = parsePropagators (stubEnv [("OTEL_PROPAGATORS", "tracecontext,none")])
map traceContextName traces @?= []
map baggageName baggages @?= []
, testCase "unrecognised tokens are ignored" $ do
let (traces, baggages) = parsePropagators (stubEnv [("OTEL_PROPAGATORS", "nonsense,b3multi")])
map traceContextName traces @?= ["b3multi"]
map baggageName baggages @?= []
]
samplerTests :: [TestTree]
samplerTests =
[ testCase "always_on" $
samplerName (parseSampler (stubEnv [("OTEL_TRACES_SAMPLER", "always_on")])) @?= "AlwaysOn"
, testCase "always_off" $
samplerName (parseSampler (stubEnv [("OTEL_TRACES_SAMPLER", "always_off")])) @?= "AlwaysOff"
, testCase "traceidratio uses the arg" $
samplerName
( parseSampler
( stubEnv
[ ("OTEL_TRACES_SAMPLER", "traceidratio")
, ("OTEL_TRACES_SAMPLER_ARG", "0.25")
]
)
)
@?= "TraceIdRatioBased{0.25}"
, testCase "traceidratio defaults the ratio to 1.0 when the arg is absent" $
samplerName (parseSampler (stubEnv [("OTEL_TRACES_SAMPLER", "traceidratio")]))
@?= "TraceIdRatioBased{1.0}"
, testCase "parentbased_traceidratio wraps the ratio sampler" $
samplerName
( parseSampler
( stubEnv
[ ("OTEL_TRACES_SAMPLER", "parentbased_traceidratio")
, ("OTEL_TRACES_SAMPLER_ARG", "0.1")
]
)
)
@?= "ParentBased{TraceIdRatioBased{0.1}}"
, testCase "an unrecognised name degrades to parentbased_always_on" $
samplerName (parseSampler (stubEnv [("OTEL_TRACES_SAMPLER", "made_up")]))
@?= "ParentBased{AlwaysOn}"
, testCase "unset degrades to parentbased_always_on" $
samplerName (parseSampler (stubEnv [])) @?= "ParentBased{AlwaysOn}"
]
defaultTests :: [TestTree]
defaultTests =
[ testCase "defaultEnvConfig has no service name" $
serviceName defaultEnvConfig @?= Nothing
, testCase "defaultEnvConfig has the default propagators" $ do
map traceContextName (traceContextPropagators defaultEnvConfig) @?= ["tracecontext"]
map baggageName (baggagePropagators defaultEnvConfig) @?= ["baggage"]
, testCase "defaultEnvConfig has the default sampler" $
samplerName (tracesSampler defaultEnvConfig) @?= "ParentBased{AlwaysOn}"
]