nvfetcher-0.8.0.0: test/Utils.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Utils where
import Control.Concurrent.Async
import Control.Concurrent.Extra
import Control.Concurrent.STM
import Control.Exception (Handler (..), SomeException, bracket, catches, throwIO)
import Control.Monad (void)
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import Data.Default (def)
import Data.Map (Map)
import Data.Maybe (isJust)
import Development.Shake
import Development.Shake.Database
import NvFetcher.Config
import NvFetcher.Core (coreRules)
import NvFetcher.Types
import NvFetcher.Types.ShakeExtras
import qualified System.Directory.Extra as Extra
import qualified System.IO.Extra as Extra
import System.Time.Extra
import Test.Hspec
import UnliftIO (MonadUnliftIO (withRunInIO))
--------------------------------------------------------------------------------
runAction :: ActionQueue -> Action a -> IO (Maybe a)
runAction chan x = do
barrier <- newBarrier
atomically $ writeTQueue chan $ x >>= liftIO . signalBarrier barrier
-- TODO
timeout 30 $ waitBarrier barrier
type ActionQueue = TQueue (Action ())
newAsyncActionQueue :: Map PackageKey Package -> Config -> IO (ActionQueue, Async ())
newAsyncActionQueue pkgs config = Extra.withTempDir $ \dir -> do
shakeExtras <- liftIO $ initShakeExtras config {buildDir = dir} pkgs mempty
chan <- atomically newTQueue
(getShakeDb, _) <-
shakeOpenDatabase
shakeOptions
{ shakeExtra = addShakeExtra shakeExtras (shakeExtra shakeOptions),
shakeFiles = dir,
shakeVerbosity = Quiet
}
coreRules
shakeDb <- getShakeDb
let runner restore = do
-- sequentially
act <- liftIO $ atomically $ readTQueue chan
catches
(restore $ void $ shakeRunDatabase shakeDb [act])
[ Handler $ \(e :: AsyncCancelled) -> throwIO e,
Handler $ \(e :: SomeException) -> putStrLn $ "an exception arose in action runner: " <> show e
]
runner restore
runnerTask <- asyncWithUnmask $ \r -> runner r
pure (chan, runnerTask)
--------------------------------------------------------------------------------
aroundShake :: SpecWith ActionQueue -> Spec
aroundShake = aroundShake' mempty def
aroundShake' :: Map PackageKey Package -> Config -> SpecWith ActionQueue -> Spec
aroundShake' pkgs config = aroundAll $ \f ->
bracket
(newAsyncActionQueue pkgs config)
(\(_, runnerTask) -> cancel runnerTask)
(\(chan, _) -> f chan)
shouldReturnJust :: (Show a, Eq a, MonadUnliftIO m) => m (Maybe a) -> a -> m ()
shouldReturnJust f x = withRunInIO $ \run -> run f `shouldReturn` Just x
shouldBeJust :: (MonadIO m, Show a) => Maybe a -> m ()
shouldBeJust x = liftIO $ x `shouldSatisfy` isJust
shouldExist :: (MonadIO m) => FilePath -> m ()
shouldExist path = liftIO $ Extra.doesFileExist path `shouldReturn` True
specifyChan :: (HasCallStack) => String -> ReaderT ActionQueue IO () -> SpecWith ActionQueue
specifyChan s m = specify s $ \r -> runReaderT m r
runActionChan :: Action a -> ReaderT ActionQueue IO (Maybe a)
runActionChan m = ask >>= \chan -> liftIO $ runAction chan m