{-# LANGUAGE RebindableSyntax #-}
module Network.Transport.Tests where
import Prelude hiding
( (>>=)
, return
, fail
, (>>)
#if ! MIN_VERSION_base(4,6,0)
, catch
#endif
)
import Control.Concurrent (forkIO, killThread, yield)
import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar, readMVar, tryTakeMVar, modifyMVar_, newMVar)
import Control.Exception
( evaluate
, throw
, throwIO
, bracket
, catch
, ErrorCall(..)
)
import Control.Monad (replicateM, replicateM_, when, guard, forM_, unless)
import Control.Monad.Error ()
import Control.Applicative ((<$>))
import Network.Transport
import Network.Transport.Internal (tlog, tryIO, timeoutMaybe)
import Network.Transport.Util (spawn)
import System.Random (randomIO)
import Data.ByteString (ByteString)
import Data.ByteString.Char8 (pack)
import Data.Map (Map)
import qualified Data.Map as Map (empty, insert, delete, findWithDefault, adjust, null, toList, map)
import Data.String (fromString)
import Data.List (permutations)
import Network.Transport.Tests.Auxiliary (forkTry, runTests, trySome, randomThreadDelay)
import Network.Transport.Tests.Traced
echoServer :: EndPoint -> IO ()
echoServer endpoint = do
go Map.empty
where
go :: Map ConnectionId Connection -> IO ()
go cs = do
event <- receive endpoint
case event of
ConnectionOpened cid rel addr -> do
tlog $ "Opened new connection " ++ show cid
Right conn <- connect endpoint addr rel defaultConnectHints
go (Map.insert cid conn cs)
Received cid payload -> do
send (Map.findWithDefault (error $ "Received: Invalid cid " ++ show cid) cid cs) payload
go cs
ConnectionClosed cid -> do
tlog $ "Close connection " ++ show cid
close (Map.findWithDefault (error $ "ConnectionClosed: Invalid cid " ++ show cid) cid cs)
go (Map.delete cid cs)
ReceivedMulticast _ _ ->
go cs
ErrorEvent _ ->
putStrLn $ "Echo server received error event: " ++ show event
EndPointClosed ->
return ()
ping :: EndPoint -> EndPointAddress -> Int -> ByteString -> IO ()
ping endpoint server numPings msg = do
tlog "Connect to echo server"
Right conn <- connect endpoint server ReliableOrdered defaultConnectHints
tlog "Wait for ConnectionOpened message"
ConnectionOpened cid _ _ <- receive endpoint
tlog "Send ping and wait for reply"
replicateM_ numPings $ do
send conn [msg]
Received cid' [reply] <- receive endpoint ; True <- return $ cid == cid' && reply == msg
return ()
tlog "Close the connection"
close conn
tlog "Wait for ConnectionClosed message"
ConnectionClosed cid' <- receive endpoint ; True <- return $ cid == cid'
tlog "Ping client done"
testPingPong :: Transport -> Int -> IO ()
testPingPong transport numPings = do
tlog "Starting ping pong test"
server <- spawn transport echoServer
result <- newEmptyMVar
forkTry $ do
tlog "Ping client"
Right endpoint <- newEndPoint transport
ping endpoint server numPings "ping"
putMVar result ()
takeMVar result
testEndPoints :: Transport -> Int -> IO ()
testEndPoints transport numPings = do
server <- spawn transport echoServer
dones <- replicateM 2 newEmptyMVar
forM_ (zip dones ['A'..]) $ \(done, name) -> forkTry $ do
let name' :: ByteString
name' = pack [name]
Right endpoint <- newEndPoint transport
tlog $ "Ping client " ++ show name' ++ ": " ++ show (address endpoint)
ping endpoint server numPings name'
putMVar done ()
forM_ dones takeMVar
testConnections :: Transport -> Int -> IO ()
testConnections transport numPings = do
server <- spawn transport echoServer
result <- newEmptyMVar
forkTry $ do
Right endpoint <- newEndPoint transport
Right conn1 <- connect endpoint server ReliableOrdered defaultConnectHints
ConnectionOpened serv1 _ _ <- receive endpoint
Right conn2 <- connect endpoint server ReliableOrdered defaultConnectHints
ConnectionOpened serv2 _ _ <- receive endpoint
forkTry $ replicateM_ numPings $ send conn1 ["pingA"]
forkTry $ replicateM_ numPings $ send conn2 ["pingB"]
let verifyResponse 0 = putMVar result ()
verifyResponse n = do
event <- receive endpoint
case event of
Received cid [payload] -> do
when (cid == serv1 && payload /= "pingA") $ error "Wrong message"
when (cid == serv2 && payload /= "pingB") $ error "Wrong message"
verifyResponse (n - 1)
_ ->
verifyResponse n
verifyResponse (2 * numPings)
takeMVar result
testCloseOneConnection :: Transport -> Int -> IO ()
testCloseOneConnection transport numPings = do
server <- spawn transport echoServer
result <- newEmptyMVar
forkTry $ do
Right endpoint <- newEndPoint transport
Right conn1 <- connect endpoint server ReliableOrdered defaultConnectHints
ConnectionOpened serv1 _ _ <- receive endpoint
Right conn2 <- connect endpoint server ReliableOrdered defaultConnectHints
ConnectionOpened serv2 _ _ <- receive endpoint
forkTry $ do
replicateM_ numPings $ send conn1 ["pingA"]
close conn1
forkTry $ replicateM_ (numPings * 2) $ send conn2 ["pingB"]
let verifyResponse 0 = putMVar result ()
verifyResponse n = do
event <- receive endpoint
case event of
Received cid [payload] -> do
when (cid == serv1 && payload /= "pingA") $ error "Wrong message"
when (cid == serv2 && payload /= "pingB") $ error "Wrong message"
verifyResponse (n - 1)
_ ->
verifyResponse n
verifyResponse (3 * numPings)
takeMVar result
testCloseOneDirection :: Transport -> Int -> IO ()
testCloseOneDirection transport numPings = do
addrA <- newEmptyMVar
addrB <- newEmptyMVar
doneA <- newEmptyMVar
doneB <- newEmptyMVar
forkTry $ do
tlog "A"
Right endpoint <- newEndPoint transport
tlog (show (address endpoint))
putMVar addrA (address endpoint)
tlog "Connect to B"
Right conn <- readMVar addrB >>= \addr -> connect endpoint addr ReliableOrdered defaultConnectHints
tlog "Wait for B"
ConnectionOpened cid _ _ <- receive endpoint
tlog "Send pings to B"
replicateM_ numPings $ send conn ["ping"]
tlog "Close connection"
close conn
tlog "Wait for pongs from B"
replicateM_ numPings $ do Received _ _ <- receive endpoint ; return ()
tlog "Wait for B to close connection"
ConnectionClosed cid' <- receive endpoint
guard (cid == cid')
tlog "Done"
putMVar doneA ()
forkTry $ do
tlog "B"
Right endpoint <- newEndPoint transport
tlog (show (address endpoint))
putMVar addrB (address endpoint)
tlog "Wait for A to connect"
ConnectionOpened cid _ _ <- receive endpoint
tlog "Connect to A"
Right conn <- readMVar addrA >>= \addr -> connect endpoint addr ReliableOrdered defaultConnectHints
tlog "Wait for pings from A"
replicateM_ numPings $ do Received _ _ <- receive endpoint ; return ()
tlog "Wait for A to close connection"
ConnectionClosed cid' <- receive endpoint
guard (cid == cid')
tlog "Send pongs to A"
replicateM_ numPings $ send conn ["pong"]
tlog "Close connection to A"
close conn
tlog "Done"
putMVar doneB ()
mapM_ takeMVar [doneA, doneB]
collect :: EndPoint -> Maybe Int -> Maybe Int -> IO [(ConnectionId, [[ByteString]])]
collect endPoint maxEvents timeout = go maxEvents Map.empty Map.empty
where
go (Just 0) open closed = finish open closed
go n open closed = do
mEvent <- tryIO . timeoutMaybe timeout (userError "timeout") $ receive endPoint
case mEvent of
Left _ -> finish open closed
Right event -> do
let n' = (\x -> x - 1) <$> n
case event of
ConnectionOpened cid _ _ ->
go n' (Map.insert cid [] open) closed
ConnectionClosed cid ->
let list = Map.findWithDefault (error "Invalid ConnectionClosed") cid open in
go n' (Map.delete cid open) (Map.insert cid list closed)
Received cid msg ->
go n' (Map.adjust (msg :) cid open) closed
ReceivedMulticast _ _ ->
fail "Unexpected multicast"
ErrorEvent _ ->
fail "Unexpected error"
EndPointClosed ->
fail "Unexpected endpoint closure"
finish open closed =
if Map.null open
then return . Map.toList . Map.map reverse $ closed
else fail $ "Open connections: " ++ show (map fst . Map.toList $ open)
testCloseReopen :: Transport -> Int -> IO ()
testCloseReopen transport numPings = do
addrB <- newEmptyMVar
doneB <- newEmptyMVar
let numRepeats = 2 :: Int
forkTry $ do
Right endpoint <- newEndPoint transport
forM_ [1 .. numRepeats] $ \i -> do
tlog "A connecting"
Right conn <- readMVar addrB >>= \addr -> connect endpoint addr ReliableOrdered defaultConnectHints
tlog "A pinging"
forM_ [1 .. numPings] $ \j -> send conn [pack $ "ping" ++ show i ++ "/" ++ show j]
tlog "A closing"
close conn
tlog "A finishing"
forkTry $ do
Right endpoint <- newEndPoint transport
putMVar addrB (address endpoint)
eventss <- collect endpoint (Just (numRepeats * (numPings + 2))) Nothing
forM_ (zip [1 .. numRepeats] eventss) $ \(i, (_, events)) -> do
forM_ (zip [1 .. numPings] events) $ \(j, event) -> do
guard (event == [pack $ "ping" ++ show i ++ "/" ++ show j])
putMVar doneB ()
takeMVar doneB
testParallelConnects :: Transport -> Int -> IO ()
testParallelConnects transport numPings = do
server <- spawn transport echoServer
done <- newEmptyMVar
Right endpoint <- newEndPoint transport
forM_ [1 .. numPings] $ \i -> forkTry $ do
Right conn <- connect endpoint server ReliableOrdered defaultConnectHints
send conn [pack $ "ping" ++ show i]
send conn [pack $ "ping" ++ show i]
close conn
forkTry $ do
eventss <- collect endpoint (Just (numPings * 4)) Nothing
forM_ eventss $ \(_, [[ping1], [ping2]]) ->
guard (ping1 == ping2)
putMVar done ()
takeMVar done
testSelfSend :: Transport -> IO ()
testSelfSend transport = do
Right endpoint <- newEndPoint transport
Right conn <- connect endpoint (address endpoint) ReliableOrdered
defaultConnectHints
ConnectionOpened _ _ _ <- receive endpoint
do send conn [ error "bang!" ]
error "testSelfSend: send didn't fail"
`catch` (\(ErrorCall "bang!") -> return ())
close conn
ConnectionClosed _ <- receive endpoint
closeEndPoint endpoint
testSendAfterClose :: Transport -> Int -> IO ()
testSendAfterClose transport numRepeats = do
server <- spawn transport echoServer
clientDone <- newEmptyMVar
forkTry $ do
Right endpoint <- newEndPoint transport
replicateM numRepeats $ do
Right conn1 <- connect endpoint server ReliableOrdered defaultConnectHints
Right conn2 <- connect endpoint server ReliableOrdered defaultConnectHints
close conn2
Left (TransportError SendClosed _) <- send conn2 ["ping2"]
close conn1
Left (TransportError SendClosed _) <- send conn2 ["ping2"]
return ()
putMVar clientDone ()
takeMVar clientDone
testCloseTwice :: Transport -> Int -> IO ()
testCloseTwice transport numRepeats = do
server <- spawn transport echoServer
clientDone <- newEmptyMVar
forkTry $ do
Right endpoint <- newEndPoint transport
replicateM numRepeats $ do
Right conn1 <- connect endpoint server ReliableOrdered defaultConnectHints
Right conn2 <- connect endpoint server ReliableOrdered defaultConnectHints
close conn2
close conn2
send conn1 ["ping"]
close conn1
ConnectionOpened cid1 _ _ <- receive endpoint
ConnectionOpened cid2 _ _ <- receive endpoint
ms <- replicateM 3 $ receive endpoint
True <- return $ testStreams ms [ [ ConnectionClosed cid2 ]
, [ Received cid1 ["ping"]
, ConnectionClosed cid1 ]
]
return ()
putMVar clientDone ()
takeMVar clientDone
testConnectToSelf :: Transport -> Int -> IO ()
testConnectToSelf transport numPings = do
done <- newEmptyMVar
reconnect <- newEmptyMVar
Right endpoint <- newEndPoint transport
tlog "Creating self-connection"
Right conn <- connect endpoint (address endpoint) ReliableOrdered defaultConnectHints
tlog "Talk to myself"
forkTry $ do
tlog $ "writing"
tlog $ "Sending ping"
replicateM_ numPings $ send conn ["ping"]
tlog $ "Closing connection"
close conn
readMVar reconnect
ConnectionOpened cid' _ _ <- receive endpoint
ConnectionClosed cid'' <- receive endpoint ; True <- return $ cid' == cid''
return ()
forkTry $ do
tlog $ "reading"
tlog "Waiting for ConnectionOpened"
ConnectionOpened cid _ addr <- receive endpoint
tlog "Waiting for Received"
replicateM_ numPings $ do
Received cid' ["ping"] <- receive endpoint ; True <- return $ cid == cid'
return ()
tlog "Waiting for ConnectionClosed"
ConnectionClosed cid' <- receive endpoint ; True <- return $ cid == cid'
putMVar reconnect ()
Right conn <- connect endpoint addr ReliableOrdered defaultConnectHints
close conn
tlog "Done"
putMVar done ()
takeMVar done
testConnectToSelfTwice :: Transport -> Int -> IO ()
testConnectToSelfTwice transport numPings = do
done <- newEmptyMVar
Right endpoint <- newEndPoint transport
tlog "Talk to myself"
firstConnectionMade <- newEmptyMVar
forkTry $ do
tlog "Creating self-connection"
Right conn1 <- connect endpoint (address endpoint) ReliableOrdered defaultConnectHints
putMVar firstConnectionMade ()
tlog $ "writing"
tlog $ "Sending ping"
replicateM_ numPings $ send conn1 ["pingA"]
tlog $ "Closing connection"
close conn1
forkTry $ do
takeMVar firstConnectionMade
tlog "Creating self-connection"
Right conn2 <- connect endpoint (address endpoint) ReliableOrdered defaultConnectHints
tlog $ "writing"
tlog $ "Sending ping"
replicateM_ numPings $ send conn2 ["pingB"]
tlog $ "Closing connection"
close conn2
forkTry $ do
tlog $ "reading"
[(_, events1), (_, events2)] <- collect endpoint (Just (2 * (numPings + 2))) Nothing
True <- return $ events1 == replicate numPings ["pingA"]
True <- return $ events2 == replicate numPings ["pingB"]
tlog "Done"
putMVar done ()
takeMVar done
testCloseSelf :: IO (Either String Transport) -> IO ()
testCloseSelf newTransport = do
Right transport <- newTransport
Right endpoint1 <- newEndPoint transport
Right endpoint2 <- newEndPoint transport
Right conn1 <- connect endpoint1 (address endpoint1) ReliableOrdered defaultConnectHints
ConnectionOpened _ _ _ <- receive endpoint1
Right conn2 <- connect endpoint1 (address endpoint1) ReliableOrdered defaultConnectHints
ConnectionOpened _ _ _ <- receive endpoint1
Right conn3 <- connect endpoint2 (address endpoint2) ReliableOrdered defaultConnectHints
ConnectionOpened _ _ _ <- receive endpoint2
close conn1
ConnectionClosed _ <- receive endpoint1
Left (TransportError SendClosed _) <- send conn1 ["ping"]
closeEndPoint endpoint1
EndPointClosed <- receive endpoint1
Left (TransportError SendFailed _) <- send conn2 ["ping"]
Left (TransportError ConnectFailed _) <- connect endpoint1 (address endpoint1) ReliableOrdered defaultConnectHints
Right () <- send conn3 ["ping"]
Received _ _ <- receive endpoint2
closeTransport transport
Left (TransportError SendFailed _) <- send conn3 ["ping"]
Left r <- connect endpoint2 (address endpoint2) ReliableOrdered defaultConnectHints
case r of
TransportError ConnectFailed _ -> return ()
_ -> do putStrLn $ "Actual: " ++ show r
TransportError ConnectFailed _ <- return r
return ()
return ()
testCloseEndPoint :: Transport -> Int -> IO ()
testCloseEndPoint transport _ = do
serverFirstTestDone <- newEmptyMVar
serverDone <- newEmptyMVar
clientDone <- newEmptyMVar
clientAddr1 <- newEmptyMVar
clientAddr2 <- newEmptyMVar
serverAddr <- newEmptyMVar
forkTry $ do
Right endpoint <- newEndPoint transport
putMVar serverAddr (address endpoint)
do
theirAddr <- readMVar clientAddr1
ConnectionOpened cid ReliableOrdered addr <- receive endpoint
Right conn <- connect endpoint addr ReliableOrdered defaultConnectHints
close conn
putMVar serverFirstTestDone ()
ConnectionClosed cid' <- receive endpoint ; True <- return $ cid == cid'
putMVar serverAddr (address endpoint)
return ()
do
theirAddr <- readMVar clientAddr2
ConnectionOpened cid ReliableOrdered addr <- receive endpoint
Right conn <- connect endpoint addr ReliableOrdered defaultConnectHints
close conn
Received cid' ["ping"] <- receive endpoint ; True <- return $ cid == cid'
Right conn <- connect endpoint theirAddr ReliableOrdered defaultConnectHints
send conn ["pong"]
ConnectionClosed cid'' <- receive endpoint ; True <- return $ cid == cid''
ErrorEvent (TransportError (EventConnectionLost addr') _) <- receive endpoint ; True <- return $ addr' == theirAddr
Left (TransportError SendFailed _) <- send conn ["pong2"]
return ()
putMVar serverDone ()
forkTry $ do
do
theirAddr <- takeMVar serverAddr
Right endpoint <- newEndPoint transport
putMVar clientAddr1 (address endpoint)
Right _ <- connect endpoint theirAddr ReliableOrdered defaultConnectHints
ConnectionOpened cid _ _ <- receive endpoint
ConnectionClosed cid' <- receive endpoint ; True <- return $ cid == cid'
readMVar serverFirstTestDone
closeEndPoint endpoint
EndPointClosed <- receive endpoint
return ()
do
theirAddr <- takeMVar serverAddr
Right endpoint <- newEndPoint transport
putMVar clientAddr2 (address endpoint)
Right conn <- connect endpoint theirAddr ReliableOrdered defaultConnectHints
ConnectionOpened cid _ _ <- receive endpoint
ConnectionClosed cid' <- receive endpoint ; True <- return $ cid == cid'
send conn ["ping"]
ConnectionOpened cid ReliableOrdered addr <- receive endpoint
Received cid' ["pong"] <- receive endpoint ; True <- return $ cid == cid'
closeEndPoint endpoint
EndPointClosed <- receive endpoint
Left (TransportError SendFailed _) <- send conn ["ping2"]
() <- close conn
Left (TransportError ConnectFailed _) <- connect endpoint theirAddr ReliableOrdered defaultConnectHints
return ()
putMVar clientDone ()
mapM_ takeMVar [serverDone, clientDone]
testCloseTransport :: IO (Either String Transport) -> IO ()
testCloseTransport newTransport = do
serverDone <- newEmptyMVar
clientDone <- newEmptyMVar
clientAddr1 <- newEmptyMVar
clientAddr2 <- newEmptyMVar
serverAddr <- newEmptyMVar
forkTry $ do
Right transport <- newTransport
Right endpoint <- newEndPoint transport
putMVar serverAddr (address endpoint)
theirAddr1 <- readMVar clientAddr1
ConnectionOpened cid1 ReliableOrdered addr <- receive endpoint
Right conn <- connect endpoint theirAddr1 ReliableOrdered defaultConnectHints
close conn
Right conn <- connect endpoint addr ReliableOrdered defaultConnectHints
close conn
theirAddr2 <- readMVar clientAddr2
ConnectionOpened cid2 ReliableOrdered addr' <- receive endpoint
Received cid2' ["ping"] <- receive endpoint ; True <- return $ cid2' == cid2
Right conn <- connect endpoint theirAddr2 ReliableOrdered defaultConnectHints
send conn ["pong"]
close conn
Right conn <- connect endpoint addr' ReliableOrdered defaultConnectHints
send conn ["pong"]
evs <- replicateM 3 $ receive endpoint
let expected = [ ConnectionClosed cid1
, ConnectionClosed cid2
, ErrorEvent (TransportError (EventConnectionLost addr') "")
]
True <- return $ expected `elem` permutations evs
Left (TransportError SendFailed _) <- send conn ["pong2"]
putMVar serverDone ()
forkTry $ do
Right transport <- newTransport
theirAddr <- readMVar serverAddr
Right endpoint1 <- newEndPoint transport
putMVar clientAddr1 (address endpoint1)
Right _ <- connect endpoint1 theirAddr ReliableOrdered defaultConnectHints
ConnectionOpened cid ReliableOrdered _ <- receive endpoint1
ConnectionClosed cid' <- receive endpoint1 ; True <- return $ cid == cid'
ConnectionOpened cid ReliableOrdered _ <- receive endpoint1
ConnectionClosed cid' <- receive endpoint1 ; True <- return $ cid == cid'
Right endpoint2 <- newEndPoint transport
putMVar clientAddr2 (address endpoint2)
Right conn <- connect endpoint2 theirAddr ReliableOrdered defaultConnectHints
send conn ["ping"]
ConnectionOpened cid ReliableOrdered _ <- receive endpoint2
Received cid' ["pong"] <- receive endpoint2 ; True <- return $ cid == cid'
ConnectionClosed cid'' <- receive endpoint2 ; True <- return $ cid == cid''
ConnectionOpened cid ReliableOrdered _ <- receive endpoint2
Received cid' ["pong"] <- receive endpoint2 ; True <- return $ cid == cid'
closeTransport transport
EndPointClosed <- receive endpoint1
EndPointClosed <- receive endpoint2
Left (TransportError SendFailed _) <- send conn ["ping2"]
() <- close conn
Left (TransportError ConnectFailed _) <- connect endpoint1 theirAddr ReliableOrdered defaultConnectHints
Left (TransportError ConnectFailed _) <- connect endpoint2 theirAddr ReliableOrdered defaultConnectHints
Left (TransportError NewEndPointFailed _) <- newEndPoint transport
putMVar clientDone ()
mapM_ takeMVar [serverDone, clientDone]
testConnectClosedEndPoint :: Transport -> IO ()
testConnectClosedEndPoint transport = do
serverAddr <- newEmptyMVar
serverClosed <- newEmptyMVar
clientDone <- newEmptyMVar
forkTry $ do
Right endpoint <- newEndPoint transport
putMVar serverAddr (address endpoint)
closeEndPoint endpoint
putMVar serverClosed ()
forkTry $ do
Right endpoint <- newEndPoint transport
readMVar serverClosed
Left (TransportError ConnectNotFound _) <- readMVar serverAddr >>= \addr -> connect endpoint addr ReliableOrdered defaultConnectHints
putMVar clientDone ()
takeMVar clientDone
testExceptionOnReceive :: IO (Either String Transport) -> IO ()
testExceptionOnReceive newTransport = do
Right transport <- newTransport
Right endpoint1 <- newEndPoint transport
closeEndPoint endpoint1
EndPointClosed <- receive endpoint1
Left _ <- trySome (receive endpoint1 >>= evaluate)
Right endpoint2 <- newEndPoint transport
closeTransport transport
EndPointClosed <- receive endpoint2
Left _ <- trySome (receive endpoint2 >>= evaluate)
return ()
testSendException :: IO (Either String Transport) -> IO ()
testSendException newTransport = do
Right transport <- newTransport
Right endpoint1 <- newEndPoint transport
Right endpoint2 <- newEndPoint transport
Right conn <- connect endpoint1 (address endpoint2) ReliableOrdered defaultConnectHints
ConnectionOpened _ _ _ <- receive endpoint2
Left (TransportError SendFailed _) <- send conn (throw $ userError "uhoh")
ErrorEvent (TransportError (EventConnectionLost _) _) <- receive endpoint1
ErrorEvent (TransportError (EventConnectionLost _) _) <- receive endpoint2
Right conn2 <- connect endpoint1 (address endpoint2) ReliableOrdered defaultConnectHints
send conn2 ["ping"]
close conn2
ConnectionOpened _ _ _ <- receive endpoint2
Received _ ["ping"] <- receive endpoint2
ConnectionClosed _ <- receive endpoint2
return ()
testKill :: IO (Either String Transport) -> Int -> IO ()
testKill newTransport numThreads = do
Right transport1 <- newTransport
Right transport2 <- newTransport
Right endpoint1 <- newEndPoint transport1
Right endpoint2 <- newEndPoint transport2
threads <- replicateM numThreads . forkIO $ do
randomThreadDelay 100
bracket (connect endpoint1 (address endpoint2) ReliableOrdered defaultConnectHints)
(\(Right conn) -> close conn)
(\(Right conn) -> do randomThreadDelay 100
Right () <- send conn ["ping"]
randomThreadDelay 100)
numAlive <- newMVar (0 :: Int)
forkIO . forM_ threads $ \tid -> do
shouldKill <- randomIO
if shouldKill
then randomThreadDelay 600 >> killThread tid
else modifyMVar_ numAlive (return . (+ 1))
eventss <- collect endpoint2 Nothing (Just 1000000)
let actualPings = sum . map (length . snd) $ eventss
expectedPings <- takeMVar numAlive
unless (actualPings >= expectedPings) $
throwIO (userError "Missing pings")
testCrossing :: Transport -> Int -> IO ()
testCrossing transport numRepeats = do
[aAddr, bAddr] <- replicateM 2 newEmptyMVar
[aDone, bDone] <- replicateM 2 newEmptyMVar
[aGo, bGo] <- replicateM 2 newEmptyMVar
[aTimeout, bTimeout] <- replicateM 2 newEmptyMVar
let hints = defaultConnectHints {
connectTimeout = Just 5000000
}
forkTry $ do
Right endpoint <- newEndPoint transport
putMVar aAddr (address endpoint)
theirAddress <- readMVar bAddr
replicateM_ numRepeats $ do
takeMVar aGo >> yield
connectResult <- connect endpoint theirAddress ReliableOrdered hints
case connectResult of
Right conn -> close conn
Left (TransportError ConnectTimeout _) -> putMVar aTimeout ()
Left (TransportError ConnectFailed _) -> readMVar bTimeout
Left err -> throwIO . userError $ "testCrossed: " ++ show err
putMVar aDone ()
forkTry $ do
Right endpoint <- newEndPoint transport
putMVar bAddr (address endpoint)
theirAddress <- readMVar aAddr
replicateM_ numRepeats $ do
takeMVar bGo >> yield
connectResult <- connect endpoint theirAddress ReliableOrdered hints
case connectResult of
Right conn -> close conn
Left (TransportError ConnectTimeout _) -> putMVar bTimeout ()
Left (TransportError ConnectFailed _) -> readMVar aTimeout
Left err -> throwIO . userError $ "testCrossed: " ++ show err
putMVar bDone ()
forM_ [1 .. numRepeats] $ \_i -> do
tryTakeMVar aTimeout
tryTakeMVar bTimeout
b <- randomIO
if b then do putMVar aGo () ; putMVar bGo ()
else do putMVar bGo () ; putMVar aGo ()
yield
takeMVar aDone
takeMVar bDone
testTransport :: IO (Either String Transport) -> IO ()
testTransport = testTransportWithFilter (const True)
testTransportWithFilter :: (String -> Bool) -> IO (Either String Transport) -> IO ()
testTransportWithFilter p newTransport = do
Right transport <- newTransport
runTests $ filter (p . fst)
[ ("PingPong", testPingPong transport numPings)
, ("EndPoints", testEndPoints transport numPings)
, ("Connections", testConnections transport numPings)
, ("CloseOneConnection", testCloseOneConnection transport numPings)
, ("CloseOneDirection", testCloseOneDirection transport numPings)
, ("CloseReopen", testCloseReopen transport numPings)
, ("ParallelConnects", testParallelConnects transport numPings)
, ("SelfSend", testSelfSend transport)
, ("SendAfterClose", testSendAfterClose transport 100)
, ("Crossing", testCrossing transport 10)
, ("CloseTwice", testCloseTwice transport 100)
, ("ConnectToSelf", testConnectToSelf transport numPings)
, ("ConnectToSelfTwice", testConnectToSelfTwice transport numPings)
, ("CloseSelf", testCloseSelf newTransport)
, ("CloseEndPoint", testCloseEndPoint transport numPings)
, ("CloseTransport", testCloseTransport newTransport)
, ("ConnectClosedEndPoint", testConnectClosedEndPoint transport)
, ("ExceptionOnReceive", testExceptionOnReceive newTransport)
, ("SendException", testSendException newTransport)
, ("Kill", testKill newTransport 1000)
]
where
numPings = 10000 :: Int
testStreams :: Eq a => [a] -> [[a]] -> Bool
testStreams [] ys = all null ys
testStreams (x:xs) ys =
case go [] ys of
[] -> False
ys' -> testStreams xs ys'
where
go _ [] = []
go c ([]:zss) = go c zss
go c (z'@(z:zs):zss)
| x == z = (zs:c)++zss
| otherwise = go (z':c) zss