packages feed

method-0.1.0.0: src/Test/Method/Monitor/Internal.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- |
-- Module : Test.Method.Monitor.Internal
-- License: BSD-3
-- Maintainer: autotaker@gmail.com
-- Stability: experimental
module Test.Method.Monitor.Internal where

import Data.Typeable (typeOf)
import RIO
  ( IORef,
    MonadIO,
    SomeException,
    SomeRef,
    Typeable,
    modifySomeRef,
    newIORef,
    newSomeRef,
    readIORef,
    writeIORef,
  )

-- | 'Tick' represents call identifier
newtype Tick = Tick {unTick :: Int}
  deriving (Eq, Ord, Show, Enum)

-- | @'Event' args ret@ is a function call event
data Event args ret
  = Enter {eventTick :: !Tick, eventArgs :: !args}
  | Leave
      { eventTick :: !Tick,
        eventEnterTick :: !Tick,
        eventRet :: !(Either (EqUptoShow SomeException) ret)
      }
  deriving (Eq, Ord, Show)

type Clock = IORef Tick

-- | newtype to implement show instance which shows its type.
newtype ShowType a = ShowType a
  deriving (Eq, Ord)

instance Typeable a => Show (ShowType a) where
  show (ShowType a) = show (typeOf a)

-- | newtype to compare values via 'show'
newtype EqUptoShow a = EqUptoShow a

instance Show a => Show (EqUptoShow a) where
  show (EqUptoShow a) = show a

instance Show a => Eq (EqUptoShow a) where
  a == b = show a == show b

instance Show a => Ord (EqUptoShow a) where
  compare a b = compare (show a) (show b)

-- | @Monitor arg ret@ is an event monitor of methods,
-- which logs method calls.
data Monitor args ret = Monitor
  { monitorTrace :: !(SomeRef [Event args ret]),
    monitorClock :: !Clock
  }

-- | Generate new instance of 'Monitor'
newMonitor :: IO (Monitor args ret)
newMonitor = Monitor <$> newSomeRef [] <*> newIORef (Tick 0)

-- | Increment the clock and return the current tick.
{-# INLINE tick #-}
tick :: MonadIO m => Monitor args ret -> m Tick
tick Monitor {monitorClock = clock} = do
  t <- readIORef clock
  writeIORef clock $! succ t
  pure t

-- | logs an event
{-# INLINE logEvent #-}
logEvent :: MonadIO m => Monitor args ret -> Event args ret -> m ()
logEvent Monitor {monitorTrace = tr} event = modifySomeRef tr (event :)