2015-12-01 08:13:47 +03:00
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
|
2018-02-13 02:01:00 +03:00
|
|
|
module Integration.SingleThreaded where
|
2015-11-16 05:48:54 +03:00
|
|
|
|
|
|
|
import Control.Exception (ArithException(..), ArrayException(..))
|
2017-10-11 12:10:48 +03:00
|
|
|
import Test.DejaFu (Failure(..), gives, gives', isUncaughtException)
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2018-02-13 01:25:09 +03:00
|
|
|
import Control.Monad (replicateM_)
|
2016-03-23 06:13:25 +03:00
|
|
|
import Control.Concurrent.Classy
|
2018-02-13 01:25:09 +03:00
|
|
|
import Control.Monad.IO.Class (liftIO)
|
2017-09-20 01:40:38 +03:00
|
|
|
import Test.DejaFu.Conc (subconcurrency)
|
2018-02-13 01:25:09 +03:00
|
|
|
import qualified Data.IORef as IORef
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2017-09-20 01:17:02 +03:00
|
|
|
import Common
|
2015-11-16 05:48:54 +03:00
|
|
|
|
2018-02-12 18:26:02 +03:00
|
|
|
tests :: [TestTree]
|
2015-12-01 07:11:44 +03:00
|
|
|
tests =
|
2017-09-20 11:05:23 +03:00
|
|
|
[ testGroup "MVar" mvarTests
|
|
|
|
, testGroup "CRef" crefTests
|
|
|
|
, testGroup "STM" stmTests
|
|
|
|
, testGroup "Exceptions" exceptionTests
|
|
|
|
, testGroup "Capabilities" capabilityTests
|
|
|
|
, testGroup "Subconcurrency" subconcurrencyTests
|
2018-02-13 01:25:09 +03:00
|
|
|
, testGroup "IO" ioTests
|
2015-11-16 05:48:54 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
2017-09-20 11:05:23 +03:00
|
|
|
|
2018-02-12 18:26:02 +03:00
|
|
|
mvarTests :: [TestTree]
|
2018-02-16 23:04:52 +03:00
|
|
|
mvarTests = toTestList
|
2017-09-20 11:05:23 +03:00
|
|
|
[ djfu "Taking from an empty MVar blocks" (gives [Left Deadlock]) $ do
|
|
|
|
var <- newEmptyMVarInt
|
|
|
|
takeMVar var
|
|
|
|
|
|
|
|
, djfu "Non-blockingly taking from an empty MVar gives nothing" (gives' [Nothing]) $ do
|
|
|
|
var <- newEmptyMVarInt
|
|
|
|
tryTakeMVar var
|
|
|
|
|
|
|
|
, djfu "Putting into an empty MVar updates it" (gives' [True]) $ do
|
|
|
|
var <- newEmptyMVarInt
|
|
|
|
putMVar var 7
|
|
|
|
(==7) <$> readMVar var
|
|
|
|
|
|
|
|
, djfu "Non-blockingly putting into an empty MVar updates it" (gives' [True]) $ do
|
|
|
|
var <- newEmptyMVarInt
|
|
|
|
_ <- tryPutMVar var 7
|
|
|
|
(==7) <$> readMVar var
|
|
|
|
|
|
|
|
, djfu "Reading an empty MVar blocks" (gives [Left Deadlock]) $ do
|
|
|
|
var <- newEmptyMVarInt
|
|
|
|
readMVar var
|
|
|
|
|
|
|
|
, djfu "Non-blockingly reading an empty MVar gives nothing" (gives' [Nothing]) $ do
|
|
|
|
var <- newEmptyMVarInt
|
|
|
|
tryReadMVar var
|
|
|
|
|
|
|
|
, djfu "Putting into a full MVar blocks" (gives [Left Deadlock]) $ do
|
|
|
|
var <- newMVarInt 7
|
|
|
|
putMVar var 10
|
|
|
|
|
|
|
|
, djfu "Non-blockingly putting into a full MVar fails" (gives' [False]) $ do
|
|
|
|
var <- newMVarInt 7
|
|
|
|
tryPutMVar var 10
|
|
|
|
|
|
|
|
, djfu "Taking from a full MVar works" (gives' [True]) $ do
|
|
|
|
var <- newMVarInt 7
|
|
|
|
(==7) <$> takeMVar var
|
|
|
|
|
|
|
|
, djfu "Non-blockingly taking from a full MVar works" (gives' [True]) $ do
|
|
|
|
var <- newMVarInt 7
|
|
|
|
(==Just 7) <$> tryTakeMVar var
|
|
|
|
|
|
|
|
, djfu "Reading a full MVar works" (gives' [True]) $ do
|
|
|
|
var <- newMVarInt 7
|
|
|
|
(==7) <$> readMVar var
|
|
|
|
|
|
|
|
, djfu "Non-blockingly reading a full MVar works" (gives' [True]) $ do
|
|
|
|
var <- newMVarInt 7
|
|
|
|
(==Just 7) <$> tryReadMVar var
|
|
|
|
]
|
2017-09-06 17:44:20 +03:00
|
|
|
|
2015-11-16 05:48:54 +03:00
|
|
|
--------------------------------------------------------------------------------
|
2017-09-20 11:05:23 +03:00
|
|
|
|
2018-02-12 18:26:02 +03:00
|
|
|
crefTests :: [TestTree]
|
2018-02-16 23:04:52 +03:00
|
|
|
crefTests = toTestList
|
2017-09-20 11:05:23 +03:00
|
|
|
[ djfu "Reading a non-updated CRef gives its initial value" (gives' [True]) $ do
|
|
|
|
ref <- newCRefInt 5
|
|
|
|
(5==) <$> readCRef ref
|
|
|
|
|
|
|
|
, djfu "Reading an updated CRef gives its new value" (gives' [True]) $ do
|
|
|
|
ref <- newCRefInt 5
|
|
|
|
writeCRef ref 6
|
|
|
|
(6==) <$> readCRef ref
|
|
|
|
|
|
|
|
, djfu "Updating a CRef by a function changes its value" (gives' [True]) $ do
|
|
|
|
ref <- newCRefInt 5
|
|
|
|
atomicModifyCRef ref (\i -> (i+1, ()))
|
|
|
|
(6==) <$> readCRef ref
|
|
|
|
|
|
|
|
, djfu "A ticket contains the value of the CRef at the time of its creation" (gives' [True]) $ do
|
|
|
|
ref <- newCRefInt 5
|
|
|
|
tick <- readForCAS ref
|
|
|
|
writeCRef ref 6
|
|
|
|
(5==) <$> peekTicket tick
|
|
|
|
|
|
|
|
, djfu "Compare-and-swap returns a ticket containing the new value" (gives' [True]) $ do
|
|
|
|
ref <- newCRefInt 5
|
|
|
|
tick <- readForCAS ref
|
|
|
|
(_, tick') <- casCRef ref tick 6
|
|
|
|
(6==) <$> peekTicket tick'
|
|
|
|
|
|
|
|
, djfu "Compare-and-swap on an unmodified CRef succeeds" (gives' [True]) $ do
|
|
|
|
ref <- newCRefInt 5
|
|
|
|
tick <- readForCAS ref
|
|
|
|
(suc, _) <- casCRef ref tick 6
|
|
|
|
val <- readCRef ref
|
|
|
|
return (suc && (6 == val))
|
|
|
|
|
|
|
|
, djfu "Compare-and-swap on a modified CRef fails" (gives' [True]) $ do
|
|
|
|
ref <- newCRefInt 5
|
|
|
|
tick <- readForCAS ref
|
|
|
|
writeCRef ref 6
|
|
|
|
(suc, _) <- casCRef ref tick 7
|
|
|
|
val <- readCRef ref
|
|
|
|
return (not suc && not (7 == val))
|
|
|
|
]
|
2015-11-16 05:48:54 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
2017-09-20 11:05:23 +03:00
|
|
|
|
2018-02-12 18:26:02 +03:00
|
|
|
stmTests :: [TestTree]
|
2018-02-16 23:04:52 +03:00
|
|
|
stmTests = toTestList
|
2017-09-20 11:05:23 +03:00
|
|
|
[ djfu "When a TVar is updated, its new value is visible later in same transaction" (gives' [True]) $
|
|
|
|
(6==) <$> atomically (do { v <- newTVarInt 5; writeTVar v 6; readTVar v })
|
|
|
|
|
|
|
|
, djfu "When a TVar is updated, its new value is visible in a later transaction" (gives' [True]) $ do
|
|
|
|
ctv <- atomically $ newTVarInt 5
|
|
|
|
(5==) <$> atomically (readTVar ctv)
|
|
|
|
|
|
|
|
, djfu "Aborting a transaction blocks the thread" (gives [Left STMDeadlock]) $
|
|
|
|
(atomically retry :: MonadConc m => m ()) -- avoid an ambiguous type
|
|
|
|
|
|
|
|
, djfu "Aborting a transaction can be caught and recovered from" (gives' [True]) $ do
|
|
|
|
ctv <- atomically $ newTVarInt 5
|
|
|
|
atomically $ orElse retry (writeTVar ctv 6)
|
|
|
|
(6==) <$> atomically (readTVar ctv)
|
|
|
|
|
|
|
|
, djfu "An exception thrown in a transaction can be caught" (gives' [True]) $ do
|
|
|
|
ctv <- atomically $ newTVarInt 5
|
|
|
|
atomically $ catchArithException
|
|
|
|
(throwSTM Overflow)
|
|
|
|
(\_ -> writeTVar ctv 6)
|
|
|
|
(6==) <$> atomically (readTVar ctv)
|
|
|
|
|
|
|
|
, djfu "Nested exception handlers in transactions work" (gives' [True]) $ do
|
|
|
|
ctv <- atomically $ newTVarInt 5
|
|
|
|
atomically $ catchArithException
|
|
|
|
(catchArrayException
|
|
|
|
(throwSTM Overflow)
|
|
|
|
(\_ -> writeTVar ctv 0))
|
|
|
|
(\_ -> writeTVar ctv 6)
|
|
|
|
(6==) <$> atomically (readTVar ctv)
|
|
|
|
|
2017-10-11 12:10:48 +03:00
|
|
|
, djfu "MonadSTM is a MonadFail" (alwaysFailsWith isUncaughtException) $
|
2017-09-20 11:05:23 +03:00
|
|
|
(atomically $ fail "hello world" :: MonadConc m => m ()) -- avoid an ambiguous type
|
|
|
|
]
|
2017-09-20 01:40:38 +03:00
|
|
|
|
2015-11-16 05:48:54 +03:00
|
|
|
--------------------------------------------------------------------------------
|
2017-09-20 11:05:23 +03:00
|
|
|
|
2018-02-12 18:26:02 +03:00
|
|
|
exceptionTests :: [TestTree]
|
2018-02-16 23:04:52 +03:00
|
|
|
exceptionTests = toTestList
|
2017-09-20 11:05:23 +03:00
|
|
|
[ djfu "An exception thrown can be caught" (gives' [True]) $
|
|
|
|
catchArithException
|
2015-11-16 05:48:54 +03:00
|
|
|
(throw Overflow)
|
|
|
|
(\_ -> return True)
|
2017-09-20 11:05:23 +03:00
|
|
|
|
|
|
|
, djfu "Nested exception handlers work" (gives' [True]) $
|
|
|
|
catchArithException
|
|
|
|
(catchArrayException
|
|
|
|
(throw Overflow)
|
|
|
|
(\_ -> return False))
|
2015-11-16 05:48:54 +03:00
|
|
|
(\_ -> return True)
|
|
|
|
|
2017-10-11 12:10:48 +03:00
|
|
|
, djfu "Uncaught exceptions kill the computation" (alwaysFailsWith isUncaughtException) $
|
2017-09-20 11:05:23 +03:00
|
|
|
catchArithException
|
|
|
|
(throw $ IndexOutOfBounds "")
|
|
|
|
(\_ -> return False)
|
|
|
|
|
|
|
|
, djfu "SomeException matches all exception types" (gives' [True]) $ do
|
|
|
|
a <- catchSomeException
|
|
|
|
(throw Overflow)
|
|
|
|
(\_ -> return True)
|
|
|
|
b <- catchSomeException
|
|
|
|
(throw $ IndexOutOfBounds "")
|
|
|
|
(\_ -> return True)
|
|
|
|
return (a && b)
|
|
|
|
|
|
|
|
, djfu "Exceptions thrown in a transaction can be caught outside it" (gives' [True]) $
|
|
|
|
catchArithException
|
|
|
|
(atomically $ throwSTM Overflow)
|
|
|
|
(\_ -> return True)
|
2015-11-30 19:17:09 +03:00
|
|
|
|
2017-10-11 12:10:48 +03:00
|
|
|
, djfu "Throwing an unhandled exception to the main thread kills it" (alwaysFailsWith isUncaughtException) $ do
|
2017-09-20 11:05:23 +03:00
|
|
|
tid <- myThreadId
|
|
|
|
throwTo tid Overflow
|
2017-03-04 05:58:27 +03:00
|
|
|
|
2017-09-20 11:05:23 +03:00
|
|
|
, djfu "Throwing a handled exception to the main thread does not kill it" (gives' [True]) $ do
|
|
|
|
tid <- myThreadId
|
|
|
|
catchArithException (throwTo tid Overflow >> pure False) (\_ -> pure True)
|
2017-08-21 18:22:49 +03:00
|
|
|
|
2017-10-11 12:10:48 +03:00
|
|
|
, djfu "MonadConc is a MonadFail" (alwaysFailsWith isUncaughtException) $
|
2017-09-20 11:05:23 +03:00
|
|
|
(fail "hello world" :: MonadConc m => m ()) -- avoid an ambiguous type
|
|
|
|
]
|
2017-08-21 18:22:49 +03:00
|
|
|
|
2015-11-30 19:17:09 +03:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2018-02-12 18:26:02 +03:00
|
|
|
capabilityTests :: [TestTree]
|
2018-02-16 23:04:52 +03:00
|
|
|
capabilityTests = toTestList
|
2017-09-20 11:05:23 +03:00
|
|
|
[ djfu "Reading the capabilities twice without update gives the same result" (gives' [True]) $ do
|
|
|
|
c1 <- getNumCapabilities
|
|
|
|
c2 <- getNumCapabilities
|
|
|
|
return (c1 == c2)
|
|
|
|
|
|
|
|
, djfu "Getting the updated capabilities gives the new value" (gives' [True]) $ do
|
|
|
|
caps <- getNumCapabilities
|
|
|
|
setNumCapabilities (caps + 1)
|
|
|
|
(== caps + 1) <$> getNumCapabilities
|
|
|
|
]
|
2017-02-02 15:26:40 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
2017-09-20 11:05:23 +03:00
|
|
|
|
2018-02-12 18:26:02 +03:00
|
|
|
subconcurrencyTests :: [TestTree]
|
2018-02-16 23:04:52 +03:00
|
|
|
subconcurrencyTests = toTestList
|
|
|
|
[ djfuS "Failures in subconcurrency can be observed" (gives' [True]) $ do
|
2017-09-20 11:05:23 +03:00
|
|
|
x <- subconcurrency (newEmptyMVar >>= readMVar)
|
|
|
|
pure (either (==Deadlock) (const False) x)
|
|
|
|
|
2018-02-16 23:04:52 +03:00
|
|
|
, djfuS "Actions after a failing subconcurrency still happen" (gives' [True]) $ do
|
2017-09-20 11:05:23 +03:00
|
|
|
var <- newMVarInt 0
|
|
|
|
x <- subconcurrency (putMVar var 1)
|
|
|
|
y <- readMVar var
|
|
|
|
pure (either (==Deadlock) (const False) x && y == 0)
|
|
|
|
|
2018-02-16 23:04:52 +03:00
|
|
|
, djfuS "Non-failing subconcurrency returns the final result" (gives' [True]) $ do
|
2017-09-20 11:05:23 +03:00
|
|
|
var <- newMVarInt 3
|
|
|
|
x <- subconcurrency (takeMVar var)
|
|
|
|
pure (either (const False) (==3) x)
|
|
|
|
]
|
2018-02-13 01:25:09 +03:00
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
ioTests :: [TestTree]
|
|
|
|
ioTests = toTestList
|
|
|
|
[ djfu "Lifted IO is performed" (gives' [3]) $ do
|
|
|
|
r <- liftIO (IORef.newIORef 0)
|
|
|
|
replicateM_ 3 (liftIO (IORef.atomicModifyIORef r (\i -> (i+1, ()))))
|
|
|
|
liftIO (IORef.readIORef r)
|
|
|
|
]
|