module Main where
#if ! MIN_VERSION_base(4,6,0)
import Prelude hiding (catch)
#endif
import Control.Monad (forever)
import Control.Concurrent.MVar
( newEmptyMVar
, putMVar
, takeMVar
, withMVar
)
import qualified Network.Transport as NT (Transport)
import Network.Transport.TCP()
import Control.DeepSeq (NFData)
import Control.Distributed.Process
import Control.Distributed.Process.Node
import Control.Distributed.Process.Serializable()
import Control.Distributed.Process.Extras.Time
import Control.Distributed.Process.Extras.Timer
import Control.Distributed.Process.SysTest.Utils
import Test.Framework (Test, testGroup, defaultMain)
import Test.Framework.Providers.HUnit (testCase)
import Network.Transport.TCP
import qualified Network.Transport as NT
import GHC.Generics
instance NFData Ping where
testSendAfter :: TestResult Bool -> Process ()
testSendAfter result =
let delay = seconds 1 in do
sleep $ seconds 10
pid <- getSelfPid
_ <- sendAfter delay pid Ping
hdInbox <- receiveTimeout (asTimeout (seconds 2)) [
match (\m@(Ping) -> return m)
]
case hdInbox of
Just Ping -> stash result True
Nothing -> stash result False
testRunAfter :: TestResult Bool -> Process ()
testRunAfter result =
let delay = seconds 2 in do
parentPid <- getSelfPid
_ <- spawnLocal $ do
_ <- runAfter delay $ send parentPid Ping
return ()
msg <- expectTimeout ((asTimeout delay) * 4)
case msg of
Just Ping -> stash result True
Nothing -> stash result False
return ()
testCancelTimer :: TestResult Bool -> Process ()
testCancelTimer result = do
let delay = milliSeconds 50
pid <- periodically delay noop
ref <- monitor pid
_ <- getProcessInfo pid
cancelTimer pid
_ <- receiveWait [
match (\(ProcessMonitorNotification ref' pid' _) ->
stash result $ ref == ref' && pid == pid')
]
return ()
testPeriodicSend :: TestResult Bool -> Process ()
testPeriodicSend result = do
let delay = milliSeconds 100
self <- getSelfPid
ref <- ticker delay self
listener 0 ref
liftIO $ putMVar result True
where listener :: Int -> TimerRef -> Process ()
listener n tRef | n > 10 = cancelTimer tRef
| otherwise = waitOne >> listener (n + 1) tRef
-- get a single tick, blocking indefinitely
waitOne :: Process ()
waitOne = do
Tick <- expect
return ()
testTimerReset :: TestResult Int -> Process ()
testTimerReset result = do
let delay = seconds 10
counter <- liftIO $ newEmptyMVar
listenerPid <- spawnLocal $ do
stash counter 0
-- we continually listen for 'ticks' and increment counter for each
forever $ do
Tick <- expect
liftIO $ withMVar counter (\n -> (return (n + 1)))
-- this ticker will 'fire' every 10 seconds
ref <- ticker delay listenerPid
sleep $ seconds 2
resetTimer ref
-- at this point, the timer should be back to roughly a 5 second count down
-- so our few remaining cycles no ticks ought to make it to the listener
-- therefore we kill off the timer and the listener now and take the count
cancelTimer ref
kill listenerPid "stop!"
-- how many 'ticks' did the listener observer? (hopefully none!)
count <- liftIO $ takeMVar counter
liftIO $ putMVar result count
testTimerFlush :: TestResult Bool -> Process ()
testTimerFlush result = do
let delay = seconds 1
self <- getSelfPid
ref <- ticker delay self
-- sleep so we *should* have a message in our 'mailbox'
sleep $ milliSeconds 2
-- flush it out if it's there
flushTimer ref Tick (Delay $ seconds 3)
m <- expectTimeout 10
case m of
Nothing -> stash result True
Just Tick -> stash result False
testSleep :: TestResult Bool -> Process ()
testSleep r = do
sleep $ seconds 20
stash r True
--------------------------------------------------------------------------------
-- Utilities and Plumbing --
--------------------------------------------------------------------------------
tests :: LocalNode -> [Test]
tests localNode = [
testGroup "Timer Tests" [
testCase "testSendAfter"
(delayedAssertion
"expected Ping within 1 second"
localNode True testSendAfter)
, testCase "testRunAfter"
(delayedAssertion
"expecting run (which pings parent) within 2 seconds"
localNode True testRunAfter)
, testCase "testCancelTimer"
(delayedAssertion
"expected cancelTimer to exit the timer process normally"
localNode True testCancelTimer)
, testCase "testPeriodicSend"
(delayedAssertion
"expected ten Ticks to have been sent before exiting"
localNode True testPeriodicSend)
, testCase "testTimerReset"
(delayedAssertion
"expected no Ticks to have been sent before resetting"
localNode 0 testTimerReset)
, testCase "testTimerFlush"
(delayedAssertion
"expected all Ticks to have been flushed"
localNode True testTimerFlush)
, testCase "testSleep"
(delayedAssertion
"why am I not seeing a delay!?"
localNode True testTimerFlush)
]
]
timerTests :: NT.Transport -> IO [Test]
timerTests transport = do
localNode <- newLocalNode transport initRemoteTable
let testData = tests localNode
return testData
-- | Given a @builder@ function, make and run a test suite on a single transport
testMain :: (NT.Transport -> IO [Test]) -> IO ()
testMain builder = do
Right (transport, _) <- createTransportExposeInternals
"127.0.0.1" "0" defaultTCPParameters
testData <- builder transport
defaultMain testData
main :: IO ()
main = testMain $ timerTests