packages feed

hs-opentelemetry-instrumentation-auto-0.1.0.0: test/Main.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE LambdaCase #-}
module Main (main) where

import           Data.Kind (Constraint)
import           Data.Text (Text)
import qualified Data.HashMap.Strict as H
import           OpenTelemetry.Attributes
import qualified OpenTelemetry.Context as Context
import           OpenTelemetry.Context.ThreadLocal
import           OpenTelemetry.Exporter.InMemory
import           OpenTelemetry.Trace
import           OpenTelemetry.Trace.Core
import           OpenTelemetry.Trace.Sampler
import           Test.Tasty
import           Test.Tasty.HUnit
import           UnliftIO hiding (getChanContents)

data SpanInfo = SpanInfo
  { name :: Text
  , parentName :: Maybe Text
  , attrs :: H.HashMap Text Attribute
  } deriving (Show, Eq)

mkSpanInfo :: ImmutableSpan -> IO SpanInfo
mkSpanInfo s = do
  parentSpan <- traverse unsafeReadSpan $ spanParent s
  pure SpanInfo
    { name = spanName s
    , parentName = spanName <$> parentSpan
    , attrs = H.delete "thread.id" . snd . getAttributes $ spanAttributes s
    }

withGlobalTracing :: (OutChan ImmutableSpan -> IO a) -> IO a
withGlobalTracing act = do
  _ <- attachContext Context.empty
  bracket
    initializeTracing
    (shutdownTracerProvider . fst)
    (\(_, ref) -> act ref)

