1{-# LANGUAGE DeriveGeneric #-}
2module Control.Distributed.Process.Tests.Mx (tests) where
4import Control.Distributed.Process.Tests.Internal.Utils
5import Network.Transport.Test (TestTransport(..))
7import Control.Distributed.Process
8import Control.Distributed.Process.Node
9import Control.Distributed.Process.Management
24import Control.Monad (void, unless)
25import Control.Rematch (equalTo)
27import Data.List (find, sort)
28import Data.Maybe (isJust, isNothing)
31#if ! MIN_VERSION_base(4,6,0)
32import Prelude hiding (catch, log)
39import Test.Framework.Providers.HUnit (testCase)
42 deriving (Typeable, Generic, Eq)
44instance Binary Publish where
46awaitExit :: ProcessId -> Process ()
48 withMonitorRef pid $ \ref -> do
50 [ matchIf (\(ProcessMonitorNotification r _ _) -> r == ref)
54 withMonitorRef pid = bracket (monitor pid) unmonitor
56testAgentBroadcast :: TestResult () -> Process ()
57testAgentBroadcast result = do
58 (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
60 publisher <- mxAgent (MxAgentId "publisher-agent") () [
61 mxSink $ \() -> mxBroadcast Publish >> mxReady
64 consumer <- mxAgent (MxAgentId "consumer-agent") () [
65 mxSink $ \Publish -> (liftMX $ sendChan resultSP ()) >> mxReady
69 -- Once the publisher has seen our message, it will broadcast the Publish
70 -- and the consumer will see that and send the result to our typed channel.
71 stash result =<< receiveChan resultRP
73 kill publisher "finished" >> awaitExit publisher
74 kill consumer "finished" >> awaitExit consumer
76testAgentDualInput :: TestResult (Maybe Int) -> Process ()
77testAgentDualInput result = do
79 s <- mxAgent (MxAgentId "sum-agent") (0 :: Int) [
80 mxSink $ (\(i :: Int) -> do
81 mxSetLocal . (+i) =<< mxGetLocal
84 then do mxGetLocal >>= liftMX . sendChan sp
85 mxDeactivate "finished"
90 nsend "sum-agent" (3 :: Int)
92 nsend "sum-agent" (4 :: Int)
95 stash result =<< receiveChanTimeout 10000000 rp
98testAgentPrioritisation :: TestResult [String] -> Process ()
99testAgentPrioritisation result = do
101 -- TODO: this isn't really testing how we /prioritise/ one source
102 -- over another at all, but I've not yet figured out the right way
103 -- to do so, since we're at the whim of the scheduler with regards
104 -- the timeliness of nsend versus mxNotify anyway.
106 let name = "prioritising-agent"
108 s <- mxAgent (MxAgentId name) ["first"] [
109 mxSink (\(s :: String) -> do
113 n | n == 5 -> do liftMX $ sendChan sp st
114 mxDeactivate "finished"
115 _ -> mxReceive -- go to the mailbox
124 stash result . sort =<< receiveChan rp
127testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
128testAgentMailboxHandling result = do
130 agent <- mxAgent (MxAgentId "mailbox-agent") () [
131 mxSink $ \() -> (liftMX $ sendChan sp ()) >> mxReady
134 nsend "mailbox-agent" ()
136 stash result =<< receiveChanTimeout 1000000 rp
137 kill agent "finished" >> awaitExit agent
139testAgentEventHandling :: TestResult Bool -> Process ()
140testAgentEventHandling result = do
141 let initState = [] :: [MxEvent]
144 agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
149 (MxSpawned _) -> mxSetLocal (ev:st)
150 (MxProcessDied _ _) -> mxSetLocal (ev:st)
152 act >> (liftMX $ sendChan rc ()) >> mxReady),
153 (mxSink $ \(ev, sp :: SendPort Bool) -> do
158 isJust $ find (\ev' ->
160 (MxSpawned p') -> p' == p
163 isJust $ find (\ev' ->
165 (MxProcessDied p' r') -> p' == p && r == r'
168 liftMX $ sendChan sp found
172 -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
175 _ <- monitor agentPid
178 pid <- spawnLocal $ expect >>= sendChan sp
180 -- By waiting for a monitor notification, we have a
181 -- higher probably that the agent has seen the spawn and died events
187 receiveWait [ match (\(ProcessMonitorNotification _ _ _) -> return ()) ]
189 -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
192 (replyTo, reply) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
193 mxNotify (MxSpawned pid, replyTo)
194 mxNotify (MxProcessDied pid DiedNormal, replyTo)
196 seenAlive <- receiveChan reply
197 seenDead <- receiveChan reply
199 stash result $ seenAlive && seenDead
200 kill agentPid "test-complete" >> awaitExit agentPid
203 res <- receiveChanTimeout delay port
204 unless (isNothing res) $ do
208testMxRegEvents :: TestResult () -> Process ()
209testMxRegEvents result = do
210 {- This test only deals with the local case, to ensure that we are being
211 notified in the expected order - the remote cases related to the
212 behaviour of the node controller are contained in the CH test suite. -}
213 ensure (stash result ()) $ do
214 let label = "testMxRegEvents"
215 let agentLabel = "mxRegEvents-agent"
217 (regChan, regSink) <- newChan
218 (unRegChan, unRegSink) <- newChan
219 agent <- mxAgent (MxAgentId agentLabel) () [
222 MxRegistered pid label'
223 | label' == label -> liftMX $ sendChan regChan (label', pid)
224 MxUnRegistered pid label'
225 | label' == label -> liftMX $ sendChan unRegChan (label', pid)
230 p1 <- spawnLocal expect
231 p2 <- spawnLocal expect
234 reg1 <- receiveChanTimeout delay regSink
235 reg1 `shouldBe` equalTo (Just (label, p1))
238 unreg1 <- receiveChanTimeout delay unRegSink
239 unreg1 `shouldBe` equalTo (Just (label, p1))
242 reg2 <- receiveChanTimeout delay regSink
243 reg2 `shouldBe` equalTo (Just (label, p2))
246 unreg2 <- receiveChanTimeout delay unRegSink
247 unreg2 `shouldBe` equalTo (Just (label, p2))
249 reg3 <- receiveChanTimeout delay regSink
250 reg3 `shouldBe` equalTo (Just (label, p1))
252 mapM_ (flip kill $ "test-complete") [agent, p1, p2]
255testMxRegMon :: LocalNode -> TestResult () -> Process ()
256testMxRegMon remoteNode result = do
257 ensure (stash result ()) $ do
258 -- ensure that when a registered process dies, we get a notification that
259 -- it has been unregistered as well as seeing the name get removed
262 let isValid l = l == label1 || l == label2
263 let agentLabel = "mxRegMon-agent"
265 (regChan, regSink) <- newChan
266 (unRegChan, unRegSink) <- newChan
267 agent <- mxAgent (MxAgentId agentLabel) () [
270 MxRegistered pid label
271 | isValid label -> liftMX $ sendChan regChan (label, pid)
272 MxUnRegistered pid label
273 | isValid label -> liftMX $ sendChan unRegChan (label, pid)
279 liftIO $ forkProcess remoteNode $ do
280 getSelfPid >>= sendChan sp
286 reg1 <- receiveChanTimeout delay regSink
287 reg1 `shouldBe` equalTo (Just (label1, p1))
290 reg2 <- receiveChanTimeout delay regSink
291 reg2 `shouldBe` equalTo (Just (label2, p1))
294 n1 `shouldBe` equalTo (Just p1)
297 n2 `shouldBe` equalTo (Just p1)
301 unreg1 <- receiveChanTimeout delay unRegSink
302 unreg2 <- receiveChanTimeout delay unRegSink
304 let evts = [unreg1, unreg2]
305 -- we can't rely on the order of the values in the node controller's
306 -- map (it's either racy to do so, or no such guarantee exists for Data.Map),
307 -- so we simply verify that we received the un-registration events we expect
308 evts `shouldContain` (Just (label1, p1))
309 evts `shouldContain` (Just (label2, p1))
311 kill agent "test-complete" >> awaitExit agent
313ensure :: Process () -> Process () -> Process ()
316tests :: TestTransport -> IO [Test]
317tests TestTransport{..} = do
318 node1 <- newLocalNode testTransport initRemoteTable
319 node2 <- newLocalNode testTransport initRemoteTable
321 testGroup "Mx Agents" [
322 testCase "Event Handling"
324 "expected True, but events where not as expected"
325 node1 True testAgentEventHandling)
326 , testCase "Inter-Agent Broadcast"
328 "expected (), but no broadcast was received"
329 node1 () testAgentBroadcast)
330 , testCase "Agent Mailbox Handling"
332 "expected (Just ()), but no regular (mailbox) input was handled"
333 node1 (Just ()) testAgentMailboxHandling)
334 , testCase "Agent Dual Input Handling"
336 "expected sum = 15, but the result was Nothing"
337 node1 (Just 15 :: Maybe Int) testAgentDualInput)
338 , testCase "Agent Input Prioritisation"
340 "expected [first, second, third, fourth, fifth], but result diverged"
341 node1 (sort ["first", "second",
343 "fifth"]) testAgentPrioritisation)
345 , testGroup "Mx Events" [
346 testCase "Name Registration Events"
348 "expected registration events to map to the correct ProcessId"
349 node1 () testMxRegEvents)
350 , testCase "Post Death Name UnRegistration Events"
352 "expected process deaths to result in unregistration events"
353 node1 () (testMxRegMon node2))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list