{-# LANGUAGE NoImplicitPrelude, UnicodeSyntax, DeriveDataTypeable #-} module Main where ------------------------------------------------------------------------------- -- Imports ------------------------------------------------------------------------------- -- from base: import Control.Concurrent ( threadDelay ) import Control.Exception ( Exception, fromException , AsyncException(ThreadKilled) , throwIO , unblock, block, blocked ) import Control.Monad ( return, (>>=), fail, (>>) ) import Data.Bool ( Bool(False, True), not ) import Data.Eq ( Eq ) import Data.Either ( either ) import Data.Function ( ($), id, const ) import Data.Functor ( fmap, (<$>) ) import Data.IORef ( newIORef, readIORef, writeIORef ) import Data.Maybe ( maybe ) import Data.Typeable ( Typeable ) import Prelude ( fromInteger ) import System.Timeout ( timeout ) import System.IO ( IO ) import Text.Show ( Show ) -- from base-unicode-symbols: import Prelude.Unicode ( (⋅) ) import Data.Eq.Unicode ( (≡) ) import Data.Function.Unicode ( (∘) ) -- from concurrent-extra: import qualified Control.Concurrent.Lock as Lock -- from HUnit: import Test.HUnit ( Assertion, assert ) -- from test-framework: import Test.Framework ( Test, defaultMain, testGroup ) -- from test-framework-hunit: import Test.Framework.Providers.HUnit ( testCase ) -- from threads: import qualified Control.Concurrent.Thread as Thread import qualified Control.Concurrent.Thread.Group as ThreadGroup import TestUtils ( a_moment ) import Utils ( (<$$>) ) ------------------------------------------------------------------------------- -- Tests ------------------------------------------------------------------------------- main ∷ IO () main = defaultMain tests tests ∷ [Test] tests = [ testGroup "Thread" $ [ testCase "wait" $ test_wait Thread.forkIO , testCase "isRunning" $ test_isRunning Thread.forkIO , testCase "blockedState" $ test_blockedState Thread.forkIO , testCase "unblockedState" $ test_unblockedState Thread.forkIO , testCase "sync exception" $ test_sync_exception Thread.forkIO , testCase "async exception" $ test_async_exception Thread.forkIO ] , testGroup "ThreadGroup" $ [ testCase "wait" $ wrap test_wait , testCase "isRunning" $ wrap test_isRunning , testCase "blockedState" $ wrap test_blockedState , testCase "unblockedState" $ wrap test_unblockedState , testCase "sync exception" $ wrap test_sync_exception , testCase "async exception" $ wrap test_async_exception , testCase "group single wait" test_group_single_wait , testCase "group single isRunning" test_group_single_isRunning ] ] ------------------------------------------------------------------------------- -- General properties ------------------------------------------------------------------------------- type Fork α = IO α → IO (Thread.ThreadId α) test_wait ∷ Fork () → Assertion test_wait fork = assert $ fmap (maybe False id) $ timeout (10 ⋅ a_moment) $ do r ← newIORef False tid ← fork $ do threadDelay $ 2 ⋅ a_moment writeIORef r True _ ← Thread.wait tid readIORef r test_isRunning ∷ Fork () → Assertion test_isRunning fork = assert $ fmap (maybe False id) $ timeout (10 ⋅ a_moment) $ do l ← Lock.newAcquired tid ← fork $ Lock.acquire l r ← Thread.isRunning tid Lock.release l return r test_blockedState ∷ Fork Bool → Assertion test_blockedState fork = (block $ fork $ blocked) >>= Thread.unsafeWait >>= assert test_unblockedState ∷ Fork Bool → Assertion test_unblockedState fork = (unblock $ fork $ not <$> blocked) >>= Thread.unsafeWait >>= assert test_sync_exception ∷ Fork () → Assertion test_sync_exception fork = assert $ fork (throwIO MyException) >>= waitForException MyException waitForException ∷ (Exception e, Eq e) ⇒ e → Thread.ThreadId α → IO Bool waitForException e tid = Thread.wait tid <$$> either (maybe False (≡ e) ∘ fromException) (const False) test_async_exception ∷ Fork () → Assertion test_async_exception fork = assert $ do l ← Lock.newAcquired tid ← fork $ Lock.acquire l Thread.throwTo tid MyException waitForException MyException tid data MyException = MyException deriving (Show, Eq, Typeable) instance Exception MyException test_killThread ∷ Fork () → Assertion test_killThread fork = assert $ do l ← Lock.newAcquired tid ← fork $ Lock.acquire l Thread.killThread tid waitForException ThreadKilled tid ------------------------------------------------------------------------------- -- ThreadGroup ------------------------------------------------------------------------------- wrap ∷ (Fork α → IO β) → IO β wrap test = ThreadGroup.new >>= test ∘ ThreadGroup.forkIO test_group_single_wait ∷ Assertion test_group_single_wait = assert $ fmap (maybe False id) $ timeout (10 ⋅ a_moment) $ do tg ← ThreadGroup.new r ← newIORef False _ ← ThreadGroup.forkIO tg $ do threadDelay $ 2 ⋅ a_moment writeIORef r True _ ← ThreadGroup.wait tg readIORef r test_group_single_isRunning ∷ Assertion test_group_single_isRunning = assert $ fmap (maybe False id) $ timeout (10 ⋅ a_moment) $ do tg ← ThreadGroup.new l ← Lock.newAcquired _ ← ThreadGroup.forkIO tg $ Lock.acquire l r ← ThreadGroup.isAnyRunning tg Lock.release l return r -- The End ---------------------------------------------------------------------