initializeTracing :: IO (TracerProvider, OutChan ImmutableSpan)
initializeTracing = do
  (_, tracerOptions') <- getTracerProviderInitializationOptions

  (inMemoryProc, spansChan) <- inMemoryChannelExporter
  let processors' = [inMemoryProc]

  provider <-
    createTracerProvider
      processors'
      tracerOptions' {tracerProviderOptionsSampler = alwaysOn}
  setGlobalTracerProvider provider

  pure (provider, spansChan)

type Instrumented a = IO a

t1 :: Instrumented ()
t1 = do
  t2
  t3
  t2

t2 :: Instrumented ()
t2 = pure ()

t3 :: IO ()
t3 = pure ()

type NotInstrumented = IO

t4 :: NotInstrumented ()
t4 = t2

type InstrumentC :: Constraint
type InstrumentC = ()

t5 :: InstrumentC => IO ()
t5 = t2

type NoInstrumentC :: Constraint
type NoInstrumentC = ()

t6 :: NoInstrumentC => Instrumented ()
t6 = t2

type Partial a b = IO b

t7 :: Partial Bool ()
t7 = t2

t8 :: Partial () ()
t8 = t2

type WildCard = IO
type WildCardX = IO

t9 :: WildCard (Maybe Bool)
t9 = pure Nothing

t10 :: WildCard ()
t10 = pure ()

t11 :: WildCardX (Maybe Bool)
t11 = pure Nothing

type C1 :: Constraint
type C1 = ()

type C2 :: Constraint
type C2 = ()

t12 :: C1 => IO ()
t12 = pure ()

t13 :: (C1, C2) => IO ()
t13 = pure ()

type X1 :: Constraint
type X1 = ()

type X2 :: Constraint
type X2 = ()

t14 :: (C1, X1, C2, X2) => IO ()
t14 = pure ()

t15 :: (X1, X2) => Instrumented ()
t15 = pure ()

t16 :: X2 => Instrumented ()
t16 = pure ()

t17 :: a -> Instrumented a
t17 = pure

main :: IO ()
main =
  withGlobalTracing $ \spansChan -> do
    defaultMain (testTree spansChan)

testTree :: OutChan ImmutableSpan -> TestTree
testTree spansChan = testGroup "Tests"
  [ testCase "nested spans" (nestedSpans spansChan)
  , testCase "ignore excluded constructor" (excludedCon spansChan)
  , testCase "simple constraint rule" (simpleConstraint spansChan)
  , testCase "ignore excluded constraint" (excludeConstraint spansChan)
  , testCase "partially applied constructor rule" (partialCon spansChan)
  , testCase "rule with wildcard placeholder" (wildCard spansChan)
  , testCase "multi constraint rule" (multiPred spansChan)
  , testCase "multi constraint exclusion" (multiPredX spansChan)
  , testCase "point-free" (pointFree spansChan)
  ]

nestedSpans :: OutChan ImmutableSpan -> Assertion
nestedSpans spansChan = do
  t1
  spans <- getSpans spansChan
  spans @?=
    [ spanInfo "67" "t2" (Just "t1")
    , spanInfo "67" "t2" (Just "t1")
    , spanInfo "61" "t1" Nothing
    ]

excludedCon :: OutChan ImmutableSpan -> Assertion
excludedCon spansChan = do
  t4
  spans <- getSpans spansChan
  spans @?=
    [ spanInfo "67" "t2" Nothing
    ]

simpleConstraint :: OutChan ImmutableSpan -> Assertion
simpleConstraint spansChan = do
  t5
  spans <- getSpans spansChan
  spans @?=
    [ spanInfo "67" "t2" (Just "t5")
    , spanInfo "81" "t5" Nothing
    ]

excludeConstraint :: OutChan ImmutableSpan -> Assertion
excludeConstraint spansChan = do
  t6
  spans <- getSpans spansChan
  spans @?=
    [ spanInfo "67" "t2" Nothing ]

partialCon :: OutChan ImmutableSpan -> Assertion
partialCon spansChan = do
  t7
  t8
  spans <- getSpans spansChan
  spans @?=
    [ spanInfo "67" "t2" Nothing
    , spanInfo "67" "t2" (Just "t8")
    , spanInfo "95" "t8" Nothing
    ]

wildCard :: OutChan ImmutableSpan -> Assertion
wildCard spansChan = do
  _ <- t9
  t10
  _ <- t11
  spans <- getSpans spansChan
  spans @?=
    [ spanInfo "101" "t9" Nothing ]

multiPred :: OutChan ImmutableSpan -> Assertion
multiPred spansChan = do
  t12
  t13
  spans <- getSpans spansChan
  spans @?=
    [ spanInfo "119" "t13" Nothing ]

multiPredX :: OutChan ImmutableSpan -> Assertion
multiPredX spansChan = do
  t14
  t15
  t16
  spans <- getSpans spansChan
  spans @?=
    [ spanInfo "134" "t16" Nothing ]

pointFree :: OutChan ImmutableSpan -> Assertion
pointFree spansChan = do
  t17 ()
  spans <- getSpans spansChan
  spans @?=
    [ spanInfo "137" "t17" Nothing ]

spanInfo :: Text -> Text -> Maybe Text -> SpanInfo
spanInfo lineNo funName mParentName =
  SpanInfo
    { name = funName
    , parentName = mParentName
    , attrs =
      [ ("code.lineno", AttributeValue (TextAttribute lineNo))
      , ("code.filepath", AttributeValue (TextAttribute "test/Main.hs"))
      , ("code.function", AttributeValue (TextAttribute funName))
      , ("code.namespace", AttributeValue (TextAttribute "Main"))
      , ("code.package", AttributeValue (TextAttribute "main"))
      ]
    }

getSpans :: OutChan ImmutableSpan -> IO [SpanInfo]
getSpans chan = do
  (element, _) <- tryReadChan chan
  tryRead element >>= \case
    Nothing -> do
      addPlaceholderSpan
      pure []
    Just e -> do
      si <- mkSpanInfo e
      (si :) <$> getSpans chan

addPlaceholderSpan :: IO ()
addPlaceholderSpan = do
  tp <- getGlobalTracerProvider
  let tracer = makeTracer tp "test" tracerOptions
  inSpan tracer "_placeholder_" defaultSpanArguments $ pure ()