scheduler-2.0.0: tests/Control/SchedulerSpec.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Control.SchedulerSpec
( spec
) where
import Data.Int
import Control.Concurrent (killThread, myThreadId, threadDelay, yield)
import Control.Concurrent.MVar
import Control.DeepSeq
import qualified Control.Exception as EUnsafe
import Control.Exception.Base (ArithException(DivideByZero),
AsyncException(ThreadKilled))
import Control.Monad
import Control.Monad.ST
import Control.Scheduler as S
import Data.Bits (complement)
import qualified Data.Foldable as F (toList, traverse_)
import Data.IORef
import Data.List (groupBy, sort, sortOn)
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
import Test.QuickCheck.Function
import Test.QuickCheck.Monadic
import Test.Validity.Eq
import Test.Validity.Functor
import Test.Validity.Monoid
import Test.Validity.Ord
import Test.Validity.Show
import UnliftIO.Async
import UnliftIO.Exception hiding (assert)
#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup
#endif
concurrentProperty :: Testable prop => prop -> Property
concurrentProperty = within 2000000
concurrentExpectation :: Expectation -> Property
concurrentExpectation = concurrentProperty
concurrentPropertyIO :: Testable prop => IO prop -> Property
concurrentPropertyIO = concurrentProperty . monadicIO . run
instance Arbitrary Comp where
arbitrary = frequency [(20, pure Seq), (80, getNonSeq <$> arbitrary)]
newtype NonSeq = NonSeq {getNonSeq :: Comp }
deriving (Show, Eq)
instance Arbitrary NonSeq where
arbitrary =
NonSeq <$>
frequency [(10, pure Par), (35, ParOn <$> arbitrary), (35, ParN . getSmall <$> arbitrary)]
newtype SeqLike = SeqLike {getSeqLike :: Comp }
deriving (Show, Eq)
instance Arbitrary SeqLike where
arbitrary = SeqLike <$> oneof [pure Seq, ParOn . pure <$> arbitrary, pure $ ParN 1]
prop_SameList :: Comp -> [Int] -> Property
prop_SameList comp xs =
concurrentPropertyIO $ do
xs' <- withScheduler comp $ \scheduler -> mapM_ (scheduleWork scheduler . return) xs
return (xs === xs')
prop_Recursive :: Comp -> [Int] -> Property
prop_Recursive comp xs =
concurrentPropertyIO $ do
xs' <- withScheduler comp (schedule xs)
return (sort xs === sort xs')
where
schedule [] _ = return ()
schedule (y:ys) scheduler = scheduleWork scheduler (schedule ys scheduler >> return y)
prop_Serially :: Comp -> [Int] -> Property
prop_Serially comp xs =
concurrentPropertyIO $ do
xs' <- schedule xs
return (xs === concat xs')
where
schedule [] = return []
schedule (y:ys) = do
y' <- withScheduler comp (`scheduleWork` pure y)
ys' <- schedule ys
return (y':ys')
prop_Nested :: Comp -> [Int] -> Property
prop_Nested comp xs =
concurrentPropertyIO $ do
xs' <- schedule xs
return (sort xs === sort (concat xs'))
where
schedule [] = return []
schedule (y:ys) =
withScheduler comp (\s -> scheduleWork s (schedule ys >>= \ys' -> return (y : concat ys')))
-- | Check whether all jobs have been completed (similar roprop_Traverse)
prop_AllJobsProcessed :: Comp -> [Int] -> Property
prop_AllJobsProcessed comp jobs =
concurrentProperty $
monadicIO
((=== jobs) <$>
run (withScheduler comp $ \scheduler -> mapM_ (scheduleWork scheduler . pure) jobs))
prop_Traverse :: Comp -> [Int] -> Fun Int Int -> Property
prop_Traverse comp xs f =
concurrentPropertyIO $ (===) <$> traverse f' xs <*> traverseConcurrently comp f' xs
where
f' = pure . apply f
replicateLike :: ([Word] -> [Word]) -> (Int -> IO Word -> IO [Word]) -> Int -> Fun Word Word -> IO ()
replicateLike adjust justAs n f = do
iRef <- newIORef 0
jRef <- newIORef 0
let g ref = atomicModifyIORef' ref (\i -> (apply f i, i + 1))
xs <- replicateM n (g jRef)
ys <- justAs n (g iRef)
adjust ys `shouldBe` adjust xs
prop_ReplicateM :: ([Word] -> [Word]) -> Comp -> Int -> Fun Word Word -> Property
prop_ReplicateM adjust comp i =
concurrentPropertyIO . replicateLike adjust (S.replicateConcurrently comp) i
prop_ReplicateWork :: ([Word] -> [Word]) -> Comp -> Int -> Fun Word Word -> Property
prop_ReplicateWork adjust comp i =
concurrentPropertyIO .
replicateLike adjust (\n g -> withScheduler comp (\s -> replicateWork s n g)) i
prop_ReplicateWork_ :: ([Word] -> [Word]) -> Comp -> Int -> Fun Word Word -> Property
prop_ReplicateWork_ adjust comp i =
concurrentPropertyIO . replicateLike adjust scheduleAndCollect i
where
scheduleAndCollect n g = do
ref <- newIORef []
withScheduler_ comp $ \s ->
replicateWork_ s n $ do
x <- g
atomicModifyIORef' ref (\xs -> (x : xs, ()))
reverse <$> readIORef ref
prop_ManyJobsInChunks :: Property
prop_ManyJobsInChunks = noShrinking $ \ comp (jss :: [[Int]]) ->
concurrentExpectation $ do
xs <- withScheduler comp $ \s ->
forM_ jss $ \js -> do
rs <- runBatch s $ \ _ -> mapM_ (scheduleWork s . pure) js
rs `shouldBe` js
xs `shouldBe` []
prop_ArbitraryCompNested :: [(Comp, Int)] -> Property
prop_ArbitraryCompNested xs =
concurrentPropertyIO $ do
xs' <- schedule xs
return (sort (map snd xs) === sort (concat xs'))
where
schedule [] = return []
schedule ((c, y):ys) =
withScheduler c (\s -> scheduleWork s (schedule ys >>= \ys' -> return (y : concat ys')))
-- | Ensure proper exception handling.
prop_CatchDivideByZero :: Comp -> Int -> [Positive Int] -> Property
prop_CatchDivideByZero comp k xs =
concurrentProperty $
assertExceptionIO
(== DivideByZero)
(traverseConcurrently
comp
(\i -> return (k `div` i))
(map getPositive xs ++ [0] ++ map getPositive xs))
-- | Ensure proper exception handling.
prop_CatchDivideByZeroNested :: Comp -> Int -> Positive Int -> Property
prop_CatchDivideByZeroNested comp a (Positive k) =
concurrentProperty $ assertExceptionIO (== DivideByZero) (schedule k)
where
schedule i
| i < 0 = return []
| otherwise =
withScheduler comp (\s -> scheduleWork s (schedule (i - 1) >> return (a `div` i)))
-- | Make sure one co-worker can kill another one, of course when there are at least two of.
prop_KillBlockedCoworker :: Comp -> Property
prop_KillBlockedCoworker comp =
concurrentProperty $
assertExceptionIO
(== DivideByZero)
(withScheduler_ comp $ \scheduler ->
if numWorkers scheduler < 2
then scheduleWork scheduler $ return ((1 :: Int) `div` (0 :: Int))
else do
mv <- newEmptyMVar
scheduleWork scheduler $ readMVar mv
scheduleWork scheduler $ return ((1 :: Int) `div` (0 :: Int)))
-- | Make sure one co-worker can kill another one, of course when there are at least two of.
prop_KillSleepingCoworker :: Comp -> Property
prop_KillSleepingCoworker comp =
concurrentProperty $
assertExceptionIO
(== DivideByZero)
(withScheduler_ comp $ \scheduler -> do
scheduleWork scheduler $ return ((1 :: Int) `div` (0 :: Int))
scheduleWork scheduler $ do
threadDelay 500000
error "This should never happen! Thread should have been killed by now.")
prop_ExpectAsyncException :: Comp -> Property
prop_ExpectAsyncException comp =
concurrentProperty $
let didAWorkerDie =
EUnsafe.handleJust EUnsafe.asyncExceptionFromException (return . (== EUnsafe.ThreadKilled)) .
fmap or
in (monadicIO . run . didAWorkerDie . withScheduler comp $ \s ->
scheduleWork s (myThreadId >>= killThread >> pure False)) .&&.
(monadicIO . run . fmap not . didAWorkerDie . withScheduler Par $ \s ->
scheduleWork s $ pure False)
prop_WorkerCaughtAsyncException :: Positive Int -> Property
prop_WorkerCaughtAsyncException (Positive n) =
concurrentProperty $
assertExceptionIO (== DivideByZero) $ do
lock <- newEmptyMVar
result <-
race (readMVar lock) $
withScheduler_ (ParN 2) $ \scheduler -> do
scheduleWork scheduler $ do
threadDelay (n `mod` 1000000)
EUnsafe.throwIO DivideByZero
scheduleWork scheduler $ do
e <- tryAny $ replicateM_ 5 $ threadDelay 1000000
case e of
Right _ -> throwString "Impossible, shouldn't have waited for so long"
Left exc -> do
putMVar lock exc
throwString $
"I should not have survived: " ++ displayException (exc :: SomeException)
void $ throwString $
case result of
Left innerError -> "Scheduled job cought async exception: " ++ displayException innerError
Right () -> "Scheduler terminated properly. Should not have happened"
-- | Make sure there is no problems if sub-schedules worker get killed
prop_AllWorkersDied :: Comp -> Comp -> Positive Int -> Property
prop_AllWorkersDied comp1 comp (Positive n) =
concurrentProperty $
assertAsyncExceptionIO
(== ThreadKilled)
(withScheduler_ comp1 $ \scheduler1 ->
scheduleWork
scheduler1
(withScheduler_ comp $ \scheduler ->
replicateM_ n (scheduleWork scheduler (myThreadId >>= killThread))))
prop_FinishEarly_ :: NonSeq -> Property
prop_FinishEarly_ (NonSeq comp) =
concurrentPropertyIO $ do
ref <- newIORef True
withScheduler_ comp $ \scheduler ->
scheduleWork_
scheduler
(terminate_ scheduler >> yield >> threadDelay 10000 >> writeIORef ref False)
counterexample "Scheduler did not terminate early" <$> readIORef ref
prop_FinishEarly :: Comp -> Property
prop_FinishEarly comp =
concurrentPropertyIO $ do
let scheduleJobs scheduler = do
scheduleWork scheduler (pure (2 :: Int))
scheduleWork scheduler (threadDelay 10000 >> terminate scheduler 3 >> pure 1)
res <- withScheduler comp scheduleJobs
res' <- withSchedulerR comp scheduleJobs
pure (res === [2, 3] .&&. res' === FinishedEarly [2] 3)
prop_FinishEarlyWith :: Comp -> Int -> Property
prop_FinishEarlyWith comp n =
concurrentPropertyIO $ do
let scheduleJobs scheduler = do
scheduleWork scheduler $ pure (complement (n + 1))
scheduleWork scheduler $ terminateWith scheduler n >> pure (complement n)
res <- withScheduler comp scheduleJobs
res' <- withSchedulerR comp scheduleJobs
pure (res === [n] .&&. res' === FinishedEarlyWith n)
prop_FinishBeforeStarting :: Comp -> Property
prop_FinishBeforeStarting comp =
concurrentPropertyIO $ do
res <-
withScheduler comp $ \scheduler -> do
void $ terminate scheduler 1
scheduleWork scheduler (threadDelay 10000 >> pure 2)
pure (res === [1 :: Int])
prop_FinishWithBeforeStarting :: Comp -> Int -> Property
prop_FinishWithBeforeStarting comp n =
concurrentPropertyIO $ do
res <-
withScheduler comp $ \scheduler -> do
void $ terminateWith scheduler n
scheduleWork scheduler $ pure (complement n)
pure (res === [n])
prop_TrivialSchedulerSameAsSeq_ :: SeqLike -> [Int] -> Property
prop_TrivialSchedulerSameAsSeq_ (SeqLike comp) zs =
concurrentPropertyIO $ do
let consRef xsRef x = atomicModifyIORef' xsRef $ \ xs -> (x:xs, ())
trivial = trivialScheduler_
nRef <- newIORef False
xRefs <- newIORef []
yRefs <- newIORef []
withScheduler_ comp $ \scheduler -> do
writeIORef nRef (numWorkers scheduler == numWorkers trivial)
mapM_ (scheduleWork_ scheduler . consRef xRefs) zs
mapM_ (scheduleWork_ trivial . consRef yRefs) zs
nSame <- readIORef nRef
xs <- readIORef xRefs
ys <- readIORef yRefs
pure (nSame .&&. xs === ys)
prop_SameAsTrivialScheduler :: Comp -> [Int] -> Fun Int Int -> Property
prop_SameAsTrivialScheduler comp zs f =
concurrentPropertyIO $ do
let schedule scheduler = forM_ zs (scheduleWork scheduler . pure . apply f)
xs <- withScheduler comp schedule
ys <- withTrivialScheduler schedule
pure (xs === ys)
prop_Terminate ::
(Show a, Eq a)
=> ((Scheduler RealWorld Int -> IO ()) -> IO a)
-> (Scheduler RealWorld Int -> Int -> IO Int)
-> ([Int] -> Int -> a)
-> [Int]
-> Int
-> [Int]
-> Property
prop_Terminate withSchedulerR' term expected xs x ys =
concurrentExpectation $ do
rs <-
withSchedulerR' $ \scheduler -> do
forM_ xs (scheduleWork scheduler . pure)
_ <- scheduleWork scheduler $ term scheduler x
forM_ ys (scheduleWork scheduler . pure)
rs `shouldBe` expected xs x
-- prop_TerminateSeq ::
-- ((Scheduler IO Int -> IO ()) -> IO (Results Int)) -> [Int] -> Int -> [Int] -> Expectation
-- prop_TerminateSeq withSchedulerR' xs x ys = do
-- rs <- withSchedulerR' $ \ scheduler -> do
-- forM_ xs (scheduleWork scheduler . pure)
-- _ <- scheduleWork scheduler $ terminate scheduler x
-- forM_ ys (scheduleWork scheduler . pure)
-- rs `shouldBe` FinishedEarly xs x
-- prop_TerminateWithSeq ::
-- ((Scheduler IO Int -> IO ()) -> IO (Results Int)) -> [Int] -> Int -> [Int] -> Expectation
-- prop_TerminateWithSeq withSchedulerR' xs x ys = do
-- rs <- withSchedulerR' $ \ scheduler -> do
-- forM_ xs (scheduleWork scheduler . pure)
-- _ <- scheduleWork scheduler $ terminateWith scheduler x
-- forM_ ys (scheduleWork scheduler . pure)
-- rs `shouldBe` FinishedEarlyWith x
newtype Elem = Elem Int deriving (Eq, Show)
instance Exception Elem
-- | Check if an element is in the list with an exception
prop_TraverseConcurrently_ :: Comp -> [Int] -> Int -> Property
prop_TraverseConcurrently_ comp xs x =
concurrentPropertyIO $ do
let f i
| i == x = throwIO $ Elem x
| otherwise = pure ()
eRes :: Either Elem () <- try $ traverse_ f xs
eRes' <- try $ traverseConcurrently_ comp f xs
return (eRes === eRes')
-- TODO: fix the infinite property for single worker schedulers
-- | Check if an element is in the list with an exception, where we know that list is infinite and
-- element is part of that list.
prop_TraverseConcurrentlyInfinite_ :: NonSeq -> [Int] -> Int -> Property
prop_TraverseConcurrentlyInfinite_ (NonSeq comp) xs x =
concurrentPropertyIO $ do
let f i
| i == x = throwIO $ Elem x
| otherwise = pure ()
xs' = xs ++ [x] -- ++ [0 ..]
eRes :: Either Elem () <- try $ F.traverse_ f xs'
eRes' <- try $ traverseConcurrently_ comp f xs'
return (eRes === eRes')
prop_WorkerStateExclusive :: Comp -> NonNegative Int -> Property
prop_WorkerStateExclusive comp (NonNegative n) =
concurrentExpectation $ do
state <- initWorkerStates comp (\wid -> (,) wid <$> newIORef (0 :: Int))
workerStatesComp state `shouldBe` comp
nWorkers <- getCompWorkers comp
let scheduleJobs schedulerWS = do
replicateM n $
scheduleWorkState schedulerWS $ \(wid, ref) -> do
counter <- readIORef ref
writeIORef ref (counter + 1)
pure (wid, counter)
gather = map (sortOn snd) . groupBy (\x y -> fst x == fst y) . sortOn fst
isMonotonicStartingAt _ [] = True
isMonotonicStartingAt k (k':ks) = k == k' && isMonotonicStartingAt (k + 1) ks
baseIds = [(wid, -1) | wid <- [0 .. WorkerId nWorkers - 1]]
ids <- withSchedulerWS state scheduleJobs
length ids `shouldBe` n
let gathered = gather (ids ++ baseIds)
map (map snd) gathered `shouldSatisfy` all (isMonotonicStartingAt (-1))
ids' <- withSchedulerWSR state scheduleJobs
length ids' `shouldBe` n
let gathered' = gather (baseIds ++ ids ++ F.toList ids')
map (map snd) gathered' `shouldSatisfy` all (isMonotonicStartingAt (-1))
withSchedulerWS_ state $ \schedulerWS -> do
numWorkers (unwrapSchedulerWS schedulerWS) `shouldBe` nWorkers
replicateM (10 * n) $
scheduleWorkState_ schedulerWS $ \(wid, ref) -> do
counter <- readIORef ref
when (counter > 0) $ snd (last (gathered' !! getWorkerId wid)) `shouldBe` pred counter
prop_MutexException :: Comp -> Property
prop_MutexException comp =
concurrentProperty $
assertExceptionIO (== MutexException) $ do
state <- initWorkerStates comp (pure . getWorkerId)
withSchedulerWS_ state $ \schedulerWS ->
scheduleWorkState_ schedulerWS $ \_s -> withSchedulerWS_ state $ \_s' -> pure ()
prop_FindCancelResume :: Comp -> Int64 -> ([Int64], [Int64]) -> [Int64] -> Property
prop_FindCancelResume comp x' (xs1', xs2') ys =
concurrentExpectation $ do
let f = (10 *)
g = (100 *)
xs1 = filter (/= x') xs1'
xs2 = filter (/= x') xs2'
xs = concat [xs1, [x'], xs2]
res <-
withSchedulerR comp $ \s -> do
r <- runBatchR s $ \batch -> do
forM_ xs $ \x ->
scheduleWork s $ do
if x == x'
then Just x <$ cancelBatchWith batch (Just (f x))
else pure Nothing
r `shouldBe` FinishedEarlyWith (Just (f x'))
r' <- runBatchR s $ \_batch -> forM_ ys (scheduleWork s . pure . Just)
r' `shouldBe` Finished (map Just ys)
batch <- getCurrentBatch s
forM_ xs $ \x ->
scheduleWork s $ do
if x == x'
then Just x <$ cancelBatch batch (Just (g x))
else pure $ Just (f x)
case res of
FinishedEarly rs r -> do
r `shouldBe` Just (g x')
rs `satisfyOrderedPartialPrefix` concat [map (Just . f) xs1, [Just x'], map (Just . f) xs2]
fr -> expectationFailure $ "Unexpected result: " ++ show fr
where
satisfyOrderedPartialPrefix as bs =
unless (orderedPartialPrefixOf as bs) $
expectationFailure $
"Expected " ++
show as ++ " to be prefix of " ++ show bs ++ " possibly with some elements skipped"
-- Make sure the first list is the prefix of the second
orderedPartialPrefixOf [] _ = True
orderedPartialPrefixOf (_:_) [] = False
orderedPartialPrefixOf (a:as) (b:bs)
| a == b = orderedPartialPrefixOf as bs
| otherwise = orderedPartialPrefixOf (a : as) bs
-- prop_CancelBatchEarly_ :: NonSeq -> Property
-- prop_CancelBatchEarly_ (NonSeq comp) =
-- concurrentPropertyIO $ do
-- ref <- newIORef True
-- withScheduler_ comp $ \scheduler ->
-- scheduleWork_
-- scheduler
-- (cancelBatch_ scheduler >> yield >> threadDelay 10000 >> writeIORef ref False)
-- counterexample "Scheduler did not terminate early" <$> readIORef ref
-- prop_FinishEarly :: Comp -> Property
-- prop_FinishEarly comp =
-- concurrentPropertyIO $ do
-- let scheduleJobs scheduler = do
-- scheduleWork scheduler (pure (2 :: Int))
-- scheduleWork scheduler (threadDelay 10000 >> terminate scheduler 3 >> pure 1)
-- res <- withScheduler comp scheduleJobs
-- res' <- withSchedulerR comp scheduleJobs
-- pure (res === [2, 3] .&&. res' === FinishedEarly [2] 3)
-- prop_FinishEarlyWith :: Comp -> Int -> Property
-- prop_FinishEarlyWith comp n =
-- concurrentPropertyIO $ do
-- let scheduleJobs scheduler = do
-- scheduleWork scheduler $ pure (complement (n + 1))
-- scheduleWork scheduler $ terminateWith scheduler n >> pure (complement n)
-- res <- withScheduler comp scheduleJobs
-- res' <- withSchedulerR comp scheduleJobs
-- pure (res === [n] .&&. res' === FinishedEarlyWith n)
spec :: Spec
spec = do
describe "Comp" $ do
describe "Monoid" $ do
it "x <> mempty = x" $ property $ \(x :: Comp) -> x <> mempty === x
it "mempty <> x = x" $ property $ \(x :: Comp) -> mempty <> x === x
it "x <> (y <> z) = (x <> y) <> z" $
property $ \(x :: Comp) y z -> x <> (y <> z) === (x <> y) <> z
it "mconcat = foldr '(<>)' mempty" $
property $ \(xs :: [Comp]) -> mconcat xs === foldr (<>) mempty xs
eqSpecOnArbitrary @Comp
monoidSpecOnArbitrary @Comp
describe "Show" $ do
it "show == showsPrec 0" $ property $ \(x :: Comp) -> x `deepseq` show x === showsPrec 0 x ""
it "(show) == showsPrec 1" $
property $ \(x :: Comp) (Positive n) ->
x /= Seq && x /= Par ==> ("(" <> show x <> ")" === showsPrec n x "")
describe "Results" $ do
eqSpecOnArbitrary @(Results Int)
functorSpecOnArbitrary @Results
showReadSpecOnArbitrary @(Results Int)
it "Traversable" $ property $ \(rs :: Results Int) (f :: Fun Int (Maybe Int)) ->
traverse (apply f) (F.toList rs) === fmap F.toList (traverse (apply f) rs)
describe "WorkerId" $ do
eqSpecOnArbitrary @WorkerId
ordSpecOnArbitrary @WorkerId
it "MaxMin" $ property $ \x y ->
conjoin [ max (WorkerId x) (WorkerId y) === WorkerId (max x y)
, min (WorkerId x) (WorkerId y) === WorkerId (min x y)
]
showReadSpecOnArbitrary @WorkerId
describe "Enum" $ do
it "toEnumFromEnum" $ property $ \ wid@(WorkerId i) ->
toEnum (getWorkerId wid) === wid .&&. fromEnum wid === i
it "succ . pred" $ property $ \ wid@(WorkerId i) ->
i /= minBound && i /= maxBound ==>
succ (pred wid) === wid .&&. pred (succ wid) === wid
describe "Trivial" $ do
it "WorkerIdIsZero" $ do
scheduleWorkId trivialScheduler_ (`shouldBe` 0)
withTrivialScheduler (`scheduleWorkId` pure) `shouldReturn` [0]
it "TerminateDoesNothing" $ do
terminate_ trivialScheduler_ `shouldReturn` ()
terminate trivialScheduler_ () `shouldReturn` ()
terminateWith trivialScheduler_ () `shouldReturn` ()
prop "TerminateSeq" $ prop_Terminate withTrivialScheduler terminate (\xs x -> xs ++ [x])
prop "TerminateWithSeq" $ prop_Terminate withTrivialScheduler terminateWith (\_ x -> [x])
prop "TerminateSeqR" $ prop_Terminate withTrivialSchedulerR terminate FinishedEarly
prop "TerminateWithSeqR" $
prop_Terminate withTrivialSchedulerR terminateWith (const FinishedEarlyWith)
describe "Seq" $ do
prop "SameList" $ prop_SameList Seq
prop "Recursive" $ prop_Recursive Seq
prop "Nested" $ prop_Nested Seq
prop "Serially" $ prop_Serially Seq
prop "TrivialAsSeq_" prop_TrivialSchedulerSameAsSeq_
prop "replicateConcurrently == replicateM" $ prop_ReplicateM id . getSeqLike
prop "replicateWork == replicateM" $ prop_ReplicateWork id . getSeqLike
prop "replicateWork_ == replicateM" $ prop_ReplicateWork_ id . getSeqLike
it "WorkerIdIsZero" $
withScheduler Seq (`scheduleWorkId` pure) `shouldReturn` [0]
prop "TerminateSeq" $ prop_Terminate (withScheduler Seq) terminate (\xs x -> xs ++ [x])
prop "TerminateWithSeq" $ prop_Terminate (withScheduler Seq) terminateWith (\_ x -> [x])
prop "TerminateSeqR" $ prop_Terminate (withSchedulerR Seq) terminate FinishedEarly
prop "TerminateWithSeqR" $
prop_Terminate (withSchedulerR Seq) terminateWith (const FinishedEarlyWith)
describe "ParOn" $ do
prop "SameList" $ \cs -> prop_SameList (ParOn cs)
prop "Recursive" $ \cs -> prop_Recursive (ParOn cs)
prop "Nested" $ \cs -> prop_Nested (ParOn cs)
prop "Serially" $ \cs -> prop_Serially (ParOn cs)
describe "Arbitrary Comp" $ do
prop "Trivial" prop_SameAsTrivialScheduler
prop "ArbitraryCompNested" prop_ArbitraryCompNested
prop "AllJobsProcessed" prop_AllJobsProcessed
prop "traverseConcurrently == traverse" prop_Traverse
prop "replicateConcurrently == replicateM" $ prop_ReplicateM sort
prop "replicateWork == replicateM" $ prop_ReplicateWork sort
prop "replicateWork_ == replicateM" $ prop_ReplicateWork_ sort
describe "Exceptions" $ do
prop "CatchDivideByZero" prop_CatchDivideByZero
prop "CatchDivideByZeroNested" prop_CatchDivideByZeroNested
prop "KillBlockedCoworker" prop_KillBlockedCoworker
prop "KillSleepingCoworker" prop_KillSleepingCoworker
prop "ExpectAsyncException" prop_ExpectAsyncException
prop "WorkerCaughtAsyncException" prop_WorkerCaughtAsyncException
prop "AllWorkersDied" prop_AllWorkersDied
prop "traverseConcurrently_" prop_TraverseConcurrently_
prop "traverseConcurrentlyInfinite_" prop_TraverseConcurrentlyInfinite_
describe "Premature" $ do
prop "FinishEarly" prop_FinishEarly
prop "FinishEarly_" prop_FinishEarly_
prop "FinishEarlyWith" prop_FinishEarlyWith
prop "FinishBeforeStarting" prop_FinishBeforeStarting
prop "FinishWithBeforeStarting" prop_FinishWithBeforeStarting
describe "WorkerState" $ do
prop "MutexException" prop_MutexException
prop "WorkerStateExclusive" prop_WorkerStateExclusive
describe "Restartable" $ do
prop "ManyJobsInChunks" prop_ManyJobsInChunks
prop "FindCancelResume" prop_FindCancelResume
instance Arbitrary WorkerId where
arbitrary = WorkerId <$> arbitrary
instance Arbitrary a => Arbitrary (Results a) where
arbitrary =
oneof
[ Finished <$> arbitrary
, FinishedEarly <$> arbitrary <*> arbitrary
, FinishedEarlyWith <$> arbitrary
]
-- | Assert a synchronous exception
assertExceptionIO :: (NFData a, Exception exc) =>
(exc -> Bool) -- ^ Return True if that is the exception that was expected
-> IO a -- ^ IO Action that should throw an exception
-> Property
assertExceptionIO isExc action =
monadicIO $ do
hasFailed <-
run $
catch
(do res <- action
res `deepseq` return False) $ \exc -> displayException exc `deepseq` return (isExc exc)
assert hasFailed
assertAsyncExceptionIO :: (Exception e, NFData a) => (e -> Bool) -> IO a -> Property
assertAsyncExceptionIO isAsyncExc action =
monadicIO $ do
hasFailed <-
run $
EUnsafe.catch
(do res <- action
res `deepseq` return False)
(\exc ->
case EUnsafe.asyncExceptionFromException exc of
Just asyncExc
| isAsyncExc asyncExc -> displayException asyncExc `deepseq` pure True
_ -> EUnsafe.throwIO exc)
assert hasFailed