git.iko.soy/distributed-process
on master help sign in
Merge pull request #259 from haskell-distributed/import-d-p-tests

Reimport distributed-process-tests.

commit5bed8d1800f80ddd67ef732befcf9f645be5497e authorFacundo Domínguez <facundominguez@gmail.com> dateMon 26 Oct 2015, 01:53 (11 years ago) parentd6f53736419364
18 files changed+3572−10
.travis.yml+10−5 REPOS−5 distributed-process-tests/.gitignore+4 distributed-process-tests/CONTRIBUTING.md+1 distributed-process-tests/LICENSE+31 distributed-process-tests/README.md+14 distributed-process-tests/distributed-process-tests.cabal+143 distributed-process-tests/src/Control/Distributed/Process/Tests/CH.hs+1602 distributed-process-tests/src/Control/Distributed/Process/Tests/Closure.hs+548 distributed-process-tests/src/Control/Distributed/Process/Tests/Internal/Utils.hs+232 distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hs+286 distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hs+158 distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hs+125 distributed-process-tests/src/Control/Distributed/Process/Tests/Tracing.hs+326 distributed-process-tests/src/Network/Transport/Test.hs+12 distributed-process-tests/tests/runInMemory.hs+31 distributed-process-tests/tests/runTCP.hs+40 stack.yaml+9
.travis.yml+10−5
@@ -21,13 +21,18 @@ before_install:
2121 # workaround for https://ghc.haskell.org/trac/ghc/ticket/9221
2222 # taken from https://github.com/hvr/multi-ghc-travis/blob/0fa68f78c2b1b059f904c9abc85510a3bb4f57e2/README.md
2323 - sed -i 's/^jobs:/-- jobs:/' $HOME/.cabal/config
24− - cabal sandbox init
25− - for i in `cat REPOS`; do git clone http://github.com/haskell-distributed/$i; done
26− - for i in `cat REPOS`; do cabal sandbox add-source $i; done
2724
2825install:
29− - cabal install --only-dependencies --jobs=2
26+ - cabal install --jobs=2 --enable-tests --only-dependencies . distributed-process-tests/
3027
3128script:
32− - cabal configure
29+ - cabal install --jobs=2
30+ - cd distributed-process-tests/
31+ - cabal configure --enable-tests
3332 - cabal build --jobs=2
33+ # skip intermittent tests Reconnect and SpawnReconnect
34+ - dist/build/TestCHInMemory/TestCHInMemory --plain -t '!Flaky'
35+ - dist/build/TestClosure/TestClosure --plain -t '!SpawnReconnect'
36+ - dist/build/TestStats/TestStats --plain
37+ - dist/build/TestMx/TestMx --plain
38+ - dist/build/TestTracing/TestTracing --plain
REPOSdeleted−5
@@ -1,5 +0,0 @@
1−distributed-process-tests
2−distributed-static
3−network-transport
4−network-transport-tcp
5−rank1dynamic
distributed-process-tests/.gitignorenew file+4
@@ -0,0 +1,4 @@
1+dist/
2+.cabal-sandbox
3+cabal.sandbox.config
4+.stack*
distributed-process-tests/CONTRIBUTING.mdnew file+1
@@ -0,0 +1 @@
1+See https://github.com/haskell-distributed/cloud-haskell/blob/master/CONTRIBUTING.md.
distributed-process-tests/LICENSEnew file+31
@@ -0,0 +1,31 @@
1+Copyright Well-Typed LLP, 2011-2012
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of the owner nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
distributed-process-tests/README.mdnew file+14
@@ -0,0 +1,14 @@
1+# distributed-process-tests
2+[![travis](https://secure.travis-ci.org/haskell-distributed/distributed-process-tests.png)](http://travis-ci.org/haskell-distributed/distributed-process-tests)
3+[![Release](https://img.shields.io/hackage/v/distributed-process-tests.svg)](https://hackage.haskell.org/package/distributed-process-tests)
4+
5+See http://haskell-distributed.github.com for documentation, user guides,
6+tutorials and assistance.
7+
8+## Getting Help / Raising Issues
9+
10+Please visit the [bug tracker](https://github.com/haskell-distributed/distributed-process-tests/issues) to submit issues. You can contact the distributed-haskell@googlegroups.com mailing list for help and comments.
11+
12+## License
13+
14+This package is made available under a 3-clause BSD-style license.
distributed-process-tests/distributed-process-tests.cabalnew file+143
@@ -0,0 +1,143 @@
1+name: distributed-process-tests
2+version: 0.4.2
3+synopsis: Tests and test support tools for distributed-process.
4+homepage: http://github.com/haskell-distributed/distributed-process-tests
5+license: BSD3
6+license-file: LICENSE
7+Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
8+Maintainer: watson.timothy@gmail.com, edsko@well-typed.com, duncan@well-typed.com
9+copyright: Well-Typed LLP
10+category: Control, Cloud Haskell
11+build-type: Simple
12+cabal-version: >=1.8
13+
14+flag tcp
15+ Description: build and run TCP tests
16+ Default: False
17+
18+library
19+ exposed-modules: Network.Transport.Test
20+ Control.Distributed.Process.Tests.CH
21+ Control.Distributed.Process.Tests.Closure
22+ Control.Distributed.Process.Tests.Mx
23+ Control.Distributed.Process.Tests.Receive
24+ Control.Distributed.Process.Tests.Stats
25+ Control.Distributed.Process.Tests.Tracing
26+ Control.Distributed.Process.Tests.Internal.Utils
27+ Build-Depends: base >= 4.4 && < 5,
28+ ansi-terminal >= 0.5 && < 0.7,
29+ binary >= 0.5 && < 0.8,
30+ bytestring >= 0.9 && < 0.11,
31+ distributed-process >= 0.5.3 && < 0.6,
32+ distributed-static,
33+ HUnit >= 1.2 && < 1.4,
34+ network-transport >= 0.4.1.0 && < 0.5,
35+ network >= 2.5 && < 2.7,
36+ random >= 1.0 && < 1.2,
37+ rematch >= 0.1.2.1,
38+ test-framework >= 0.6 && < 0.9,
39+ test-framework-hunit >= 0.2.0 && < 0.4,
40+ stm
41+ hs-source-dirs: src
42+ ghc-options: -Wall -fno-warn-unused-do-bind
43+ extensions: CPP,
44+ ExistentialQuantification,
45+ FlexibleInstances,
46+ DeriveDataTypeable,
47+ DeriveGeneric,
48+ GeneralizedNewtypeDeriving,
49+ OverlappingInstances,
50+ RankNTypes,
51+ RecordWildCards,
52+ ScopedTypeVariables
53+ if impl(ghc <= 7.4.2)
54+ Build-Depends: ghc-prim == 0.2.0.0
55+
56+Test-Suite TestCHInMemory
57+ Type: exitcode-stdio-1.0
58+ Main-Is: runInMemory.hs
59+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.CH
60+ Build-Depends: base >= 4.4 && < 5,
61+ distributed-process-tests,
62+ network >= 2.3 && < 2.7,
63+ network-transport >= 0.4.1.0 && < 0.5,
64+ network-transport-inmemory >= 0.5,
65+ test-framework >= 0.6 && < 0.9
66+ Extensions: CPP
67+ ghc-options: -Wall -threaded -debug -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
68+ HS-Source-Dirs: tests
69+
70+Test-Suite TestCHInTCP
71+ Type: exitcode-stdio-1.0
72+ Main-Is: runTCP.hs
73+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.CH
74+ if flag(tcp)
75+ Build-Depends: base >= 4.4 && < 5,
76+ distributed-process-tests,
77+ network >= 2.3 && < 2.7,
78+ network-transport >= 0.4.1.0 && < 0.5,
79+ network-transport-tcp >= 0.3 && < 0.5,
80+ test-framework >= 0.6 && < 0.9
81+ else
82+ Buildable: False
83+ Extensions: CPP
84+ ghc-options: -Wall -threaded -debug -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
85+ HS-Source-Dirs: tests
86+
87+
88+Test-Suite TestClosure
89+ Type: exitcode-stdio-1.0
90+ Main-Is: runInMemory.hs
91+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Closure
92+ Build-Depends: base >= 4.4 && < 5,
93+ distributed-process-tests,
94+ network >= 2.3 && < 2.7,
95+ network-transport >= 0.4.1.0 && < 0.5,
96+ network-transport-inmemory >= 0.5,
97+ test-framework >= 0.6 && < 0.9
98+ Extensions: CPP
99+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
100+ HS-Source-Dirs: tests
101+
102+Test-Suite TestStats
103+ Type: exitcode-stdio-1.0
104+ Main-Is: runInMemory.hs
105+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Stats
106+ Build-Depends: base >= 4.4 && < 5,
107+ distributed-process-tests,
108+ network >= 2.3 && < 2.7,
109+ network-transport >= 0.4.1.0 && < 0.5,
110+ network-transport-inmemory >= 0.5,
111+ test-framework >= 0.6 && < 0.9
112+ Extensions: CPP
113+ ghc-options: -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
114+ HS-Source-Dirs: tests
115+
116+
117+Test-Suite TestMx
118+ Type: exitcode-stdio-1.0
119+ Main-Is: runInMemory.hs
120+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Mx
121+ Build-Depends: base >= 4.4 && < 5,
122+ distributed-process-tests,
123+ network >= 2.3 && < 2.7,
124+ network-transport >= 0.4.1.0 && < 0.5,
125+ network-transport-inmemory >= 0.5,
126+ test-framework >= 0.6 && < 0.9
127+ Extensions: CPP
128+ ghc-options: -Wall -debug -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
129+ HS-Source-Dirs: tests
130+
131+Test-Suite TestTracing
132+ Type: exitcode-stdio-1.0
133+ Main-Is: runInMemory.hs
134+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Tracing
135+ Build-Depends: base >= 4.4 && < 5,
136+ distributed-process-tests,
137+ network >= 2.3 && < 2.7,
138+ network-transport >= 0.4.1.0 && < 0.5,
139+ network-transport-inmemory >= 0.5,
140+ test-framework >= 0.6 && < 0.9
141+ Extensions: CPP
142+ ghc-options: -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
143+ HS-Source-Dirs: tests
distributed-process-tests/src/Control/Distributed/Process/Tests/CH.hsnew file+1602
@@ -0,0 +1,1602 @@
1+module Control.Distributed.Process.Tests.CH (tests) where
2+
3+#if ! MIN_VERSION_base(4,6,0)
4+import Prelude hiding (catch)
5+#endif
6+
7+import Network.Transport.Test (TestTransport(..))
8+
9+import Data.Binary (Binary(..))
10+import Data.Typeable (Typeable)
11+import Data.Foldable (forM_)
12+import Data.IORef
13+ ( readIORef
14+ , writeIORef
15+ , newIORef
16+ )
17+import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId, yield)
18+import Control.Concurrent.MVar
19+ ( MVar
20+ , newEmptyMVar
21+ , putMVar
22+ , takeMVar
23+ , readMVar
24+ )
25+import Control.Monad (replicateM_, replicateM, forever, void, unless, join)
26+import Control.Exception (SomeException, throwIO, ErrorCall(..))
27+import qualified Control.Exception as Ex (catch)
28+import Control.Applicative ((<$>), (<*>), pure, (<|>))
29+import qualified Network.Transport as NT (closeEndPoint, EndPointAddress)
30+import Control.Distributed.Process
31+import Control.Distributed.Process.Internal.Types
32+ ( NodeId(nodeAddress)
33+ , LocalNode(localEndPoint)
34+ , ProcessExitException(..)
35+ , nullProcessId
36+ , createUnencodedMessage
37+ )
38+import Control.Distributed.Process.Node
39+import Control.Distributed.Process.Serializable (Serializable)
40+
41+import Test.HUnit (Assertion, assertFailure)
42+import Test.Framework (Test, testGroup)
43+import Test.Framework.Providers.HUnit (testCase)
44+import Control.Rematch hiding (match)
45+import Control.Rematch.Run (Match(..))
46+
47+newtype Ping = Ping ProcessId
48+ deriving (Typeable, Binary, Show)
49+
50+newtype Pong = Pong ProcessId
51+ deriving (Typeable, Binary, Show)
52+
53+--------------------------------------------------------------------------------
54+-- Supporting definitions --
55+--------------------------------------------------------------------------------
56+
57+expectThat :: a -> Matcher a -> Assertion
58+expectThat a matcher = case res of
59+ MatchSuccess -> return ()
60+ (MatchFailure msg) -> assertFailure msg
61+ where res = runMatch matcher a
62+
63+-- | Like fork, but throw exceptions in the child thread to the parent
64+forkTry :: IO () -> IO ThreadId
65+forkTry p = do
66+ tid <- myThreadId
67+ forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))
68+
69+-- | The ping server from the paper
70+ping :: Process ()
71+ping = do
72+ Pong partner <- expect
73+ self <- getSelfPid
74+ send partner (Ping self)
75+ ping
76+
77+-- | Quick and dirty synchronous version of whereisRemoteAsync
78+whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
79+whereisRemote nid string = do
80+ whereisRemoteAsync nid string
81+ WhereIsReply _ mPid <- expect
82+ return mPid
83+
84+data Add = Add ProcessId Double Double deriving (Typeable)
85+data Divide = Divide ProcessId Double Double deriving (Typeable)
86+data DivByZero = DivByZero deriving (Typeable)
87+
88+instance Binary Add where
89+ put (Add pid x y) = put pid >> put x >> put y
90+ get = Add <$> get <*> get <*> get
91+
92+instance Binary Divide where
93+ put (Divide pid x y) = put pid >> put x >> put y
94+ get = Divide <$> get <*> get <*> get
95+
96+instance Binary DivByZero where
97+ put DivByZero = return ()
98+ get = return DivByZero
99+
100+-- The math server from the paper
101+math :: Process ()
102+math = do
103+ receiveWait
104+ [ match (\(Add pid x y) -> send pid (x + y))
105+ , matchIf (\(Divide _ _ y) -> y /= 0)
106+ (\(Divide pid x y) -> send pid (x / y))
107+ , match (\(Divide pid _ _) -> send pid DivByZero)
108+ ]
109+ math
110+
111+-- | Monitor or link to a remote node
112+monitorOrLink :: Bool -- ^ 'True' for monitor, 'False' for link
113+ -> ProcessId -- ^ Process to monitor/link to
114+ -> Maybe (MVar ()) -- ^ MVar to signal on once the monitor has been set up
115+ -> Process (Maybe MonitorRef)
116+monitorOrLink mOrL pid mSignal = do
117+ result <- if mOrL then Just <$> monitor pid
118+ else link pid >> return Nothing
119+ -- Monitor is asynchronous, which usually does not matter but if we want a
120+ -- *specific* signal then it does. Therefore we wait until the MonitorRef is
121+ -- listed in the ProcessInfo and hope that this means the monitor has been set
122+ -- up.
123+ forM_ mSignal $ \signal -> do
124+ self <- getSelfPid
125+ spawnLocal $ do
126+ let waitForMOrL = do
127+ liftIO $ threadDelay 100000
128+ mpinfo <- getProcessInfo pid
129+ case mpinfo of
130+ Nothing -> waitForMOrL
131+ Just pinfo ->
132+ if mOrL then
133+ unless (result == lookup self (infoMonitors pinfo)) waitForMOrL
134+ else
135+ unless (elem self $ infoLinks pinfo) waitForMOrL
136+ waitForMOrL
137+ liftIO $ putMVar signal ()
138+ return result
139+
140+monitorTestProcess :: ProcessId -- Process to monitor/link to
141+ -> Bool -- 'True' for monitor, 'False' for link
142+ -> Bool -- Should we unmonitor?
143+ -> DiedReason -- Expected cause of death
144+ -> Maybe (MVar ()) -- Signal for 'monitor set up'
145+ -> MVar () -- Signal for successful termination
146+ -> Process ()
147+monitorTestProcess theirAddr mOrL un reason monitorSetup done =
148+ catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup
149+ case (un, mRef) of
150+ (True, Nothing) -> do
151+ unlink theirAddr
152+ liftIO $ putMVar done ()
153+ (True, Just ref) -> do
154+ unmonitor ref
155+ liftIO $ putMVar done ()
156+ (False, ref) -> do
157+ ProcessMonitorNotification ref' pid reason' <- expect
158+ True <- return $ Just ref' == ref && pid == theirAddr && mOrL && reason == reason'
159+ liftIO $ putMVar done ()
160+ )
161+ (\(ProcessLinkException pid reason') -> do
162+ True <- return $ pid == theirAddr && not mOrL && not un && reason == reason'
163+ liftIO $ putMVar done ()
164+ )
165+
166+--------------------------------------------------------------------------------
167+-- The tests proper --
168+--------------------------------------------------------------------------------
169+
170+-- | Basic ping test
171+testPing :: TestTransport -> Assertion
172+testPing TestTransport{..} = do
173+ serverAddr <- newEmptyMVar
174+ clientDone <- newEmptyMVar
175+
176+ -- Server
177+ forkIO $ do
178+ localNode <- newLocalNode testTransport initRemoteTable
179+ addr <- forkProcess localNode ping
180+ putMVar serverAddr addr
181+
182+ -- Client
183+ forkIO $ do
184+ localNode <- newLocalNode testTransport initRemoteTable
185+ pingServer <- readMVar serverAddr
186+
187+ let numPings = 10000
188+
189+ runProcess localNode $ do
190+ pid <- getSelfPid
191+ replicateM_ numPings $ do
192+ send pingServer (Pong pid)
193+ Ping _ <- expect
194+ return ()
195+
196+ putMVar clientDone ()
197+
198+ takeMVar clientDone
199+
200+-- | Monitor a process on an unreachable node
201+testMonitorUnreachable :: TestTransport -> Bool -> Bool -> Assertion
202+testMonitorUnreachable TestTransport{..} mOrL un = do
203+ deadProcess <- newEmptyMVar
204+ done <- newEmptyMVar
205+
206+ forkIO $ do
207+ localNode <- newLocalNode testTransport initRemoteTable
208+ addr <- forkProcess localNode expect
209+ closeLocalNode localNode
210+ putMVar deadProcess addr
211+
212+ forkIO $ do
213+ localNode <- newLocalNode testTransport initRemoteTable
214+ theirAddr <- readMVar deadProcess
215+ runProcess localNode $
216+ monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done
217+
218+ takeMVar done
219+
220+-- | Monitor a process which terminates normally
221+testMonitorNormalTermination :: TestTransport -> Bool -> Bool -> Assertion
222+testMonitorNormalTermination TestTransport{..} mOrL un = do
223+ monitorSetup <- newEmptyMVar
224+ monitoredProcess <- newEmptyMVar
225+ done <- newEmptyMVar
226+
227+ forkIO $ do
228+ localNode <- newLocalNode testTransport initRemoteTable
229+ addr <- forkProcess localNode $
230+ liftIO $ readMVar monitorSetup
231+ putMVar monitoredProcess addr
232+
233+ forkIO $ do
234+ localNode <- newLocalNode testTransport initRemoteTable
235+ theirAddr <- readMVar monitoredProcess
236+ runProcess localNode $
237+ monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done
238+
239+ takeMVar done
240+
241+-- | Monitor a process which terminates abnormally
242+testMonitorAbnormalTermination :: TestTransport -> Bool -> Bool -> Assertion
243+testMonitorAbnormalTermination TestTransport{..} mOrL un = do
244+ monitorSetup <- newEmptyMVar
245+ monitoredProcess <- newEmptyMVar
246+ done <- newEmptyMVar
247+
248+ let err = userError "Abnormal termination"
249+
250+ forkIO $ do
251+ localNode <- newLocalNode testTransport initRemoteTable
252+ addr <- forkProcess localNode . liftIO $ do
253+ readMVar monitorSetup
254+ throwIO err
255+ putMVar monitoredProcess addr
256+
257+ forkIO $ do
258+ localNode <- newLocalNode testTransport initRemoteTable
259+ theirAddr <- readMVar monitoredProcess
260+ runProcess localNode $
261+ monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done
262+
263+ takeMVar done
264+
265+-- | Monitor a local process that is already dead
266+testMonitorLocalDeadProcess :: TestTransport -> Bool -> Bool -> Assertion
267+testMonitorLocalDeadProcess TestTransport{..} mOrL un = do
268+ processAddr <- newEmptyMVar
269+ localNode <- newLocalNode testTransport initRemoteTable
270+ done <- newEmptyMVar
271+
272+ forkIO $ do
273+ addr <- forkProcess localNode $ return ()
274+ putMVar processAddr addr
275+
276+ forkIO $ do
277+ theirAddr <- readMVar processAddr
278+ runProcess localNode $ do
279+ monitor theirAddr
280+ -- wait for the process to die
281+ ProcessMonitorNotification _ _ _ <- expect
282+ monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
283+
284+ takeMVar done
285+
286+-- | Monitor a remote process that is already dead
287+testMonitorRemoteDeadProcess :: TestTransport -> Bool -> Bool -> Assertion
288+testMonitorRemoteDeadProcess TestTransport{..} mOrL un = do
289+ processDead <- newEmptyMVar
290+ processAddr <- newEmptyMVar
291+ done <- newEmptyMVar
292+
293+ forkIO $ do
294+ localNode <- newLocalNode testTransport initRemoteTable
295+ addr <- forkProcess localNode . liftIO $ putMVar processDead ()
296+ putMVar processAddr addr
297+
298+ forkIO $ do
299+ localNode <- newLocalNode testTransport initRemoteTable
300+ theirAddr <- readMVar processAddr
301+ readMVar processDead
302+ runProcess localNode $ do
303+ monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
304+
305+ takeMVar done
306+
307+-- | Monitor a process that becomes disconnected
308+testMonitorDisconnect :: TestTransport -> Bool -> Bool -> Assertion
309+testMonitorDisconnect TestTransport{..} mOrL un = do
310+ processAddr <- newEmptyMVar
311+ processAddr2 <- newEmptyMVar
312+ monitorSetup <- newEmptyMVar
313+ done <- newEmptyMVar
314+
315+ forkIO $ do
316+ localNode <- newLocalNode testTransport initRemoteTable
317+ addr <- forkProcess localNode $ expect
318+ addr2 <- forkProcess localNode $ return ()
319+ putMVar processAddr addr
320+ readMVar monitorSetup
321+ NT.closeEndPoint (localEndPoint localNode)
322+ putMVar processAddr2 addr2
323+
324+ forkIO $ do
325+ localNode <- newLocalNode testTransport initRemoteTable
326+ theirAddr <- readMVar processAddr
327+ forkProcess localNode $ do
328+ lc <- liftIO $ readMVar processAddr2
329+ send lc ()
330+ runProcess localNode $ do
331+ monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done
332+
333+ takeMVar done
334+
335+-- | Test the math server (i.e., receiveWait)
336+testMath :: TestTransport -> Assertion
337+testMath TestTransport{..} = do
338+ serverAddr <- newEmptyMVar
339+ clientDone <- newEmptyMVar
340+
341+ -- Server
342+ forkIO $ do
343+ localNode <- newLocalNode testTransport initRemoteTable
344+ addr <- forkProcess localNode math
345+ putMVar serverAddr addr
346+
347+ -- Client
348+ forkIO $ do
349+ localNode <- newLocalNode testTransport initRemoteTable
350+ mathServer <- readMVar serverAddr
351+
352+ runProcess localNode $ do
353+ pid <- getSelfPid
354+ send mathServer (Add pid 1 2)
355+ 3 <- expect :: Process Double
356+ send mathServer (Divide pid 8 2)
357+ 4 <- expect :: Process Double
358+ send mathServer (Divide pid 8 0)
359+ DivByZero <- expect
360+ liftIO $ putMVar clientDone ()
361+
362+ takeMVar clientDone
363+
364+-- | Send first message (i.e. connect) to an already terminated process
365+-- (without monitoring); then send another message to a second process on
366+-- the same remote node (we're checking that the remote node did not die)
367+testSendToTerminated :: TestTransport -> Assertion
368+testSendToTerminated TestTransport{..} = do
369+ serverAddr1 <- newEmptyMVar
370+ serverAddr2 <- newEmptyMVar
371+ clientDone <- newEmptyMVar
372+
373+ forkIO $ do
374+ terminated <- newEmptyMVar
375+ localNode <- newLocalNode testTransport initRemoteTable
376+ addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()
377+ addr2 <- forkProcess localNode $ ping
378+ readMVar terminated
379+ putMVar serverAddr1 addr1
380+ putMVar serverAddr2 addr2
381+
382+ forkIO $ do
383+ localNode <- newLocalNode testTransport initRemoteTable
384+ server1 <- readMVar serverAddr1
385+ server2 <- readMVar serverAddr2
386+ runProcess localNode $ do
387+ pid <- getSelfPid
388+ send server1 "Hi"
389+ send server2 (Pong pid)
390+ Ping pid' <- expect
391+ True <- return $ pid' == server2
392+ liftIO $ putMVar clientDone ()
393+
394+ takeMVar clientDone
395+
396+-- | Test (non-zero) timeout
397+testTimeout :: TestTransport -> Assertion
398+testTimeout TestTransport{..} = do
399+ localNode <- newLocalNode testTransport initRemoteTable
400+ done <- newEmptyMVar
401+
402+ runProcess localNode $ do
403+ Nothing <- receiveTimeout 1000000 [match (\(Add _ _ _) -> return ())]
404+ liftIO $ putMVar done ()
405+
406+ takeMVar done
407+
408+-- | Test zero timeout
409+testTimeout0 :: TestTransport -> Assertion
410+testTimeout0 TestTransport{..} = do
411+ serverAddr <- newEmptyMVar
412+ clientDone <- newEmptyMVar
413+ messagesSent <- newEmptyMVar
414+
415+ forkIO $ do
416+ localNode <- newLocalNode testTransport initRemoteTable
417+ addr <- forkProcess localNode $ do
418+ liftIO $ readMVar messagesSent >> threadDelay 1000000
419+ -- Variation on the venerable ping server which uses a zero timeout
420+ -- Since we wait for all messages to be sent before doing this receive,
421+ -- we should nevertheless find the right message immediately
422+ Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)]
423+ self <- getSelfPid
424+ send partner (Ping self)
425+ putMVar serverAddr addr
426+
427+ forkIO $ do
428+ localNode <- newLocalNode testTransport initRemoteTable
429+ server <- readMVar serverAddr
430+ runProcess localNode $ do
431+ pid <- getSelfPid
432+ -- Send a bunch of messages. A large number of messages that the server
433+ -- is not interested in, and then a single message that it wants
434+ replicateM_ 10000 $ send server "Irrelevant message"
435+ send server (Pong pid)
436+ liftIO $ putMVar messagesSent ()
437+ Ping _ <- expect
438+ liftIO $ putMVar clientDone ()
439+
440+ takeMVar clientDone
441+
442+-- | Test typed channels
443+testTypedChannels :: TestTransport -> Assertion
444+testTypedChannels TestTransport{..} = do
445+ serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))
446+ clientDone <- newEmptyMVar
447+
448+ forkIO $ do
449+ localNode <- newLocalNode testTransport initRemoteTable
450+ forkProcess localNode $ do
451+ (serverSendPort, rport) <- newChan
452+ liftIO $ putMVar serverChannel serverSendPort
453+ (clientSendPort, i) <- receiveChan rport
454+ sendChan clientSendPort (even i)
455+ return ()
456+
457+ forkIO $ do
458+ localNode <- newLocalNode testTransport initRemoteTable
459+ serverSendPort <- readMVar serverChannel
460+ runProcess localNode $ do
461+ (clientSendPort, rport) <- newChan
462+ sendChan serverSendPort (clientSendPort, 5)
463+ False <- receiveChan rport
464+ liftIO $ putMVar clientDone ()
465+
466+ takeMVar clientDone
467+
468+-- | Test merging receive ports
469+testMergeChannels :: TestTransport -> Assertion
470+testMergeChannels TestTransport{..} = do
471+ localNode <- newLocalNode testTransport initRemoteTable
472+ testFlat localNode True "aaabbbccc"
473+ testFlat localNode False "abcabcabc"
474+ testNested localNode True True "aaabbbcccdddeeefffggghhhiii"
475+ testNested localNode True False "adgadgadgbehbehbehcficficfi"
476+ testNested localNode False True "abcabcabcdefdefdefghighighi"
477+ testNested localNode False False "adgbehcfiadgbehcfiadgbehcfi"
478+ testBlocked localNode True
479+ testBlocked localNode False
480+ where
481+ -- Single layer of merging
482+ testFlat :: LocalNode -> Bool -> String -> IO ()
483+ testFlat localNode biased expected = do
484+ done <- newEmptyMVar
485+ forkProcess localNode $ do
486+ rs <- mapM charChannel "abc"
487+ m <- mergePorts biased rs
488+ xs <- replicateM 9 $ receiveChan m
489+ True <- return $ xs == expected
490+ liftIO $ putMVar done ()
491+ takeMVar done
492+
493+ -- Two layers of merging
494+ testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
495+ testNested localNode biasedInner biasedOuter expected = do
496+ done <- newEmptyMVar
497+ forkProcess localNode $ do
498+ rss <- mapM (mapM charChannel) ["abc", "def", "ghi"]
499+ ms <- mapM (mergePorts biasedInner) rss
500+ m <- mergePorts biasedOuter ms
501+ xs <- replicateM (9 * 3) $ receiveChan m
502+ True <- return $ xs == expected
503+ liftIO $ putMVar done ()
504+ takeMVar done
505+
506+ -- Test that if no messages are (immediately) available, the scheduler makes no difference
507+ testBlocked :: LocalNode -> Bool -> IO ()
508+ testBlocked localNode biased = do
509+ vs <- replicateM 3 newEmptyMVar
510+ done <- newEmptyMVar
511+
512+ forkProcess localNode $ do
513+ [sa, sb, sc] <- liftIO $ mapM readMVar vs
514+ mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)
515+ [ -- a, b, c
516+ (sa, 'a')
517+ , (sb, 'b')
518+ , (sc, 'c')
519+ -- a, c, b
520+ , (sa, 'a')
521+ , (sc, 'c')
522+ , (sb, 'b')
523+ -- b, a, c
524+ , (sb, 'b')
525+ , (sa, 'a')
526+ , (sc, 'c')
527+ -- b, c, a
528+ , (sb, 'b')
529+ , (sc, 'c')
530+ , (sa, 'a')
531+ -- c, a, b
532+ , (sc, 'c')
533+ , (sa, 'a')
534+ , (sb, 'b')
535+ -- c, b, a
536+ , (sc, 'c')
537+ , (sb, 'b')
538+ , (sa, 'a')
539+ ]
540+
541+ forkProcess localNode $ do
542+ (ss, rs) <- unzip <$> replicateM 3 newChan
543+ liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
544+ m <- mergePorts biased rs
545+ xs <- replicateM (6 * 3) $ receiveChan m
546+ True <- return $ xs == "abcacbbacbcacabcba"
547+ liftIO $ putMVar done ()
548+
549+ takeMVar done
550+
551+ mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
552+ mergePorts True = mergePortsBiased
553+ mergePorts False = mergePortsRR
554+
555+ charChannel :: Char -> Process (ReceivePort Char)
556+ charChannel c = do
557+ (sport, rport) <- newChan
558+ replicateM_ 3 $ sendChan sport c
559+ liftIO $ threadDelay 10000 -- Make sure messages have been sent
560+ return rport
561+
562+testTerminate :: TestTransport -> Assertion
563+testTerminate TestTransport{..} = do
564+ localNode <- newLocalNode testTransport initRemoteTable
565+ done <- newEmptyMVar
566+
567+ pid <- forkProcess localNode $ do
568+ liftIO $ threadDelay 100000
569+ terminate
570+
571+ runProcess localNode $ do
572+ ref <- monitor pid
573+ ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
574+ True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException
575+ liftIO $ putMVar done ()
576+
577+ takeMVar done
578+
579+testMonitorNode :: TestTransport -> Assertion
580+testMonitorNode TestTransport{..} = do
581+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
582+ done <- newEmptyMVar
583+
584+ closeLocalNode node1
585+
586+ runProcess node2 $ do
587+ ref <- monitorNode (localNodeId node1)
588+ NodeMonitorNotification ref' nid DiedDisconnect <- expect
589+ True <- return $ ref == ref' && nid == localNodeId node1
590+ liftIO $ putMVar done ()
591+
592+ takeMVar done
593+
594+testMonitorLiveNode :: TestTransport -> Assertion
595+testMonitorLiveNode TestTransport{..} = do
596+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
597+ ready <- newEmptyMVar
598+ readyr <- newEmptyMVar
599+ done <- newEmptyMVar
600+
601+ p <- forkProcess node1 $ return ()
602+ forkProcess node2 $ do
603+ ref <- monitorNode (localNodeId node1)
604+ liftIO $ putMVar ready ()
605+ liftIO $ takeMVar readyr
606+ send p ()
607+ NodeMonitorNotification ref' nid _ <- expect
608+ True <- return $ ref == ref' && nid == localNodeId node1
609+ liftIO $ putMVar done ()
610+
611+ takeMVar ready
612+ closeLocalNode node1
613+ putMVar readyr ()
614+
615+ takeMVar done
616+
617+testMonitorChannel :: TestTransport -> Assertion
618+testMonitorChannel TestTransport{..} = do
619+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
620+ gotNotification <- newEmptyMVar
621+
622+ pid <- forkProcess node1 $ do
623+ sport <- expect :: Process (SendPort ())
624+ ref <- monitorPort sport
625+ PortMonitorNotification ref' port' reason <- expect
626+ -- reason might be DiedUnknownId if the receive port is GCed before the
627+ -- monitor is established (TODO: not sure that this is reasonable)
628+ return $ ref' == ref && port' == sendPortId sport && (reason == DiedNormal || reason == DiedUnknownId)
629+ liftIO $ putMVar gotNotification ()
630+
631+ runProcess node2 $ do
632+ (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
633+ send pid sport
634+ liftIO $ threadDelay 100000
635+
636+ takeMVar gotNotification
637+
638+testRegistry :: TestTransport -> Assertion
639+testRegistry TestTransport{..} = do
640+ node <- newLocalNode testTransport initRemoteTable
641+ done <- newEmptyMVar
642+
643+ pingServer <- forkProcess node ping
644+ deadProcess <- forkProcess node (return ())
645+
646+ runProcess node $ do
647+ register "ping" pingServer
648+ Just pid <- whereis "ping"
649+ True <- return $ pingServer == pid
650+ us <- getSelfPid
651+ nsend "ping" (Pong us)
652+ Ping pid' <- expect
653+ True <- return $ pingServer == pid'
654+ Left (ProcessRegistrationException "dead" Nothing) <- try $ register "dead" deadProcess
655+ Left (ProcessRegistrationException "ping" (Just x)) <- try $ register "ping" deadProcess
656+ True <- return $ x == pingServer
657+ Left (ProcessRegistrationException "dead" Nothing) <- try $ unregister "dead"
658+ liftIO $ putMVar done ()
659+
660+ takeMVar done
661+
662+testRegistryRemoteProcess :: TestTransport -> Assertion
663+testRegistryRemoteProcess TestTransport{..} = do
664+ node1 <- newLocalNode testTransport initRemoteTable
665+ node2 <- newLocalNode testTransport initRemoteTable
666+ done <- newEmptyMVar
667+
668+ pingServer <- forkProcess node1 ping
669+
670+ runProcess node2 $ do
671+ register "ping" pingServer
672+ Just pid <- whereis "ping"
673+ True <- return $ pingServer == pid
674+ us <- getSelfPid
675+ nsend "ping" (Pong us)
676+ Ping pid' <- expect
677+ True <- return $ pingServer == pid'
678+ liftIO $ putMVar done ()
679+
680+ takeMVar done
681+
682+testRemoteRegistry :: TestTransport -> Assertion
683+testRemoteRegistry TestTransport{..} = do
684+ node1 <- newLocalNode testTransport initRemoteTable
685+ node2 <- newLocalNode testTransport initRemoteTable
686+ done <- newEmptyMVar
687+
688+ pingServer <- forkProcess node1 ping
689+ deadProcess <- forkProcess node1 (return ())
690+
691+ runProcess node2 $ do
692+ let nid1 = localNodeId node1
693+ registerRemoteAsync nid1 "ping" pingServer
694+ receiveWait [
695+ matchIf (\(RegisterReply label' _ (Just pid)) ->
696+ "ping" == label' && pid == pingServer)
697+ (\(RegisterReply _ _ _) -> return ()) ]
698+
699+ Just pid <- whereisRemote nid1 "ping"
700+ True <- return $ pingServer == pid
701+ us <- getSelfPid
702+ nsendRemote nid1 "ping" (Pong us)
703+ Ping pid' <- expect
704+ True <- return $ pingServer == pid'
705+
706+ -- test that if process was not registered Nothing is returned
707+ -- in owner field.
708+ registerRemoteAsync nid1 "dead" deadProcess
709+ receiveWait [
710+ matchIf (\(RegisterReply label' False Nothing) -> "dead" == label')
711+ (\(RegisterReply _ _ _) -> return ()) ]
712+ registerRemoteAsync nid1 "ping" deadProcess
713+ receiveWait [
714+ matchIf (\(RegisterReply label' False (Just pid)) ->
715+ "ping" == label' && pid == pingServer)
716+ (\(RegisterReply _ _ _) -> return ()) ]
717+ unregisterRemoteAsync nid1 "dead"
718+ receiveWait [
719+ matchIf (\(RegisterReply label' False Nothing) ->
720+ "dead" == label' && pid == pingServer)
721+ (\(RegisterReply _ _ _) -> return ()) ]
722+ liftIO $ putMVar done ()
723+
724+ takeMVar done
725+
726+testRemoteRegistryRemoteProcess :: TestTransport -> Assertion
727+testRemoteRegistryRemoteProcess TestTransport{..} = do
728+ node1 <- newLocalNode testTransport initRemoteTable
729+ node2 <- newLocalNode testTransport initRemoteTable
730+ done <- newEmptyMVar
731+
732+ pingServer <- forkProcess node2 ping
733+
734+ runProcess node2 $ do
735+ let nid1 = localNodeId node1
736+ registerRemoteAsync nid1 "ping" pingServer
737+ receiveWait [
738+ matchIf (\(RegisterReply label' _ _) -> "ping" == label')
739+ (\(RegisterReply _ _ _) -> return ()) ]
740+ Just pid <- whereisRemote nid1 "ping"
741+ True <- return $ pingServer == pid
742+ us <- getSelfPid
743+ nsendRemote nid1 "ping" (Pong us)
744+ Ping pid' <- expect
745+ True <- return $ pingServer == pid'
746+ liftIO $ putMVar done ()
747+
748+ takeMVar done
749+
750+testSpawnLocal :: TestTransport -> Assertion
751+testSpawnLocal TestTransport{..} = do
752+ node <- newLocalNode testTransport initRemoteTable
753+ done <- newEmptyMVar
754+
755+ runProcess node $ do
756+ us <- getSelfPid
757+
758+ pid <- spawnLocal $ do
759+ sport <- expect
760+ sendChan sport (1234 :: Int)
761+
762+ sport <- spawnChannelLocal $ \rport -> do
763+ (1234 :: Int) <- receiveChan rport
764+ send us ()
765+
766+ send pid sport
767+ () <- expect
768+ liftIO $ putMVar done ()
769+
770+ takeMVar done
771+
772+testSpawnAsyncStrictness :: TestTransport -> Assertion
773+testSpawnAsyncStrictness TestTransport{..} = do
774+ node <- newLocalNode testTransport initRemoteTable
775+ done <- newEmptyMVar
776+
777+ runProcess node $ do
778+ here <-getSelfNode
779+
780+ ev <- try $ spawnAsync here (error "boom")
781+ liftIO $ case ev of
782+ Right _ -> putMVar done (error "Exception didn't fire")
783+ Left (_::SomeException) -> putMVar done (return ())
784+
785+ join $ takeMVar done
786+
787+testReconnect :: TestTransport -> Assertion
788+testReconnect TestTransport{..} = do
789+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
790+ let nid1 = localNodeId node1
791+ processA <- newEmptyMVar
792+ [sendTestOk, registerTestOk] <- replicateM 2 newEmptyMVar
793+
794+ forkProcess node1 $ do
795+ us <- getSelfPid
796+ liftIO $ putMVar processA us
797+ msg1 <- expect
798+ msg2 <- expect
799+ True <- return $ msg1 == "message 1" && msg2 == "message 3"
800+ liftIO $ putMVar sendTestOk ()
801+
802+ forkProcess node2 $ do
803+ {-
804+ - Make sure there is no implicit reconnect on normal message sending
805+ -}
806+
807+ them <- liftIO $ readMVar processA
808+ send them "message 1" >> liftIO (threadDelay 100000)
809+
810+ -- Simulate network failure
811+ liftIO $ syncBreakConnection testBreakConnection node1 node2
812+
813+
814+ -- Should not arrive
815+ send them "message 2"
816+
817+ -- Should arrive
818+ reconnect them
819+ send them "message 3"
820+
821+ liftIO $ takeMVar sendTestOk
822+
823+ {-
824+ - Test that there *is* implicit reconnect on node controller messages
825+ -}
826+
827+ us <- getSelfPid
828+ registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous
829+ receiveWait [
830+ matchIf (\(RegisterReply label' _ _) -> "a" == label')
831+ (\(RegisterReply _ _ _) -> return ()) ]
832+
833+ Just _ <- whereisRemote nid1 "a"
834+
835+
836+ -- Simulate network failure
837+ liftIO $ syncBreakConnection testBreakConnection node1 node2
838+
839+ -- This will happen due to implicit reconnect
840+ registerRemoteAsync nid1 "b" us
841+ receiveWait [
842+ matchIf (\(RegisterReply label' _ _) -> "b" == label')
843+ (\(RegisterReply _ _ _) -> return ()) ]
844+
845+ -- Should happen
846+ registerRemoteAsync nid1 "c" us
847+ receiveWait [
848+ matchIf (\(RegisterReply label' _ _) -> "c" == label')
849+ (\(RegisterReply _ _ _) -> return ()) ]
850+
851+ -- Check
852+ Nothing <- whereisRemote nid1 "a" -- this will fail because the name is removed when the node is disconnected
853+ Just _ <- whereisRemote nid1 "b" -- this will suceed because the value is set after thereconnect
854+ Just _ <- whereisRemote nid1 "c"
855+
856+ liftIO $ putMVar registerTestOk ()
857+
858+ takeMVar registerTestOk
859+
860+-- | Tests that unreliable messages arrive sorted even when there are connection
861+-- failures.
862+testUSend :: (ProcessId -> Int -> Process ())
863+ -> TestTransport -> Int -> Assertion
864+testUSend usendPrim TestTransport{..} numMessages = do
865+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
866+ let nid1 = localNodeId node1
867+ nid2 = localNodeId node2
868+ processA <- newEmptyMVar
869+ usendTestOk <- newEmptyMVar
870+
871+ forkProcess node1 $ flip catch (\e -> liftIO $ print (e :: SomeException) ) $ do
872+ us <- getSelfPid
873+ liftIO $ putMVar processA us
874+ them <- expect
875+ send them ()
876+ _ <- monitor them
877+ let -- Collects messages from 'them' until the sender dies.
878+ -- Disconnection notifications are ignored.
879+ receiveMessages :: Process [Int]
880+ receiveMessages = receiveWait
881+ [ match $ \mn -> case mn of
882+ ProcessMonitorNotification _ _ DiedDisconnect -> do
883+ monitor them
884+ receiveMessages
885+ _ -> return []
886+ , match $ \i -> fmap (i :) receiveMessages
887+ ]
888+ msgs <- receiveMessages
889+ let -- Checks that the input list is sorted.
890+ isSorted :: [Int] -> Bool
891+ isSorted (x : xs@(y : _)) = x <= y && isSorted xs
892+ isSorted _ = True
893+ -- The list can't be null since there are no failures after sending
894+ -- the last message.
895+ True <- return $ isSorted msgs && not (null msgs)
896+ liftIO $ putMVar usendTestOk ()
897+
898+ forkProcess node2 $ do
899+ them <- liftIO $ readMVar processA
900+ getSelfPid >>= send them
901+ () <- expect
902+ forM_ [1..numMessages] $ \i -> do
903+ liftIO $ testBreakConnection (nodeAddress nid1) (nodeAddress nid2)
904+ usendPrim them i
905+ liftIO (threadDelay 30000)
906+
907+ takeMVar usendTestOk
908+
909+-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
910+-- in between
911+testMatchAny :: TestTransport -> Assertion
912+testMatchAny TestTransport{..} = do
913+ proxyAddr <- newEmptyMVar
914+ clientDone <- newEmptyMVar
915+
916+ -- Math server
917+ forkIO $ do
918+ localNode <- newLocalNode testTransport initRemoteTable
919+ mathServer <- forkProcess localNode math
920+ proxyServer <- forkProcess localNode $ forever $ do
921+ msg <- receiveWait [ matchAny return ]
922+ forward msg mathServer
923+ putMVar proxyAddr proxyServer
924+
925+ -- Client
926+ forkIO $ do
927+ localNode <- newLocalNode testTransport initRemoteTable
928+ mathServer <- readMVar proxyAddr
929+
930+ runProcess localNode $ do
931+ pid <- getSelfPid
932+ send mathServer (Add pid 1 2)
933+ 3 <- expect :: Process Double
934+ send mathServer (Divide pid 8 2)
935+ 4 <- expect :: Process Double
936+ send mathServer (Divide pid 8 0)
937+ DivByZero <- expect
938+ liftIO $ putMVar clientDone ()
939+
940+ takeMVar clientDone
941+
942+-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
943+-- in between, however we block 'Divide' requests ....
944+testMatchAnyHandle :: TestTransport -> Assertion
945+testMatchAnyHandle TestTransport{..} = do
946+ proxyAddr <- newEmptyMVar
947+ clientDone <- newEmptyMVar
948+
949+ -- Math server
950+ forkIO $ do
951+ localNode <- newLocalNode testTransport initRemoteTable
952+ mathServer <- forkProcess localNode math
953+ proxyServer <- forkProcess localNode $ forever $ do
954+ receiveWait [
955+ matchAny (maybeForward mathServer)
956+ ]
957+ putMVar proxyAddr proxyServer
958+
959+ -- Client
960+ forkIO $ do
961+ localNode <- newLocalNode testTransport initRemoteTable
962+ mathServer <- readMVar proxyAddr
963+
964+ runProcess localNode $ do
965+ pid <- getSelfPid
966+ send mathServer (Add pid 1 2)
967+ 3 <- expect :: Process Double
968+ send mathServer (Divide pid 8 2)
969+ Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
970+ liftIO $ putMVar clientDone ()
971+
972+ takeMVar clientDone
973+ where maybeForward :: ProcessId -> Message -> Process (Maybe ())
974+ maybeForward s msg =
975+ handleMessage msg (\m@(Add _ _ _) -> send s m)
976+
977+testMatchAnyNoHandle :: TestTransport -> Assertion
978+testMatchAnyNoHandle TestTransport{..} = do
979+ addr <- newEmptyMVar
980+ clientDone <- newEmptyMVar
981+ serverDone <- newEmptyMVar
982+
983+ -- Math server
984+ forkIO $ do
985+ localNode <- newLocalNode testTransport initRemoteTable
986+ server <- forkProcess localNode $ forever $ do
987+ receiveWait [
988+ matchAnyIf
989+ -- the condition has type `Add -> Bool`
990+ (\(Add _ _ _) -> True)
991+ -- the match `AbstractMessage -> Process ()` will succeed!
992+ (\m -> do
993+ -- `String -> Process ()` does *not* match the input types however
994+ r <- (handleMessage m (\(_ :: String) -> die "NONSENSE" ))
995+ case r of
996+ Nothing -> return ()
997+ Just _ -> die "NONSENSE")
998+ ]
999+ -- we *must* have removed the message from our mailbox though!!!
1000+ Nothing <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
1001+ liftIO $ putMVar serverDone ()
1002+ putMVar addr server
1003+
1004+ -- Client
1005+ forkIO $ do
1006+ localNode <- newLocalNode testTransport initRemoteTable
1007+ server <- readMVar addr
1008+
1009+ runProcess localNode $ do
1010+ pid <- getSelfPid
1011+ send server (Add pid 1 2)
1012+ -- we only care about the client having sent a message, so we're done
1013+ liftIO $ putMVar clientDone ()
1014+
1015+ takeMVar clientDone
1016+ takeMVar serverDone
1017+
1018+-- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests
1019+-- unless the text body @/= "bar"@ - this case should time out rather than
1020+-- removing the message from the process mailbox.
1021+testMatchAnyIf :: TestTransport -> Assertion
1022+testMatchAnyIf TestTransport{..} = do
1023+ echoAddr <- newEmptyMVar
1024+ clientDone <- newEmptyMVar
1025+
1026+ -- echo server
1027+ forkIO $ do
1028+ localNode <- newLocalNode testTransport initRemoteTable
1029+ echoServer <- forkProcess localNode $ forever $ do
1030+ receiveWait [
1031+ matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")
1032+ tryHandleMessage
1033+ ]
1034+ putMVar echoAddr echoServer
1035+
1036+ -- Client
1037+ forkIO $ do
1038+ localNode <- newLocalNode testTransport initRemoteTable
1039+ server <- readMVar echoAddr
1040+
1041+ runProcess localNode $ do
1042+ pid <- getSelfPid
1043+ send server (pid, "foo")
1044+ "foo" <- expect
1045+ send server (pid, "baz")
1046+ "baz" <- expect
1047+ send server (pid, "bar")
1048+ Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
1049+ liftIO $ putMVar clientDone ()
1050+
1051+ takeMVar clientDone
1052+ where tryHandleMessage :: Message -> Process (Maybe ())
1053+ tryHandleMessage msg =
1054+ handleMessage msg (\(pid :: ProcessId, (m :: String))
1055+ -> do { send pid m; return () })
1056+
1057+testMatchMessageWithUnwrap :: TestTransport -> Assertion
1058+testMatchMessageWithUnwrap TestTransport{..} = do
1059+ echoAddr <- newEmptyMVar
1060+ clientDone <- newEmptyMVar
1061+
1062+ -- echo server
1063+ forkIO $ do
1064+ localNode <- newLocalNode testTransport initRemoteTable
1065+ echoServer <- forkProcess localNode $ forever $ do
1066+ msg <- receiveWait [
1067+ matchMessage (\(m :: Message) -> do
1068+ return m)
1069+ ]
1070+ unwrapped <- unwrapMessage msg :: Process (Maybe (ProcessId, Message))
1071+ case unwrapped of
1072+ (Just (p, msg')) -> forward msg' p
1073+ Nothing -> die "unable to unwrap the message"
1074+ putMVar echoAddr echoServer
1075+
1076+ -- Client
1077+ forkIO $ do
1078+ localNode <- newLocalNode testTransport initRemoteTable
1079+ server <- readMVar echoAddr
1080+
1081+ runProcess localNode $ do
1082+ pid <- getSelfPid
1083+ send server (pid, wrapMessage ("foo" :: String))
1084+ "foo" <- expect
1085+ send server (pid, wrapMessage ("baz" :: String))
1086+ "baz" <- expect
1087+ liftIO $ putMVar clientDone ()
1088+
1089+ takeMVar clientDone
1090+
1091+-- Test 'receiveChanTimeout'
1092+testReceiveChanTimeout :: TestTransport -> Assertion
1093+testReceiveChanTimeout TestTransport{..} = do
1094+ done <- newEmptyMVar
1095+ sendPort <- newEmptyMVar
1096+
1097+ forkTry $ do
1098+ localNode <- newLocalNode testTransport initRemoteTable
1099+ runProcess localNode $ do
1100+ -- Create a typed channel
1101+ (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
1102+ liftIO $ putMVar sendPort sp
1103+
1104+ -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second
1105+ Nothing <- receiveChanTimeout 1000000 rp
1106+
1107+ -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds
1108+ Just True <- receiveChanTimeout 1000000 rp
1109+
1110+ -- Wait for a message with zero timeout: non-blocking check. No message is available, we get Nothing
1111+ Nothing <- receiveChanTimeout 0 rp
1112+
1113+ -- Again, but now there is a message available
1114+ liftIO $ threadDelay 1000000
1115+ Just False <- receiveChanTimeout 0 rp
1116+
1117+ liftIO $ putMVar done ()
1118+
1119+ forkTry $ do
1120+ localNode <- newLocalNode testTransport initRemoteTable
1121+ runProcess localNode $ do
1122+ sp <- liftIO $ readMVar sendPort
1123+
1124+ liftIO $ threadDelay 1500000
1125+ sendChan sp True
1126+
1127+ liftIO $ threadDelay 500000
1128+ sendChan sp False
1129+
1130+ takeMVar done
1131+
1132+-- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan
1133+testReceiveChanFeatures :: TestTransport -> Assertion
1134+testReceiveChanFeatures TestTransport{..} = do
1135+ done <- newEmptyMVar
1136+
1137+ forkTry $ do
1138+ localNode <- newLocalNode testTransport initRemoteTable
1139+ runProcess localNode $ do
1140+ (spInt, rpInt) <- newChan :: Process (SendPort Int, ReceivePort Int)
1141+ (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
1142+
1143+ -- Test Functor instance
1144+
1145+ sendChan spInt 2
1146+ sendChan spBool False
1147+
1148+ rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
1149+
1150+ True <- receiveChan rp1
1151+ False <- receiveChan rp1
1152+
1153+ -- Test Applicative instance
1154+
1155+ sendChan spInt 3
1156+ sendChan spInt 4
1157+
1158+ let rp2 = pure (+) <*> rpInt <*> rpInt
1159+
1160+ 7 <- receiveChan rp2
1161+
1162+ -- Test Alternative instance
1163+
1164+ sendChan spInt 3
1165+ sendChan spBool True
1166+
1167+ let rp3 = (even <$> rpInt) <|> rpBool
1168+
1169+ False <- receiveChan rp3
1170+ True <- receiveChan rp3
1171+
1172+ -- Test Monad instance
1173+
1174+ sendChan spBool True
1175+ sendChan spBool False
1176+ sendChan spInt 5
1177+
1178+ let rp4 :: ReceivePort Int
1179+ rp4 = do b <- rpBool
1180+ if b
1181+ then rpInt
1182+ else return 7
1183+
1184+ 5 <- receiveChan rp4
1185+ 7 <- receiveChan rp4
1186+
1187+ liftIO $ putMVar done ()
1188+
1189+ takeMVar done
1190+
1191+testKillLocal :: TestTransport -> Assertion
1192+testKillLocal TestTransport{..} = do
1193+ localNode <- newLocalNode testTransport initRemoteTable
1194+ done <- newEmptyMVar
1195+
1196+ pid <- forkProcess localNode $ do
1197+ liftIO $ threadDelay 1000000
1198+
1199+ runProcess localNode $ do
1200+ ref <- monitor pid
1201+ us <- getSelfPid
1202+ kill pid "TestKill"
1203+ ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
1204+ True <- return $ ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill"
1205+ liftIO $ putMVar done ()
1206+
1207+ takeMVar done
1208+
1209+testKillRemote :: TestTransport -> Assertion
1210+testKillRemote TestTransport{..} = do
1211+ node1 <- newLocalNode testTransport initRemoteTable
1212+ node2 <- newLocalNode testTransport initRemoteTable
1213+ done <- newEmptyMVar
1214+
1215+ pid <- forkProcess node1 $ do
1216+ liftIO $ threadDelay 1000000
1217+
1218+ runProcess node2 $ do
1219+ ref <- monitor pid
1220+ us <- getSelfPid
1221+ kill pid "TestKill"
1222+ ProcessMonitorNotification ref' pid' (DiedException reason) <- expect
1223+ True <- return $ ref == ref' && pid == pid' && reason == "killed-by=" ++ show us ++ ",reason=TestKill"
1224+ liftIO $ putMVar done ()
1225+
1226+ takeMVar done
1227+
1228+testCatchesExit :: TestTransport -> Assertion
1229+testCatchesExit TestTransport{..} = do
1230+ localNode <- newLocalNode testTransport initRemoteTable
1231+ done <- newEmptyMVar
1232+
1233+ _ <- forkProcess localNode $ do
1234+ (die ("foobar", 123 :: Int))
1235+ `catchesExit` [
1236+ (\_ m -> handleMessage m (\(_ :: String) -> return ()))
1237+ , (\_ m -> handleMessage m (\(_ :: Maybe Int) -> return ()))
1238+ , (\_ m -> handleMessage m (\(_ :: String, _ :: Int)
1239+ -> (liftIO $ putMVar done ()) >> return ()))
1240+ ]
1241+
1242+ takeMVar done
1243+
1244+testHandleMessageIf :: TestTransport -> Assertion
1245+testHandleMessageIf TestTransport{..} = do
1246+ localNode <- newLocalNode testTransport initRemoteTable
1247+ done <- newEmptyMVar
1248+ _ <- forkProcess localNode $ do
1249+ self <- getSelfPid
1250+ send self (5 :: Integer, 10 :: Integer)
1251+ msg <- receiveWait [ matchMessage return ]
1252+ Nothing <- handleMessageIf msg (\() -> True) (\() -> die $ "whoops")
1253+ handleMessageIf msg (\(x :: Integer, y :: Integer) -> x == 5 && y == 10)
1254+ (\input -> liftIO $ putMVar done input)
1255+ return ()
1256+
1257+ result <- takeMVar done
1258+ expectThat result $ equalTo (5, 10)
1259+
1260+testCatches :: TestTransport -> Assertion
1261+testCatches TestTransport{..} = do
1262+ localNode <- newLocalNode testTransport initRemoteTable
1263+ done <- newEmptyMVar
1264+
1265+ _ <- forkProcess localNode $ do
1266+ node <- getSelfNode
1267+ (liftIO $ throwIO (ProcessLinkException (nullProcessId node) DiedNormal))
1268+ `catches` [
1269+ Handler (\(ProcessLinkException _ _) -> liftIO $ putMVar done ())
1270+ ]
1271+
1272+ takeMVar done
1273+
1274+testMaskRestoreScope :: TestTransport -> Assertion
1275+testMaskRestoreScope TestTransport{..} = do
1276+ localNode <- newLocalNode testTransport initRemoteTable
1277+ parentPid <- newEmptyMVar :: IO (MVar ProcessId)
1278+ spawnedPid <- newEmptyMVar :: IO (MVar ProcessId)
1279+
1280+ void $ runProcess localNode $ mask $ \unmask -> do
1281+ getSelfPid >>= liftIO . putMVar parentPid
1282+ void $ spawnLocal $ unmask (getSelfPid >>= liftIO . putMVar spawnedPid)
1283+
1284+ parent <- liftIO $ takeMVar parentPid
1285+ child <- liftIO $ takeMVar spawnedPid
1286+ expectThat parent $ isNot $ equalTo child
1287+
1288+testDie :: TestTransport -> Assertion
1289+testDie TestTransport{..} = do
1290+ localNode <- newLocalNode testTransport initRemoteTable
1291+ done <- newEmptyMVar
1292+
1293+ _ <- forkProcess localNode $ do
1294+ (die ("foobar", 123 :: Int))
1295+ `catchExit` \_from reason -> do
1296+ -- TODO: should verify that 'from' has the right value
1297+ True <- return $ reason == ("foobar", 123 :: Int)
1298+ liftIO $ putMVar done ()
1299+
1300+ takeMVar done
1301+
1302+testPrettyExit :: TestTransport -> Assertion
1303+testPrettyExit TestTransport{..} = do
1304+ localNode <- newLocalNode testTransport initRemoteTable
1305+ done <- newEmptyMVar
1306+
1307+ _ <- forkProcess localNode $ do
1308+ (die "timeout")
1309+ `catch` \ex@(ProcessExitException from _) ->
1310+ let expected = "exit-from=" ++ (show from)
1311+ in do
1312+ True <- return $ (show ex) == expected
1313+ liftIO $ putMVar done ()
1314+
1315+ takeMVar done
1316+
1317+testExitLocal :: TestTransport -> Assertion
1318+testExitLocal TestTransport{..} = do
1319+ localNode <- newLocalNode testTransport initRemoteTable
1320+ supervisedDone <- newEmptyMVar
1321+ supervisorDone <- newEmptyMVar
1322+ -- XXX: we guarantee that exception handler will be set up
1323+ -- regardless if forkProcess preserve masking state or not.
1324+ handlerSetUp <- newEmptyMVar
1325+
1326+ pid <- forkProcess localNode $ do
1327+ (liftIO (putMVar handlerSetUp ()) >> expect) `catchExit` \_from reason -> do
1328+ -- TODO: should verify that 'from' has the right value
1329+ True <- return $ reason == "TestExit"
1330+ liftIO $ putMVar supervisedDone ()
1331+
1332+ runProcess localNode $ do
1333+ liftIO $ takeMVar handlerSetUp
1334+ ref <- monitor pid
1335+ exit pid "TestExit"
1336+ -- This time the client catches the exception, so it dies normally
1337+ ProcessMonitorNotification ref' pid' DiedNormal <- expect
1338+ True <- return $ ref == ref' && pid == pid'
1339+ liftIO $ putMVar supervisorDone ()
1340+
1341+ takeMVar supervisedDone
1342+ takeMVar supervisorDone
1343+
1344+testExitRemote :: TestTransport -> Assertion
1345+testExitRemote TestTransport{..} = do
1346+ node1 <- newLocalNode testTransport initRemoteTable
1347+ node2 <- newLocalNode testTransport initRemoteTable
1348+ supervisedDone <- newEmptyMVar
1349+ supervisorDone <- newEmptyMVar
1350+
1351+ pid <- forkProcess node1 $ do
1352+ (receiveWait [] :: Process ()) -- block forever
1353+ `catchExit` \_from reason -> do
1354+ -- TODO: should verify that 'from' has the right value
1355+ True <- return $ reason == "TestExit"
1356+ liftIO $ putMVar supervisedDone ()
1357+
1358+ runProcess node2 $ do
1359+ ref <- monitor pid
1360+ exit pid "TestExit"
1361+ ProcessMonitorNotification ref' pid' DiedNormal <- expect
1362+ True <- return $ ref == ref' && pid == pid'
1363+ liftIO $ putMVar supervisorDone ()
1364+
1365+ takeMVar supervisedDone
1366+ takeMVar supervisorDone
1367+
1368+testUnsafeSend :: TestTransport -> Assertion
1369+testUnsafeSend TestTransport{..} = do
1370+ serverAddr <- newEmptyMVar
1371+ clientDone <- newEmptyMVar
1372+
1373+ localNode <- newLocalNode testTransport initRemoteTable
1374+ void $ forkProcess localNode $ do
1375+ self <- getSelfPid
1376+ liftIO $ putMVar serverAddr self
1377+ clientAddr <- expect
1378+ unsafeSend clientAddr ()
1379+
1380+ void $ forkProcess localNode $ do
1381+ serverPid <- liftIO $ takeMVar serverAddr
1382+ getSelfPid >>= unsafeSend serverPid
1383+ () <- expect
1384+ liftIO $ putMVar clientDone ()
1385+
1386+ takeMVar clientDone
1387+
1388+testUnsafeNSend :: TestTransport -> Assertion
1389+testUnsafeNSend TestTransport{..} = do
1390+ clientDone <- newEmptyMVar
1391+
1392+ localNode <- newLocalNode testTransport initRemoteTable
1393+
1394+ pid <- forkProcess localNode $ do
1395+ () <- expect
1396+ liftIO $ putMVar clientDone ()
1397+
1398+ void $ runProcess localNode $ do
1399+ register "foobar" pid
1400+ unsafeNSend "foobar" ()
1401+
1402+ takeMVar clientDone
1403+
1404+testUnsafeSendChan :: TestTransport -> Assertion
1405+testUnsafeSendChan TestTransport{..} = do
1406+ serverAddr <- newEmptyMVar
1407+ clientDone <- newEmptyMVar
1408+
1409+ localNode <- newLocalNode testTransport initRemoteTable
1410+ void $ forkProcess localNode $ do
1411+ self <- getSelfPid
1412+ liftIO $ putMVar serverAddr self
1413+ sp <- expect
1414+ unsafeSendChan sp ()
1415+
1416+ void $ forkProcess localNode $ do
1417+ serverPid <- liftIO $ takeMVar serverAddr
1418+ (sp, rp) <- newChan
1419+ unsafeSend serverPid sp
1420+ () <- receiveChan rp
1421+ liftIO $ putMVar clientDone ()
1422+
1423+ takeMVar clientDone
1424+
1425+testCallLocal :: TestTransport -> Assertion
1426+testCallLocal TestTransport{..} = do
1427+ node <- newLocalNode testTransport initRemoteTable
1428+
1429+ -- Testing that (/=) <$> getSelfPid <*> callLocal getSelfPid.
1430+ result <- newEmptyMVar
1431+ runProcess node $ do
1432+ r <- (/=) <$> getSelfPid <*> callLocal getSelfPid
1433+ liftIO $ putMVar result r
1434+ True <- takeMVar result
1435+ return ()
1436+
1437+ -- Testing that when callLocal is interrupted, the worker is interrupted.
1438+ ibox <- newIORef False
1439+ runProcess node $ do
1440+ keeper <- getSelfPid
1441+ spawnLocal $ do
1442+ caller <- getSelfPid
1443+ send keeper caller
1444+ onException
1445+ (callLocal $ do
1446+ onException (do send keeper caller
1447+ expect)
1448+ (do liftIO $ writeIORef ibox True))
1449+ (send keeper ())
1450+ caller <- expect
1451+ exit caller "test"
1452+ () <- expect
1453+ return ()
1454+ True <- readIORef ibox
1455+ return ()
1456+
1457+ -- Testing that when the worker raises an exception, the exception is propagated to the parent.
1458+ ibox2 <- newIORef False
1459+ runProcess node $ do
1460+ r <- try (callLocal $ error "e" >> return ())
1461+ liftIO $ writeIORef ibox2 $ case r of
1462+ Left (ErrorCall "e") -> True
1463+ _ -> False
1464+ True <- readIORef ibox
1465+ return ()
1466+
1467+ -- Test that caller waits for the worker in correct situation
1468+ ibox3 <- newIORef False
1469+ result3 <- newEmptyMVar
1470+ runProcess node $ do
1471+ keeper <- getSelfPid
1472+ spawnLocal $ do
1473+ callLocal $
1474+ (do us <- getSelfPid
1475+ send keeper us
1476+ () <- expect
1477+ liftIO yield)
1478+ `finally` (liftIO $ writeIORef ibox3 True)
1479+ liftIO $ putMVar result3 =<< readIORef ibox3
1480+ worker <- expect
1481+ send worker ()
1482+ True <- takeMVar result3
1483+ return ()
1484+
1485+ -- Test that caller waits for the worker in case when caller gets an exception
1486+ ibox4 <- newIORef False
1487+ result4 <- newEmptyMVar
1488+ runProcess node $ do
1489+ keeper <- getSelfPid
1490+ spawnLocal $ do
1491+ caller <- getSelfPid
1492+ callLocal
1493+ ((do send keeper caller
1494+ expect)
1495+ `finally` (liftIO $ writeIORef ibox4 True))
1496+ `finally` (liftIO $ putMVar result4 =<< readIORef ibox4)
1497+ caller <- expect
1498+ exit caller "hi!"
1499+ True <- takeMVar result4
1500+ return ()
1501+ -- XXX: Testing that when mask_ $ callLocal p runs p in masked state.
1502+
1503+
1504+
1505+
1506+
1507+tests :: TestTransport -> IO [Test]
1508+tests testtrans = return [
1509+ testGroup "Basic features" [
1510+ testCase "Ping" (testPing testtrans)
1511+ , testCase "Math" (testMath testtrans)
1512+ , testCase "Timeout" (testTimeout testtrans)
1513+ , testCase "Timeout0" (testTimeout0 testtrans)
1514+ , testCase "SendToTerminated" (testSendToTerminated testtrans)
1515+ , testCase "TypedChannnels" (testTypedChannels testtrans)
1516+ , testCase "Terminate" (testTerminate testtrans)
1517+ , testCase "RegistryRemoteProcess" (testRegistryRemoteProcess testtrans)
1518+ , testCase "RemoteRegistry" (testRemoteRegistry testtrans)
1519+ , testCase "RemoteRegistryRemoteProcess" (testRemoteRegistryRemoteProcess testtrans)
1520+ , testCase "SpawnLocal" (testSpawnLocal testtrans)
1521+ , testCase "SpawnAsyncStrictness" (testSpawnAsyncStrictness testtrans)
1522+ , testCase "HandleMessageIf" (testHandleMessageIf testtrans)
1523+ , testCase "MatchAny" (testMatchAny testtrans)
1524+ , testCase "MatchAnyHandle" (testMatchAnyHandle testtrans)
1525+ , testCase "MatchAnyNoHandle" (testMatchAnyNoHandle testtrans)
1526+ , testCase "MatchAnyIf" (testMatchAnyIf testtrans)
1527+ , testCase "MatchMessageUnwrap" (testMatchMessageWithUnwrap testtrans)
1528+ , testCase "ReceiveChanTimeout" (testReceiveChanTimeout testtrans)
1529+ , testCase "ReceiveChanFeatures" (testReceiveChanFeatures testtrans)
1530+ , testCase "KillLocal" (testKillLocal testtrans)
1531+ , testCase "KillRemote" (testKillRemote testtrans)
1532+ , testCase "Die" (testDie testtrans)
1533+ , testCase "PrettyExit" (testPrettyExit testtrans)
1534+ , testCase "CatchesExit" (testCatchesExit testtrans)
1535+ , testCase "Catches" (testCatches testtrans)
1536+ , testCase "MaskRestoreScope" (testMaskRestoreScope testtrans)
1537+ , testCase "ExitLocal" (testExitLocal testtrans)
1538+ , testCase "ExitRemote" (testExitRemote testtrans)
1539+ , testCase "TextCallLocal" (testCallLocal testtrans)
1540+ -- Unsafe Primitives
1541+ , testCase "TestUnsafeSend" (testUnsafeSend testtrans)
1542+ , testCase "TestUnsafeNSend" (testUnsafeNSend testtrans)
1543+ , testCase "TestUnsafeSendChan" (testUnsafeSendChan testtrans)
1544+ -- usend
1545+ , testCase "USend" (testUSend usend testtrans 50)
1546+ , testCase "UForward"
1547+ (testUSend (\p m -> uforward (createUnencodedMessage m) p)
1548+ testtrans 50
1549+ )
1550+ ]
1551+ , testGroup "Monitoring and Linking" [
1552+ -- Monitoring processes
1553+ --
1554+ -- The "missing" combinations in the list below don't make much sense, as
1555+ -- we cannot guarantee that the monitor reply or link exception will not
1556+ -- happen before the unmonitor or unlink
1557+ testCase "MonitorNormalTermination" (testMonitorNormalTermination testtrans True False)
1558+ , testCase "MonitorAbnormalTermination" (testMonitorAbnormalTermination testtrans True False)
1559+ , testCase "MonitorLocalDeadProcess" (testMonitorLocalDeadProcess testtrans True False)
1560+ , testCase "MonitorRemoteDeadProcess" (testMonitorRemoteDeadProcess testtrans True False)
1561+ , testCase "MonitorDisconnect" (testMonitorDisconnect testtrans True False)
1562+ , testCase "LinkUnreachable" (testMonitorUnreachable testtrans False False)
1563+ , testCase "LinkNormalTermination" (testMonitorNormalTermination testtrans False False)
1564+ , testCase "LinkAbnormalTermination" (testMonitorAbnormalTermination testtrans False False)
1565+ , testCase "LinkLocalDeadProcess" (testMonitorLocalDeadProcess testtrans False False)
1566+ , testCase "LinkRemoteDeadProcess" (testMonitorRemoteDeadProcess testtrans False False)
1567+ , testCase "LinkDisconnect" (testMonitorDisconnect testtrans False False)
1568+ , testCase "UnmonitorNormalTermination" (testMonitorNormalTermination testtrans True True)
1569+ , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination testtrans True True)
1570+ , testCase "UnmonitorDisconnect" (testMonitorDisconnect testtrans True True)
1571+ , testCase "UnlinkNormalTermination" (testMonitorNormalTermination testtrans False True)
1572+ , testCase "UnlinkAbnormalTermination" (testMonitorAbnormalTermination testtrans False True)
1573+ , testCase "UnlinkDisconnect" (testMonitorDisconnect testtrans False True)
1574+ -- Monitoring nodes and channels
1575+ , testCase "MonitorNode" (testMonitorNode testtrans)
1576+ , testCase "MonitorLiveNode" (testMonitorLiveNode testtrans)
1577+ , testCase "MonitorChannel" (testMonitorChannel testtrans)
1578+ -- Reconnect
1579+ ]
1580+
1581+ -- Tests that fail occasionally and should be revised
1582+ , testGroup "Flaky" [
1583+ testCase "Reconnect" (testReconnect testtrans)
1584+ , testCase "Registry" (testRegistry testtrans)
1585+ , testCase "MergeChannels" (testMergeChannels testtrans)
1586+ , testCase "MonitorUnreachable" (testMonitorUnreachable testtrans True False)
1587+ ]
1588+ ]
1589+
1590+syncBreakConnection :: (NT.EndPointAddress -> NT.EndPointAddress -> IO ()) -> LocalNode -> LocalNode -> IO ()
1591+syncBreakConnection breakConnection nid0 nid1 = do
1592+ m <- newEmptyMVar
1593+ _ <- forkProcess nid1 $ getSelfPid >>= liftIO . putMVar m
1594+ runProcess nid0 $ do
1595+ them <- liftIO $ takeMVar m
1596+ pinger <- spawnLocal $ forever $ send them ()
1597+ _ <- monitorNode (localNodeId nid1)
1598+ liftIO $ breakConnection (nodeAddress $ localNodeId nid0)
1599+ (nodeAddress $ localNodeId nid1)
1600+ NodeMonitorNotification _ _ _ <- expect
1601+ kill pinger "finished"
1602+ return ()
distributed-process-tests/src/Control/Distributed/Process/Tests/Closure.hsnew file+548
@@ -0,0 +1,548 @@
1+{-# LANGUAGE TemplateHaskell, KindSignatures #-}
2+module Control.Distributed.Process.Tests.Closure (tests) where
3+
4+import Network.Transport.Test (TestTransport(..))
5+
6+import Data.ByteString.Lazy (empty)
7+import Data.IORef
8+import Data.Typeable (Typeable)
9+import Data.Maybe
10+import Control.Monad (join, replicateM, forever, replicateM_, void, when, unless)
11+import Control.Exception (IOException, throw)
12+import Control.Concurrent (forkIO, threadDelay)
13+import Control.Concurrent.MVar
14+ ( MVar
15+ , newEmptyMVar
16+ , readMVar
17+ , takeMVar
18+ , putMVar
19+ , modifyMVar_
20+ , newMVar
21+ )
22+import Control.Applicative ((<$>))
23+import System.Random (randomIO)
24+import Control.Distributed.Process
25+import Control.Distributed.Process.Closure
26+import Control.Distributed.Process.Node
27+import Control.Distributed.Process.Internal.Types
28+ ( NodeId(nodeAddress)
29+ , createMessage
30+ , messageToPayload
31+ )
32+import Control.Distributed.Static (staticLabel, staticClosure)
33+import qualified Network.Transport as NT
34+
35+import Test.HUnit (Assertion)
36+import Test.Framework (Test)
37+import Test.Framework.Providers.HUnit (testCase)
38+
39+--------------------------------------------------------------------------------
40+-- Supporting definitions --
41+--------------------------------------------------------------------------------
42+
43+quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
44+quintuple a b c d e = (a, b, c, d, e)
45+
46+sdictInt :: SerializableDict Int
47+sdictInt = SerializableDict
48+
49+factorial :: Int -> Process Int
50+factorial 0 = return 1
51+factorial n = (n *) <$> factorial (n - 1)
52+
53+addInt :: Int -> Int -> Int
54+addInt x y = x + y
55+
56+putInt :: Int -> MVar Int -> IO ()
57+putInt = flip putMVar
58+
59+sendPid :: ProcessId -> Process ()
60+sendPid toPid = do
61+ fromPid <- getSelfPid
62+ send toPid fromPid
63+
64+wait :: Int -> Process ()
65+wait = liftIO . threadDelay
66+
67+expectUnit :: Process ()
68+expectUnit = expect
69+
70+isPrime :: Integer -> Process Bool
71+isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
72+ where
73+ sieve :: [Integer] -> [Integer]
74+ sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
75+ sieve [] = error "Uh oh -- we've run out of primes"
76+
77+-- | First argument indicates empty closure environment
78+typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
79+typedPingServer () rport = forever $ do
80+ sport <- receiveChan rport
81+ sendChan sport ()
82+
83+signal :: ProcessId -> Process ()
84+signal pid = send pid ()
85+
86+remotable [ 'factorial
87+ , 'addInt
88+ , 'putInt
89+ , 'sendPid
90+ , 'sdictInt
91+ , 'wait
92+ , 'expectUnit
93+ , 'typedPingServer
94+ , 'isPrime
95+ , 'quintuple
96+ , 'signal
97+ ]
98+
99+randomElement :: [a] -> IO a
100+randomElement xs = do
101+ ix <- randomIO
102+ return (xs !! (ix `mod` length xs))
103+
104+remotableDecl [
105+ [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
106+ dfib (_, reply, 0) = sendChan reply 0
107+ dfib (_, reply, 1) = sendChan reply 1
108+ dfib (nids, reply, n) = do
109+ nid1 <- liftIO $ randomElement nids
110+ nid2 <- liftIO $ randomElement nids
111+ (sport, rport) <- newChan
112+ spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
113+ spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
114+ n1 <- receiveChan rport
115+ n2 <- receiveChan rport
116+ sendChan reply $ n1 + n2
117+ |]
118+ ]
119+
120+-- Just try creating a static polymorphic value
121+staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
122+ => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))
123+staticQuintuple = $(mkStatic 'quintuple)
124+
125+factorialClosure :: Int -> Closure (Process Int)
126+factorialClosure = $(mkClosure 'factorial)
127+
128+addIntClosure :: Int -> Closure (Int -> Int)
129+addIntClosure = $(mkClosure 'addInt)
130+
131+putIntClosure :: Int -> Closure (MVar Int -> IO ())
132+putIntClosure = $(mkClosure 'putInt)
133+
134+sendPidClosure :: ProcessId -> Closure (Process ())
135+sendPidClosure = $(mkClosure 'sendPid)
136+
137+sendFac :: Int -> ProcessId -> Closure (Process ())
138+sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid
139+
140+factorialOf :: Closure (Int -> Process Int)
141+factorialOf = staticClosure $(mkStatic 'factorial)
142+
143+factorial' :: Int -> Closure (Process Int)
144+factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
145+
146+waitClosure :: Int -> Closure (Process ())
147+waitClosure = $(mkClosure 'wait)
148+
149+simulateNetworkFailure :: TestTransport -> LocalNode -> LocalNode -> Process ()
150+simulateNetworkFailure TestTransport{..} from to = liftIO $ do
151+ m <- newEmptyMVar
152+ _ <- forkProcess to $ getSelfPid >>= liftIO . putMVar m
153+ runProcess from $ do
154+ them <- liftIO $ takeMVar m
155+ pinger <- spawnLocal $ forever $ send them ()
156+ _ <- monitorNode (localNodeId to)
157+ liftIO $ testBreakConnection (nodeAddress $ localNodeId from)
158+ (nodeAddress $ localNodeId to)
159+ NodeMonitorNotification _ _ _ <- expect
160+ kill pinger "finished"
161+ return ()
162+
163+--------------------------------------------------------------------------------
164+-- The tests proper --
165+--------------------------------------------------------------------------------
166+
167+testUnclosure :: TestTransport -> RemoteTable -> Assertion
168+testUnclosure TestTransport{..} rtable = do
169+ node <- newLocalNode testTransport rtable
170+ done <- newEmptyMVar
171+ forkProcess node $ do
172+ 120 <- join . unClosure $ factorialClosure 5
173+ liftIO $ putMVar done ()
174+ takeMVar done
175+
176+testBind :: TestTransport -> RemoteTable -> Assertion
177+testBind TestTransport{..} rtable = do
178+ node <- newLocalNode testTransport rtable
179+ done <- newEmptyMVar
180+ runProcess node $ do
181+ us <- getSelfPid
182+ join . unClosure $ sendFac 6 us
183+ (720 :: Int) <- expect
184+ liftIO $ putMVar done ()
185+ takeMVar done
186+
187+testSendPureClosure :: TestTransport -> RemoteTable -> Assertion
188+testSendPureClosure TestTransport{..} rtable = do
189+ serverAddr <- newEmptyMVar
190+ serverDone <- newEmptyMVar
191+
192+ forkIO $ do
193+ node <- newLocalNode testTransport rtable
194+ addr <- forkProcess node $ do
195+ cl <- expect
196+ fn <- unClosure cl :: Process (Int -> Int)
197+ 13 <- return $ fn 6
198+ liftIO $ putMVar serverDone ()
199+ putMVar serverAddr addr
200+
201+ forkIO $ do
202+ node <- newLocalNode testTransport rtable
203+ theirAddr <- readMVar serverAddr
204+ runProcess node $ send theirAddr (addIntClosure 7)
205+
206+ takeMVar serverDone
207+
208+testSendIOClosure :: TestTransport -> RemoteTable -> Assertion
209+testSendIOClosure TestTransport{..} rtable = do
210+ serverAddr <- newEmptyMVar
211+ serverDone <- newEmptyMVar
212+
213+ forkIO $ do
214+ node <- newLocalNode testTransport rtable
215+ addr <- forkProcess node $ do
216+ cl <- expect
217+ io <- unClosure cl :: Process (MVar Int -> IO ())
218+ liftIO $ do
219+ someMVar <- newEmptyMVar
220+ io someMVar
221+ 5 <- readMVar someMVar
222+ putMVar serverDone ()
223+ putMVar serverAddr addr
224+
225+ forkIO $ do
226+ node <- newLocalNode testTransport rtable
227+ theirAddr <- readMVar serverAddr
228+ runProcess node $ send theirAddr (putIntClosure 5)
229+
230+ takeMVar serverDone
231+
232+testSendProcClosure :: TestTransport -> RemoteTable -> Assertion
233+testSendProcClosure TestTransport{..} rtable = do
234+ serverAddr <- newEmptyMVar
235+ clientDone <- newEmptyMVar
236+
237+ forkIO $ do
238+ node <- newLocalNode testTransport rtable
239+ addr <- forkProcess node $ do
240+ cl <- expect
241+ pr <- unClosure cl :: Process (Int -> Process ())
242+ pr 5
243+ putMVar serverAddr addr
244+
245+ forkIO $ do
246+ node <- newLocalNode testTransport rtable
247+ theirAddr <- readMVar serverAddr
248+ runProcess node $ do
249+ pid <- getSelfPid
250+ send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
251+ 5 <- expect :: Process Int
252+ liftIO $ putMVar clientDone ()
253+
254+ takeMVar clientDone
255+
256+testSpawn :: TestTransport -> RemoteTable -> Assertion
257+testSpawn TestTransport{..} rtable = do
258+ serverNodeAddr <- newEmptyMVar
259+ clientDone <- newEmptyMVar
260+
261+ forkIO $ do
262+ node <- newLocalNode testTransport rtable
263+ putMVar serverNodeAddr (localNodeId node)
264+
265+ forkIO $ do
266+ node <- newLocalNode testTransport rtable
267+ nid <- readMVar serverNodeAddr
268+ runProcess node $ do
269+ pid <- getSelfPid
270+ pid' <- spawn nid (sendPidClosure pid)
271+ pid'' <- expect
272+ True <- return $ pid' == pid''
273+ liftIO $ putMVar clientDone ()
274+
275+ takeMVar clientDone
276+
277+-- | Tests that spawn executes the supplied closure even if the caller dies
278+-- immediately after calling spawn.
279+--
280+-- This situation is of interest because the implementation of spawn has the
281+-- remote peer monitor the caller. See DP-99.
282+--
283+-- The condition is tested by using a transport which refuses to send to the
284+-- remote peer the message that it is waiting to stop monitoring the caller,
285+-- namely @()@.
286+--
287+testSpawnRace :: TestTransport -> RemoteTable -> Assertion
288+testSpawnRace TestTransport{..} rtable = do
289+ node1 <- newLocalNode (wrapTransport testTransport) rtable
290+ node2 <- newLocalNode testTransport rtable
291+
292+ runProcess node1 $ do
293+ pid <- getSelfPid
294+ spawnLocal $ spawn (localNodeId node2) (sendPidClosure pid) >>= send pid
295+ pid' <- expect :: Process ProcessId
296+ pid'' <- expect :: Process ProcessId
297+ True <- return $ pid' == pid''
298+ return ()
299+
300+ where
301+
302+ wrapTransport (NT.Transport ne ct) = NT.Transport (fmap (fmap wrapEP) ne) ct
303+
304+ wrapEP :: NT.EndPoint -> NT.EndPoint
305+ wrapEP e =
306+ e { NT.connect = \x y z -> do
307+ healthy <- newIORef True
308+ fmap (fmap $ wrapConnection healthy) $ NT.connect e x y z
309+ }
310+
311+ wrapConnection :: IORef Bool -> NT.Connection -> NT.Connection
312+ wrapConnection healthy (NT.Connection s closeC) =
313+ flip NT.Connection closeC $ \msg -> do
314+ when (msg == messageToPayload (createMessage ())) $ do
315+ writeIORef healthy False
316+ isHealthy <- readIORef healthy
317+ if isHealthy then s msg
318+ else return $ Left $ NT.TransportError NT.SendFailed ""
319+
320+testCall :: TestTransport -> RemoteTable -> Assertion
321+testCall TestTransport{..} rtable = do
322+ serverNodeAddr <- newEmptyMVar
323+ clientDone <- newEmptyMVar
324+
325+ forkIO $ do
326+ node <- newLocalNode testTransport rtable
327+ putMVar serverNodeAddr (localNodeId node)
328+
329+ forkIO $ do
330+ node <- newLocalNode testTransport rtable
331+ nid <- readMVar serverNodeAddr
332+ runProcess node $ do
333+ (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
334+ liftIO $ putMVar clientDone ()
335+
336+ takeMVar clientDone
337+
338+testCallBind :: TestTransport -> RemoteTable -> Assertion
339+testCallBind TestTransport{..} rtable = do
340+ serverNodeAddr <- newEmptyMVar
341+ clientDone <- newEmptyMVar
342+
343+ forkIO $ do
344+ node <- newLocalNode testTransport rtable
345+ putMVar serverNodeAddr (localNodeId node)
346+
347+ forkIO $ do
348+ node <- newLocalNode testTransport rtable
349+ nid <- readMVar serverNodeAddr
350+ runProcess node $ do
351+ (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
352+ liftIO $ putMVar clientDone ()
353+
354+ takeMVar clientDone
355+
356+testSeq :: TestTransport -> RemoteTable -> Assertion
357+testSeq TestTransport{..} rtable = do
358+ node <- newLocalNode testTransport rtable
359+ done <- newEmptyMVar
360+ runProcess node $ do
361+ us <- getSelfPid
362+ join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
363+ 120 :: Int <- expect
364+ 720 :: Int <- expect
365+ liftIO $ putMVar done ()
366+ takeMVar done
367+
368+-- Test 'spawnSupervised'
369+--
370+-- Set up a supervisor, spawn a child, then have a third process monitor the
371+-- child. The supervisor then throws an exception, the child dies because it
372+-- was linked to the supervisor, and the third process notices that the child
373+-- dies.
374+testSpawnSupervised :: TestTransport -> RemoteTable -> Assertion
375+testSpawnSupervised TestTransport{..} rtable = do
376+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
377+ [superPid, childPid] <- replicateM 2 $ newEmptyMVar
378+ thirdProcessDone <- newEmptyMVar
379+ linkUp <- newEmptyMVar
380+
381+ forkProcess node1 $ do
382+ us <- getSelfPid
383+ liftIO $ putMVar superPid us
384+ (child, _ref) <- spawnSupervised (localNodeId node2)
385+ (sendPidClosure us `seqCP` $(mkStaticClosure 'expectUnit))
386+ _ <- expect :: Process ProcessId
387+
388+ liftIO $ do putMVar childPid child
389+ -- Give the child a chance to link to us
390+ takeMVar linkUp
391+ throw supervisorDeath
392+
393+ forkProcess node2 $ do
394+ [super, child] <- liftIO $ mapM readMVar [superPid, childPid]
395+ ref <- monitor child
396+ self <- getSelfPid
397+ let waitForMOrL = do
398+ liftIO $ threadDelay 10000
399+ mpinfo <- getProcessInfo child
400+ case mpinfo of
401+ Nothing -> waitForMOrL
402+ Just pinfo ->
403+ unless (isJust $ lookup self (infoMonitors pinfo)) waitForMOrL
404+ waitForMOrL
405+ liftIO $ putMVar linkUp ()
406+ -- because monitor message was sent before message to process
407+ -- we hope that it will be processed before
408+ ProcessMonitorNotification ref' pid' (DiedException e) <- expect
409+ True <- return $ ref' == ref
410+ && pid' == child
411+ && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))
412+ liftIO $ putMVar thirdProcessDone ()
413+
414+ takeMVar thirdProcessDone
415+ where
416+ supervisorDeath :: IOException
417+ supervisorDeath = userError "Supervisor died"
418+
419+testSpawnInvalid :: TestTransport -> RemoteTable -> Assertion
420+testSpawnInvalid TestTransport{..} rtable = do
421+ node <- newLocalNode testTransport rtable
422+ done <- newEmptyMVar
423+ forkProcess node $ do
424+ (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
425+ ProcessMonitorNotification ref' pid' _reason <- expect
426+ -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
427+ True <- return $ ref' == ref && pid == pid'
428+ liftIO $ putMVar done ()
429+ takeMVar done
430+
431+testClosureExpect :: TestTransport -> RemoteTable -> Assertion
432+testClosureExpect TestTransport{..} rtable = do
433+ node <- newLocalNode testTransport rtable
434+ done <- newEmptyMVar
435+ runProcess node $ do
436+ nodeId <- getSelfNode
437+ us <- getSelfPid
438+ them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
439+ send them (1234 :: Int)
440+ (1234 :: Int) <- expect
441+ liftIO $ putMVar done ()
442+ takeMVar done
443+
444+testSpawnChannel :: TestTransport -> RemoteTable -> Assertion
445+testSpawnChannel TestTransport{..} rtable = do
446+ done <- newEmptyMVar
447+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
448+
449+ forkProcess node1 $ do
450+ pingServer <- spawnChannel
451+ (sdictSendPort sdictUnit)
452+ (localNodeId node2)
453+ ($(mkClosure 'typedPingServer) ())
454+ (sendReply, receiveReply) <- newChan
455+ sendChan pingServer sendReply
456+ receiveChan receiveReply
457+ liftIO $ putMVar done ()
458+
459+ takeMVar done
460+
461+testTDict :: TestTransport -> RemoteTable -> Assertion
462+testTDict TestTransport{..} rtable = do
463+ done <- newEmptyMVar
464+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
465+ forkProcess node1 $ do
466+ True <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
467+ liftIO $ putMVar done ()
468+ takeMVar done
469+
470+testFib :: TestTransport -> RemoteTable -> Assertion
471+testFib TestTransport{..} rtable = do
472+ nodes <- replicateM 4 $ newLocalNode testTransport rtable
473+ done <- newEmptyMVar
474+
475+ forkProcess (head nodes) $ do
476+ (sport, rport) <- newChan
477+ spawnLocal $ dfib (map localNodeId nodes, sport, 10)
478+ 55 <- receiveChan rport :: Process Integer
479+ liftIO $ putMVar done ()
480+
481+ takeMVar done
482+
483+testSpawnReconnect :: TestTransport -> RemoteTable -> Assertion
484+testSpawnReconnect testtrans@TestTransport{..} rtable = do
485+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
486+ let nid1 = localNodeId node1
487+ nid2 = localNodeId node2
488+ done <- newEmptyMVar
489+ iv <- newMVar (0 :: Int)
490+
491+ incr <- forkProcess node1 $ forever $ do
492+ () <- expect
493+ liftIO $ modifyMVar_ iv (return . (+ 1))
494+
495+ forkProcess node2 $ do
496+ _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
497+ simulateNetworkFailure testtrans node2 node1
498+ _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
499+ _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
500+
501+ liftIO $ threadDelay 100000
502+
503+ count <- liftIO $ takeMVar iv
504+ True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
505+
506+ liftIO $ putMVar done ()
507+
508+ takeMVar done
509+
510+-- | 'spawn' used to ave a race condition which would be triggered if the
511+-- spawning process terminates immediately after spawning
512+testSpawnTerminate :: TestTransport -> RemoteTable -> Assertion
513+testSpawnTerminate TestTransport{..} rtable = do
514+ slave <- newLocalNode testTransport rtable
515+ master <- newLocalNode testTransport rtable
516+ masterDone <- newEmptyMVar
517+
518+ runProcess master $ do
519+ us <- getSelfPid
520+ replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
521+ replicateM_ 1000 $ (expect :: Process ())
522+ liftIO $ putMVar masterDone ()
523+
524+ takeMVar masterDone
525+
526+tests :: TestTransport -> IO [Test]
527+tests testtrans = do
528+ let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
529+ return
530+ [ testCase "Unclosure" (testUnclosure testtrans rtable)
531+ , testCase "Bind" (testBind testtrans rtable)
532+ , testCase "SendPureClosure" (testSendPureClosure testtrans rtable)
533+ , testCase "SendIOClosure" (testSendIOClosure testtrans rtable)
534+ , testCase "SendProcClosure" (testSendProcClosure testtrans rtable)
535+ , testCase "Spawn" (testSpawn testtrans rtable)
536+ , testCase "SpawnRace" (testSpawnRace testtrans rtable)
537+ , testCase "Call" (testCall testtrans rtable)
538+ , testCase "CallBind" (testCallBind testtrans rtable)
539+ , testCase "Seq" (testSeq testtrans rtable)
540+ , testCase "SpawnSupervised" (testSpawnSupervised testtrans rtable)
541+ , testCase "SpawnInvalid" (testSpawnInvalid testtrans rtable)
542+ , testCase "ClosureExpect" (testClosureExpect testtrans rtable)
543+ , testCase "SpawnChannel" (testSpawnChannel testtrans rtable)
544+ , testCase "TDict" (testTDict testtrans rtable)
545+ , testCase "Fib" (testFib testtrans rtable)
546+ , testCase "SpawnTerminate" (testSpawnTerminate testtrans rtable)
547+ , testCase "SpawnReconnect" (testSpawnReconnect testtrans rtable)
548+ ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Internal/Utils.hsnew file+232
@@ -0,0 +1,232 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE TemplateHaskell #-}
4+{-# LANGUAGE CPP #-}
5+
6+-----------------------------------------------------------------------------
7+-- |
8+-- Module : Control.Distributed.Process.Tests.Internal.Utils
9+-- Copyright : (c) Tim Watson, Jeff Epstein 2013
10+-- License : BSD3 (see the file LICENSE)
11+--
12+-- Maintainer : Tim Watson
13+-- Stability : experimental
14+-- Portability : non-portable (requires concurrency)
15+--
16+-- This module provides basic building blocks for testing Cloud Haskell programs.
17+-----------------------------------------------------------------------------
18+module Control.Distributed.Process.Tests.Internal.Utils
19+ ( TestResult
20+ -- ping !
21+ , Ping(Ping)
22+ , ping
23+ , shouldBe
24+ , shouldMatch
25+ , shouldContain
26+ , shouldNotContain
27+ , expectThat
28+ , synchronisedAssertion
29+ -- test process utilities
30+ , TestProcessControl
31+ , startTestProcess
32+ , runTestProcess
33+ , testProcessGo
34+ , testProcessStop
35+ , testProcessReport
36+ , delayedAssertion
37+ , assertComplete
38+ -- logging
39+ , Logger()
40+ , newLogger
41+ , putLogMsg
42+ , stopLogger
43+ -- runners
44+ , tryRunProcess
45+ , tryForkProcess
46+ , noop
47+ , stash
48+ ) where
49+
50+#if ! MIN_VERSION_base(4,6,0)
51+import Prelude hiding (catch)
52+#endif
53+import Control.Concurrent
54+ ( ThreadId
55+ , myThreadId
56+ , forkIO
57+ )
58+import Control.Concurrent.STM
59+ ( TQueue
60+ , newTQueueIO
61+ , readTQueue
62+ , writeTQueue
63+ )
64+import Control.Concurrent.MVar
65+ ( MVar
66+ , newEmptyMVar
67+ , takeMVar
68+ )
69+
70+import Control.Concurrent
71+ ( throwTo
72+ )
73+import Control.Concurrent.MVar
74+ ( putMVar
75+ )
76+import Control.Distributed.Process
77+import Control.Distributed.Process.Node
78+import Control.Distributed.Process.Serializable()
79+
80+import Control.Exception (AsyncException(ThreadKilled), SomeException)
81+import Control.Monad (forever)
82+import Control.Monad.STM (atomically)
83+import Control.Rematch hiding (match)
84+import Control.Rematch.Run
85+import Data.Binary
86+import Data.Typeable (Typeable)
87+
88+import Test.HUnit (Assertion, assertFailure)
89+import Test.HUnit.Base (assertBool)
90+
91+import GHC.Generics
92+
93+-- | A mutable cell containing a test result.
94+type TestResult a = MVar a
95+
96+-- | A simple @Ping@ signal
97+data Ping = Ping
98+ deriving (Typeable, Generic, Eq, Show)
99+instance Binary Ping where
100+
101+ping :: ProcessId -> Process ()
102+ping pid = send pid Ping
103+
104+-- | Control signals used to manage /test processes/
105+data TestProcessControl = Stop | Go | Report ProcessId
106+ deriving (Typeable, Generic)
107+
108+instance Binary TestProcessControl where
109+
110+-- | Does exactly what it says on the tin, doing so in the @Process@ monad.
111+noop :: Process ()
112+noop = return ()
113+
114+synchronisedAssertion :: Eq a
115+ => String
116+ -> LocalNode
117+ -> a
118+ -> (TestResult a -> Process ())
119+ -> MVar ()
120+ -> Assertion
121+synchronisedAssertion note localNode expected testProc lock = do
122+ result <- newEmptyMVar
123+ _ <- forkProcess localNode $ do
124+ acquire lock
125+ finally (testProc result)
126+ (release lock)
127+ assertComplete note result expected
128+ where acquire lock' = liftIO $ takeMVar lock'
129+ release lock' = liftIO $ putMVar lock' ()
130+
131+stash :: TestResult a -> a -> Process ()
132+stash mvar x = liftIO $ putMVar mvar x
133+
134+expectThat :: a -> Matcher a -> Process ()
135+expectThat a matcher = case res of
136+ MatchSuccess -> return ()
137+ (MatchFailure msg) -> liftIO $ assertFailure msg
138+ where res = runMatch matcher a
139+
140+shouldBe :: a -> Matcher a -> Process ()
141+shouldBe = expectThat
142+
143+shouldContain :: (Show a, Eq a) => [a] -> a -> Process ()
144+shouldContain xs x = expectThat xs $ hasItem (equalTo x)
145+
146+shouldNotContain :: (Show a, Eq a) => [a] -> a -> Process ()
147+shouldNotContain xs x = expectThat xs $ isNot (hasItem (equalTo x))
148+
149+shouldMatch :: a -> Matcher a -> Process ()
150+shouldMatch = expectThat
151+
152+-- | Run the supplied @testProc@ using an @MVar@ to collect and assert
153+-- against its result. Uses the supplied @note@ if the assertion fails.
154+delayedAssertion :: (Eq a) => String -> LocalNode -> a ->
155+ (TestResult a -> Process ()) -> Assertion
156+delayedAssertion note localNode expected testProc = do
157+ result <- newEmptyMVar
158+ _ <- forkProcess localNode $ testProc result
159+ assertComplete note result expected
160+
161+-- | Takes the value of @mv@ (using @takeMVar@) and asserts that it matches @a@
162+assertComplete :: (Eq a) => String -> MVar a -> a -> IO ()
163+assertComplete msg mv a = do
164+ b <- takeMVar mv
165+ assertBool msg (a == b)
166+
167+-- synchronised logging
168+
169+data Logger = Logger { _tid :: ThreadId, msgs :: TQueue String }
170+
171+-- | Create a new Logger.
172+-- Logger uses a 'TQueue' to receive and process messages on a worker thread.
173+newLogger :: IO Logger
174+newLogger = do
175+ tid <- liftIO $ myThreadId
176+ q <- liftIO $ newTQueueIO
177+ _ <- forkIO $ logger q
178+ return $ Logger tid q
179+ where logger q' = forever $ do
180+ msg <- atomically $ readTQueue q'
181+ putStrLn msg
182+
183+-- | Send a message to the Logger
184+putLogMsg :: Logger -> String -> Process ()
185+putLogMsg logger msg = liftIO $ atomically $ writeTQueue (msgs logger) msg
186+
187+-- | Stop the worker thread for the given Logger
188+stopLogger :: Logger -> IO ()
189+stopLogger = (flip throwTo) ThreadKilled . _tid
190+
191+-- | Starts a test process on the local node.
192+startTestProcess :: Process () -> Process ProcessId
193+startTestProcess proc =
194+ spawnLocal $ do
195+ getSelfPid >>= register "test-process"
196+ runTestProcess proc
197+
198+-- | Runs a /test process/ around the supplied @proc@, which is executed
199+-- whenever the outer process loop receives a 'Go' signal.
200+runTestProcess :: Process () -> Process ()
201+runTestProcess proc = do
202+ ctl <- expect
203+ case ctl of
204+ Stop -> return ()
205+ Go -> proc >> runTestProcess proc
206+ Report p -> receiveWait [matchAny (\m -> forward m p)] >> runTestProcess proc
207+
208+-- | Tell a /test process/ to continue executing
209+testProcessGo :: ProcessId -> Process ()
210+testProcessGo pid = send pid Go
211+
212+-- | Tell a /test process/ to stop (i.e., 'terminate')
213+testProcessStop :: ProcessId -> Process ()
214+testProcessStop pid = send pid Stop
215+
216+-- | Tell a /test process/ to send a report (message)
217+-- back to the calling process
218+testProcessReport :: ProcessId -> Process ()
219+testProcessReport pid = do
220+ self <- getSelfPid
221+ send pid $ Report self
222+
223+tryRunProcess :: LocalNode -> Process () -> IO ()
224+tryRunProcess node p = do
225+ tid <- liftIO myThreadId
226+ runProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
227+
228+tryForkProcess :: LocalNode -> Process () -> IO ProcessId
229+tryForkProcess node p = do
230+ tid <- liftIO myThreadId
231+ forkProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
232+
distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hsnew file+286
@@ -0,0 +1,286 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+module Control.Distributed.Process.Tests.Mx (tests) where
3+
4+import Control.Distributed.Process.Tests.Internal.Utils
5+import Network.Transport.Test (TestTransport(..))
6+
7+import Control.Concurrent (threadDelay)
8+import Control.Distributed.Process
9+import Control.Distributed.Process.Node
10+import Control.Distributed.Process.Management
11+ ( MxEvent(..)
12+ , MxAgentId(..)
13+ , mxAgent
14+ , mxSink
15+ , mxReady
16+ , mxReceive
17+ , mxDeactivate
18+ , liftMX
19+ , mxGetLocal
20+ , mxSetLocal
21+ , mxUpdateLocal
22+ , mxNotify
23+ , mxBroadcast
24+ , mxGetId
25+ , mxGet
26+ , mxSet
27+ , mxClear
28+ , mxPurgeTable
29+ , mxDropTable
30+ )
31+import Control.Monad (void)
32+import Data.Binary
33+import Data.List (find, sort)
34+import Data.Maybe (isJust)
35+import Data.Typeable
36+import GHC.Generics
37+#if ! MIN_VERSION_base(4,6,0)
38+import Prelude hiding (catch, log)
39+#endif
40+
41+import Test.Framework
42+ ( Test
43+ , testGroup
44+ )
45+import Test.Framework.Providers.HUnit (testCase)
46+
47+data Publish = Publish
48+ deriving (Typeable, Generic, Eq)
49+
50+instance Binary Publish where
51+
52+testAgentBroadcast :: TestResult () -> Process ()
53+testAgentBroadcast result = do
54+ (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
55+
56+ publisher <- mxAgent (MxAgentId "publisher-agent") () [
57+ mxSink $ \() -> mxBroadcast Publish >> mxReady
58+ ]
59+
60+ consumer <- mxAgent (MxAgentId "consumer-agent") () [
61+ mxSink $ \Publish -> (liftMX $ sendChan resultSP ()) >> mxReady
62+ ]
63+
64+ mxNotify ()
65+ -- Once the publisher has seen our message, it will broadcast the Publish
66+ -- and the consumer will see that and send the result to our typed channel.
67+ stash result =<< receiveChan resultRP
68+
69+ kill publisher "finished"
70+ kill consumer "finished"
71+
72+testAgentDualInput :: TestResult (Maybe Int) -> Process ()
73+testAgentDualInput result = do
74+ (sp, rp) <- newChan
75+ _ <- mxAgent (MxAgentId "sum-agent") (0 :: Int) [
76+ mxSink $ (\(i :: Int) -> do
77+ mxSetLocal . (+i) =<< mxGetLocal
78+ i' <- mxGetLocal
79+ if i' == 15
80+ then do mxGetLocal >>= liftMX . sendChan sp
81+ mxDeactivate "finished"
82+ else mxReady)
83+ ]
84+
85+ mxNotify (1 :: Int)
86+ nsend "sum-agent" (3 :: Int)
87+ mxNotify (2 :: Int)
88+ nsend "sum-agent" (4 :: Int)
89+ mxNotify (5 :: Int)
90+
91+ stash result =<< receiveChanTimeout 10000000 rp
92+
93+testAgentPrioritisation :: TestResult [String] -> Process ()
94+testAgentPrioritisation result = do
95+
96+ -- TODO: this isn't really testing how we /prioritise/ one source
97+ -- over another at all, but I've not yet figured out the right way
98+ -- to do so, since we're at the whim of the scheduler with regards
99+ -- the timeliness of nsend versus mxNotify anyway.
100+
101+ let name = "prioritising-agent"
102+ (sp, rp) <- newChan
103+ void $ mxAgent (MxAgentId name) ["first"] [
104+ mxSink (\(s :: String) -> do
105+ mxUpdateLocal ((s:))
106+ st <- mxGetLocal
107+ case length st of
108+ n | n == 5 -> do liftMX $ sendChan sp st
109+ mxDeactivate "finished"
110+ _ -> mxReceive -- go to the mailbox
111+ )
112+ ]
113+
114+ nsend name "second"
115+ mxNotify "third"
116+ mxNotify "fourth"
117+ nsend name "fifth"
118+
119+ stash result . sort =<< receiveChan rp
120+
121+testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
122+testAgentMailboxHandling result = do
123+ (sp, rp) <- newChan
124+ agent <- mxAgent (MxAgentId "listener-agent") () [
125+ mxSink $ \() -> (liftMX $ sendChan sp ()) >> mxReady
126+ ]
127+
128+ nsend "listener-agent" ()
129+
130+ stash result =<< receiveChanTimeout 1000000 rp
131+ kill agent "finished"
132+
133+testAgentEventHandling :: TestResult Bool -> Process ()
134+testAgentEventHandling result = do
135+ let initState = [] :: [MxEvent]
136+ agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
137+ (mxSink $ \ev -> do
138+ st <- mxGetLocal
139+ let act =
140+ case ev of
141+ (MxSpawned _) -> mxSetLocal (ev:st)
142+ (MxProcessDied _ _) -> mxSetLocal (ev:st)
143+ _ -> return ()
144+ act >> mxReady),
145+ (mxSink $ \(ev, sp :: SendPort Bool) -> do
146+ st <- mxGetLocal
147+ let found =
148+ case ev of
149+ MxSpawned p ->
150+ isJust $ find (\ev' ->
151+ case ev' of
152+ (MxSpawned p') -> p' == p
153+ _ -> False) st
154+ MxProcessDied p r ->
155+ isJust $ find (\ev' ->
156+ case ev' of
157+ (MxProcessDied p' r') -> p' == p && r == r'
158+ _ -> False) st
159+ _ -> False
160+ liftMX $ sendChan sp found
161+ mxReady)
162+ ]
163+
164+ _ <- monitor agentPid
165+ (sp, rp) <- newChan
166+ pid <- spawnLocal $ sendChan sp ()
167+ () <- receiveChan rp
168+
169+ -- By waiting for a monitor notification, we have a
170+ -- higher probably that the agent has seen
171+ monitor pid
172+ receiveWait [ match (\(ProcessMonitorNotification _ _ _) -> return ()) ]
173+
174+ (replyTo, reply) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
175+ mxNotify (MxSpawned pid, replyTo)
176+ mxNotify (MxProcessDied pid DiedNormal, replyTo)
177+
178+ seenAlive <- receiveChan reply
179+ seenDead <- receiveChan reply
180+
181+ stash result $ seenAlive && seenDead
182+
183+testAgentPublication :: TestResult (Maybe Publish) -> Process ()
184+testAgentPublication result = do
185+ (syncChan, rp) <- newChan
186+ let ourId = MxAgentId "publication-agent"
187+ agentPid <- mxAgent ourId () [
188+ mxSink $ \() -> do
189+ selfId <- mxGetId
190+ liftMX $ mxSet selfId "publish" Publish >> sendChan syncChan ()
191+ mxReady
192+ ]
193+
194+ mxNotify ()
195+ () <- receiveChan rp
196+
197+ stash result =<< mxGet ourId "publish"
198+
199+ mref <- monitor agentPid
200+ kill agentPid "finished"
201+ receiveWait [
202+ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mref)
203+ ((\_ -> return ()))
204+ ]
205+
206+testAgentTableClear :: Int -> TestResult (Maybe Int, Maybe Int) -> Process ()
207+testAgentTableClear val result =
208+ let tId = (MxAgentId "agent-1")
209+ tKey = "key-1" in do
210+ mxSet tId tKey val
211+ get1 <- mxGet tId tKey
212+ mxClear tId tKey
213+ get2 <- mxGet tId tKey
214+ stash result (get1, get2)
215+
216+testAgentTablePurge :: TestResult (Maybe Int) -> Process ()
217+testAgentTablePurge result =
218+ let tId = MxAgentId "agent-2"
219+ tKey = "key-2" in do
220+ mxSet tId tKey (12345 :: Int)
221+ mxPurgeTable tId
222+ stash result =<< mxGet tId tKey
223+
224+testAgentTableDelete :: Int
225+ -> TestResult (Maybe Int, Maybe Int, Maybe Int)
226+ -> Process ()
227+testAgentTableDelete val result =
228+ let tId = (MxAgentId "agent-3")
229+ tKey = "key-3" in do
230+ mxSet tId tKey val
231+ get1 <- mxGet tId tKey
232+ mxDropTable tId
233+ get2 <- mxGet tId tKey
234+ mxSet tId tKey val
235+ get3 <- mxGet tId tKey
236+ stash result (get1, get2, get3)
237+
238+tests :: TestTransport -> IO [Test]
239+tests TestTransport{..} = do
240+ node1 <- newLocalNode testTransport initRemoteTable
241+ return [
242+ testGroup "Mx Agents" [
243+ testCase "Event Handling"
244+ (delayedAssertion
245+ "expected True, but events where not as expected"
246+ node1 True testAgentEventHandling)
247+ , testCase "Inter-Agent Broadcast"
248+ (delayedAssertion
249+ "expected (), but no broadcast was received"
250+ node1 () testAgentBroadcast)
251+ , testCase "Agent Mailbox Handling"
252+ (delayedAssertion
253+ "expected (Just ()), but no regular (mailbox) input was handled"
254+ node1 (Just ()) testAgentMailboxHandling)
255+ , testCase "Agent Dual Input Handling"
256+ (delayedAssertion
257+ "expected sum = 15, but the result was Nothing"
258+ node1 (Just 15 :: Maybe Int) testAgentDualInput)
259+ , testCase "Agent Input Prioritisation"
260+ (delayedAssertion
261+ "expected [first, second, third, fourth, fifth], but result diverged"
262+ node1 (sort ["first", "second",
263+ "third", "fourth",
264+ "fifth"]) testAgentPrioritisation)
265+ ],
266+ testGroup "Mx Global Properties" [
267+ testCase "Global Property Publication"
268+ (delayedAssertion
269+ "expected (Just Publish), but no table entry was found"
270+ node1 (Just Publish) testAgentPublication)
271+ , testCase "Clearing Global Properties"
272+ (delayedAssertion
273+ "expected (Just 1024, Nothing): invalid table entry found!"
274+ node1 (Just 1024, Nothing) (testAgentTableClear 1024))
275+ , testCase "Purging Global Tables"
276+ (delayedAssertion
277+ "expected Nothing, but a table entry was found"
278+ node1 Nothing testAgentTablePurge)
279+ , testCase "Deleting and (Re)Creating Global Tables"
280+ (delayedAssertion
281+ "expected (Just 15, Nothing, Just 15): invalid table entry found!"
282+ node1 (Just 15, Nothing, Just 15) (testAgentTableDelete 15))
283+ -- Wait for other tests to finish.
284+ , testCase "Wait" $
285+ threadDelay 100000
286+ ]]
distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hsnew file+158
@@ -0,0 +1,158 @@
1+{-# LANGUAGE TemplateHaskell, DeriveDataTypeable #-}
2+{-# OPTIONS_GHC -Wall #-}
3+
4+-- | XXX test doesn't work, because failure exceptions don't get propagated. The
5+-- test always claims to succeed, even if it failed.
6+
7+module Control.Distributed.Process.Tests.Receive (tests) where
8+
9+import Network.Transport.Test (TestTransport(..))
10+
11+import Network.Transport (Transport)
12+import Control.Distributed.Process
13+import Control.Distributed.Process.Closure
14+import Control.Distributed.Process.Node
15+
16+import Control.Monad
17+import Text.Printf
18+import Data.Binary
19+import Data.Typeable
20+
21+import Test.HUnit (Assertion, (@?=))
22+import Test.Framework (Test, defaultMain)
23+import Test.Framework.Providers.HUnit (testCase)
24+
25+-- Tests:
26+
27+-- 1. 2 matchChans, receive on each one
28+-- 2. matchChan/matchIf, receive on each one
29+-- 3. matchIf/matchChan, receive on each one
30+-- 4. matchIf/matchChan/matchIf, receive on each one
31+
32+recTest1 :: ReceivePort ()
33+ -> SendPort String
34+ -> ReceivePort String -> ReceivePort String
35+ -> Process ()
36+recTest1 wait sync r1 r2 = do
37+ forever $ do
38+ receiveChan wait
39+ r <- receiveWait
40+ [ matchChan r1 $ \s -> return ("received1 " ++ s)
41+ , matchChan r2 $ \s -> return ("received2 " ++ s)
42+ ]
43+ sendChan sync r
44+
45+recTest2 :: ReceivePort ()
46+ -> SendPort String
47+ -> ReceivePort String -> ReceivePort String
48+ -> Process ()
49+recTest2 wait sync r1 r2 = do
50+ forever $ do
51+ receiveChan wait
52+ r <- receiveWait
53+ [ matchChan r1 $ \s -> return ("received1 " ++ s)
54+ , matchIf (== "foo") $ \s -> return ("received2 " ++ s)
55+ ]
56+ sendChan sync r
57+
58+recTest3 :: ReceivePort ()
59+ -> SendPort String
60+ -> ReceivePort String -> ReceivePort String
61+ -> Process ()
62+recTest3 wait sync r1 r2 = do
63+ forever $ do
64+ receiveChan wait
65+ r <- receiveWait
66+ [ matchIf (== "foo") $ \s -> return ("received1 " ++ s)
67+ , matchChan r1 $ \s -> return ("received2 " ++ s)
68+ ]
69+ sendChan sync r
70+
71+recTest4 :: ReceivePort ()
72+ -> SendPort String
73+ -> ReceivePort String -> ReceivePort String
74+ -> Process ()
75+recTest4 wait sync r1 r2 = do
76+ forever $ do
77+ receiveChan wait
78+ r <- receiveWait
79+ [ matchIf (== "foo") $ \s -> return ("received1 " ++ s)
80+ , matchChan r1 $ \s -> return ("received2 " ++ s)
81+ , matchIf (== "bar") $ \s -> return ("received3 " ++ s)
82+ ]
83+ sendChan sync r
84+
85+master :: Process ()
86+master = do
87+ (waits,waitr) <- newChan
88+ (syncs,syncr) <- newChan
89+ let go expect = do
90+ sendChan waits ()
91+ r <- receiveChan syncr
92+ liftIO $ print (r,expect, r == expect)
93+ liftIO $ r @?= expect
94+
95+ liftIO $ putStrLn "---- Test 1 ----"
96+ (s1,r1) <- newChan
97+ (s2,r2) <- newChan
98+ p <- spawnLocal (recTest1 waitr syncs r1 r2)
99+
100+ sendChan s1 "a" >> go "received1 a"
101+ sendChan s2 "b" >> go "received2 b"
102+ sendChan s1 "a" >> sendChan s2 "b" >> go "received1 a"
103+ go "received2 b"
104+
105+ kill p "BANG"
106+
107+ liftIO $ putStrLn "\n---- Test 2 ----"
108+ (s1,r1) <- newChan
109+ (s2,r2) <- newChan
110+ p <- spawnLocal (recTest2 waitr syncs r1 r2)
111+
112+ sendChan s1 "a" >> go "received1 a"
113+ send p "foo" >> go "received2 foo"
114+ sendChan s1 "a" >> send p "foo" >> go "received1 a"
115+ sendChan s1 "a" >> send p "bar" >> go "received1 a"
116+ go "received2 foo"
117+
118+ kill p "BANG"
119+
120+ liftIO $ putStrLn "\n---- Test 3 ----"
121+ (s1,r1) <- newChan
122+ (s2,r2) <- newChan
123+ p <- spawnLocal (recTest3 waitr syncs r1 r2)
124+
125+ sendChan s1 "a" >> go "received2 a"
126+ send p "foo" >> go "received1 foo"
127+ sendChan s1 "a" >> send p "foo" >> go "received1 foo"
128+ sendChan s1 "a" >> send p "bar" >> go "received2 a"
129+ go "received2 a"
130+
131+ kill p "BANG"
132+
133+ liftIO $ putStrLn "\n---- Test 4 ----"
134+ (s1,r1) <- newChan
135+ (s2,r2) <- newChan
136+ p <- spawnLocal (recTest4 waitr syncs r1 r2)
137+
138+ sendChan s1 "a" >> go "received2 a"
139+ send p "foo" >> go "received1 foo"
140+ send p "bar" >> go "received3 bar"
141+ sendChan s1 "a" >> send p "foo" >> go "received1 foo"
142+ send p "bar" >> go "received2 a"
143+ send p "foo" >> go "received1 foo" >> go "received3 bar"
144+
145+ kill p "BANG"
146+
147+ terminate
148+
149+testReceive :: Transport -> RemoteTable -> Assertion
150+testReceive transport rtable = do
151+ node <- newLocalNode transport rtable
152+ runProcess node $ master
153+
154+tests :: TestTransport -> IO [Test]
155+tests TestTransport{..} = do
156+ let rtable = initRemoteTable
157+ return
158+ [ testCase "testReceive" (testReceive testTransport rtable) ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hsnew file+125
@@ -0,0 +1,125 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# OPTIONS_GHC -fno-warn-orphans #-}
3+module Control.Distributed.Process.Tests.Stats (tests) where
4+
5+import Control.Distributed.Process.Tests.Internal.Utils
6+import Network.Transport.Test (TestTransport(..))
7+
8+import Control.Concurrent.MVar
9+ ( MVar
10+ , newEmptyMVar
11+ , putMVar
12+ , takeMVar
13+ )
14+import Control.Distributed.Process
15+import Control.Distributed.Process.Node
16+import Data.Binary ()
17+import Data.Typeable ()
18+
19+#if ! MIN_VERSION_base(4,6,0)
20+import Prelude hiding (catch)
21+#endif
22+
23+import Test.Framework
24+ ( Test
25+ , testGroup
26+ )
27+import Test.HUnit (Assertion)
28+import Test.Framework.Providers.HUnit (testCase)
29+
30+testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
31+testLocalDeadProcessInfo result = do
32+ pid <- spawnLocal $ do "finish" <- expect; return ()
33+ mref <- monitor pid
34+ send pid "finish"
35+ _ <- receiveWait [
36+ matchIf (\(ProcessMonitorNotification ref' pid' r) ->
37+ ref' == mref && pid' == pid && r == DiedNormal)
38+ (\p -> return p)
39+ ]
40+ getProcessInfo pid >>= stash result
41+
42+testLocalLiveProcessInfo :: TestResult Bool -> Process ()
43+testLocalLiveProcessInfo result = do
44+ self <- getSelfPid
45+ node <- getSelfNode
46+ register "foobar" self
47+
48+ mon <- liftIO $ newEmptyMVar
49+ -- TODO: we can't get the mailbox's length
50+ -- mapM (send self) ["hello", "there", "mr", "process"]
51+ pid <- spawnLocal $ do
52+ link self
53+ mRef <- monitor self
54+ stash mon mRef
55+ "die" <- expect
56+ return ()
57+
58+ monRef <- liftIO $ takeMVar mon
59+
60+ mpInfo <- getProcessInfo self
61+ case mpInfo of
62+ Nothing -> stash result False
63+ Just p -> verifyPInfo p pid monRef node
64+ where verifyPInfo :: ProcessInfo
65+ -> ProcessId
66+ -> MonitorRef
67+ -> NodeId
68+ -> Process ()
69+ verifyPInfo pInfo pid mref node =
70+ stash result $ infoNode pInfo == node &&
71+ infoLinks pInfo == [pid] &&
72+ infoMonitors pInfo == [(pid, mref)] &&
73+-- infoMessageQueueLength pInfo == Just 4 &&
74+ infoRegisteredNames pInfo == ["foobar"]
75+
76+testRemoteLiveProcessInfo :: TestTransport -> LocalNode -> Assertion
77+testRemoteLiveProcessInfo TestTransport{..} node1 = do
78+ serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)
79+ liftIO $ launchRemote serverAddr
80+ serverPid <- liftIO $ takeMVar serverAddr
81+ withActiveRemote node1 $ \result -> do
82+ self <- getSelfPid
83+ link serverPid
84+ -- our send op shouldn't overtake link or monitor requests AFAICT
85+ -- so a little table tennis should get us synchronised properly
86+ send serverPid (self, "ping")
87+ "pong" <- expect
88+ pInfo <- getProcessInfo serverPid
89+ stash result $ pInfo /= Nothing
90+ where
91+ launchRemote :: MVar ProcessId -> IO ()
92+ launchRemote locMV = do
93+ node2 <- liftIO $ newLocalNode testTransport initRemoteTable
94+ _ <- liftIO $ forkProcess node2 $ do
95+ self <- getSelfPid
96+ liftIO $ putMVar locMV self
97+ _ <- receiveWait [
98+ match (\(pid, "ping") -> send pid "pong")
99+ ]
100+ "stop" <- expect
101+ return ()
102+ return ()
103+
104+ withActiveRemote :: LocalNode
105+ -> ((TestResult Bool -> Process ()) -> Assertion)
106+ withActiveRemote n = do
107+ a <- delayedAssertion "getProcessInfo remotePid failed" n True
108+ return a
109+
110+tests :: TestTransport -> IO [Test]
111+tests testtrans@TestTransport{..} = do
112+ node1 <- newLocalNode testTransport initRemoteTable
113+ return [
114+ testGroup "Process Info" [
115+ testCase "testLocalDeadProcessInfo"
116+ (delayedAssertion
117+ "expected dead process-info to be ProcessInfoNone"
118+ node1 (Nothing) testLocalDeadProcessInfo)
119+ , testCase "testLocalLiveProcessInfo"
120+ (delayedAssertion
121+ "expected process-info to be correctly populated"
122+ node1 True testLocalLiveProcessInfo)
123+ , testCase "testRemoveLiveProcessInfo"
124+ (testRemoteLiveProcessInfo testtrans node1)
125+ ] ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Tracing.hsnew file+326
@@ -0,0 +1,326 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# OPTIONS_GHC -fno-warn-orphans #-}
3+module Control.Distributed.Process.Tests.Tracing (tests) where
4+
5+import Control.Distributed.Process.Tests.Internal.Utils
6+import Network.Transport.Test (TestTransport(..))
7+
8+import Control.Concurrent (threadDelay)
9+import Control.Concurrent.MVar
10+ ( MVar
11+ , newEmptyMVar
12+ , newMVar
13+ , putMVar
14+ , takeMVar
15+ )
16+import Control.Distributed.Process
17+import Control.Distributed.Process.Node
18+import Control.Distributed.Process.Debug
19+import Control.Distributed.Process.Management
20+ ( MxEvent(..)
21+ )
22+
23+#if ! MIN_VERSION_base(4,6,0)
24+import Prelude hiding (catch, log)
25+#endif
26+
27+import Test.Framework
28+ ( Test
29+ , testGroup
30+ )
31+import Test.Framework.Providers.HUnit (testCase)
32+
33+testSpawnTracing :: TestResult Bool -> Process ()
34+testSpawnTracing result = do
35+ setTraceFlags defaultTraceFlags {
36+ traceSpawned = (Just TraceAll)
37+ , traceDied = (Just TraceAll)
38+ }
39+
40+ evSpawned <- liftIO $ newEmptyMVar
41+ evDied <- liftIO $ newEmptyMVar
42+ tracer <- startTracer $ \ev -> do
43+ case ev of
44+ (MxSpawned p) -> liftIO $ putMVar evSpawned p
45+ (MxProcessDied p r) -> liftIO $ putMVar evDied (p, r)
46+ _ -> return ()
47+
48+ (sp, rp) <- newChan
49+ pid <- spawnLocal $ sendChan sp ()
50+ () <- receiveChan rp
51+
52+ tracedAlive <- liftIO $ takeMVar evSpawned
53+ (tracedDead, tracedReason) <- liftIO $ takeMVar evDied
54+
55+ mref <- monitor tracer
56+ stopTracer -- this is asynchronous, so we need to wait...
57+ receiveWait [
58+ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mref)
59+ ((\_ -> return ()))
60+ ]
61+ setTraceFlags defaultTraceFlags
62+ stash result (tracedAlive == pid &&
63+ tracedDead == pid &&
64+ tracedReason == DiedNormal)
65+
66+testTraceRecvExplicitPid :: TestResult Bool -> Process ()
67+testTraceRecvExplicitPid result = do
68+ res <- liftIO $ newEmptyMVar
69+ pid <- spawnLocal $ do
70+ self <- getSelfPid
71+ expect >>= (flip sendChan) self
72+ withFlags defaultTraceFlags {
73+ traceRecv = traceOnly [pid]
74+ } $ do
75+ withTracer
76+ (\ev ->
77+ case ev of
78+ (MxReceived pid' _) -> stash res (pid == pid')
79+ _ -> return ()) $ do
80+ (sp, rp) <- newChan
81+ send pid sp
82+ p <- receiveChan rp
83+ res' <- liftIO $ takeMVar res
84+ stash result (res' && (p == pid))
85+ return ()
86+
87+testTraceRecvNamedPid :: TestResult Bool -> Process ()
88+testTraceRecvNamedPid result = do
89+ res <- liftIO $ newEmptyMVar
90+ pid <- spawnLocal $ do
91+ self <- getSelfPid
92+ register "foobar" self
93+ expect >>= (flip sendChan) self
94+ withFlags defaultTraceFlags {
95+ traceRecv = traceOnly ["foobar"]
96+ } $ do
97+ withTracer
98+ (\ev ->
99+ case ev of
100+ (MxReceived pid' _) -> stash res (pid == pid')
101+ _ -> return ()) $ do
102+ (sp, rp) <- newChan
103+ send pid sp
104+ p <- receiveChan rp
105+ res' <- liftIO $ takeMVar res
106+ stash result (res' && (p == pid))
107+ return ()
108+
109+testTraceSending :: TestResult Bool -> Process ()
110+testTraceSending result = do
111+ pid <- spawnLocal $ (expect :: Process String) >> return ()
112+ self <- getSelfPid
113+ res <- liftIO $ newEmptyMVar
114+ withFlags defaultTraceFlags { traceSend = traceOn } $ do
115+ withTracer
116+ (\ev ->
117+ case ev of
118+ (MxSent to from msg) -> do
119+ (Just s) <- unwrapMessage msg :: Process (Maybe String)
120+ stash res (to == pid && from == self && s == "hello there")
121+ stash res (to == pid && from == self)
122+ _ ->
123+ return ()) $ do
124+ send pid "hello there"
125+ res' <- liftIO $ takeMVar res
126+ stash result res'
127+
128+testTraceRegistration :: TestResult Bool -> Process ()
129+testTraceRegistration result = do
130+ (sp, rp) <- newChan
131+ pid <- spawnLocal $ do
132+ self <- getSelfPid
133+ () <- expect
134+ register "foobar" self
135+ sendChan sp ()
136+ () <- expect
137+ return ()
138+ res <- liftIO $ newEmptyMVar
139+ withFlags defaultTraceFlags { traceRegistered = traceOn } $ do
140+ withTracer
141+ (\ev ->
142+ case ev of
143+ MxRegistered p s ->
144+ stash res (p == pid && s == "foobar")
145+ _ ->
146+ return ()) $ do
147+ _ <- monitor pid
148+ send pid ()
149+ () <- receiveChan rp
150+ send pid ()
151+ receiveWait [
152+ match (\(ProcessMonitorNotification _ _ _) -> return ())
153+ ]
154+ res' <- liftIO $ takeMVar res
155+ stash result res'
156+
157+testTraceUnRegistration :: TestResult Bool -> Process ()
158+testTraceUnRegistration result = do
159+ pid <- spawnLocal $ do
160+ () <- expect
161+ unregister "foobar"
162+ () <- expect
163+ return ()
164+ register "foobar" pid
165+ res <- liftIO $ newEmptyMVar
166+ withFlags defaultTraceFlags { traceUnregistered = traceOn } $ do
167+ withTracer
168+ (\ev ->
169+ case ev of
170+ MxUnRegistered p n -> do
171+ stash res (p == pid && n == "foobar")
172+ send pid ()
173+ _ ->
174+ return ()) $ do
175+ mref <- monitor pid
176+ send pid ()
177+ receiveWait [
178+ matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
179+ (\_ -> return ())
180+ ]
181+ res' <- liftIO $ takeMVar res
182+ stash result res'
183+
184+testTraceLayering :: TestResult () -> Process ()
185+testTraceLayering result = do
186+ pid <- spawnLocal $ do
187+ getSelfPid >>= register "foobar"
188+ () <- expect
189+ traceMessage ("traceMsg", 123 :: Int)
190+ return ()
191+ withFlags defaultTraceFlags {
192+ traceDied = traceOnly [pid]
193+ , traceRecv = traceOnly ["foobar"]
194+ } $ doTest pid result
195+ return ()
196+ where
197+ doTest :: ProcessId -> MVar () -> Process ()
198+ doTest pid result' = do
199+ -- TODO: this is pretty gross, even for a test case
200+ died <- liftIO $ newEmptyMVar
201+ withTracer
202+ (\ev ->
203+ case ev of
204+ MxProcessDied _ _ -> liftIO $ putMVar died ()
205+ _ -> return ())
206+ ( do {
207+ recv <- liftIO $ newEmptyMVar
208+ ; withTracer
209+ (\ev' ->
210+ case ev' of
211+ MxReceived _ _ -> liftIO $ putMVar recv ()
212+ _ -> return ())
213+ ( do {
214+ user <- liftIO $ newEmptyMVar
215+ ; withTracer
216+ (\ev'' ->
217+ case ev'' of
218+ MxUser _ -> liftIO $ putMVar user ()
219+ _ -> return ())
220+ (send pid () >> (liftIO $ takeMVar user))
221+ ; liftIO $ takeMVar recv
222+ })
223+ ; liftIO $ takeMVar died
224+ })
225+ liftIO $ putMVar result' ()
226+
227+testRemoteTraceRelay :: TestTransport -> TestResult Bool -> Process ()
228+testRemoteTraceRelay TestTransport{..} result =
229+ let flags = defaultTraceFlags { traceSpawned = traceOn }
230+ in do
231+ node2 <- liftIO $ newLocalNode testTransport initRemoteTable
232+ mvNid <- liftIO $ newEmptyMVar
233+
234+ -- As well as needing node2's NodeId, we want to
235+ -- redirect all its logs back here, to avoid generating
236+ -- garbage on stderr for the duration of the test run.
237+ -- Here we set up that relay, and then wait for a signal
238+ -- that the tracer (on node1) has seen the expected
239+ -- MxSpawned message, at which point we're finished
240+ (Just log') <- whereis "logger"
241+ pid <- liftIO $ forkProcess node2 $ do
242+ logRelay <- spawnLocal $ relay log'
243+ reregister "logger" logRelay
244+ getSelfNode >>= stash mvNid >> (expect :: Process ())
245+
246+ nid <- liftIO $ takeMVar mvNid
247+ mref <- monitor pid
248+ observedPid <- liftIO $ newEmptyMVar
249+ spawnedPid <- liftIO $ newEmptyMVar
250+ setTraceFlagsRemote flags nid
251+
252+ withFlags defaultTraceFlags { traceSpawned = traceOn } $ do
253+ withTracer
254+ (\ev ->
255+ case ev of
256+ MxSpawned p -> stash observedPid p >> send pid ()
257+ _ -> return ()) $ do
258+ relayPid <- startTraceRelay nid
259+ liftIO $ threadDelay 1000000
260+ p <- liftIO $ forkProcess node2 $ do
261+ expectTimeout 1000000 :: Process (Maybe ())
262+ return ()
263+ stash spawnedPid p
264+
265+ -- Now we wait for (the outer) pid to exit. This won't happen until
266+ -- our tracer has seen the trace event for `p' and sent `p' the
267+ -- message it's waiting for prior to exiting
268+ receiveWait [
269+ matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
270+ (\_ -> return ())
271+ ]
272+
273+ relayRef <- monitor relayPid
274+ kill relayPid "stop"
275+ receiveWait [
276+ matchIf (\(ProcessMonitorNotification rref' _ _) -> rref' == relayRef)
277+ (\_ -> return ())
278+ ]
279+ observed <- liftIO $ takeMVar observedPid
280+ expected <- liftIO $ takeMVar spawnedPid
281+ stash result (observed == expected)
282+ -- and just to be polite...
283+ liftIO $ closeLocalNode node2
284+
285+tests :: TestTransport -> IO [Test]
286+tests testtrans@TestTransport{..} = do
287+ node1 <- newLocalNode testTransport initRemoteTable
288+ -- if we execute the test cases in parallel, the
289+ -- various tracers will race with one another and
290+ -- we'll get garbage results (or worse, deadlocks)
291+ lock <- liftIO $ newMVar ()
292+ return [
293+ testGroup "Tracing" [
294+ testCase "Spawn Tracing"
295+ (synchronisedAssertion
296+ "expected dead process-info to be ProcessInfoNone"
297+ node1 True testSpawnTracing lock)
298+ , testCase "Recv Tracing (Explicit Pid)"
299+ (synchronisedAssertion
300+ "expected a recv trace for the supplied pid"
301+ node1 True testTraceRecvExplicitPid lock)
302+ , testCase "Recv Tracing (Named Pid)"
303+ (synchronisedAssertion
304+ "expected a recv trace for the process registered as 'foobar'"
305+ node1 True testTraceRecvNamedPid lock)
306+ , testCase "Trace Send(er)"
307+ (synchronisedAssertion
308+ "expected a 'send' trace with the requisite fields set"
309+ node1 True testTraceSending lock)
310+ , testCase "Trace Registration"
311+ (synchronisedAssertion
312+ "expected a 'registered' trace"
313+ node1 True testTraceRegistration lock)
314+ , testCase "Trace Unregistration"
315+ (synchronisedAssertion
316+ "expected an 'unregistered' trace"
317+ node1 True testTraceUnRegistration lock)
318+ , testCase "Trace Layering"
319+ (synchronisedAssertion
320+ "expected blah"
321+ node1 () testTraceLayering lock)
322+ , testCase "Remote Trace Relay"
323+ (synchronisedAssertion
324+ "expected blah"
325+ node1 True (testRemoteTraceRelay testtrans) lock)
326+ ] ]
distributed-process-tests/src/Network/Transport/Test.hsnew file+12
@@ -0,0 +1,12 @@
1+module Network.Transport.Test where
2+
3+import qualified Network.Transport as NT
4+import Data.Typeable (Typeable)
5+
6+-- | Extra operations required of transports for the purposes of testing.
7+data TestTransport = TestTransport
8+ { -- | The transport to use for testing.
9+ testTransport :: NT.Transport
10+ -- | IO action to perform to simulate losing a connection.
11+ , testBreakConnection :: NT.EndPointAddress -> NT.EndPointAddress -> IO ()
12+ } deriving (Typeable)
distributed-process-tests/tests/runInMemory.hsnew file+31
@@ -0,0 +1,31 @@
1+-- Run tests using the TCP transport.
2+
3+module Main where
4+
5+import TEST_SUITE_MODULE (tests)
6+
7+import Network.Transport.Test (TestTransport(..))
8+import Network.Transport.InMemory
9+import Test.Framework (defaultMainWithArgs)
10+
11+import Control.Concurrent (threadDelay)
12+import System.Environment (getArgs)
13+
14+main :: IO ()
15+main = do
16+ (transport, internals) <- createTransportExposeInternals
17+ ts <- tests TestTransport
18+ { testTransport = transport
19+ , testBreakConnection = \addr1 addr2 -> breakConnection internals addr1 addr2 "user error"
20+ }
21+ args <- getArgs
22+ -- Tests are time sensitive. Running the tests concurrently can slow them
23+ -- down enough that threads using threadDelay would wake up later than
24+ -- expected, thus changing the order in which messages were expected.
25+ -- Therefore we run the tests sequentially by passing "-j 1" to
26+ -- test-framework. This does not solve the issue but makes it less likely.
27+ --
28+ -- The problem was first detected with
29+ -- 'Control.Distributed.Process.Tests.CH.testMergeChannels'
30+ -- in particular.
31+ defaultMainWithArgs ts ("-j" : "1" : args)
distributed-process-tests/tests/runTCP.hsnew file+40
@@ -0,0 +1,40 @@
1+-- Run tests using the TCP transport.
2+--
3+module Main where
4+
5+import TEST_SUITE_MODULE (tests)
6+
7+import Network.Transport.Test (TestTransport(..))
8+import Network.Socket (sClose)
9+import Network.Transport.TCP
10+ ( createTransportExposeInternals
11+ , TransportInternals(socketBetween)
12+ , defaultTCPParameters
13+ )
14+import Test.Framework (defaultMainWithArgs)
15+
16+import Control.Concurrent (threadDelay)
17+import System.Environment (getArgs)
18+
19+main :: IO ()
20+main = do
21+ Right (transport, internals) <-
22+ createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
23+ ts <- tests TestTransport
24+ { testTransport = transport
25+ , testBreakConnection = \addr1 addr2 -> do
26+ sock <- socketBetween internals addr1 addr2
27+ sClose sock
28+ threadDelay 10000
29+ }
30+ args <- getArgs
31+ -- Tests are time sensitive. Running the tests concurrently can slow them
32+ -- down enough that threads using threadDelay would wake up later than
33+ -- expected, thus changing the order in which messages were expected.
34+ -- Therefore we run the tests sequentially by passing "-j 1" to
35+ -- test-framework. This does not solve the issue but makes it less likely.
36+ --
37+ -- The problem was first detected with
38+ -- 'Control.Distributed.Process.Tests.CH.testMergeChannels'
39+ -- in particular.
40+ defaultMainWithArgs ts ("-j" : "1" : args)
stack.yamlnew file+9
@@ -0,0 +1,9 @@
1+resolver: nightly-2015-10-09
2+packages:
3+- '.'
4+- distributed-process-tests/
5+extra-deps:
6+- distributed-static-0.3.2.0
7+- network-transport-inmemory-0.5.0
8+- rank1dynamic-0.3.1.0
9+- rematch-0.2.0.0