packages feed

grapesy-1.0.0: test-grapesy/Test/Util.hs

{-# LANGUAGE CPP #-}

module Test.Util (
    -- * Timeouts
    Timeout(..)
  , within

    -- * Files
  , withTemporaryFile
  ) where

import Control.Concurrent
import Control.Exception
import Control.Monad.Catch
import Control.Monad.IO.Class
import GHC.Stack
import System.IO
import System.IO.Temp

{-------------------------------------------------------------------------------
  Timeouts
-------------------------------------------------------------------------------}

data Timeout = forall a. Show a => Timeout a CallStack
  deriving anyclass (Exception)

deriving stock instance Show Timeout

-- | Limit execution time of an action
within :: forall m a info.
     (HasCallStack, Show info, MonadIO m, MonadMask m)
  => Int   -- ^ Maximum duration in seconds
  -> info  -- ^ Additional information to include in the exception if thrown
  -> m a -> m a
within t info io = do
    me <- liftIO $ myThreadId

    let timer :: IO ()
        timer = do
            interruptible $ -- Ensure that the timer can be killed
              threadDelay (t * 1_000_000)
            throwTo me $ Timeout info callStack

        startTimer :: m ThreadId
        startTimer = liftIO $ forkIO timer

        stopTimer :: ThreadId -> ExitCase a -> m ()
        stopTimer tid _ = liftIO $ killThread tid

    fmap fst $
      generalBracket startTimer stopTimer $ \_ -> io

withTemporaryFile :: (FilePath -> IO a) -> IO a
withTemporaryFile k =
    withSystemTempFile "grapesy-test-suite.txt" (\fp h -> hClose h >> k fp)