@@ -1,11 +1,24 @@
1−{-# LANGUAGE DeriveGeneric #-}
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE ParallelListComp #-}
3+
24module Control.Distributed.Process.Tests.Mx (tests) where
35
46import Control.Distributed.Process.Tests.Internal.Utils
57import Network.Transport.Test (TestTransport(..))
6−
7−import Control.Distributed.Process
8+import Control.Exception (SomeException)
9+import Control.Distributed.Process hiding (bracket, finally, try)
10+import Control.Distributed.Process.Internal.Types
11+ ( ProcessExitException(..)
12+ , unsafeCreateUnencodedMessage
13+ )
814import Control.Distributed.Process.Node
15+import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
16+ ( send
17+ , nsend
18+ , nsendRemote
19+ , usend
20+ , sendChan
21+ )
922import Control.Distributed.Process.Management
1023 ( MxEvent(..)
1124 , MxAgentId(..)
@@ -22,12 +35,13 @@ import Control.Distributed.Process.Management
2235 , mxBroadcast
2336 )
2437import Control.Monad (void, unless)
38+import Control.Monad.Catch(finally, bracket, try)
2539import Control.Rematch (equalTo)
2640import Data.Binary
27−import Data.List (find, sort)
28−import Data.Maybe (isJust, isNothing)
41+import Data.List (find, sort, intercalate)
42+import Data.Maybe (isJust, fromJust, isNothing, fromMaybe)
2943import Data.Typeable
30−import GHC.Generics
44+import GHC.Generics hiding (from)
3145#if ! MIN_VERSION_base(4,6,0)
3246import Prelude hiding (catch, log)
3347#endif
@@ -36,6 +50,7 @@ import Test.Framework
3650 ( Test
3751 , testGroup
3852 )
53+import Test.HUnit (Assertion)
3954import Test.Framework.Providers.HUnit (testCase)
4055
4156data Publish = Publish
@@ -51,7 +66,7 @@ awaitExit pid =
5166 (\_ -> return ())
5267 ]
5368 where
54− withMonitorRef pid = bracket (monitor pid) unmonitor
69+ withMonitorRef p = bracket (monitor p) unmonitor
5570
5671testAgentBroadcast :: TestResult () -> Process ()
5772testAgentBroadcast result = do
@@ -313,43 +328,204 @@ testMxRegMon remoteNode result = do
313328ensure :: Process () -> Process () -> Process ()
314329ensure = flip finally
315330
331+testNSend :: (String -> () -> Process ())
332+ -> Maybe LocalNode
333+ -> Process ()
334+testNSend op n = testMxSend n $ \p1 sink -> do
335+ let delay = 5000000
336+ let label = "testMxSend"
337+ let isValid = isValidLabel n label
338+
339+ register label p1
340+ reg1 <- receiveChanTimeout delay sink
341+ case reg1 of
342+ Just (MxRegistered pd lb)
343+ | pd == p1 && isValid lb -> return ()
344+ _ -> die $ "Reg-Failed: " ++ show reg1
345+
346+ op label ()
347+
348+ us <- getSelfPid
349+ sent <- receiveChanTimeout delay sink
350+ case sent of
351+ Just (MxSentToName lb by _)
352+ | by == us && isValid lb -> return True
353+ _ -> die $ "Send-Failed: " ++ show sent
354+
355+ where
356+ isValidLabel n l1 l2
357+ | l2 == l2 = True
358+ | isJust n = l2 == l1 ++ "@" ++ show (localNodeId $ fromJust n)
359+ | otherwise = False
360+
361+testSend :: (ProcessId -> () -> Process ())
362+ -> Maybe LocalNode
363+ -> Process ()
364+testSend op n = testMxSend n $ \p1 sink -> do
365+ -- initiate a send
366+ op p1 ()
367+
368+ -- verify the management event
369+ us <- getSelfPid
370+ sent <- receiveChanTimeout 5000000 sink
371+ case sent of
372+ Just (MxSent pidTo pidFrom _)
373+ | pidTo == p1 && pidFrom == us -> return True
374+ _ -> return False
375+
376+testChan :: (SendPort () -> () -> Process ())
377+ -> Maybe LocalNode
378+ -> Process ()
379+testChan op n = testMxSend n $ \p1 sink -> do
380+
381+ us <- getSelfPid
382+ send p1 us
383+
384+ cleared <- receiveChan sink
385+ case cleared of
386+ (MxSent pidTo pidFrom _)
387+ | pidTo == p1 && pidFrom == us -> return ()
388+ _ -> die "Received uncleared Mx Event"
389+
390+ chan <- receiveTimeout 5000000 [ match return ]
391+ let ch' = fromMaybe (error "No reply chan received") chan
392+
393+ -- initiate a send
394+ op ch' ()
395+
396+ -- verify the management event
397+ sent <- receiveChanTimeout 5000000 sink
398+ case sent of
399+ Just (MxSentToPort sId spId _)
400+ | sId == us && spId == sendPortId ch' -> return True
401+ _ -> return False
402+
403+type SendTest = ProcessId -> ReceivePort MxEvent -> Process Bool
404+
405+testMxSend :: Maybe LocalNode -> SendTest -> Process ()
406+testMxSend mNode test = do
407+ us <- getSelfPid
408+ (sp, rp) <- newChan
409+ (chan, sink) <- newChan
410+ agent <- mxAgent (MxAgentId $ agentLabel us) () [
411+ mxSink $ \ev -> do
412+ case ev of
413+ m@(MxSentToPort _ cid _)
414+ | cid /= sendPortId chan
415+ && cid /= sendPortId sp -> liftMX $ sendChan chan m
416+ m@(MxSent _ fromPid _)
417+ | fromPid == us -> liftMX $ sendChan chan m
418+ m@(MxSentToName _ fromPid _)
419+ | fromPid == us -> liftMX $ sendChan chan m
420+ m@(MxRegistered _ name)
421+ | name == label -> liftMX $ sendChan chan m
422+ _ -> return ()
423+ mxReady
424+ ]
425+
426+ case mNode of
427+ Nothing -> void $ spawnLocal (proc sp)
428+ Just remoteNode -> void $ liftIO $ forkProcess remoteNode $ proc sp
429+
430+ p1 <- receiveChan rp
431+ res <- try (test p1 sink)
432+
433+ kill agent "bye"
434+ kill p1 "bye"
435+
436+ case res of
437+ Left (ProcessExitException _ m) -> (liftIO $ putStrLn $ "SomeException-" ++ show m) >> die m
438+ Right tr -> tr `shouldBe` equalTo True
439+
440+
441+ where
442+ label = "testMxSend"
443+ agentLabel s = "mx-unsafe-check-agent-" ++ show s
444+ proc sp' = getSelfPid >>= sendChan sp' >> go Nothing
445+
446+ go :: Maybe (ReceivePort ()) -> Process ()
447+ go Nothing = receiveTimeout 5000000 [ match replyChannel ] >>= go
448+ go c@(Just c') = receiveWait [ matchChan c' return ] >> go c
449+
450+ replyChannel p' = do
451+ (s, r) <- newChan
452+ send p' s
453+ return r
454+
316455tests :: TestTransport -> IO [Test]
317−tests TestTransport{..} = do
456+tests tt@TestTransport{..} = do
318457 node1 <- newLocalNode testTransport initRemoteTable
319458 node2 <- newLocalNode testTransport initRemoteTable
320459 return [
321− testGroup "Mx Agents" [
322− testCase "Event Handling"
460+ testGroup "MxAgents" [
461+ testCase "EventHandling"
323462 (delayedAssertion
324463 "expected True, but events where not as expected"
325464 node1 True testAgentEventHandling)
326− , testCase "Inter-Agent Broadcast"
465+ , testCase "InterAgentBroadcast"
327466 (delayedAssertion
328467 "expected (), but no broadcast was received"
329468 node1 () testAgentBroadcast)
330− , testCase "Agent Mailbox Handling"
469+ , testCase "AgentMailboxHandling"
331470 (delayedAssertion
332471 "expected (Just ()), but no regular (mailbox) input was handled"
333472 node1 (Just ()) testAgentMailboxHandling)
334− , testCase "Agent Dual Input Handling"
473+ , testCase "AgentDualInputHandling"
335474 (delayedAssertion
336475 "expected sum = 15, but the result was Nothing"
337476 node1 (Just 15 :: Maybe Int) testAgentDualInput)
338− , testCase "Agent Input Prioritisation"
477+ , testCase "AgentInputPrioritisation"
339478 (delayedAssertion
340479 "expected [first, second, third, fourth, fifth], but result diverged"
341480 node1 (sort ["first", "second",
342481 "third", "fourth",
343482 "fifth"]) testAgentPrioritisation)
344483 ]
345− , testGroup "Mx Events" [
346− testCase "Name Registration Events"
484+ , testGroup "MxEvents" [
485+ testCase "NameRegistrationEvents"
347486 (delayedAssertion
348487 "expected registration events to map to the correct ProcessId"
349488 node1 () testMxRegEvents)
350− , testCase "Post Death Name UnRegistration Events"
489+ , testCase "PostDeathNameUnRegistrationEvents"
351490 (delayedAssertion
352491 "expected process deaths to result in unregistration events"
353492 node1 () (testMxRegMon node2))
493+ , testGroup "SendEvents" $ buildTestCases tt node2
354494 ]
355495 ]
496+ where
497+ buildTestCases tt' n2 = let nid = localNodeId n2 in build tt' n2 [
498+ ("NSend", [
499+ ("nsend", testNSend nsend)
500+ , ("Unsafe.nsend", testNSend Unsafe.nsend)
501+ , ("nsendRemote", testNSend (nsendRemote nid))
502+ , ("Unsafe.nsendRemote", testNSend (Unsafe.nsendRemote nid))
503+ ])
504+ , ("Send", [
505+ ("send", testSend send)
506+ , ("Unsafe.send", testSend Unsafe.send)
507+ , ("usend", testSend usend)
508+ , ("Unsafe.usend", testSend Unsafe.usend)
509+ , ("sendChan", testChan sendChan)
510+ , ("Unsafe.sendChan", testChan Unsafe.sendChan)
511+ ])
512+ , ("Forward", [
513+ ("forward", testSend (\p m -> forward (unsafeCreateUnencodedMessage m) p))
514+ , ("uforward", testSend (\p m -> uforward (unsafeCreateUnencodedMessage m) p))
515+ ])
516+ ]
517+
518+ build :: TestTransport
519+ -> LocalNode
520+ -> [(String, [(String, (Maybe LocalNode -> Process ()))])]
521+ -> [Test]
522+ build TestTransport{..} ln specs =
523+ [ testGroup (intercalate "-" [groupName, caseSuffix]) [
524+ testCase (intercalate "-" [caseName, caseSuffix])
525+ (newLocalNode testTransport initRemoteTable >>= \n ->
526+ tryRunProcess n (caseImpl caseNode))
527+ | (caseName, caseImpl) <- groupCases
528+ ]
529+ | (groupName, groupCases) <- specs
530+ , (caseSuffix, caseNode) <- [("RemotePid", Just ln), ("LocalPid", Nothing)]
531+ ]