parallel-io-0.2: Control/Concurrent/ParallelIO/Tests.hs
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
module Main where
import Data.IORef
import Data.List
import Test.Framework
import Test.Framework.Providers.HUnit
import Test.HUnit ((@?))
import GHC.Conc
import Control.Monad
import qualified Control.Concurrent.ParallelIO.Global as Global
import Control.Concurrent.ParallelIO.Local
main :: IO ()
main = do
defaultMain tests
Global.stopGlobalPool
tests :: [Test]
tests = [ testCase "parallel_ executes correct number of actions" $ repeatTest parallel__execution_count_correct
, testCase "parallel_ doesn't spawn too many threads" $ repeatTest parallel__doesnt_spawn_too_many_threads
, testCase "parallel executes correct actions" $ repeatTest parallel_executes_correct_actions
, testCase "parallel doesn't spawn too many threads" $ repeatTest parallel_doesnt_spawn_too_many_threads
, testCase "parallelInterleaved executes correct actions" $ repeatTest parallelInterleaved_executes_correct_actions
, testCase "parallelInterleaved doesn't spawn too many threads" $ repeatTest parallelInterleaved_doesnt_spawn_too_many_threads
]
parallel__execution_count_correct n = do
ref <- newIORef 0
Global.parallel_ (replicate n (atomicModifyIORef_ ref (+ 1)))
fmap (==n) $ readIORef ref
parallel_executes_correct_actions n = fmap (expected ==) actual
where actual = Global.parallel (map (return . (+1)) [0..n])
expected = [(1 :: Int)..n + 1]
parallelInterleaved_executes_correct_actions n = fmap ((expected ==) . sort) actual
where actual = Global.parallelInterleaved (map (return . (+1)) [0..n])
expected = [(1 :: Int)..n + 1]
parallel__doesnt_spawn_too_many_threads = doesnt_spawn_too_many_threads parallel_
parallel_doesnt_spawn_too_many_threads = doesnt_spawn_too_many_threads parallel
parallelInterleaved_doesnt_spawn_too_many_threads = doesnt_spawn_too_many_threads parallelInterleaved
doesnt_spawn_too_many_threads the_parallel n = do
threadcountref <- newIORef 0
maxref <- newIORef 0
-- NB: we use a local pool rather than the global one because otherwise we get interference effects
-- when we run the testsuite in parallel
withPool numCapabilities $ \pool -> do
_ <- the_parallel pool $ replicate n $ do
tc' <- atomicModifyIORef_ threadcountref (+ 1)
_ <- atomicModifyIORef_ maxref (`max` tc')
-- This delay and 'yield' combination was experimentally determined. The test
-- can and does still nondeterministically fail with a non-zero probability
-- dependening on runtime scheduling behaviour. It seems that the first instance
-- of this test to run in the process is especially vulnerable.
yield
threadDelay 20000
yield
atomicModifyIORef_ threadcountref (\tc -> tc - 1)
seenmax <- readIORef maxref
let expected_max_concurrent_threads = numCapabilities `min` n
if expected_max_concurrent_threads == seenmax
then return True
else putStrLn ("Expected at most " ++ show expected_max_concurrent_threads ++ ", got " ++ show seenmax) >> return False
atomicModifyIORef_ :: IORef a -> (a -> a) -> IO a
atomicModifyIORef_ ref f = atomicModifyIORef ref (\x -> let x' = f x in x' `seq` (x', x'))
repeatTest :: (Int -> IO Bool) -> IO ()
repeatTest testcase = forM_ [0..100] $ \n -> testcase n @? "n=" ++ show n