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