|
| 1 | +{-# LANGUAGE RankNTypes #-} |
| 2 | +module Control.Concurrent.ConciergeSpec |
| 3 | + ( spec |
| 4 | + ) where |
| 5 | + |
| 6 | +import Prelude |
| 7 | + |
| 8 | +import Control.Concurrent.Concierge |
| 9 | + ( atomicallyWithLifted, newConcierge ) |
| 10 | +import Control.Monad.Class.MonadFork |
| 11 | + ( forkIO ) |
| 12 | +import Control.Monad.Class.MonadSay |
| 13 | + ( say ) |
| 14 | +import Control.Monad.Class.MonadTimer |
| 15 | + ( threadDelay ) |
| 16 | +import Control.Monad.IOSim |
| 17 | + ( IOSim, runSimTrace, selectTraceEventsSay ) |
| 18 | +import Control.Monad.Trans.Class |
| 19 | + ( lift ) |
| 20 | +import Control.Monad.Trans.Except |
| 21 | + ( ExceptT, catchE, runExceptT, throwE ) |
| 22 | +import Test.Hspec |
| 23 | + ( Spec, describe, it, parallel ) |
| 24 | +import Test.QuickCheck |
| 25 | + ( Property, (===) ) |
| 26 | + |
| 27 | +spec :: Spec |
| 28 | +spec = do |
| 29 | + parallel $ describe "Control.Concurrent.Concierge" $ do |
| 30 | + it "Atomic operations do not interleave" |
| 31 | + unit_atomic |
| 32 | + |
| 33 | + it "throwE in ExceptT releases lock" |
| 34 | + unit_exceptT_release_lock |
| 35 | + |
| 36 | +{------------------------------------------------------------------------------- |
| 37 | + Properties |
| 38 | +-------------------------------------------------------------------------------} |
| 39 | +-- | Deterministic test for atomicity. |
| 40 | +-- We have to compare a program run that interleaves |
| 41 | +-- against one that is atomic. |
| 42 | +unit_atomic :: Bool |
| 43 | +unit_atomic = |
| 44 | + ("ABAB" == sayings testInterleave) && ("AABB" == sayings testAtomic) |
| 45 | + where |
| 46 | + sayings :: (forall s. IOSim s a) -> String |
| 47 | + sayings x = concat . selectTraceEventsSay $ runSimTrace x |
| 48 | + |
| 49 | + testAtomic = do |
| 50 | + concierge <- newConcierge |
| 51 | + test $ atomicallyWithLifted id concierge () |
| 52 | + testInterleave = test id |
| 53 | + |
| 54 | + test :: (forall a. IOSim s a -> IOSim s a) -> IOSim s () |
| 55 | + test atomically = do |
| 56 | + _ <- forkIO $ atomically (delay 1 >> action "B") |
| 57 | + atomically $ action "A" |
| 58 | + delay 4 |
| 59 | + |
| 60 | + action :: String -> IOSim s () |
| 61 | + action s = say s >> delay 2 >> say s |
| 62 | + |
| 63 | + delay :: Int -> IOSim s () |
| 64 | + delay n = threadDelay (fromIntegral n*0.1) |
| 65 | + |
| 66 | +-- | Check that using 'throwE' in the 'ExceptE' monad releases the lock |
| 67 | +unit_exceptT_release_lock :: Property |
| 68 | +unit_exceptT_release_lock = |
| 69 | + ["A"] === selectTraceEventsSay (runSimTrace $ runExceptT test) |
| 70 | + where |
| 71 | + liftE :: IOSim s a -> ExceptT String (IOSim s) a |
| 72 | + liftE = lift |
| 73 | + |
| 74 | + test :: ExceptT String (IOSim s) () |
| 75 | + test = do |
| 76 | + concierge <- liftE newConcierge |
| 77 | + let atomically = atomicallyWithLifted liftE concierge () |
| 78 | + _ <- tryE $ atomically $ throwE "X" |
| 79 | + atomically $ liftE $ say "A" |
| 80 | + |
| 81 | +-- not exported in transformers <= 0.5.6 |
| 82 | +tryE :: Monad m => ExceptT e m a -> ExceptT e m (Either e a) |
| 83 | +tryE action = (Right <$> action) `catchE` (pure . Left) |
0 commit comments