dejafu/dejafu-tests/Cases/SingleThreaded.hs

250 lines
7.3 KiB
Haskell

module Cases.SingleThreaded (tests) where
import Control.Exception (ArithException(..), ArrayException(..))
import Test.DejaFu (Failure(..), gives, gives')
import Test.Framework (Test, testGroup)
import Test.Framework.Providers.HUnit (hUnitTestToTests)
import Test.HUnit (test)
import Test.HUnit.DejaFu (testDejafu)
import Control.Concurrent.CVar
import Control.Monad.Conc.Class
import Control.Monad.STM.Class
import Utils
tests :: [Test]
tests =
[ testGroup "CVar" . hUnitTestToTests $ test
[ testDejafu emptyCVarTake "empty take" $ gives' [True]
, testDejafu emptyCVarPut "empty put" $ gives' [()]
, testDejafu fullCVarPut "full put" $ gives' [True]
, testDejafu fullCVarTake "full take" $ gives' [True]
, testDejafu fullCVarRead "full read" $ gives' [True]
]
, testGroup "CRef" . hUnitTestToTests $ test
[ testDejafu crefRead "read" $ gives' [True]
, testDejafu crefWrite "write" $ gives' [True]
, testDejafu crefModify "modify" $ gives' [True]
, testDejafu crefTicketPeek "ticket peek" $ gives' [True]
, testDejafu crefCas1 "cas" $ gives' [(True, True)]
, testDejafu crefCas2 "cas (modified)" $ gives' [(False, False)]
]
, testGroup "STM" . hUnitTestToTests $ test
[ testDejafu stmWrite "write" $ gives' [True]
, testDejafu stmPreserve "write (across transactions)" $ gives' [True]
, testDejafu stmRetry "retry" $ gives [Left STMDeadlock]
, testDejafu stmOrElse "or else" $ gives' [True]
, testDejafu stmCatch1 "single catch" $ gives' [True]
, testDejafu stmCatch2 "nested catch" $ gives' [True]
]
, testGroup "Exceptions" . hUnitTestToTests $ test
[ testDejafu excCatch "single catch" $ gives' [True]
, testDejafu excNest "nested catch" $ gives' [True]
, testDejafu excEscape "uncaught" $ gives [Left UncaughtException]
, testDejafu excCatchAll "catch all" $ gives' [(True, True)]
, testDejafu excSTM "from stm" $ gives' [True]
]
, testGroup "Capabilities" . hUnitTestToTests $ test
[ testDejafu capsGet "get" $ gives' [True]
, testDejafu capsSet "set" $ gives' [True]
]
]
--------------------------------------------------------------------------------
-- @CVar@s
-- | An empty @CVar@ cannot be taken from.
emptyCVarTake :: MonadConc m => m Bool
emptyCVarTake = do
var <- newEmptyCVar
res <- tryTakeCVar var
return $ (res :: Maybe ()) == Nothing
-- | An empty @CVar@ can be put into.
emptyCVarPut :: MonadConc m => m ()
emptyCVarPut = do
var <- newEmptyCVar
putCVar var ()
-- | A full @CVar@ cannot be put into.
fullCVarPut :: MonadConc m => m Bool
fullCVarPut = do
var <- newCVar ()
not <$> tryPutCVar var ()
-- | A full @CVar@ can be taken from.
fullCVarTake :: MonadConc m => m Bool
fullCVarTake = do
var <- newCVar ()
(() ==) <$> takeCVar var
-- | A full @CVar@ can be read from.
fullCVarRead :: MonadConc m => m Bool
fullCVarRead = do
var <- newCVar ()
(() ==) <$> readCVar var
--------------------------------------------------------------------------------
-- @CRef@s
-- | A @CRef@ can be read from.
crefRead :: MonadConc m => m Bool
crefRead = do
ref <- newCRef (5::Int)
(5==) <$> readCRef ref
-- | A @CRef@ can be written to.
crefWrite :: MonadConc m => m Bool
crefWrite = do
ref <- newCRef (5::Int)
writeCRef ref 6
(6==) <$> readCRef ref
-- | A @CRef@ can be modified.
crefModify :: MonadConc m => m Bool
crefModify = do
ref <- newCRef (5::Int)
modifyCRef ref (\i -> (i+1, ()))
(6==) <$> readCRef ref
-- | A @Ticket@ contains the value as of when it was created.
crefTicketPeek :: MonadConc m => m Bool
crefTicketPeek = do
ref <- newCRef (5::Int)
tick <- readForCAS ref
writeCRef ref 6
(5==) <$> peekTicket tick
-- | A compare-and-swap can be done on a @CRef@ which hasn't been
-- modified.
crefCas1 :: MonadConc m => m (Bool, Bool)
crefCas1 = do
ref <- newCRef (5::Int)
tick <- readForCAS ref
(suc, _) <- casCRef ref tick 6
val <- readCRef ref
return (suc, 6 == val)
-- | A compare-and-swap cannot be done on a @CRef@ which has been
-- modified.
crefCas2 :: MonadConc m => m (Bool, Bool)
crefCas2 = do
ref <- newCRef (5::Int)
tick <- readForCAS ref
writeCRef ref 6
(suc, _) <- casCRef ref tick 7
val <- readCRef ref
return (suc, 7 == val)
--------------------------------------------------------------------------------
-- STM
-- | A @CTVar@ can be written to.
stmWrite :: MonadConc m => m Bool
stmWrite =
(6==) <$> atomically (do { v <- newCTVar (5::Int); writeCTVar v 6; readCTVar v })
-- | A @CTVar@ preserves its value between transactions.
stmPreserve :: MonadConc m => m Bool
stmPreserve = do
ctv <- atomically $ newCTVar (5::Int)
(5==) <$> atomically (readCTVar ctv)
-- | A transaction can be aborted, which blocks the thread.
stmRetry :: MonadConc m => m ()
stmRetry = atomically retry
-- | An abort can be caught by an @orElse@.
stmOrElse :: MonadConc m => m Bool
stmOrElse = do
ctv <- atomically $ newCTVar (5::Int)
atomically $ orElse retry (writeCTVar ctv 6)
(6==) <$> atomically (readCTVar ctv)
-- | An exception can be caught by an appropriate handler.
stmCatch1 :: MonadConc m => m Bool
stmCatch1 = do
ctv <- atomically $ newCTVar (5::Int)
atomically $ catchArithException
(throwSTM Overflow)
(\_ -> writeCTVar ctv 6)
(6==) <$> atomically (readCTVar ctv)
-- | Nested exception handlers can catch different types of exception.
stmCatch2 :: MonadConc m => m Bool
stmCatch2 = do
ctv <- atomically $ newCTVar (5::Int)
atomically $ catchArithException
(catchArrayException
(throwSTM Overflow)
(\_ -> writeCTVar ctv 0))
(\_ -> writeCTVar ctv 6)
(6==) <$> atomically (readCTVar ctv)
--------------------------------------------------------------------------------
-- Exceptions
-- | An exception can be caught by an appropriate handler.
excCatch :: MonadConc m => m Bool
excCatch = catchArithException
(throw Overflow)
(\_ -> return True)
-- | Nested exception handlers can catch different types of exception.
excNest :: MonadConc m => m Bool
excNest = catchArithException
(catchArrayException
(throw Overflow)
(\_ -> return False))
(\_ -> return True)
-- | Exceptions of the wrong type kill the computation
excEscape :: MonadConc m => m ()
excEscape = catchArithException
(throw $ IndexOutOfBounds "")
(\_ -> return undefined)
-- | @SomeException@ matches all exception types.
excCatchAll :: MonadConc m => m (Bool, Bool)
excCatchAll = do
a <- catchSomeException
(throw Overflow)
(\_ -> return True)
b <- catchSomeException
(throw $ IndexOutOfBounds "")
(\_ -> return True)
return (a, b)
-- | Exceptions thrown from STM can be caught.
excSTM :: MonadConc m => m Bool
excSTM = catchArithException
(atomically $ throwSTM Overflow)
(\_ -> return True)
--------------------------------------------------------------------------------
-- Capabilities
-- | Check that the capabilities are consistent when retrieved.
capsGet :: MonadConc m => m Bool
capsGet = (==) <$> getNumCapabilities <*> getNumCapabilities
-- | Check that the capabilities can be set.
capsSet :: MonadConc m => m Bool
capsSet = do
caps <- getNumCapabilities
setNumCapabilities $ caps + 1
(== caps + 1) <$> getNumCapabilities