primal-0.3.0.0: tests/Test/Prim/MVarSpec.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Test.Prim.MVarSpec (spec) where
import qualified Control.Concurrent as Base
import Control.Prim.Concurrent
import Control.Prim.Concurrent.MVar
import Control.Prim.Exception
import Data.Maybe
import Data.Prim
import Foreign.Prim.WeakPtr
import Test.Hspec
import System.Mem (performGC)
instance Typeable a => Show (MVar a RW) where
show _ = "MVar " ++ showsType (Proxy :: Proxy a) " RW"
data MVarException =
MVarException
deriving (Show, Eq)
instance Exception MVarException
-- | Turn a deadlock into a failing test.
failAfter :: Int -> Expectation -> Expectation
failAfter n test =
timeout n test >>= \case
Nothing -> expectationFailure $ "Did not finish within " ++ show (n `div` 1000) ++ " ms. "
Just () -> pure ()
wit :: String -> Expectation-> Spec
wit n t = it n $ failAfter 1000000 t
spec :: Spec
spec = do
describe "MVar" $ do
wit "isEmptyMVar" $ do
m :: MVar Int RW <- newEmptyMVar
isEmptyMVar m `shouldReturn` True
putMVar m 0
isEmptyMVar m `shouldReturn` False
(newMVar 'H' >>= isEmptyMVar) `shouldReturn` False
wit "isSameMVar" $ do
m1 :: MVar Int RW <- newEmptyMVar
isSameMVar m1 m1 `shouldBe` True
m1 `shouldBe` m1
m2 :: MVar Int RW <- newEmptyMVar
isSameMVar m1 m2 `shouldBe` False
m1 `shouldSatisfy` (/= m2)
wit "newMVar" $ do
m <- newMVar 'h'
readMVar m `shouldReturn` 'h'
newMVar (impureThrow MVarException) `shouldThrow` (== MVarException)
n :: MVar (Maybe Integer) RW <- newMVar (Just (impureThrow MVarException))
mRes <- takeMVar n
mRes `shouldSatisfy` isJust
deepeval mRes `shouldThrow` (== MVarException)
wit "newLazyMVar" $ do
m <- newLazyMVar 'h'
tryTakeMVar m `shouldReturn` Just 'h'
n <- newLazyMVar (impureThrow MVarException)
evalM (takeMVar n) `shouldThrow` (== MVarException)
wit "newDeepMVar" $ do
m <- newDeepMVar 'h'
takeMVar m `shouldReturn` 'h'
newDeepMVar (impureThrow MVarException :: Int) `shouldThrow` (== MVarException)
newDeepMVar (Just (impureThrow MVarException :: Integer)) `shouldThrow` (== MVarException)
wit "putMVar" $ do
m <- newEmptyMVar
void $ fork $ putMVar m "Hello"
takeMVar m `shouldReturn` "Hello"
n <- newMVar "World"
timeout 50000 (putMVar n "Already full") `shouldReturn` Nothing
void $ fork $ putMVar n (impureThrow MVarException)
putMVar n (impureThrow MVarException) `shouldThrow` (== MVarException)
takeMVar n `shouldReturn` "World"
putMVar n ('f':impureThrow MVarException)
res <- takeMVar n
head res `shouldBe` 'f'
deepeval res `shouldThrow` (== MVarException)
wit "putLazyMVar" $ do
m <- newEmptyMVar
void $ fork $ putLazyMVar m "Hello"
readMVar m `shouldReturn` "Hello"
timeout 50000 (putLazyMVar m "Already full") `shouldReturn` Nothing
n <- newEmptyMVar
void $ fork $ putLazyMVar n (impureThrow MVarException)
res <- takeMVar n
eval res `shouldThrow` (== MVarException)
wit "putDeepMVar" $ do
m <- newEmptyMVar
void $ fork $ putDeepMVar m "Hello"
readMVar m `shouldReturn` "Hello"
timeout 50000 (putDeepMVar m "Already full") `shouldReturn` Nothing
n <- newMVar "World"
void $ fork $ putDeepMVar n ("Bar" ++ impureThrow MVarException)
putDeepMVar n ("Foo" ++ impureThrow MVarException) `shouldThrow` (== MVarException)
threadDelay 10000
takeMVar n `shouldReturn` "World"
wit "tryPutMVar" $ do
m <- newEmptyMVar
tryPutMVar m "Hello" `shouldReturn` True
tryPutMVar m "World" `shouldReturn` False
tryPutMVar m (impureThrow MVarException) `shouldThrow` (== MVarException)
takeMVar m `shouldReturn` "Hello"
n <- newEmptyMVar
void $ fork $ void $ tryPutMVar n (impureThrow MVarException)
tryPutMVar n (impureThrow MVarException) `shouldThrow` (== MVarException)
threadDelay 10000
isEmptyMVar n `shouldReturn` True
wit "tryPutLazyMVar" $ do
m <- newEmptyMVar
tryPutLazyMVar m "Hello" `shouldReturn` True
tryPutLazyMVar m "World" `shouldReturn` False
tryPutLazyMVar m (impureThrow MVarException) `shouldReturn` False
takeMVar m `shouldReturn` "Hello"
done <- newEmptyMVar
n <- newEmptyMVar
void $ fork $ do
res <- tryPutLazyMVar n (impureThrow MVarException)
void $ tryPutLazyMVar done res
takeMVar done `shouldReturn` True
isEmptyMVar n `shouldReturn` False
res <- takeMVar n
eval res `shouldThrow` (== MVarException)
wit "tryPutDeepMVar" $ do
m <- newEmptyMVar
tryPutMVar m "Hello" `shouldReturn` True
tryPutMVar m "World" `shouldReturn` False
tryPutDeepMVar m ("Happy" ++ impureThrow MVarException) `shouldThrow` (== MVarException)
takeMVar m `shouldReturn` "Hello"
n <- newEmptyMVar
tryPutDeepMVar n ("World" ++ impureThrow MVarException) `shouldThrow` (== MVarException)
isEmptyMVar n `shouldReturn` True
wit "writeMVar" $ do
m <- newEmptyMVar
writeMVar m "Hello"
readMVar m `shouldReturn` "Hello"
writeMVar m "World"
readMVar m `shouldReturn` "World"
wit "swapMVar" $ do
m <- newMVar "Hello"
swapMVar m "World" `shouldReturn` "Hello"
swapMVar m (impureThrow MVarException) `shouldThrow` (== MVarException)
readMVar m `shouldReturn` "World"
wit "swapLazyMVar" $ do
m <- newMVar "Hello"
swapLazyMVar m "World" `shouldReturn` "Hello"
readMVar m `shouldReturn` "World"
swapLazyMVar m (impureThrow MVarException) `shouldReturn` "World"
res <- takeMVar m
eval res `shouldThrow` (== MVarException)
wit "swapDeepMVar" $ do
m <- newMVar "Hello"
swapDeepMVar m "World" `shouldReturn` "Hello"
swapDeepMVar m ("Booyah" ++ impureThrow MVarException) `shouldThrow` (== MVarException)
readMVar m `shouldReturn` "World"
wit "takeMVar" $ do
m <- newMVar "Hello"
takeMVar m `shouldReturn` "Hello"
isEmptyMVar m `shouldReturn` True
timeout 50000 (takeMVar m) `shouldReturn` Nothing
wit "tryTakeMVar" $ do
m <- newMVar "Hello"
tryTakeMVar m `shouldReturn` Just "Hello"
isEmptyMVar m `shouldReturn` True
tryTakeMVar m `shouldReturn` Nothing
wit "readMVar" $ do
m <- newMVar "Hello"
readMVar m `shouldReturn` "Hello"
isEmptyMVar m `shouldReturn` False
clearMVar m
timeout 50000 (readMVar m) `shouldReturn` Nothing
wit "tryReadMVar" $ do
m <- newEmptyMVar
tryReadMVar m `shouldReturn` Nothing
putMVar m "Hello"
tryReadMVar m `shouldReturn` Just "Hello"
isEmptyMVar m `shouldReturn` False
wit "clearMVar" $ do
m <- newEmptyMVar
clearMVar m
isEmptyMVar m `shouldReturn` True
putMVar m "Hello"
clearMVar m
isEmptyMVar m `shouldReturn` True
wit "withMVar" $ do
m <- newEmptyMVar
void $ fork $ putMVar m "Hello"
-- check masking state
res <- withMVar m $ \x -> do
x `shouldBe` "Hello"
getMaskingState
res `shouldBe` Unmasked
-- check restoration of value on exception
withMVar m (\_ -> do
isEmptyMVar m `shouldReturn` True
throwM MVarException)
`shouldThrow` (==MVarException)
readMVar m `shouldReturn` "Hello"
-- check that it is interruptible and that the value is overwritten
timeout 50000 (withMVar m (\_ -> putMVar m "World")) `shouldReturn` Nothing
readMVar m `shouldReturn` "World"
-- check that it is interruptible in the exception handler and that the value is
-- overwritten
timeout 50000 (withMVar m (\_ -> do
putMVar m "Goodbye"
() <$ throwM MVarException
)) `shouldReturn` Nothing
takeMVar m `shouldReturn` "Goodbye"
-- -- check that it is interruptible on empty
-- timeout 50000 (withMVar m pure) `shouldReturn` Nothing
wit "withMVarMasked" $ do
m <- newMVar "Hello"
-- check masking state
res <- withMVarMasked m $ \x -> do
x `shouldBe` "Hello"
getMaskingState
res `shouldBe` MaskedInterruptible
-- check restoration of value on exception
withMVarMasked m (\_ -> do
isEmptyMVar m `shouldReturn` True
throw MVarException)
`shouldThrow` (==MVarException)
readMVar m `shouldReturn` "Hello"
-- check that it is interruptible and that the value is overwritten
timeout 50000 (withMVarMasked m (\_ -> putMVar m "World")) `shouldReturn` Nothing
readMVar m `shouldReturn` "World"
-- check that it is interruptible in the exception handler and that the value is
-- overwritten
timeout 50000 (withMVarMasked m (\_ -> do
putMVar m "Goodbye"
() <$ throw MVarException
)) `shouldReturn` Nothing
takeMVar m `shouldReturn` "Goodbye"
-- check that it is interruptible on empty
timeout 50000 (withMVarMasked m pure) `shouldReturn` Nothing
wit "modifyMVar_" $ do
m <- newMVar "Hello"
-- check masking state and actual modification
modifyMVar_ m $ \x -> do
x `shouldBe` "Hello"
getMaskingState `shouldReturn` Unmasked
pure $ x ++ " World"
-- Verify value restoration on WHNF evaluation error
modifyMVar_ m (\x -> do
isEmptyMVar m `shouldReturn` True
x `shouldBe` "Hello World"
pure $ impureThrow MVarException)
`shouldThrow` (==MVarException)
readMVar m `shouldReturn` "Hello World"
-- check that it is interruptible and that the value is overwritten
timeout 50000 (modifyMVar_ m (\_ -> putMVar m "Foo" >> pure "Bar")) `shouldReturn` Nothing
readMVar m `shouldReturn` "Foo"
-- check that it is interruptible in the exception handler and that the value is
-- overwritten
timeout 50000 (modifyMVar_ m (\_ -> do
putMVar m "Goodbye"
"World" <$ throw MVarException
)) `shouldReturn` Nothing
takeMVar m `shouldReturn` "Goodbye"
-- check that it is interruptible on empty
timeout 50000 (modifyMVar_ m pure) `shouldReturn` Nothing
wit "modifyMVarMasked_" $ do
m <- newMVar "Hello"
-- check masking state and actual modification
modifyMVarMasked_ m $ \x -> do
x `shouldBe` "Hello"
getMaskingState `shouldReturn` MaskedInterruptible
pure $ x ++ " World"
-- Verify value restoration on WHNF evaluation error
modifyMVarMasked_ m (\x -> do
isEmptyMVar m `shouldReturn` True
x `shouldBe` "Hello World"
pure $ impureThrow MVarException)
`shouldThrow` (==MVarException)
readMVar m `shouldReturn` "Hello World"
-- check that it is interruptible and that the value is overwritten
timeout 50000 (modifyMVarMasked_ m (\_ -> putMVar m "Foo" >> pure "Bar"))
`shouldReturn` Nothing
readMVar m `shouldReturn` "Foo"
-- check that it is interruptible in the exception handler and that the value is
-- overwritten
timeout 50000 (modifyMVarMasked_ m (\_ -> do
putMVar m "Goodbye"
"World" <$ throw MVarException
)) `shouldReturn` Nothing
takeMVar m `shouldReturn` "Goodbye"
-- check that it is interruptible on empty
timeout 50000 (modifyMVarMasked_ m pure) `shouldReturn` Nothing
wit "modifyFetchOldMVar" $ do
m <- newMVar "Hello"
modifyFetchOldMVar m (pure . (++ " World")) `shouldReturn` "Hello"
readMVar m `shouldReturn` "Hello World"
modifyFetchOldMVar m (\ _ -> pure $ impureThrow MVarException)
`shouldThrow` (==MVarException)
takeMVar m `shouldReturn` "Hello World"
wit "modifyFetchOldMVarMasked" $ do
m <- newMVar "Hello"
modifyFetchOldMVarMasked m (pure . (++ " World")) `shouldReturn` "Hello"
readMVar m `shouldReturn` "Hello World"
modifyFetchOldMVarMasked m (\ _ -> pure $ impureThrow MVarException)
`shouldThrow` (==MVarException)
takeMVar m `shouldReturn` "Hello World"
wit "modifyFetchNewMVar" $ do
m <- newMVar "Hello"
modifyFetchNewMVar m (pure . (++ " World")) `shouldReturn` "Hello World"
readMVar m `shouldReturn` "Hello World"
modifyFetchNewMVar m (\ _ -> pure $ impureThrow MVarException)
`shouldThrow` (==MVarException)
takeMVar m `shouldReturn` "Hello World"
wit "modifyFetchNewMVarMasked" $ do
m <- newMVar "Hello"
modifyFetchNewMVarMasked m (pure . (++ " World")) `shouldReturn` "Hello World"
readMVar m `shouldReturn` "Hello World"
modifyFetchNewMVarMasked m (\ _ -> pure $ impureThrow MVarException)
`shouldThrow` (==MVarException)
takeMVar m `shouldReturn` "Hello World"
-- xit "modifyMVar" (pure () :: IO ())
-- xit "modifyMVarMasked" (pure () :: IO ())
it "toBaseMVar" $ do
m <- newMVar ()
Base.takeMVar (toBaseMVar m) `shouldReturn` ()
isEmptyMVar m `shouldReturn` True
it "fromBaseMVar" $ do
m <- Base.newMVar ()
takeMVar (fromBaseMVar m) `shouldReturn` ()
Base.isEmptyMVar m `shouldReturn` True
describe "mkWeakMVar" $ do
wit "performGC" $ do
sem <- newEmptyMVar
void $ fork $ do
m <- newEmptyMVar
_weak <- mkWeakMVar m $ putMVar sem ()
performGC
takeMVar sem `shouldReturn` ()
wit "finalizeWeak" $ do
sem <- newEmptyMVar
m <- newMVar "Hello"
weak <- mkWeakMVar m $ putMVar sem ()
deRefWeak weak >>= \case
Nothing -> expectationFailure "Empty weak ref"
Just m' -> do
m' `shouldBe` m
readMVar m' `shouldReturn` "Hello"
finalizeWeak weak
takeMVar sem `shouldReturn` ()