dejafu/dejafu-tests/Cases/SingleThreaded.hs

367 lines
11 KiB
Haskell
Raw Normal View History

2015-12-01 08:13:47 +03:00
{-# LANGUAGE CPP #-}
module Cases.SingleThreaded where
2015-11-16 05:48:54 +03:00
import Control.Exception (ArithException(..), ArrayException(..))
import Data.Maybe (isNothing)
2015-11-16 05:48:54 +03:00
import Test.DejaFu (Failure(..), gives, gives')
import Test.HUnit.DejaFu (testDejafu)
import Control.Concurrent.Classy
import Test.DejaFu.Conc (ConcT, subconcurrency)
2015-11-16 05:48:54 +03:00
import Common
2015-11-16 05:48:54 +03:00
2015-12-01 08:13:47 +03:00
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>), (<*>))
#endif
tests :: [Test]
tests =
[ testGroup "MVar"
[ testDejafu emptyMVarTake "empty take" $ gives [Left Deadlock]
, testDejafu emptyMVarTryTake "empty take (try)" $ gives' [True]
, testDejafu emptyMVarPut "empty put" $ gives' [True]
, testDejafu emptyMVarTryPut "empty put (try)" $ gives' [True]
, testDejafu emptyMVarRead "empty read" $ gives [Left Deadlock]
, testDejafu emptyMVarTryRead "empty read (try)" $ gives' [True]
, testDejafu fullMVarPut "full put" $ gives [Left Deadlock]
, testDejafu fullMVarTryPut "full put (try)" $ gives' [True]
, testDejafu fullMVarTake "full take" $ gives' [True]
, testDejafu fullMVarTryTake "full take (try)" $ gives' [True]
, testDejafu fullMVarRead "full read" $ gives' [True]
, testDejafu fullMVarTryRead "full read (try)" $ gives' [True]
]
, testGroup "CRef"
[ testDejafu crefRead "read" $ gives' [True]
, testDejafu crefWrite "write" $ gives' [True]
, testDejafu crefModify "modify" $ gives' [True]
, testDejafu crefTicketPeek "ticket peek" $ gives' [True]
2017-09-08 01:37:13 +03:00
, testDejafu crefTicketPeek2 "ticket peek (2)" $ gives' [True]
, testDejafu crefCas1 "cas" $ gives' [(True, True)]
, testDejafu crefCas2 "cas (modified)" $ gives' [(False, False)]
]
, testGroup "STM"
[ 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"
[ 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]
, testDejafu excToMain1 "throw to main (uncaught)" $ gives [Left UncaughtException]
, testDejafu excToMain2 "throw to main (caught)" $ gives' [()]
2017-08-21 18:22:49 +03:00
, testDejafu excMFail "monadfail" $ gives [Left UncaughtException]
, testDejafu excMFailSTM "monadfail (stm)" $ gives [Left UncaughtException]
]
, testGroup "Capabilities"
[ testDejafu capsGet "get" $ gives' [True]
, testDejafu capsSet "set" $ gives' [True]
]
, testGroup "Subconcurrency"
[ testDejafu scDeadlock1 "deadlock1" $ gives' [Left Deadlock]
, testDejafu scDeadlock2 "deadlock2" $ gives' [(Left Deadlock, ())]
, testDejafu scSuccess "success" $ gives' [Right ()]
]
2015-11-16 05:48:54 +03:00
]
--------------------------------------------------------------------------------
2016-03-23 06:36:07 +03:00
-- @MVar@s
2015-11-16 05:48:54 +03:00
2016-03-23 06:36:07 +03:00
-- | An empty @MVar@ cannot be taken from.
emptyMVarTake :: MonadConc m => m ()
2016-03-23 06:36:07 +03:00
emptyMVarTake = do
var <- newEmptyMVar
takeMVar var
-- | An empty @MVar@ cannot be taken from.
emptyMVarTryTake :: MonadConc m => m Bool
emptyMVarTryTake = do
2016-03-23 06:36:07 +03:00
var <- newEmptyMVar
res <- tryTakeMVar var
2015-11-16 05:48:54 +03:00
return $ (res :: Maybe ()) == Nothing
2016-03-23 06:36:07 +03:00
-- | An empty @MVar@ can be put into.
emptyMVarPut :: MonadConc m => m Bool
2016-03-23 06:36:07 +03:00
emptyMVarPut = do
var <- newEmptyMVar
putMVar var 7
(==7) <$> readMVar var
-- | An empty @MVar@ can be put into.
emptyMVarTryPut :: MonadConc m => m Bool
emptyMVarTryPut = do
var <- newEmptyMVar
tryPutMVar var 7
(==7) <$> readMVar var
2016-03-23 06:36:07 +03:00
-- | An empty @MVar@ cannot be read from.
emptyMVarRead :: MonadConc m => m ()
emptyMVarRead = do
var <- newEmptyMVar
readMVar var
-- | An empty @MVar@ cannot be read from.
emptyMVarTryRead :: MonadConc m => m Bool
emptyMVarTryRead = do
var <- newEmptyMVar
isNothing <$> tryReadMVar var
2016-03-23 06:36:07 +03:00
-- | A full @MVar@ cannot be put into.
fullMVarPut :: MonadConc m => m ()
2016-03-23 06:36:07 +03:00
fullMVarPut = do
var <- newMVar ()
putMVar var ()
-- | A full @MVar@ cannot be put into.
fullMVarTryPut :: MonadConc m => m Bool
fullMVarTryPut = do
2016-03-23 06:36:07 +03:00
var <- newMVar ()
not <$> tryPutMVar var ()
-- | A full @MVar@ can be taken from.
fullMVarTake :: MonadConc m => m Bool
fullMVarTake = do
var <- newMVar ()
(() ==) <$> takeMVar var
-- | A full @MVar@ can be taken from.
fullMVarTryTake :: MonadConc m => m Bool
fullMVarTryTake = do
var <- newMVar ()
(Just () ==) <$> tryTakeMVar var
2016-03-23 06:36:07 +03:00
-- | A full @MVar@ can be read from.
fullMVarRead :: MonadConc m => m Bool
fullMVarRead = do
var <- newMVar ()
(() ==) <$> readMVar var
2015-11-16 05:48:54 +03:00
-- | A full @MVar@ can be read from.
fullMVarTryRead :: MonadConc m => m Bool
fullMVarTryRead = do
var <- newMVar ()
(Just () ==) <$> tryReadMVar var
2015-11-16 05:48:54 +03:00
--------------------------------------------------------------------------------
-- @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)
2016-03-26 09:37:27 +03:00
atomicModifyCRef ref (\i -> (i+1, ()))
2015-11-16 05:48:54 +03:00
(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 @Ticket@ contains the value as of when it was created (and
-- casCRef returns a correct new ticket).
crefTicketPeek2 :: MonadConc m => m Bool
crefTicketPeek2 = do
ref <- newCRef (5::Int)
tick <- readForCAS ref
(_, tick') <- casCRef ref tick 6
(6==) <$> peekTicket tick'
2015-11-16 05:48:54 +03:00
-- | 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
2016-03-23 06:27:51 +03:00
-- | A @TVar@ can be written to.
2015-11-16 05:48:54 +03:00
stmWrite :: MonadConc m => m Bool
stmWrite =
2016-03-23 06:27:51 +03:00
(6==) <$> atomically (do { v <- newTVar (5::Int); writeTVar v 6; readTVar v })
2015-11-16 05:48:54 +03:00
2016-03-23 06:27:51 +03:00
-- | A @TVar@ preserves its value between transactions.
2015-11-16 05:48:54 +03:00
stmPreserve :: MonadConc m => m Bool
stmPreserve = do
2016-03-23 06:27:51 +03:00
ctv <- atomically $ newTVar (5::Int)
(5==) <$> atomically (readTVar ctv)
2015-11-16 05:48:54 +03:00
-- | 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
2016-03-23 06:27:51 +03:00
ctv <- atomically $ newTVar (5::Int)
atomically $ orElse retry (writeTVar ctv 6)
2015-11-16 05:48:54 +03:00
2016-03-23 06:27:51 +03:00
(6==) <$> atomically (readTVar ctv)
2015-11-16 05:48:54 +03:00
-- | An exception can be caught by an appropriate handler.
stmCatch1 :: MonadConc m => m Bool
stmCatch1 = do
2016-03-23 06:27:51 +03:00
ctv <- atomically $ newTVar (5::Int)
2015-11-16 05:48:54 +03:00
atomically $ catchArithException
(throwSTM Overflow)
2016-03-23 06:27:51 +03:00
(\_ -> writeTVar ctv 6)
2015-11-16 05:48:54 +03:00
2016-03-23 06:27:51 +03:00
(6==) <$> atomically (readTVar ctv)
2015-11-16 05:48:54 +03:00
-- | Nested exception handlers can catch different types of exception.
stmCatch2 :: MonadConc m => m Bool
stmCatch2 = do
2016-03-23 06:27:51 +03:00
ctv <- atomically $ newTVar (5::Int)
2015-11-16 05:48:54 +03:00
atomically $ catchArithException
(catchArrayException
(throwSTM Overflow)
2016-03-23 06:27:51 +03:00
(\_ -> writeTVar ctv 0))
(\_ -> writeTVar ctv 6)
2015-11-16 05:48:54 +03:00
2016-03-23 06:27:51 +03:00
(6==) <$> atomically (readTVar ctv)
2015-11-16 05:48:54 +03:00
--------------------------------------------------------------------------------
-- 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)
2015-11-30 19:17:09 +03:00
-- | Throw an exception to the main thread with 'throwTo', without a
-- handler.
excToMain1 :: MonadConc m => m ()
excToMain1 = do
tid <- myThreadId
throwTo tid Overflow
-- | Throw an exception to the main thread with 'throwTo', with a
-- handler.
excToMain2 :: MonadConc m => m ()
excToMain2 = do
tid <- myThreadId
catchArithException (throwTo tid Overflow) (\_ -> pure ())
2017-08-21 18:22:49 +03:00
-- | Throw an exception using 'fail'. Using 'ConcT' directly to avoid
-- a 'MonadFail' constraint, which won't work with base < 4.9.
excMFail :: Monad n => ConcT r n (Either Failure ())
excMFail = fail "hello world"
-- | Throw an exception in an STM transaction using 'fail'.
excMFailSTM :: Monad n => ConcT r n (Either Failure ())
excMFailSTM = atomically $ fail "hello world"
2015-11-30 19:17:09 +03:00
--------------------------------------------------------------------------------
-- 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
--------------------------------------------------------------------------------
-- Subconcurrency
-- | Subcomputation deadlocks.
scDeadlock1 :: Monad n => ConcT r n (Either Failure ())
scDeadlock1 = subconcurrency (newEmptyMVar >>= readMVar)
-- | Subcomputation deadlocks, and action after it still happens.
scDeadlock2 :: Monad n => ConcT r n (Either Failure (), ())
scDeadlock2 = do
var <- newMVar ()
(,) <$> subconcurrency (putMVar var ()) <*> readMVar var
-- | Subcomputation successfully completes.
scSuccess :: Monad n => ConcT r n (Either Failure ())
scSuccess = do
var <- newMVar ()
subconcurrency (takeMVar var)