git.iko.soy/distributed-process
on master help sign in
Test blocking behaviour
commitb0cad335def9831759d457e2d1402e37c1bed072 authorEdsko de Vries <edsko@edsko.net> dateThu 3 May 2012, 11:42 (14 years ago) parent64d211c
5 files changed+211−83
network-transport-2/network-transport.cabal+6−3 network-transport-2/src/Network/Transport/TCP.hs+1 network-transport-2/tests/TestAuxiliary.hs+43 network-transport-2/tests/TestTCP.hs+132−37 network-transport-2/tests/TestTransport.hs+29−43
network-transport-2/network-transport.cabal+6−3
@@ -34,7 +34,8 @@ Test-Suite TestTCP
3434 network,
3535 regex-applicative,
3636 mtl,
37− transformers
37+ transformers,
38+ ansi-terminal
3839 extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface, FlexibleContexts, BangPatterns
3940 ghc-options: -Wall -fno-warn-unused-do-bind
4041 HS-Source-Dirs: tests src
@@ -47,7 +48,8 @@ Test-Suite TestMulticastInMemory
4748 bytestring,
4849 data-lens,
4950 mtl,
50− transformers
51+ transformers,
52+ ansi-terminal
5153 extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface, FlexibleContexts, BangPatterns
5254 ghc-options: -Wall -fno-warn-unused-do-bind
5355 HS-Source-Dirs: tests src
@@ -60,7 +62,8 @@ Test-Suite TestInMemory
6062 bytestring,
6163 data-lens,
6264 mtl,
63− transformers
65+ transformers,
66+ ansi-terminal
6467 extensions: EmptyDataDecls, OverloadedStrings, ForeignFunctionInterface, FlexibleContexts, BangPatterns
6568 ghc-options: -Wall -fno-warn-unused-do-bind
6669 HS-Source-Dirs: tests src
network-transport-2/src/Network/Transport/TCP.hs+1
@@ -13,6 +13,7 @@ module Network.Transport.TCP ( -- * Main API
1313 , decodeEndPointAddress
1414 , ControlHeader(..)
1515 , ConnectionRequestResponse(..)
16+ , firstNonReservedConnectionId
1617 -- * Design notes
1718 -- $design
1819 ) where
network-transport-2/tests/TestAuxiliary.hsnew file+43
@@ -0,0 +1,43 @@
1+module TestAuxiliary (runTest, forkTry) where
2+
3+import Prelude hiding (catch)
4+import Control.Concurrent (myThreadId, forkIO, ThreadId, throwTo)
5+import Control.Exception (SomeException, try, catch)
6+import System.Timeout (timeout)
7+import System.IO (stdout, hFlush)
8+import System.Console.ANSI ( SGR(SetColor, Reset)
9+ , Color(Red, Green)
10+ , ConsoleLayer(Foreground)
11+ , ColorIntensity(Vivid)
12+ , setSGR
13+ )
14+
15+-- | Like fork, but throw exceptions in the child thread to the parent
16+forkTry :: IO () -> IO ThreadId
17+forkTry p = do
18+ tid <- myThreadId
19+ forkIO $ catch p (\e -> throwTo tid (e :: SomeException))
20+
21+-- | Run the given test, catching timeouts and exceptions
22+runTest :: String -> IO () -> IO ()
23+runTest description test = do
24+ putStr $ "Running " ++ show description ++ ": "
25+ hFlush stdout
26+ done <- try . timeout 10000000 $ test
27+ case done of
28+ Left err -> failed $ "(exception: " ++ show (err :: SomeException) ++ ")"
29+ Right Nothing -> failed $ "(timeout)"
30+ Right (Just ()) -> ok
31+ where
32+ failed :: String -> IO ()
33+ failed err = do
34+ setSGR [SetColor Foreground Vivid Red]
35+ putStr "failed "
36+ setSGR [Reset]
37+ putStrLn err
38+
39+ ok :: IO ()
40+ ok = do
41+ setSGR [SetColor Foreground Vivid Green]
42+ putStrLn "ok"
43+ setSGR [Reset]
network-transport-2/tests/TestTCP.hs+132−37
@@ -1,14 +1,20 @@
11module Main where
22
3+import Prelude hiding (catch)
34import TestTransport
5+import TestAuxiliary (forkTry, runTest)
46import Network.Transport
57import Network.Transport.TCP (createTransport)
68import Data.Int (Int32)
79import Data.Maybe (fromJust)
8−import Control.Concurrent (forkIO)
9−import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar, readMVar)
10−import Control.Monad (replicateM)
11−import Network.Transport.TCP (decodeEndPointAddress, EndPointId, ControlHeader(..), ConnectionRequestResponse(..))
10+import Control.Concurrent (threadDelay)
11+import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar, readMVar, isEmptyMVar)
12+import Control.Monad (replicateM, guard)
13+import Network.Transport.TCP ( decodeEndPointAddress
14+ , EndPointId
15+ , ControlHeader(..)
16+ , ConnectionRequestResponse(..)
17+ )
1218import Network.Transport.Internal (encodeInt32, prependLength, tlog)
1319import Network.Transport.Internal.TCP (recvInt32)
1420import qualified Network.Socket as N ( getAddrInfo
@@ -34,8 +40,8 @@ testEarlyDisconnect = do
3440 serverDone <- newEmptyMVar
3541 Right transport <- createTransport "127.0.0.1" "8081"
3642
37− forkIO $ server transport serverAddr serverDone
38− forkIO $ client transport serverAddr
43+ forkTry $ server transport serverAddr serverDone
44+ forkTry $ client transport serverAddr
3945
4046 takeMVar serverDone
4147 where
@@ -72,6 +78,36 @@ testEarlyDisconnect = do
7278 -- The server should still receive a ConnectionClosed message
7379 N.sClose sock
7480
81+-- | Test the creation of a transport with an invalid address
82+testInvalidAddress :: IO ()
83+testInvalidAddress = do
84+ Left _ <- createTransport "invalidHostName" "8082"
85+ return ()
86+
87+-- | Test connecting to invalid or non-existing endpoints
88+testInvalidConnect :: IO ()
89+testInvalidConnect = do
90+ Right transport <- createTransport "127.0.0.1" "8083"
91+ Right endpoint <- newEndPoint transport
92+
93+ -- Syntax error in the endpoint address
94+ Left (FailedWith ConnectInvalidAddress _) <-
95+ connect endpoint (EndPointAddress "InvalidAddress") ReliableOrdered
96+
97+ -- Syntax connect, but invalid hostname (TCP address lookup failure)
98+ Left (FailedWith ConnectInvalidAddress _) <-
99+ connect endpoint (EndPointAddress "invalidHost:port:0") ReliableOrdered
100+
101+ -- TCP address correct, but nobody home at that address
102+ Left (FailedWith ConnectFailed _) <-
103+ connect endpoint (EndPointAddress "127.0.0.1:9000:0") ReliableOrdered
104+
105+ -- Valid TCP address but invalid endpoint number
106+ Left (FailedWith ConnectInvalidAddress _) <-
107+ connect endpoint (EndPointAddress "127.0.0.1:8083:1") ReliableOrdered
108+
109+ return ()
110+
75111-- | Test that an endpoint can ignore CloseSocket requests (in "reality" this
76112-- would happen when the endpoint sends a new connection request before
77113-- receiving an (already underway) CloseSocket request)
@@ -81,8 +117,8 @@ testIgnoreCloseSocket = do
81117 clientDone <- newEmptyMVar
82118 Right transport <- createTransport "127.0.0.1" "8084"
83119
84− forkIO $ server transport serverAddr
85− forkIO $ client transport serverAddr clientDone
120+ forkTry $ server transport serverAddr
121+ forkTry $ client transport serverAddr clientDone
86122
87123 takeMVar clientDone
88124
@@ -163,41 +199,100 @@ testIgnoreCloseSocket = do
163199
164200 putMVar clientDone ()
165201
166−-- | Test the creation of a transport with an invalid address
167−testInvalidAddress :: IO ()
168−testInvalidAddress = do
169− Left _ <- createTransport "invalidHostName" "8082"
170− return ()
202+-- | Like 'testIgnoreSocket', but now the server requests a connection after the
203+-- client closed their connection. In the meantime, the server will have sent a
204+-- CloseSocket request to the client, and must block until the client responds.
205+testBlockAfterCloseSocket :: IO ()
206+testBlockAfterCloseSocket = do
207+ serverAddr <- newEmptyMVar
208+ clientAddr <- newEmptyMVar
209+ clientDone <- newEmptyMVar
210+ Right transport <- createTransport "127.0.0.1" "8085"
211+
212+ forkTry $ server transport serverAddr clientAddr
213+ forkTry $ client transport serverAddr clientAddr clientDone
214+
215+ takeMVar clientDone
171216
172−-- | Test connecting to invalid or non-existing endpoints
173−testInvalidConnect :: IO ()
174−testInvalidConnect = do
175− Right transport <- createTransport "127.0.0.1" "8083"
176− Right endpoint <- newEndPoint transport
217+ where
218+ server :: Transport -> MVar (N.HostName, N.ServiceName, EndPointId) -> MVar EndPointAddress -> IO ()
219+ server transport serverAddr clientAddr = do
220+ tlog "Server"
221+ Right endpoint <- newEndPoint transport
222+ putMVar serverAddr (fromJust . decodeEndPointAddress . address $ endpoint)
177223
178− -- Syntax error in the endpoint address
179− Left (FailedWith ConnectInvalidAddress _) <-
180− connect endpoint (EndPointAddress "InvalidAddress") ReliableOrdered
181−
182− -- Syntax connect, but invalid hostname (TCP address lookup failure)
183− Left (FailedWith ConnectInvalidAddress _) <-
184− connect endpoint (EndPointAddress "invalidHost:port:0") ReliableOrdered
185−
186− -- TCP address correct, but nobody home at that address
187− Left (FailedWith ConnectFailed _) <-
188− connect endpoint (EndPointAddress "127.0.0.1:9000:0") ReliableOrdered
224+ -- Wait for the client to connect and disconnect
225+ tlog "Waiting for ConnectionOpened"
226+ ConnectionOpened _ _ _ <- receive endpoint
227+ tlog "Waiting for ConnectionClosed"
228+ ConnectionClosed _ <- receive endpoint
189229
190− -- Valid TCP address but invalid endpoint number
191− Left (FailedWith ConnectInvalidAddress _) <-
192− connect endpoint (EndPointAddress "127.0.0.1:8083:1") ReliableOrdered
230+ -- At this point the server will have sent a CloseSocket request to the
231+ -- client, and must block until the client responds
232+ tlog "Server waiting to connect to the client.."
233+ Right conn <- readMVar clientAddr >>= \addr -> connect endpoint addr ReliableOrdered
234+
235+
236+ tlog "Server waiting.."
193237
194− return ()
238+ client :: Transport -> MVar (N.HostName, N.ServiceName, EndPointId) -> MVar EndPointAddress -> MVar () -> IO ()
239+ client transport serverAddr clientAddr clientDone = do
240+ tlog "Client"
241+ Right endpoint <- newEndPoint transport
242+ putMVar clientAddr (address endpoint)
243+ let EndPointAddress myAddress = address endpoint
244+
245+ -- Connect to the server
246+ addr:_ <- N.getAddrInfo Nothing (Just "127.0.0.1") (Just "8084")
247+ sock <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
248+ N.setSocketOption sock N.ReuseAddr 1
249+ N.connect sock (N.addrAddress addr)
250+
251+ tlog "Connecting to endpoint"
252+ (_, _, endPointIx) <- readMVar serverAddr
253+ sendMany sock (encodeInt32 endPointIx : prependLength [myAddress])
254+
255+ -- Wait for acknowledgement
256+ ConnectionRequestAccepted <- recvInt32 sock
257+
258+ -- Request a new connection
259+ tlog "Requesting connection"
260+ let reqId = 0 :: Int32
261+ sendMany sock [encodeInt32 RequestConnectionId, encodeInt32 reqId]
262+ response <- replicateM 4 $ recvInt32 sock :: IO [Int32]
263+
264+ -- Close the connection again
265+ tlog "Closing connection"
266+ sendMany sock [encodeInt32 CloseConnection, encodeInt32 (response !! 3)]
267+
268+ -- Server will now send a CloseSocket request as its refcount reached 0
269+ tlog "Waiting for CloseSocket request"
270+ CloseSocket <- recvInt32 sock
271+
272+ unblocked <- newEmptyMVar
273+
274+ -- We should not hear from the server until we unblock him by
275+ -- responding to the CloseSocket request (in this case, we
276+ -- respond by sending a ConnectionRequest)
277+ forkTry $ do
278+ recvInt32 sock :: IO Int32
279+ isEmptyMVar unblocked >>= guard . not
280+ putMVar clientDone ()
281+
282+ threadDelay 1000000
283+
284+ tlog "Client ignores close socket and sends connection request"
285+ tlog "This should unblock the server"
286+ putMVar unblocked ()
287+ let reqId' = 1 :: Int32
288+ sendMany sock [encodeInt32 RequestConnectionId, encodeInt32 reqId']
195289
196290main :: IO ()
197291main = do
198− runTestIO "EarlyDisconnect" testEarlyDisconnect
199− runTestIO "InvalidAddress" testInvalidAddress
200− runTestIO "InvalidConnect" testInvalidConnect
201− runTestIO "IgnoreCloseSocket" testIgnoreCloseSocket
292+ runTest "EarlyDisconnect" testEarlyDisconnect
293+ runTest "InvalidAddress" testInvalidAddress
294+ runTest "InvalidConnect" testInvalidConnect
295+ runTest "IgnoreCloseSocket" testIgnoreCloseSocket
296+ runTest "BlockAfterCloseSocket" testBlockAfterCloseSocket
202297 Right transport <- createTransport "127.0.0.1" "8080"
203298 testTransport transport
network-transport-2/tests/TestTransport.hs+29−43
@@ -1,7 +1,7 @@
11module TestTransport where
22
33import Prelude hiding (catch)
4−import Control.Concurrent (forkIO)
4+import TestAuxiliary (forkTry, runTest)
55import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar, readMVar)
66import Control.Monad (replicateM, replicateM_, when, guard, forM_)
77import Control.Monad.Reader (ReaderT, runReaderT, ask)
@@ -15,8 +15,6 @@ import Data.ByteString (ByteString)
1515import Data.ByteString.Char8 (pack)
1616import Data.Map (Map)
1717import qualified Data.Map as Map (empty, insert, delete, findWithDefault, adjust, null, toList, map)
18−import System.IO (hFlush, stdout)
19−import System.Timeout (timeout)
2018
2119-- Server that echoes messages straight back to the origin endpoint.
2220echoServer :: EndPoint -> IO ()
@@ -90,7 +88,7 @@ testPingPong transport numPings = do
9088 result <- newEmptyMVar
9189
9290 -- Client
93− forkIO $ do
91+ forkTry $ do
9492 tlog "Ping client"
9593 Right endpoint <- newEndPoint transport
9694 ping endpoint server numPings "ping"
@@ -104,7 +102,7 @@ testEndPoints transport numPings = do
104102 server <- spawn transport echoServer
105103 dones <- replicateM 2 newEmptyMVar
106104
107− forM_ (zip dones ['A'..]) $ \(done, name) -> forkIO $ do
105+ forM_ (zip dones ['A'..]) $ \(done, name) -> forkTry $ do
108106 let name' :: ByteString
109107 name' = pack [name]
110108 Right endpoint <- newEndPoint transport
@@ -121,7 +119,7 @@ testConnections transport numPings = do
121119 result <- newEmptyMVar
122120
123121 -- Client
124− forkIO $ do
122+ forkTry $ do
125123 Right endpoint <- newEndPoint transport
126124
127125 -- Open two connections to the server
@@ -132,10 +130,10 @@ testConnections transport numPings = do
132130 ConnectionOpened serv2 _ _ <- receive endpoint
133131
134132 -- One thread to send "pingA" on the first connection
135− forkIO $ replicateM_ numPings $ send conn1 ["pingA"]
133+ forkTry $ replicateM_ numPings $ send conn1 ["pingA"]
136134
137135 -- One thread to send "pingB" on the second connection
138− forkIO $ replicateM_ numPings $ send conn2 ["pingB"]
136+ forkTry $ replicateM_ numPings $ send conn2 ["pingB"]
139137
140138 -- Verify server responses
141139 let verifyResponse 0 = putMVar result ()
@@ -159,7 +157,7 @@ testCloseOneConnection transport numPings = do
159157 result <- newEmptyMVar
160158
161159 -- Client
162− forkIO $ do
160+ forkTry $ do
163161 Right endpoint <- newEndPoint transport
164162
165163 -- Open two connections to the server
@@ -170,12 +168,12 @@ testCloseOneConnection transport numPings = do
170168 ConnectionOpened serv2 _ _ <- receive endpoint
171169
172170 -- One thread to send "pingA" on the first connection
173− forkIO $ do
171+ forkTry $ do
174172 replicateM_ numPings $ send conn1 ["pingA"]
175173 close conn1
176174
177175 -- One thread to send "pingB" on the second connection
178− forkIO $ replicateM_ (numPings * 2) $ send conn2 ["pingB"]
176+ forkTry $ replicateM_ (numPings * 2) $ send conn2 ["pingB"]
179177
180178 -- Verify server responses
181179 let verifyResponse 0 = putMVar result ()
@@ -203,7 +201,7 @@ testCloseOneDirection transport numPings = do
203201 doneB <- newEmptyMVar
204202
205203 -- A
206− forkIO $ do
204+ forkTry $ do
207205 tlog "A"
208206 Right endpoint <- newEndPoint transport
209207 tlog (show (address endpoint))
@@ -239,7 +237,7 @@ testCloseOneDirection transport numPings = do
239237 putMVar doneA ()
240238
241239 -- B
242− forkIO $ do
240+ forkTry $ do
243241 tlog "B"
244242 Right endpoint <- newEndPoint transport
245243 tlog (show (address endpoint))
@@ -318,7 +316,7 @@ testCloseReopen transport numPings = do
318316 let numRepeats = 2 :: Int
319317
320318 -- A
321− forkIO $ do
319+ forkTry $ do
322320 Right endpoint <- newEndPoint transport
323321
324322 forM_ [1 .. numRepeats] $ \i -> do
@@ -337,7 +335,7 @@ testCloseReopen transport numPings = do
337335 tlog "A finishing"
338336
339337 -- B
340− forkIO $ do
338+ forkTry $ do
341339 Right endpoint <- newEndPoint transport
342340 putMVar addrB (address endpoint)
343341
@@ -360,13 +358,13 @@ testParallelConnects transport numPings = do
360358 Right endpoint <- newEndPoint transport
361359
362360 -- Spawn lots of clients
363− forM_ [1 .. numPings] $ \i -> forkIO $ do
361+ forM_ [1 .. numPings] $ \i -> forkTry $ do
364362 Right conn <- connect endpoint server ReliableOrdered
365363 send conn [pack $ "ping" ++ show i]
366364 send conn [pack $ "ping" ++ show i]
367365 close conn
368366
369− forkIO $ do
367+ forkTry $ do
370368 eventss <- collect endpoint (numPings * 4)
371369 -- Check that no pings got sent to the wrong connection
372370 forM_ eventss $ \(_, [[ping1], [ping2]]) ->
@@ -381,7 +379,7 @@ testSendAfterClose transport _ = do
381379 server <- spawn transport echoServer
382380 clientDone <- newEmptyMVar
383381
384− forkIO $ do
382+ forkTry $ do
385383 Right endpoint <- newEndPoint transport
386384
387385 -- We request two lightweight connections
@@ -403,15 +401,13 @@ testSendAfterClose transport _ = do
403401
404402 takeMVar clientDone
405403
406−
407−
408404-- Test that closing the same connection twice has no effect
409405testCloseTwice :: Transport -> Int -> IO ()
410406testCloseTwice transport _ = do
411407 server <- spawn transport echoServer
412408 clientDone <- newEmptyMVar
413409
414− forkIO $ do
410+ forkTry $ do
415411 Right endpoint <- newEndPoint transport
416412
417413 -- We request two lightweight connections
@@ -437,30 +433,20 @@ testCloseTwice transport _ = do
437433
438434 takeMVar clientDone
439435
440−runTestIO :: String -> IO () -> IO ()
441−runTestIO description test = do
442− putStr $ "Running " ++ show description ++ ": "
443− hFlush stdout
444− test
445− putStrLn "ok"
446−
447−runTest :: String -> (Transport -> Int -> IO ()) -> ReaderT (Transport, Int) IO ()
448−runTest description test = do
436+runTransportTest :: String -> (Transport -> Int -> IO ()) -> ReaderT (Transport, Int) IO ()
437+runTransportTest description test = do
449438 (transport, numPings) <- ask
450− done <- liftIO $ timeout 10000000 $ runTestIO description (test transport numPings)
451− case done of
452− Just () -> return ()
453− Nothing -> error "timeout"
439+ liftIO $ runTest description (test transport numPings)
454440
455441-- Transport tests
456442testTransport :: Transport -> IO ()
457443testTransport transport = flip runReaderT (transport, 10000) $ do
458− runTest "PingPong" testPingPong
459− runTest "EndPoints" testEndPoints
460− runTest "Connections" testConnections
461− runTest "CloseOneConnection" testCloseOneConnection
462− runTest "CloseOneDirection" testCloseOneDirection
463− runTest "CloseReopen" testCloseReopen
464− runTest "ParallelConnects" testParallelConnects
465− runTest "SendAfterClose" testSendAfterClose
466− runTest "CloseTwice" testCloseTwice
444+ runTransportTest "PingPong" testPingPong
445+ runTransportTest "EndPoints" testEndPoints
446+ runTransportTest "Connections" testConnections
447+ runTransportTest "CloseOneConnection" testCloseOneConnection
448+ runTransportTest "CloseOneDirection" testCloseOneDirection
449+ runTransportTest "CloseReopen" testCloseReopen
450+ runTransportTest "ParallelConnects" testParallelConnects
451+ runTransportTest "SendAfterClose" testSendAfterClose
452+ runTransportTest "CloseTwice" testCloseTwice