dejafu/dejafu-tests/Cases/SingleThreaded.hs
2017-09-06 16:03:28 +01:00

370 lines
11 KiB
Haskell

{-# LANGUAGE CPP #-}
module Cases.SingleThreaded where
import Control.Exception (ArithException(..), ArrayException(..))
import Data.Maybe (isNothing)
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.Classy
import Test.DejaFu.Conc (ConcT, subconcurrency)
import Utils
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>), (<*>))
#endif
tests :: [Test]
tests =
[ testGroup "MVar" . hUnitTestToTests $ test
[ 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" . hUnitTestToTests $ test
[ testDejafu crefRead "read" $ gives' [True]
, testDejafu crefWrite "write" $ gives' [True]
, testDejafu crefModify "modify" $ gives' [True]
, testDejafu crefTicketPeek "ticket peek" $ gives' [True]
, testDejafu crefTicketPeek "ticket peek (2)" $ 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]
, testDejafu excToMain1 "throw to main (uncaught)" $ gives [Left UncaughtException]
, testDejafu excToMain2 "throw to main (caught)" $ gives' [()]
, testDejafu excMFail "monadfail" $ gives [Left UncaughtException]
, testDejafu excMFailSTM "monadfail (stm)" $ gives [Left UncaughtException]
]
, testGroup "Capabilities" . hUnitTestToTests $ test
[ testDejafu capsGet "get" $ gives' [True]
, testDejafu capsSet "set" $ gives' [True]
]
, testGroup "Subconcurrency" . hUnitTestToTests $ test
[ testDejafu scDeadlock1 "deadlock1" $ gives' [Left Deadlock]
, testDejafu scDeadlock2 "deadlock2" $ gives' [(Left Deadlock, ())]
, testDejafu scSuccess "success" $ gives' [Right ()]
]
]
--------------------------------------------------------------------------------
-- @MVar@s
-- | An empty @MVar@ cannot be taken from.
emptyMVarTake :: MonadConc m => m ()
emptyMVarTake = do
var <- newEmptyMVar
takeMVar var
-- | An empty @MVar@ cannot be taken from.
emptyMVarTryTake :: MonadConc m => m Bool
emptyMVarTryTake = do
var <- newEmptyMVar
res <- tryTakeMVar var
return $ (res :: Maybe ()) == Nothing
-- | An empty @MVar@ can be put into.
emptyMVarPut :: MonadConc m => m Bool
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
-- | 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
-- | A full @MVar@ cannot be put into.
fullMVarPut :: MonadConc m => m ()
fullMVarPut = do
var <- newMVar ()
putMVar var ()
-- | A full @MVar@ cannot be put into.
fullMVarTryPut :: MonadConc m => m Bool
fullMVarTryPut = do
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
-- | A full @MVar@ can be read from.
fullMVarRead :: MonadConc m => m Bool
fullMVarRead = do
var <- newMVar ()
(() ==) <$> readMVar var
-- | A full @MVar@ can be read from.
fullMVarTryRead :: MonadConc m => m Bool
fullMVarTryRead = do
var <- newMVar ()
(Just () ==) <$> tryReadMVar 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)
atomicModifyCRef 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 @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'
-- | 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 @TVar@ can be written to.
stmWrite :: MonadConc m => m Bool
stmWrite =
(6==) <$> atomically (do { v <- newTVar (5::Int); writeTVar v 6; readTVar v })
-- | A @TVar@ preserves its value between transactions.
stmPreserve :: MonadConc m => m Bool
stmPreserve = do
ctv <- atomically $ newTVar (5::Int)
(5==) <$> atomically (readTVar 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 $ newTVar (5::Int)
atomically $ orElse retry (writeTVar ctv 6)
(6==) <$> atomically (readTVar ctv)
-- | An exception can be caught by an appropriate handler.
stmCatch1 :: MonadConc m => m Bool
stmCatch1 = do
ctv <- atomically $ newTVar (5::Int)
atomically $ catchArithException
(throwSTM Overflow)
(\_ -> writeTVar ctv 6)
(6==) <$> atomically (readTVar ctv)
-- | Nested exception handlers can catch different types of exception.
stmCatch2 :: MonadConc m => m Bool
stmCatch2 = do
ctv <- atomically $ newTVar (5::Int)
atomically $ catchArithException
(catchArrayException
(throwSTM Overflow)
(\_ -> writeTVar ctv 0))
(\_ -> writeTVar ctv 6)
(6==) <$> atomically (readTVar 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)
-- | 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 ())
-- | 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"
--------------------------------------------------------------------------------
-- 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)