@@ -1,22 +1,16 @@
1−{-# LANGUAGE DeriveGeneric #-}
1+{-# LANGUAGE DeriveGeneric #-}
2−{-# LANGUAGE ParallelListComp #-}
4module Control.Distributed.Process.Tests.Mx (tests) where
2module Control.Distributed.Process.Tests.Mx (tests) where
6import Control.Distributed.Process.Tests.Internal.Utils
4import Control.Distributed.Process.Tests.Internal.Utils
7import Network.Transport.Test (TestTransport(..))
5import Network.Transport.Test (TestTransport(..))
8−import Control.Distributed.Process hiding (bracket, finally, try)
6+
9−import Control.Distributed.Process.Internal.Types
7+import Control.Distributed.Process
10− ( ProcessExitException(..)
11− , unsafeCreateUnencodedMessage
13import Control.Distributed.Process.Node
8import Control.Distributed.Process.Node
14import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
9import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
17 , nsendRemote
12 , nsendRemote
21import Control.Distributed.Process.Management
15import Control.Distributed.Process.Management
22 ( MxEvent(..)
16 ( MxEvent(..)
@@ -34,13 +28,12 @@ import Control.Distributed.Process.Management
34 , mxBroadcast
28 , mxBroadcast
36import Control.Monad (void, unless)
30import Control.Monad (void, unless)
37−import Control.Monad.Catch(finally, bracket, try)
38import Control.Rematch (equalTo)
31import Control.Rematch (equalTo)
39import Data.Binary
32import Data.Binary
40−import Data.List (find, sort, intercalate)
33+import Data.List (find, sort)
41−import Data.Maybe (isJust, fromJust, isNothing, fromMaybe, catMaybes)
34+import Data.Maybe (isJust, isNothing)
42import Data.Typeable
35import Data.Typeable
43−import GHC.Generics hiding (from)
36+import GHC.Generics
44#if ! MIN_VERSION_base(4,6,0)
37#if ! MIN_VERSION_base(4,6,0)
45import Prelude hiding (catch, log)
38import Prelude hiding (catch, log)
@@ -64,9 +57,9 @@ awaitExit pid =
64 (\_ -> return ())
57 (\_ -> return ())
67− withMonitorRef p = bracket (monitor p) unmonitor
60+ withMonitorRef pid = bracket (monitor pid) unmonitor
69−testAgentBroadcast :: TestResult (Maybe ()) -> Process ()
62+testAgentBroadcast :: TestResult () -> Process ()
70testAgentBroadcast result = do
63testAgentBroadcast result = do
71 (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
64 (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
@@ -81,10 +74,10 @@ testAgentBroadcast result = do
81 mxNotify ()
74 mxNotify ()
82 -- Once the publisher has seen our message, it will broadcast the Publish
75 -- Once the publisher has seen our message, it will broadcast the Publish
83 -- and the consumer will see that and send the result to our typed channel.
76 -- and the consumer will see that and send the result to our typed channel.
84− stash result =<< receiveChanTimeout 10000000 resultRP
77+ stash result =<< receiveChan resultRP
86− kill publisher "finished"
79+ kill publisher "finished" >> awaitExit publisher
87− kill consumer "finished"
80+ kill consumer "finished" >> awaitExit consumer
89testAgentDualInput :: TestResult (Maybe Int) -> Process ()
82testAgentDualInput :: TestResult (Maybe Int) -> Process ()
90testAgentDualInput result = do
83testAgentDualInput result = do
@@ -99,8 +92,6 @@ testAgentDualInput result = do
99 else mxReady)
92 else mxReady)
104 mxNotify (1 :: Int)
95 mxNotify (1 :: Int)
105 nsend "sum-agent" (3 :: Int)
96 nsend "sum-agent" (3 :: Int)
106 mxNotify (2 :: Int)
97 mxNotify (2 :: Int)
@@ -108,10 +99,7 @@ testAgentDualInput result = do
108 mxNotify (5 :: Int)
99 mxNotify (5 :: Int)
110 stash result =<< receiveChanTimeout 10000000 rp
101 stash result =<< receiveChanTimeout 10000000 rp
111− died <- receiveTimeout 10000000 [
102+ awaitExit s
112− matchIf (\(ProcessMonitorNotification r _ _) -> r == mRef) (const $ return True)
114− died `shouldBe` equalTo (Just True)
116testAgentPrioritisation :: TestResult [String] -> Process ()
104testAgentPrioritisation :: TestResult [String] -> Process ()
117testAgentPrioritisation result = do
105testAgentPrioritisation result = do
@@ -125,10 +113,10 @@ testAgentPrioritisation result = do
125 (sp, rp) <- newChan
113 (sp, rp) <- newChan
126 s <- mxAgent (MxAgentId name) ["first"] [
114 s <- mxAgent (MxAgentId name) ["first"] [
127 mxSink (\(s :: String) -> do
115 mxSink (\(s :: String) -> do
128− mxUpdateLocal (s:)
116+ mxUpdateLocal ((s:))
129 st <- mxGetLocal
117 st <- mxGetLocal
130 case length st of
118 case length st of
131− n | n == 5 -> do liftMX $ sendChan sp (sort st)
119+ n | n == 5 -> do liftMX $ sendChan sp st
132 mxDeactivate "finished"
120 mxDeactivate "finished"
133 _ -> mxReceive -- go to the mailbox
121 _ -> mxReceive -- go to the mailbox
@@ -139,7 +127,7 @@ testAgentPrioritisation result = do
139 mxNotify "fourth"
127 mxNotify "fourth"
140 nsend name "fifth"
128 nsend name "fifth"
142− stash result . fromMaybe [] =<< receiveChanTimeout 10000000 rp
130+ stash result . sort =<< receiveChan rp
143 awaitExit s
131 awaitExit s
145testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
133testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
@@ -152,19 +140,12 @@ testAgentMailboxHandling result = do
152 nsend "mailbox-agent" ()
140 nsend "mailbox-agent" ()
154 stash result =<< receiveChanTimeout 1000000 rp
142 stash result =<< receiveChanTimeout 1000000 rp
155− kill agent "finished"
143+ kill agent "finished" >> awaitExit agent
157testAgentEventHandling :: TestResult Bool -> Process ()
145testAgentEventHandling :: TestResult Bool -> Process ()
158testAgentEventHandling result = do
146testAgentEventHandling result = do
160− -- because this test is a bit racy, let's ensure it can't run indefinitely
161− timer <- spawnLocal $ do
162− pause (1000000 * 60 * 5)
163− -- okay we've waited 5 mins, let's kill the test off if it's stuck...
165− kill us "Test Timed Out"
167 let initState = [] :: [MxEvent]
147 let initState = [] :: [MxEvent]
168 (rc, rs) <- newChan
149 (rc, rs) <- newChan
169 agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
150 agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
170 (mxSink $ \ev -> do
151 (mxSink $ \ev -> do
@@ -197,7 +178,8 @@ testAgentEventHandling result = do
197 -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
178 -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
198 faff 2000000 rs
179 faff 2000000 rs
181+ _ <- monitor agentPid
200− (sp, rp) <- newChan :: Process (SendPort (), ReceivePort ())
182+ (sp, rp) <- newChan
202 pid <- spawnLocal $ expect >>= sendChan sp
184 pid <- spawnLocal $ expect >>= sendChan sp
@@ -206,11 +188,9 @@ testAgentEventHandling result = do
206 monitor pid
188 monitor pid
208 send pid ()
190 send pid ()
209− rct <- receiveChanTimeout 10000000 rp
191+ () <- receiveChan rp
210− unless (isJust rct) $ die "No response on channel"
212− pmn <- receiveTimeout 2000000 [ match (\ProcessMonitorNotification{} -> return ()) ]
193+ receiveWait [ match (\(ProcessMonitorNotification _ _ _) -> return ()) ]
213− unless (isJust pmn) $ die "No monitor notification arrived"
215 -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
195 -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
216 faff 2000000 rs
196 faff 2000000 rs
@@ -219,16 +199,17 @@ testAgentEventHandling result = do
219 mxNotify (MxSpawned pid, replyTo)
199 mxNotify (MxSpawned pid, replyTo)
220 mxNotify (MxProcessDied pid DiedNormal, replyTo)
200 mxNotify (MxProcessDied pid DiedNormal, replyTo)
222− seenAlive <- receiveChanTimeout 10000000 reply
202+ seenAlive <- receiveChan reply
223− seenDead <- receiveChanTimeout 10000000 reply
203+ seenDead <- receiveChan reply
225− stash result $ foldr (&&) True $ catMaybes [seenAlive, seenDead]
205+ stash result $ seenAlive && seenDead
226− kill timer "test-complete"
206+ kill agentPid "test-complete" >> awaitExit agentPid
227− kill agentPid "test-complete"
229 faff delay port = do
208 faff delay port = do
230 res <- receiveChanTimeout delay port
209 res <- receiveChanTimeout delay port
231− unless (isNothing res) $ pause delay
210+ unless (isNothing res) $ do
233testMxRegEvents :: TestResult () -> Process ()
214testMxRegEvents :: TestResult () -> Process ()
234testMxRegEvents result = do
215testMxRegEvents result = do
@@ -275,6 +256,7 @@ testMxRegEvents result = do
275 reg3 `shouldBe` equalTo (Just (label, p1))
256 reg3 `shouldBe` equalTo (Just (label, p1))
277 mapM_ (flip kill $ "test-complete") [agent, p1, p2]
258 mapM_ (flip kill $ "test-complete") [agent, p1, p2]
279testMxRegMon :: LocalNode -> TestResult () -> Process ()
261testMxRegMon :: LocalNode -> TestResult () -> Process ()
280testMxRegMon remoteNode result = do
262testMxRegMon remoteNode result = do
@@ -332,211 +314,106 @@ testMxRegMon remoteNode result = do
332 evts `shouldContain` (Just (label1, p1))
314 evts `shouldContain` (Just (label1, p1))
333 evts `shouldContain` (Just (label2, p1))
315 evts `shouldContain` (Just (label2, p1))
335− kill agent "test-complete"
317+ kill agent "test-complete" >> awaitExit agent
337ensure :: Process () -> Process () -> Process ()
319ensure :: Process () -> Process () -> Process ()
338ensure = flip finally
320ensure = flip finally
340−testNSend :: (String -> () -> Process ())
346− let label = "testMxSend" ++ (show us)
347− let isValid = isValidLabel n label
349− testMxSend n label $ \p1 sink -> do
351− reg1 <- receiveChanTimeout delay sink
353− Just (MxRegistered pd lb)
354− | pd == p1 && isValid lb -> return ()
355− _ -> die $ "Reg-Failed: " ++ show reg1
357− op label ()
323+testMxUnsafeSendEvents :: LocalNode -> Process ()
324+testMxUnsafeSendEvents remoteNode = do
359− sent <- receiveChanTimeout delay sink
326+ -- ensure that when a registered process dies, we get a notification that
360− case sent of
327+ -- it has been unregistered as well as seeing the name get removed
361− Just (MxSentToName lb by _)
362− | by == us && isValid lb -> return True
363− _ -> die $ "Send-Failed: " ++ show sent
365− where
329+ let label1 = "aaaaa"
366− isValidLabel nd l1 l2
330+ let label2 = "bbbbb"
367− | l2 == l2 = True
331+ let isValid l = l ==label1 || l == label2
368− | isJust nd = l2 == l1 ++ "@" ++ show (localNodeId $ fromJust nd)
332+ let agentLabel = "listener-agent"
369− | otherwise = False
333+ let delay = 1000000
371−testSend :: (ProcessId -> () -> Process ())
376− let label = "testMxSend" ++ (show us)
377− testMxSend n label $ \p1 sink -> do
381− -- verify the management event
382− sent <- receiveChanTimeout 5000000 sink
384− Just (MxSent pidTo pidFrom _)
385− | pidTo == p1 && pidFrom == us -> return True
388−testChan :: (SendPort () -> () -> Process ())
391−testChan op n = testMxSend n "" $ \p1 sink -> do
396− cleared <- receiveChanTimeout 2000000 sink
398− Just (MxSent pidTo pidFrom _)
399− | pidTo == p1 && pidFrom == us -> return ()
400− _ -> die "Received uncleared Mx Event"
402− chan <- receiveTimeout 5000000 [ match return ]
403− let ch' = fromMaybe (error "No reply chan received") chan
408− -- verify the management event
409− sent <- receiveChanTimeout 5000000 sink
411− Just (MxSentToPort sId spId _)
412− | sId == us && spId == sendPortId ch' -> return True
415−type SendTest = ProcessId -> ReceivePort MxEvent -> Process Bool
417−testMxSend :: Maybe LocalNode -> String -> SendTest -> Process ()
418−testMxSend mNode label test = do
420− (sp, rp) <- newChan
334+ (regChan, regSink) <- newChan
421− (chan, sink) <- newChan
335+ (unRegChan, unRegSink) <- newChan
422− agent <- mxAgent (MxAgentId $ agentLabel us) () [
336+ agent <- mxAgent (MxAgentId agentLabel) () [
423 mxSink $ \ev -> do
337 mxSink $ \ev -> do
424 case ev of
338 case ev of
425− m@(MxSentToPort _ cid _)
426− | cid /= sendPortId chan
427− && cid /= sendPortId sp -> liftMX $ sendChan chan m
428− m@(MxSent _ fromPid _)
429− | fromPid == us -> liftMX $ sendChan chan m
430− m@(MxSentToName _ fromPid _)
431− | fromPid == us -> liftMX $ sendChan chan m
432− m@(MxRegistered _ name)
339+ MxRegistered pid label
433− | name == label -> liftMX $ sendChan chan m
340+ | isValid label -> liftMX $ sendChan regChan (label, pid)
341+ MxUnRegistered pid label
342+ | isValid label -> liftMX $ sendChan unRegChan (label, pid)
434 _ -> return ()
343 _ -> return ()
438− case mNode of
347+ (sp, rp) <- newChan
439− Nothing -> void $ spawnLocal (proc sp)
440− Just remoteNode -> void $ liftIO $ forkProcess remoteNode $ proc sp
348+ liftIO $ forkProcess remoteNode $ do
349+ getSelfPid >>= sendChan sp
350+ expect :: Process ()
442− p1 <- receiveChanTimeout 2000000 rp
352+ p1 <- receiveChan rp
443− unless (isJust p1) $ die "Timed out waiting for ProcessId"
444− res <- try $ test (fromJust p1) sink
446− kill agent "bye"
354+ register label1 p1
447− kill (fromJust p1) "bye"
355+ reg1 <- receiveChanTimeout delay regSink
356+ reg1 `shouldBe` equalTo (Just (label1, p1))
449− case res of
358+ register label2 p1
450− Left (ProcessExitException _ m) -> (liftIO $ putStrLn $ "SomeException-" ++ show m) >> die m
359+ reg2 <- receiveChanTimeout delay regSink
451− Right tr -> tr `shouldBe` equalTo True
360+ reg2 `shouldBe` equalTo (Just (label2, p1))
362+ n1 <- whereis label1
363+ n1 `shouldBe` equalTo (Just p1)
454− where
365+ n2 <- whereis label2
455− agentLabel s = "mx-unsafe-check-agent-" ++ show s
366+ n2 `shouldBe` equalTo (Just p1)
456− proc sp' = getSelfPid >>= sendChan sp' >> go Nothing
458− go :: Maybe (ReceivePort ()) -> Process ()
368+ kill p1 "goodbye"
459− go Nothing = receiveTimeout 5000000 [ match replyChannel ] >>= go
460− go c@(Just c') = receiveWait [ matchChan c' return ] >> go c
462− replyChannel p' = do
370+ unreg1 <- receiveChanTimeout delay unRegSink
463− (s, r) <- newChan
371+ unreg2 <- receiveChanTimeout delay unRegSink
465− return r
373+ sort [unreg1, unreg2]
374+ `shouldBe` equalTo [Just (label1, p1), Just (label2, p1)]
376+ kill agent "test-complete"
467tests :: TestTransport -> IO [Test]
378tests :: TestTransport -> IO [Test]
468tests TestTransport{..} = do
379tests TestTransport{..} = do
469 node1 <- newLocalNode testTransport initRemoteTable
380 node1 <- newLocalNode testTransport initRemoteTable
470 node2 <- newLocalNode testTransport initRemoteTable
381 node2 <- newLocalNode testTransport initRemoteTable
472− testGroup "MxAgents" [
383+ testGroup "Mx Agents" [
473− testCase "EventHandling"
384+ testCase "Event Handling"
474 (delayedAssertion
385 (delayedAssertion
475 "expected True, but events where not as expected"
386 "expected True, but events where not as expected"
476 node1 True testAgentEventHandling)
387 node1 True testAgentEventHandling)
477− , testCase "InterAgentBroadcast"
388+ , testCase "Inter-Agent Broadcast"
478 (delayedAssertion
389 (delayedAssertion
479 "expected (), but no broadcast was received"
390 "expected (), but no broadcast was received"
480− node1 (Just ()) testAgentBroadcast)
391+ node1 () testAgentBroadcast)
481− , testCase "AgentMailboxHandling"
392+ , testCase "Agent Mailbox Handling"
482 (delayedAssertion
393 (delayedAssertion
483 "expected (Just ()), but no regular (mailbox) input was handled"
394 "expected (Just ()), but no regular (mailbox) input was handled"
484 node1 (Just ()) testAgentMailboxHandling)
395 node1 (Just ()) testAgentMailboxHandling)
485− , testCase "AgentDualInputHandling"
396+ , testCase "Agent Dual Input Handling"
486 (delayedAssertion
397 (delayedAssertion
487 "expected sum = 15, but the result was Nothing"
398 "expected sum = 15, but the result was Nothing"
488 node1 (Just 15 :: Maybe Int) testAgentDualInput)
399 node1 (Just 15 :: Maybe Int) testAgentDualInput)
489− , testCase "AgentInputPrioritisation"
400+ , testCase "Agent Input Prioritisation"
490 (delayedAssertion
401 (delayedAssertion
491 "expected [first, second, third, fourth, fifth], but result diverged"
402 "expected [first, second, third, fourth, fifth], but result diverged"
492 node1 (sort ["first", "second",
403 node1 (sort ["first", "second",
493 "third", "fourth",
404 "third", "fourth",
494 "fifth"]) testAgentPrioritisation)
405 "fifth"]) testAgentPrioritisation)
496− , testGroup "MxEvents" [
407+ , testGroup "Mx Events" [
497− testCase "NameRegistrationEvents"
408+ testCase "Name Registration Events"
498 (delayedAssertion
409 (delayedAssertion
499 "expected registration events to map to the correct ProcessId"
410 "expected registration events to map to the correct ProcessId"
500 node1 () testMxRegEvents)
411 node1 () testMxRegEvents)
501− , testCase "PostDeathNameUnRegistrationEvents"
412+ , testCase "Post Death Name UnRegistration Events"
502 (delayedAssertion
413 (delayedAssertion
503 "expected process deaths to result in unregistration events"
414 "expected process deaths to result in unregistration events"
504 node1 () (testMxRegMon node2))
415 node1 () (testMxRegMon node2))
505− , testGroup "SendEvents" $ buildTestCases node1 node2
416+ , testCase "Monitor Events"
417+ (runProcess node1 (testMxUnsafeSendEvents node2))
509− buildTestCases n1 n2 = let nid = localNodeId n2 in build n1 n2 [
511− ("nsend", testNSend nsend)
512− , ("Unsafe.nsend", testNSend Unsafe.nsend)
513− , ("nsendRemote", testNSend (nsendRemote nid))
514− , ("Unsafe.nsendRemote", testNSend (Unsafe.nsendRemote nid))
517− ("send", testSend send)
518− , ("Unsafe.send", testSend Unsafe.send)
519− , ("usend", testSend usend)
520− , ("Unsafe.usend", testSend Unsafe.usend)
521− , ("sendChan", testChan sendChan)
522− , ("Unsafe.sendChan", testChan Unsafe.sendChan)
525− ("forward", testSend (\p m -> forward (unsafeCreateUnencodedMessage m) p))
526− , ("uforward", testSend (\p m -> uforward (unsafeCreateUnencodedMessage m) p))
532− -> [(String, [(String, (Maybe LocalNode -> Process ()))])]
535− [ testGroup (intercalate "-" [groupName, caseSuffix]) [
536− testCase (intercalate "-" [caseName, caseSuffix])
537− (runProcess n (caseImpl caseNode))
538− | (caseName, caseImpl) <- groupCases
540− | (groupName, groupCases) <- specs
541− , (caseSuffix, caseNode) <- [("RemotePid", Just ln), ("LocalPid", Nothing)]