mirror of
https://github.com/barrucadu/dejafu.git
synced 2024-12-18 19:11:37 +03:00
250 lines
7.3 KiB
Haskell
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
|