3#if ! MIN_VERSION_base(4,6,0)
4import Prelude hiding (catch)
7import Data.Binary (Binary(..))
8import Data.Typeable (Typeable)
9import Data.Foldable (forM_)
10import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId)
11import Control.Concurrent.MVar
18import Control.Monad (replicateM_, replicateM, forever)
19import Control.Exception (SomeException, throwIO)
20import qualified Control.Exception as Ex (catch)
21import Control.Applicative ((<$>), (<*>), pure, (<|>))
22import qualified Network.Transport as NT (Transport, closeEndPoint)
23import Network.Socket (sClose)
24import Network.Transport.TCP
25 ( createTransportExposeInternals
26 , TransportInternals(socketBetween)
27 , defaultTCPParameters
29import Control.Distributed.Process
30import Control.Distributed.Process.Internal.Types
32 , LocalNode(localEndPoint)
33 , ProcessExitException(..)
36import Control.Distributed.Process.Node
37import Control.Distributed.Process.Serializable (Serializable)
39import Test.HUnit (Assertion)
40import Test.Framework (Test, defaultMain, testGroup)
41import Test.Framework.Providers.HUnit (testCase)
43newtype Ping = Ping ProcessId
44 deriving (Typeable, Binary, Show)
46newtype Pong = Pong ProcessId
47 deriving (Typeable, Binary, Show)
49--------------------------------------------------------------------------------
50-- Supporting definitions --
51--------------------------------------------------------------------------------
53-- | Like fork, but throw exceptions in the child thread to the parent
54forkTry :: IO () -> IO ThreadId
57 forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))
59-- | The ping server from the paper
62 Pong partner <- expect
64 send partner (Ping self)
67-- | Quick and dirty synchronous version of whereisRemoteAsync
68whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
69whereisRemote nid string = do
70 whereisRemoteAsync nid string
71 WhereIsReply _ mPid <- expect
74data Add = Add ProcessId Double Double deriving (Typeable)
75data Divide = Divide ProcessId Double Double deriving (Typeable)
76data DivByZero = DivByZero deriving (Typeable)
78instance Binary Add where
79 put (Add pid x y) = put pid >> put x >> put y
80 get = Add <$> get <*> get <*> get
82instance Binary Divide where
83 put (Divide pid x y) = put pid >> put x >> put y
84 get = Divide <$> get <*> get <*> get
86instance Binary DivByZero where
87 put DivByZero = return ()
88 get = return DivByZero
90-- The math server from the paper
94 [ match (\(Add pid x y) -> send pid (x + y))
95 , matchIf (\(Divide _ _ y) -> y /= 0)
96 (\(Divide pid x y) -> send pid (x / y))
97 , match (\(Divide pid _ _) -> send pid DivByZero)
101-- | Monitor or link to a remote node
102monitorOrLink :: Bool -- ^ 'True' for monitor, 'False' for link
103 -> ProcessId -- Process to monitor/link to
104 -> Maybe (MVar ()) -- MVar to signal on once the monitor has been set up
105 -> Process (Maybe MonitorRef)
106monitorOrLink mOrL pid mSignal = do
107 result <- if mOrL then Just <$> monitor pid
108 else link pid >> return Nothing
109 -- Monitor is asynchronous, which usually does not matter but if we want a
110 -- *specific* signal then it does. Therefore we wait an arbitrary delay and
111 -- hope that this means the monitor has been set up
112 forM_ mSignal $ \signal -> liftIO . forkIO $ threadDelay 100000 >> putMVar signal ()
115monitorTestProcess :: ProcessId -- Process to monitor/link to
116 -> Bool -- 'True' for monitor, 'False' for link
117 -> Bool -- Should we unmonitor?
118 -> DiedReason -- Expected cause of death
119 -> Maybe (MVar ()) -- Signal for 'monitor set up'
120 -> MVar () -- Signal for successful termination
122monitorTestProcess theirAddr mOrL un reason monitorSetup done =
123 catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup
125 (True, Nothing) -> do
127 liftIO $ putMVar done ()
128 (True, Just ref) -> do
130 liftIO $ putMVar done ()
132 ProcessMonitorNotification ref' pid reason' <- expect
133 True <- return $ Just ref' == ref && pid == theirAddr && mOrL && reason == reason'
134 liftIO $ putMVar done ()
136 (\(ProcessLinkException pid reason') -> do
137 True <- return $ pid == theirAddr && not mOrL && not un && reason == reason'
138 liftIO $ putMVar done ()
141--------------------------------------------------------------------------------
142-- The tests proper --
143--------------------------------------------------------------------------------
146testPing :: NT.Transport -> Assertion
147testPing transport = do
148 serverAddr <- newEmptyMVar
149 clientDone <- newEmptyMVar
153 localNode <- newLocalNode transport initRemoteTable
154 addr <- forkProcess localNode ping
155 putMVar serverAddr addr
159 localNode <- newLocalNode transport initRemoteTable
160 pingServer <- readMVar serverAddr
164 runProcess localNode $ do
166 replicateM_ numPings $ do
167 send pingServer (Pong pid)
171 putMVar clientDone ()
175-- | Monitor a process on an unreachable node
176testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> Assertion
177testMonitorUnreachable transport mOrL un = do
178 deadProcess <- newEmptyMVar
182 localNode <- newLocalNode transport initRemoteTable
183 addr <- forkProcess localNode . liftIO $ threadDelay 1000000
184 closeLocalNode localNode
185 putMVar deadProcess addr
188 localNode <- newLocalNode transport initRemoteTable
189 theirAddr <- readMVar deadProcess
190 runProcess localNode $
191 monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done
195-- | Monitor a process which terminates normally
196testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
197testMonitorNormalTermination transport mOrL un = do
198 monitorSetup <- newEmptyMVar
199 monitoredProcess <- newEmptyMVar
203 localNode <- newLocalNode transport initRemoteTable
204 addr <- forkProcess localNode $
205 liftIO $ readMVar monitorSetup
206 putMVar monitoredProcess addr
209 localNode <- newLocalNode transport initRemoteTable
210 theirAddr <- readMVar monitoredProcess
211 runProcess localNode $
212 monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done
216-- | Monitor a process which terminates abnormally
217testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
218testMonitorAbnormalTermination transport mOrL un = do
219 monitorSetup <- newEmptyMVar
220 monitoredProcess <- newEmptyMVar
223 let err = userError "Abnormal termination"
226 localNode <- newLocalNode transport initRemoteTable
227 addr <- forkProcess localNode . liftIO $ do
228 readMVar monitorSetup
230 putMVar monitoredProcess addr
233 localNode <- newLocalNode transport initRemoteTable
234 theirAddr <- readMVar monitoredProcess
235 runProcess localNode $
236 monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done
240-- | Monitor a local process that is already dead
241testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
242testMonitorLocalDeadProcess transport mOrL un = do
243 processDead <- newEmptyMVar
244 processAddr <- newEmptyMVar
245 localNode <- newLocalNode transport initRemoteTable
249 addr <- forkProcess localNode . liftIO $ putMVar processDead ()
250 putMVar processAddr addr
253 theirAddr <- readMVar processAddr
255 runProcess localNode $ do
256 monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
260-- | Monitor a remote process that is already dead
261testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
262testMonitorRemoteDeadProcess transport mOrL un = do
263 processDead <- newEmptyMVar
264 processAddr <- newEmptyMVar
268 localNode <- newLocalNode transport initRemoteTable
269 addr <- forkProcess localNode . liftIO $ putMVar processDead ()
270 putMVar processAddr addr
273 localNode <- newLocalNode transport initRemoteTable
274 theirAddr <- readMVar processAddr
276 runProcess localNode $ do
277 monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
281-- | Monitor a process that becomes disconnected
282testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> Assertion
283testMonitorDisconnect transport mOrL un = do
284 processAddr <- newEmptyMVar
285 monitorSetup <- newEmptyMVar
289 localNode <- newLocalNode transport initRemoteTable
290 addr <- forkProcess localNode . liftIO $ threadDelay 1000000
291 putMVar processAddr addr
292 readMVar monitorSetup
293 NT.closeEndPoint (localEndPoint localNode)
296 localNode <- newLocalNode transport initRemoteTable
297 theirAddr <- readMVar processAddr
298 runProcess localNode $ do
299 monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done
303-- | Test the math server (i.e., receiveWait)
304testMath :: NT.Transport -> Assertion
305testMath transport = do
306 serverAddr <- newEmptyMVar
307 clientDone <- newEmptyMVar
311 localNode <- newLocalNode transport initRemoteTable
312 addr <- forkProcess localNode math
313 putMVar serverAddr addr
317 localNode <- newLocalNode transport initRemoteTable
318 mathServer <- readMVar serverAddr
320 runProcess localNode $ do
322 send mathServer (Add pid 1 2)
323 3 <- expect :: Process Double
324 send mathServer (Divide pid 8 2)
325 4 <- expect :: Process Double
326 send mathServer (Divide pid 8 0)
328 liftIO $ putMVar clientDone ()
332-- | Send first message (i.e. connect) to an already terminated process
333-- (without monitoring); then send another message to a second process on
334-- the same remote node (we're checking that the remote node did not die)
335testSendToTerminated :: NT.Transport -> Assertion
336testSendToTerminated transport = do
337 serverAddr1 <- newEmptyMVar
338 serverAddr2 <- newEmptyMVar
339 clientDone <- newEmptyMVar
342 terminated <- newEmptyMVar
343 localNode <- newLocalNode transport initRemoteTable
344 addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()
345 addr2 <- forkProcess localNode $ ping
347 putMVar serverAddr1 addr1
348 putMVar serverAddr2 addr2
351 localNode <- newLocalNode transport initRemoteTable
352 server1 <- readMVar serverAddr1
353 server2 <- readMVar serverAddr2
354 runProcess localNode $ do
357 send server2 (Pong pid)
359 True <- return $ pid' == server2
360 liftIO $ putMVar clientDone ()
364-- | Test (non-zero) timeout
365testTimeout :: NT.Transport -> Assertion
366testTimeout transport = do
367 localNode <- newLocalNode transport initRemoteTable
370 runProcess localNode $ do
371 Nothing <- receiveTimeout 1000000 [match (\(Add _ _ _) -> return ())]
372 liftIO $ putMVar done ()
376-- | Test zero timeout
377testTimeout0 :: NT.Transport -> Assertion
378testTimeout0 transport = do
379 serverAddr <- newEmptyMVar
380 clientDone <- newEmptyMVar
381 messagesSent <- newEmptyMVar
384 localNode <- newLocalNode transport initRemoteTable
385 addr <- forkProcess localNode $ do
386 liftIO $ readMVar messagesSent >> threadDelay 1000000
387 -- Variation on the venerable ping server which uses a zero timeout
388 -- Since we wait for all messages to be sent before doing this receive,
389 -- we should nevertheless find the right message immediately
390 Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)]
392 send partner (Ping self)
393 putMVar serverAddr addr
396 localNode <- newLocalNode transport initRemoteTable
397 server <- readMVar serverAddr
398 runProcess localNode $ do
400 -- Send a bunch of messages. A large number of messages that the server
401 -- is not interested in, and then a single message that it wants
402 replicateM_ 10000 $ send server "Irrelevant message"
403 send server (Pong pid)
404 liftIO $ putMVar messagesSent ()
406 liftIO $ putMVar clientDone ()
410-- | Test typed channels
411testTypedChannels :: NT.Transport -> Assertion
412testTypedChannels transport = do
413 serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))
414 clientDone <- newEmptyMVar
417 localNode <- newLocalNode transport initRemoteTable
418 forkProcess localNode $ do
419 (serverSendPort, rport) <- newChan
420 liftIO $ putMVar serverChannel serverSendPort
421 (clientSendPort, i) <- receiveChan rport
422 sendChan clientSendPort (even i)
426 localNode <- newLocalNode transport initRemoteTable
427 serverSendPort <- readMVar serverChannel
428 runProcess localNode $ do
429 (clientSendPort, rport) <- newChan
430 sendChan serverSendPort (clientSendPort, 5)
431 False <- receiveChan rport
432 liftIO $ putMVar clientDone ()
436-- | Test merging receive ports
437testMergeChannels :: NT.Transport -> Assertion
438testMergeChannels transport = do
439 localNode <- newLocalNode transport initRemoteTable
440 testFlat localNode True "aaabbbccc"
441 testFlat localNode False "abcabcabc"
442 testNested localNode True True "aaabbbcccdddeeefffggghhhiii"
443 testNested localNode True False "adgadgadgbehbehbehcficficfi"
444 testNested localNode False True "abcabcabcdefdefdefghighighi"
445 testNested localNode False False "adgbehcfiadgbehcfiadgbehcfi"
446 testBlocked localNode True
447 testBlocked localNode False
449 -- Single layer of merging
450 testFlat :: LocalNode -> Bool -> String -> IO ()
451 testFlat localNode biased expected = do
453 forkProcess localNode $ do
454 rs <- mapM charChannel "abc"
455 m <- mergePorts biased rs
456 xs <- replicateM 9 $ receiveChan m
457 True <- return $ xs == expected
458 liftIO $ putMVar done ()
461 -- Two layers of merging
462 testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
463 testNested localNode biasedInner biasedOuter expected = do
465 forkProcess localNode $ do
466 rss <- mapM (mapM charChannel) ["abc", "def", "ghi"]
467 ms <- mapM (mergePorts biasedInner) rss
468 m <- mergePorts biasedOuter ms
469 xs <- replicateM (9 * 3) $ receiveChan m
470 True <- return $ xs == expected
471 liftIO $ putMVar done ()
474 -- Test that if no messages are (immediately) available, the scheduler makes no difference
475 testBlocked :: LocalNode -> Bool -> IO ()
476 testBlocked localNode biased = do
477 vs <- replicateM 3 newEmptyMVar
480 forkProcess localNode $ do
481 [sa, sb, sc] <- liftIO $ mapM readMVar vs
482 mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)
509 forkProcess localNode $ do
510 (ss, rs) <- unzip <$> replicateM 3 newChan
511 liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
512 m <- mergePorts biased rs
513 xs <- replicateM (6 * 3) $ receiveChan m
514 True <- return $ xs == "abcacbbacbcacabcba"
515 liftIO $ putMVar done ()
519 mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
520 mergePorts True = mergePortsBiased
521 mergePorts False = mergePortsRR
523 charChannel :: Char -> Process (ReceivePort Char)
525 (sport, rport) <- newChan
526 replicateM_ 3 $ sendChan sport c
527 liftIO $ threadDelay 10000 -- Make sure messages have been sent
530testTerminate :: NT.Transport -> Assertion
531testTerminate transport = do
532 localNode <- newLocalNode transport initRemoteTable
535 pid <- forkProcess localNode $ do
536 liftIO $ threadDelay 100000
539 runProcess localNode $ do
541 ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
542 True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException
543 liftIO $ putMVar done ()
547testMonitorNode :: NT.Transport -> Assertion
548testMonitorNode transport = do
549 [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
554 runProcess node2 $ do
555 ref <- monitorNode (localNodeId node1)
556 NodeMonitorNotification ref' nid DiedDisconnect <- expect
557 True <- return $ ref == ref' && nid == localNodeId node1
558 liftIO $ putMVar done ()
562testMonitorChannel :: NT.Transport -> Assertion
563testMonitorChannel transport = do
564 [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
565 gotNotification <- newEmptyMVar
567 pid <- forkProcess node1 $ do
568 sport <- expect :: Process (SendPort ())
569 ref <- monitorPort sport
570 PortMonitorNotification ref' port' reason <- expect
571 -- reason might be DiedUnknownId if the receive port is GCed before the
572 -- monitor is established (TODO: not sure that this is reasonable)
573 return $ ref' == ref && port' == sendPortId sport && (reason == DiedNormal || reason == DiedUnknownId)
574 liftIO $ putMVar gotNotification ()
576 runProcess node2 $ do
577 (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
579 liftIO $ threadDelay 100000
581 takeMVar gotNotification
583testRegistry :: NT.Transport -> Assertion
584testRegistry transport = do
585 node <- newLocalNode transport initRemoteTable
588 pingServer <- forkProcess node ping
591 register "ping" pingServer
592 Just pid <- whereis "ping"
593 True <- return $ pingServer == pid
595 nsend "ping" (Pong us)
597 True <- return $ pingServer == pid'
598 liftIO $ putMVar done ()
602testRemoteRegistry :: NT.Transport -> Assertion
603testRemoteRegistry transport = do
604 node1 <- newLocalNode transport initRemoteTable
605 node2 <- newLocalNode transport initRemoteTable
608 pingServer <- forkProcess node1 ping
610 runProcess node2 $ do
611 let nid1 = localNodeId node1
612 registerRemoteAsync nid1 "ping" pingServer
614 matchIf (\(RegisterReply label' _) -> "ping" == label')
615 (\(RegisterReply _ _) -> return ()) ]
617 Just pid <- whereisRemote nid1 "ping"
618 True <- return $ pingServer == pid
620 nsendRemote nid1 "ping" (Pong us)
622 True <- return $ pingServer == pid'
623 liftIO $ putMVar done ()
627testSpawnLocal :: NT.Transport -> Assertion
628testSpawnLocal transport = do
629 node <- newLocalNode transport initRemoteTable
635 pid <- spawnLocal $ do
637 sendChan sport (1234 :: Int)
639 sport <- spawnChannelLocal $ \rport -> do
640 (1234 :: Int) <- receiveChan rport
645 liftIO $ putMVar done ()
649testReconnect :: NT.Transport -> TransportInternals -> Assertion
650testReconnect transport transportInternals = do
651 [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
652 let nid1 = localNodeId node1
653 nid2 = localNodeId node2
654 processA <- newEmptyMVar
655 [sendTestOk, registerTestOk] <- replicateM 2 newEmptyMVar
657 forkProcess node1 $ do
659 liftIO $ putMVar processA us
662 True <- return $ msg1 == "message 1" && msg2 == "message 3"
663 liftIO $ putMVar sendTestOk ()
665 forkProcess node2 $ do
667 - Make sure there is no implicit reconnect on normal message sending
670 them <- liftIO $ readMVar processA
671 send them "message 1" >> liftIO (threadDelay 100000)
673 -- Simulate network failure
675 sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
680 send them "message 2"
684 send them "message 3"
686 liftIO $ takeMVar sendTestOk
689 - Test that there *is* implicit reconnect on node controller messages
693 registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous
695 matchIf (\(RegisterReply label' _) -> "a" == label')
696 (\(RegisterReply _ _) -> return ()) ]
698 Just _ <- whereisRemote nid1 "a"
701 -- Simulate network failure
703 sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
707 -- This will happen due to implicit reconnect
708 registerRemoteAsync nid1 "b" us
710 matchIf (\(RegisterReply label' _) -> "b" == label')
711 (\(RegisterReply _ _) -> return ()) ]
714 registerRemoteAsync nid1 "c" us
716 matchIf (\(RegisterReply label' _) -> "c" == label')
717 (\(RegisterReply _ _) -> return ()) ]
720 Nothing <- whereisRemote nid1 "a" -- this will fail because the name is removed when the node is disconnected
721 Just _ <- whereisRemote nid1 "b" -- this will suceed because the value is set after thereconnect
722 Just _ <- whereisRemote nid1 "c"
724 liftIO $ putMVar registerTestOk ()
726 takeMVar registerTestOk
728-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
730testMatchAny :: NT.Transport -> Assertion
731testMatchAny transport = do
732 proxyAddr <- newEmptyMVar
733 clientDone <- newEmptyMVar
737 localNode <- newLocalNode transport initRemoteTable
738 mathServer <- forkProcess localNode math
739 proxyServer <- forkProcess localNode $ forever $ do
740 msg <- receiveWait [ matchAny return ]
741 forward msg mathServer
742 putMVar proxyAddr proxyServer
746 localNode <- newLocalNode transport initRemoteTable
747 mathServer <- readMVar proxyAddr
749 runProcess localNode $ do
751 send mathServer (Add pid 1 2)
752 3 <- expect :: Process Double
753 send mathServer (Divide pid 8 2)
754 4 <- expect :: Process Double
755 send mathServer (Divide pid 8 0)
757 liftIO $ putMVar clientDone ()
761-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
762-- in between, however we block 'Divide' requests ....
763testMatchAnyHandle :: NT.Transport -> Assertion
764testMatchAnyHandle transport = do
765 proxyAddr <- newEmptyMVar
766 clientDone <- newEmptyMVar
770 localNode <- newLocalNode transport initRemoteTable
771 mathServer <- forkProcess localNode math
772 proxyServer <- forkProcess localNode $ forever $ do
774 matchAny (maybeForward mathServer)
776 putMVar proxyAddr proxyServer
780 localNode <- newLocalNode transport initRemoteTable
781 mathServer <- readMVar proxyAddr
783 runProcess localNode $ do
785 send mathServer (Add pid 1 2)
786 3 <- expect :: Process Double
787 send mathServer (Divide pid 8 2)
788 Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
789 liftIO $ putMVar clientDone ()
792 where maybeForward :: ProcessId -> AbstractMessage -> Process (Maybe ())
794 maybeHandleMessage msg (\m@(Add _ _ _) -> send s m)
796testMatchAnyNoHandle :: NT.Transport -> Assertion
797testMatchAnyNoHandle transport = do
799 clientDone <- newEmptyMVar
800 serverDone <- newEmptyMVar
804 localNode <- newLocalNode transport initRemoteTable
805 server <- forkProcess localNode $ forever $ do
808 -- the condition has type `Add -> Bool`
809 (\(Add _ _ _) -> True)
810 -- the match `AbstractMessage -> Process ()` will succeed!
812 -- `String -> Process ()` does *not* match the input types however
813 r <- (maybeHandleMessage m (\(_ :: String) -> die "NONSENSE" ))
816 Just _ -> die "NONSENSE")
818 -- we *must* have removed the message from our mailbox though!!!
819 Nothing <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
820 liftIO $ putMVar serverDone ()
825 localNode <- newLocalNode transport initRemoteTable
826 server <- readMVar addr
828 runProcess localNode $ do
830 send server (Add pid 1 2)
831 -- we only care about the client having sent a message, so we're done
832 liftIO $ putMVar clientDone ()
837-- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests
838-- unless the text body @/= "bar"@ - this case should time out rather than
839-- removing the message from the process mailbox.
840testMatchAnyIf :: NT.Transport -> Assertion
841testMatchAnyIf transport = do
842 echoAddr <- newEmptyMVar
843 clientDone <- newEmptyMVar
847 localNode <- newLocalNode transport initRemoteTable
848 echoServer <- forkProcess localNode $ forever $ do
850 matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")
853 putMVar echoAddr echoServer
857 localNode <- newLocalNode transport initRemoteTable
858 server <- readMVar echoAddr
860 runProcess localNode $ do
862 send server (pid, "foo")
864 send server (pid, "baz")
866 send server (pid, "bar")
867 Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
868 liftIO $ putMVar clientDone ()
871 where handleMessage :: AbstractMessage -> Process (Maybe ())
873 maybeHandleMessage msg (\(pid :: ProcessId, (m :: String))
874 -> do { send pid m; return () })
876-- Test 'receiveChanTimeout'
877testReceiveChanTimeout :: NT.Transport -> Assertion
878testReceiveChanTimeout transport = do
880 sendPort <- newEmptyMVar
883 localNode <- newLocalNode transport initRemoteTable
884 runProcess localNode $ do
885 -- Create a typed channel
886 (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
887 liftIO $ putMVar sendPort sp
889 -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second
890 Nothing <- receiveChanTimeout 1000000 rp
892 -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds
893 Just True <- receiveChanTimeout 1000000 rp
895 -- Wait for a message with zero timeout: non-blocking check. No message is available, we get Nothing
896 Nothing <- receiveChanTimeout 0 rp
898 -- Again, but now there is a message available
899 liftIO $ threadDelay 1000000
900 Just False <- receiveChanTimeout 0 rp
902 liftIO $ putMVar done ()
905 localNode <- newLocalNode transport initRemoteTable
906 runProcess localNode $ do
907 sp <- liftIO $ readMVar sendPort
909 liftIO $ threadDelay 1500000
912 liftIO $ threadDelay 500000
917-- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan
918testReceiveChanFeatures :: NT.Transport -> Assertion
919testReceiveChanFeatures transport = do
923 localNode <- newLocalNode transport initRemoteTable
924 runProcess localNode $ do
925 (spInt, rpInt) <- newChan :: Process (SendPort Int, ReceivePort Int)
926 (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
928 -- Test Functor instance
931 sendChan spBool False
933 rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
935 True <- receiveChan rp1
936 False <- receiveChan rp1
938 -- Test Applicative instance
943 let rp2 = pure (+) <*> rpInt <*> rpInt
947 -- Test Alternative instance
952 let rp3 = (even <$> rpInt) <|> rpBool
954 False <- receiveChan rp3
955 True <- receiveChan rp3
957 -- Test Monad instance
960 sendChan spBool False
963 let rp4 :: ReceivePort Int
972 liftIO $ putMVar done ()
976testKillLocal :: NT.Transport -> Assertion
977testKillLocal transport = do
978 localNode <- newLocalNode transport initRemoteTable
981 pid <- forkProcess localNode $ do
982 liftIO $ threadDelay 1000000
984 runProcess localNode $ do
988 ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
989 True <- return $ ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill"
990 liftIO $ putMVar done ()
994testKillRemote :: NT.Transport -> Assertion
995testKillRemote transport = do
996 node1 <- newLocalNode transport initRemoteTable
997 node2 <- newLocalNode transport initRemoteTable
1000 pid <- forkProcess node1 $ do
1001 liftIO $ threadDelay 1000000
1003 runProcess node2 $ do
1007 ProcessMonitorNotification ref' pid' (DiedException reason) <- expect
1008 True <- return $ ref == ref' && pid == pid' && reason == "killed-by=" ++ show us ++ ",reason=TestKill"
1009 liftIO $ putMVar done ()
1013testCatchesExit :: NT.Transport -> Assertion
1014testCatchesExit transport = do
1015 localNode <- newLocalNode transport initRemoteTable
1016 done <- newEmptyMVar
1018 _ <- forkProcess localNode $ do
1019 (die ("foobar", 123 :: Int))
1021 (\_ m -> maybeHandleMessage m (\(_ :: String) -> return ()))
1022 , (\_ m -> maybeHandleMessage m (\(_ :: Maybe Int) -> return ()))
1023 , (\_ m -> maybeHandleMessage m (\(_ :: String, _ :: Int)
1024 -> (liftIO $ putMVar done ()) >> return ()))
1029testCatches :: NT.Transport -> Assertion
1030testCatches transport = do
1031 localNode <- newLocalNode transport initRemoteTable
1032 done <- newEmptyMVar
1034 _ <- forkProcess localNode $ do
1036 (liftIO $ throwIO (ProcessLinkException (nullProcessId node) DiedNormal))
1038 Handler (\(ProcessLinkException _ _) -> liftIO $ putMVar done ())
1043testDie :: NT.Transport -> Assertion
1044testDie transport = do
1045 localNode <- newLocalNode transport initRemoteTable
1046 done <- newEmptyMVar
1048 _ <- forkProcess localNode $ do
1049 (die ("foobar", 123 :: Int))
1050 `catchExit` \_from reason -> do
1051 -- TODO: should verify that 'from' has the right value
1052 True <- return $ reason == ("foobar", 123 :: Int)
1053 liftIO $ putMVar done ()
1057testPrettyExit :: NT.Transport -> Assertion
1058testPrettyExit transport = do
1059 localNode <- newLocalNode transport initRemoteTable
1060 done <- newEmptyMVar
1062 _ <- forkProcess localNode $ do
1064 `catch` \ex@(ProcessExitException from _) ->
1065 let expected = "exit-from=" ++ (show from)
1067 True <- return $ (show ex) == expected
1068 liftIO $ putMVar done ()
1072testExitLocal :: NT.Transport -> Assertion
1073testExitLocal transport = do
1074 localNode <- newLocalNode transport initRemoteTable
1075 supervisedDone <- newEmptyMVar
1076 supervisorDone <- newEmptyMVar
1078 pid <- forkProcess localNode $ do
1079 (liftIO $ threadDelay 100000)
1080 `catchExit` \_from reason -> do
1081 -- TODO: should verify that 'from' has the right value
1082 True <- return $ reason == "TestExit"
1083 liftIO $ putMVar supervisedDone ()
1085 runProcess localNode $ do
1088 -- This time the client catches the exception, so it dies normally
1089 ProcessMonitorNotification ref' pid' DiedNormal <- expect
1090 True <- return $ ref == ref' && pid == pid'
1091 liftIO $ putMVar supervisorDone ()
1093 takeMVar supervisedDone
1094 takeMVar supervisorDone
1096testExitRemote :: NT.Transport -> Assertion
1097testExitRemote transport = do
1098 node1 <- newLocalNode transport initRemoteTable
1099 node2 <- newLocalNode transport initRemoteTable
1100 supervisedDone <- newEmptyMVar
1101 supervisorDone <- newEmptyMVar
1103 pid <- forkProcess node1 $ do
1104 (liftIO $ threadDelay 100000)
1105 `catchExit` \_from reason -> do
1106 -- TODO: should verify that 'from' has the right value
1107 True <- return $ reason == "TestExit"
1108 liftIO $ putMVar supervisedDone ()
1110 runProcess node2 $ do
1113 ProcessMonitorNotification ref' pid' DiedNormal <- expect
1114 True <- return $ ref == ref' && pid == pid'
1115 liftIO $ putMVar supervisorDone ()
1117 takeMVar supervisedDone
1118 takeMVar supervisorDone
1120tests :: (NT.Transport, TransportInternals) -> [Test]
1121tests (transport, transportInternals) = [
1122 testGroup "Basic features" [
1123 testCase "Ping" (testPing transport)
1124 , testCase "Math" (testMath transport)
1125 , testCase "Timeout" (testTimeout transport)
1126 , testCase "Timeout0" (testTimeout0 transport)
1127 , testCase "SendToTerminated" (testSendToTerminated transport)
1128 , testCase "TypedChannnels" (testTypedChannels transport)
1129 , testCase "MergeChannels" (testMergeChannels transport)
1130 , testCase "Terminate" (testTerminate transport)
1131 , testCase "Registry" (testRegistry transport)
1132 , testCase "RemoteRegistry" (testRemoteRegistry transport)
1133 , testCase "SpawnLocal" (testSpawnLocal transport)
1134 , testCase "MatchAny" (testMatchAny transport)
1135 , testCase "MatchAnyHandle" (testMatchAnyHandle transport)
1136 , testCase "MatchAnyNoHandle" (testMatchAnyNoHandle transport)
1137 , testCase "MatchAnyIf" (testMatchAnyIf transport)
1138 , testCase "ReceiveChanTimeout" (testReceiveChanTimeout transport)
1139 , testCase "ReceiveChanFeatures" (testReceiveChanFeatures transport)
1140 , testCase "KillLocal" (testKillLocal transport)
1141 , testCase "KillRemote" (testKillRemote transport)
1142 , testCase "Die" (testDie transport)
1143 , testCase "PrettyExit" (testPrettyExit transport)
1144 , testCase "CatchesExit" (testCatchesExit transport)
1145 , testCase "Catches" (testCatches transport)
1146 , testCase "ExitLocal" (testExitLocal transport)
1147 , testCase "ExitRemote" (testExitRemote transport)
1149 , testGroup "Monitoring and Linking" [
1150 -- Monitoring processes
1152 -- The "missing" combinations in the list below don't make much sense, as
1153 -- we cannot guarantee that the monitor reply or link exception will not
1154 -- happen before the unmonitor or unlink
1155 testCase "MonitorUnreachable" (testMonitorUnreachable transport True False)
1156 , testCase "MonitorNormalTermination" (testMonitorNormalTermination transport True False)
1157 , testCase "MonitorAbnormalTermination" (testMonitorAbnormalTermination transport True False)
1158 , testCase "MonitorLocalDeadProcess" (testMonitorLocalDeadProcess transport True False)
1159 , testCase "MonitorRemoteDeadProcess" (testMonitorRemoteDeadProcess transport True False)
1160 , testCase "MonitorDisconnect" (testMonitorDisconnect transport True False)
1161 , testCase "LinkUnreachable" (testMonitorUnreachable transport False False)
1162 , testCase "LinkNormalTermination" (testMonitorNormalTermination transport False False)
1163 , testCase "LinkAbnormalTermination" (testMonitorAbnormalTermination transport False False)
1164 , testCase "LinkLocalDeadProcess" (testMonitorLocalDeadProcess transport False False)
1165 , testCase "LinkRemoteDeadProcess" (testMonitorRemoteDeadProcess transport False False)
1166 , testCase "LinkDisconnect" (testMonitorDisconnect transport False False)
1167 , testCase "UnmonitorNormalTermination" (testMonitorNormalTermination transport True True)
1168 , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination transport True True)
1169 , testCase "UnmonitorDisconnect" (testMonitorDisconnect transport True True)
1170 , testCase "UnlinkNormalTermination" (testMonitorNormalTermination transport False True)
1171 , testCase "UnlinkAbnormalTermination" (testMonitorAbnormalTermination transport False True)
1172 , testCase "UnlinkDisconnect" (testMonitorDisconnect transport False True)
1173 -- Monitoring nodes and channels
1174 , testCase "MonitorNode" (testMonitorNode transport)
1175 , testCase "MonitorChannel" (testMonitorChannel transport)
1177 , testCase "Reconnect" (testReconnect transport transportInternals)
1183 Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
1184 defaultMain (tests transport)
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