{-# LANGUAGE NoImplicitPrelude, UnicodeSyntax, ScopedTypeVariables #-}
module Main where
-------------------------------------------------------------------------------
-- Imports
-------------------------------------------------------------------------------
-- from base
import Control.Applicative ( (<$>) )
import Control.Concurrent ( forkIO, threadDelay )
import Control.Exception ( catch, try, throwTo, ErrorCall(..), SomeException )
import Control.Monad ( (>>=), (>>), return, fail, fmap
, mapM_, replicateM, replicateM_
)
import Data.Bool ( Bool, not )
import Data.Char ( String )
import Data.Either ( Either(Left, Right) )
import Data.Function ( ($) )
import Data.Int ( Int )
import Data.Maybe ( isJust )
import Prelude ( fromInteger )
import System.IO ( IO )
import System.Timeout ( timeout )
-- from base-unicode-symbols
import Data.Function.Unicode ( (∘) )
import Prelude.Unicode ( (⋅) )
-- from concurrent-extra
import qualified Control.Concurrent.Lock as Lock
import qualified Control.Concurrent.Event as Event
-- from HUnit
import Test.HUnit hiding ( Test )
-- from test-framework
import Test.Framework ( Test, defaultMain, testGroup )
-- from test-framework-hunit
import Test.Framework.Providers.HUnit ( testCase )
-------------------------------------------------------------------------------
-- Tests
-------------------------------------------------------------------------------
main ∷ IO ()
main = defaultMain tests
tests ∷ [Test]
tests = [ testGroup "Events"
[ testCase "set wait a" $ test_event_1 1 1
, testCase "set wait b" $ test_event_1 5 1
, testCase "set wait c" $ test_event_1 1 5
, testCase "set wait d" $ test_event_1 5 5
, testCase "conc set wait" $ test_event_2
, testCase "multi wake" $ test_event_3 10
, testCase "exception" $ test_event_4
, testCase "wait timeout" $ test_event_5
, testCase "foo!" $ test_event_6
]
, testGroup "Lock"
[ testCase "acquire release" test_lock_1
, testCase "acquire acquire" test_lock_2
, testCase "new release" test_lock_3
, testCase "new unlocked" test_lock_4
, testCase "newAcquired locked" test_lock_5
, testCase "acq rel unlocked" test_lock_6
, testCase "conc release" test_lock_7
]
, testGroup "RLock"
[
]
]
--------------------------------------------------------------------------------
-- Events
--------------------------------------------------------------------------------
-- Set an event 's' times then wait for it 'w' times. This should
-- terminate within a few moments.
test_event_1 ∷ Int → Int → Assertion
test_event_1 s w = assert $ within (10 ⋅ a_moment) $ do
e ← Event.new
replicateM_ s $ Event.set e
replicateM_ w $ Event.wait e
test_event_2 ∷ Assertion
test_event_2 = assert $ within (10 ⋅ a_moment) $ do
e1 ← Event.new
e2 ← Event.new
_ ← forkIO $ do
Event.wait e1
Event.set e2
wait_a_moment
Event.set e1
Event.wait e2
-- Waking multiple threads with a single Event.
test_event_3 ∷ Int → Assertion
test_event_3 n = assert $ within (10 ⋅ a_moment) $ do
e1 ← Event.new
es ← replicateM n $ do
e2 ← Event.new
_ ← forkIO $ do
Event.wait e1
Event.set e2
return e2
wait_a_moment
Event.set e1
mapM_ Event.wait es
-- Exception handling while waiting for an Event.
test_event_4 ∷ Assertion
test_event_4 = assert $ within (10 ⋅ a_moment) $ do
e1 ← Event.new
e2 ← Event.new
helperId ← forkIO $ catch (Event.wait e1) $ \(_ ∷ ErrorCall) → Event.set e2
wait_a_moment
throwTo helperId $ ErrorCall "Boo!"
Event.wait e2
test_event_5 ∷ Assertion
test_event_5 = assert $ within (10 ⋅ a_moment) $ do
e ← Event.new
Event.waitTimeout e a_moment
test_event_6 ∷ Assertion
test_event_6 = assert $ notWithin (10 ⋅ a_moment) $ do
e ← Event.new
Event.wait e
--------------------------------------------------------------------------------
-- Locks
--------------------------------------------------------------------------------
test_lock_1 ∷ Assertion
test_lock_1 = assert $ within a_moment $ do
l ← Lock.new
Lock.acquire l
Lock.release l
test_lock_2 ∷ Assertion
test_lock_2 = assert $ notWithin (10 ⋅ a_moment) $ do
l ← Lock.new
Lock.acquire l
Lock.acquire l
test_lock_3 ∷ Assertion
test_lock_3 = assertException "" $ do
l ← Lock.new
Lock.release l
test_lock_4 ∷ Assertion
test_lock_4 = assert $ do
l ← Lock.new
fmap not $ Lock.locked l
test_lock_5 ∷ Assertion
test_lock_5 = assert $ do
l ← Lock.newAcquired
Lock.locked l
test_lock_6 ∷ Assertion
test_lock_6 = assert $ do
l ← Lock.new
Lock.acquire l
Lock.release l
fmap not $ Lock.locked l
test_lock_7 ∷ Assertion
test_lock_7 = assert ∘ within (10 ⋅ a_moment) $ do
l ← Lock.newAcquired
_ ← forkIO $ wait_a_moment >> Lock.release l
Lock.acquire l
--------------------------------------------------------------------------------
-- RLocks
--------------------------------------------------------------------------------
-- TODO
-------------------------------------------------------------------------------
-- Misc
-------------------------------------------------------------------------------
-- Exactly 1 moment. Currently equal to 0.005 seconds.
a_moment ∷ Int
a_moment = 5000
wait_a_moment ∷ IO ()
wait_a_moment = threadDelay a_moment
-- True if the action 'a' evaluates within 't' μs.
within ∷ Int → IO α → IO Bool
within t a = isJust <$> timeout t a
notWithin ∷ Int → IO α → IO Bool
notWithin t a = not <$> within t a
assertException ∷ String → IO α → Assertion
assertException errMsg a = do e ← try a
case e of
Left (_ ∷ SomeException ) → return ()
Right _ → assertFailure errMsg