1{-# LANGUAGE TemplateHaskell #-}
4import Data.ByteString.Lazy (empty)
5import Data.Typeable (Typeable)
6import Control.Monad (join, replicateM, forever, replicateM_, void)
7import Control.Exception (IOException, throw)
8import Control.Concurrent (forkIO, threadDelay)
9import Control.Concurrent.MVar
18import Control.Applicative ((<$>))
19import System.Random (randomIO)
20import Network.Transport (Transport)
21import Network.Transport.TCP
22 ( createTransportExposeInternals
23 , defaultTCPParameters
24 , TransportInternals(socketBetween)
26import Network.Socket (sClose)
27import Control.Distributed.Process
28import Control.Distributed.Process.Closure
29import Control.Distributed.Process.Node
30import Control.Distributed.Process.Internal.Types (NodeId(nodeAddress))
31import Control.Distributed.Static (staticLabel, staticClosure)
33import Test.HUnit (Assertion)
34import Test.Framework (Test, defaultMain)
35import Test.Framework.Providers.HUnit (testCase)
37--------------------------------------------------------------------------------
38-- Supporting definitions --
39--------------------------------------------------------------------------------
41quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
42quintuple a b c d e = (a, b, c, d, e)
44sdictInt :: SerializableDict Int
45sdictInt = SerializableDict
47factorial :: Int -> Process Int
49factorial n = (n *) <$> factorial (n - 1)
51addInt :: Int -> Int -> Int
54putInt :: Int -> MVar Int -> IO ()
57sendPid :: ProcessId -> Process ()
62wait :: Int -> Process ()
63wait = liftIO . threadDelay
65isPrime :: Integer -> Process Bool
66isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
68 sieve :: [Integer] -> [Integer]
69 sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
70 sieve [] = error "Uh oh -- we've run out of primes"
72-- | First argument indicates empty closure environment
73typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
74typedPingServer () rport = forever $ do
75 sport <- receiveChan rport
78signal :: ProcessId -> Process ()
79signal pid = send pid ()
93randomElement :: [a] -> IO a
96 return (xs !! (ix `mod` length xs))
99 [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
100 dfib (_, reply, 0) = sendChan reply 0
101 dfib (_, reply, 1) = sendChan reply 1
102 dfib (nids, reply, n) = do
103 nid1 <- liftIO $ randomElement nids
104 nid2 <- liftIO $ randomElement nids
105 (sport, rport) <- newChan
106 spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
107 spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
108 n1 <- receiveChan rport
109 n2 <- receiveChan rport
110 sendChan reply $ n1 + n2
114-- Just try creating a static polymorphic value
115staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
116 => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))
117staticQuintuple = $(mkStatic 'quintuple)
119factorialClosure :: Int -> Closure (Process Int)
120factorialClosure = $(mkClosure 'factorial)
122addIntClosure :: Int -> Closure (Int -> Int)
123addIntClosure = $(mkClosure 'addInt)
125putIntClosure :: Int -> Closure (MVar Int -> IO ())
126putIntClosure = $(mkClosure 'putInt)
128sendPidClosure :: ProcessId -> Closure (Process ())
129sendPidClosure = $(mkClosure 'sendPid)
131sendFac :: Int -> ProcessId -> Closure (Process ())
132sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid
134factorialOf :: Closure (Int -> Process Int)
135factorialOf = staticClosure $(mkStatic 'factorial)
137factorial' :: Int -> Closure (Process Int)
138factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
140waitClosure :: Int -> Closure (Process ())
141waitClosure = $(mkClosure 'wait)
143simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process ()
144simulateNetworkFailure transportInternals fr to = liftIO $ do
146 sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to)
150--------------------------------------------------------------------------------
151-- The tests proper --
152--------------------------------------------------------------------------------
154testUnclosure :: Transport -> RemoteTable -> Assertion
155testUnclosure transport rtable = do
156 node <- newLocalNode transport rtable
158 forkProcess node $ do
159 120 <- join . unClosure $ factorialClosure 5
160 liftIO $ putMVar done ()
163testBind :: Transport -> RemoteTable -> Assertion
164testBind transport rtable = do
165 node <- newLocalNode transport rtable
169 join . unClosure $ sendFac 6 us
170 (720 :: Int) <- expect
171 liftIO $ putMVar done ()
174testSendPureClosure :: Transport -> RemoteTable -> Assertion
175testSendPureClosure transport rtable = do
176 serverAddr <- newEmptyMVar
177 serverDone <- newEmptyMVar
180 node <- newLocalNode transport rtable
181 addr <- forkProcess node $ do
183 fn <- unClosure cl :: Process (Int -> Int)
185 liftIO $ putMVar serverDone ()
186 putMVar serverAddr addr
189 node <- newLocalNode transport rtable
190 theirAddr <- readMVar serverAddr
191 runProcess node $ send theirAddr (addIntClosure 7)
195testSendIOClosure :: Transport -> RemoteTable -> Assertion
196testSendIOClosure transport rtable = do
197 serverAddr <- newEmptyMVar
198 serverDone <- newEmptyMVar
201 node <- newLocalNode transport rtable
202 addr <- forkProcess node $ do
204 io <- unClosure cl :: Process (MVar Int -> IO ())
206 someMVar <- newEmptyMVar
208 5 <- readMVar someMVar
209 putMVar serverDone ()
210 putMVar serverAddr addr
213 node <- newLocalNode transport rtable
214 theirAddr <- readMVar serverAddr
215 runProcess node $ send theirAddr (putIntClosure 5)
219testSendProcClosure :: Transport -> RemoteTable -> Assertion
220testSendProcClosure transport rtable = do
221 serverAddr <- newEmptyMVar
222 clientDone <- newEmptyMVar
225 node <- newLocalNode transport rtable
226 addr <- forkProcess node $ do
228 pr <- unClosure cl :: Process (Int -> Process ())
230 putMVar serverAddr addr
233 node <- newLocalNode transport rtable
234 theirAddr <- readMVar serverAddr
237 send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
238 5 <- expect :: Process Int
239 liftIO $ putMVar clientDone ()
243testSpawn :: Transport -> RemoteTable -> Assertion
244testSpawn transport rtable = do
245 serverNodeAddr <- newEmptyMVar
246 clientDone <- newEmptyMVar
249 node <- newLocalNode transport rtable
250 putMVar serverNodeAddr (localNodeId node)
253 node <- newLocalNode transport rtable
254 nid <- readMVar serverNodeAddr
257 pid' <- spawn nid (sendPidClosure pid)
259 True <- return $ pid' == pid''
260 liftIO $ putMVar clientDone ()
264testCall :: Transport -> RemoteTable -> Assertion
265testCall transport rtable = do
266 serverNodeAddr <- newEmptyMVar
267 clientDone <- newEmptyMVar
270 node <- newLocalNode transport rtable
271 putMVar serverNodeAddr (localNodeId node)
274 node <- newLocalNode transport rtable
275 nid <- readMVar serverNodeAddr
277 (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
278 liftIO $ putMVar clientDone ()
282testCallBind :: Transport -> RemoteTable -> Assertion
283testCallBind transport rtable = do
284 serverNodeAddr <- newEmptyMVar
285 clientDone <- newEmptyMVar
288 node <- newLocalNode transport rtable
289 putMVar serverNodeAddr (localNodeId node)
292 node <- newLocalNode transport rtable
293 nid <- readMVar serverNodeAddr
295 (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
296 liftIO $ putMVar clientDone ()
300testSeq :: Transport -> RemoteTable -> Assertion
301testSeq transport rtable = do
302 node <- newLocalNode transport rtable
306 join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
309 liftIO $ putMVar done ()
312-- Test 'spawnSupervised'
314-- Set up a supervisor, spawn a child, then have a third process monitor the
315-- child. The supervisor then throws an exception, the child dies because it
316-- was linked to the supervisor, and the third process notices that the child
318testSpawnSupervised :: Transport -> RemoteTable -> Assertion
319testSpawnSupervised transport rtable = do
320 [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
321 [superPid, childPid] <- replicateM 2 $ newEmptyMVar
322 thirdProcessDone <- newEmptyMVar
324 forkProcess node1 $ do
326 liftIO $ putMVar superPid us
327 (child, _ref) <- spawnSupervised (localNodeId node2) (waitClosure 1000000)
329 putMVar childPid child
330 threadDelay 500000 -- Give the child a chance to link to us
331 throw supervisorDeath
333 forkProcess node2 $ do
334 [super, child] <- liftIO $ mapM readMVar [superPid, childPid]
336 ProcessMonitorNotification ref' pid' (DiedException e) <- expect
337 True <- return $ ref' == ref
339 && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))
340 liftIO $ putMVar thirdProcessDone ()
342 takeMVar thirdProcessDone
344 supervisorDeath :: IOException
345 supervisorDeath = userError "Supervisor died"
347testSpawnInvalid :: Transport -> RemoteTable -> Assertion
348testSpawnInvalid transport rtable = do
349 node <- newLocalNode transport rtable
351 forkProcess node $ do
352 (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
353 ProcessMonitorNotification ref' pid' _reason <- expect
354 -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
355 True <- return $ ref' == ref && pid == pid'
356 liftIO $ putMVar done ()
359testClosureExpect :: Transport -> RemoteTable -> Assertion
360testClosureExpect transport rtable = do
361 node <- newLocalNode transport rtable
364 nodeId <- getSelfNode
366 them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
367 send them (1234 :: Int)
368 (1234 :: Int) <- expect
369 liftIO $ putMVar done ()
372testSpawnChannel :: Transport -> RemoteTable -> Assertion
373testSpawnChannel transport rtable = do
375 [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
377 forkProcess node1 $ do
378 pingServer <- spawnChannel
379 (sdictSendPort sdictUnit)
381 ($(mkClosure 'typedPingServer) ())
382 (sendReply, receiveReply) <- newChan
383 sendChan pingServer sendReply
384 receiveChan receiveReply
385 liftIO $ putMVar done ()
389testTDict :: Transport -> RemoteTable -> Assertion
390testTDict transport rtable = do
392 [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
393 forkProcess node1 $ do
394 True <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
395 liftIO $ putMVar done ()
398testFib :: Transport -> RemoteTable -> Assertion
399testFib transport rtable = do
400 nodes <- replicateM 4 $ newLocalNode transport rtable
403 forkProcess (head nodes) $ do
404 (sport, rport) <- newChan
405 spawnLocal $ dfib (map localNodeId nodes, sport, 10)
406 55 <- receiveChan rport :: Process Integer
407 liftIO $ putMVar done ()
411testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> Assertion
412testSpawnReconnect transport rtable transportInternals = do
413 [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
414 let nid1 = localNodeId node1
415 nid2 = localNodeId node2
417 iv <- newMVar (0 :: Int)
419 incr <- forkProcess node1 $ forever $ do
421 liftIO $ modifyMVar_ iv (return . (+ 1))
423 forkProcess node2 $ do
424 _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
425 simulateNetworkFailure transportInternals nid2 nid1
426 _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
427 _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
429 liftIO $ threadDelay 100000
431 count <- liftIO $ takeMVar iv
432 True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
434 liftIO $ putMVar done ()
438-- | 'spawn' used to ave a race condition which would be triggered if the
439-- spawning process terminates immediately after spawning
440testSpawnTerminate :: Transport -> RemoteTable -> Assertion
441testSpawnTerminate transport rtable = do
442 slave <- newLocalNode transport rtable
443 master <- newLocalNode transport rtable
444 masterDone <- newEmptyMVar
446 runProcess master $ do
448 replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
449 replicateM_ 1000 $ (expect :: Process ())
450 liftIO $ putMVar masterDone ()
454tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]
455tests (transport, transportInternals) rtable = [
456 testCase "Unclosure" (testUnclosure transport rtable)
457 , testCase "Bind" (testBind transport rtable)
458 , testCase "SendPureClosure" (testSendPureClosure transport rtable)
459 , testCase "SendIOClosure" (testSendIOClosure transport rtable)
460 , testCase "SendProcClosure" (testSendProcClosure transport rtable)
461 , testCase "Spawn" (testSpawn transport rtable)
462 , testCase "Call" (testCall transport rtable)
463 , testCase "CallBind" (testCallBind transport rtable)
464 , testCase "Seq" (testSeq transport rtable)
465 , testCase "SpawnSupervised" (testSpawnSupervised transport rtable)
466 , testCase "SpawnInvalid" (testSpawnInvalid transport rtable)
467 , testCase "ClosureExpect" (testClosureExpect transport rtable)
468 , testCase "SpawnChannel" (testSpawnChannel transport rtable)
469 , testCase "TDict" (testTDict transport rtable)
470 , testCase "Fib" (testFib transport rtable)
471 , testCase "SpawnTerminate" (testSpawnTerminate transport rtable)
472 , testCase "SpawnReconnect" (testSpawnReconnect transport rtable transportInternals)
478 Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
479 let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
480 defaultMain (tests transport rtable)
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