@@ -17,6 +17,7 @@ import Test.QuickCheck
1717 , forAllShrink
1818 , Property
1919 , Arbitrary(arbitrary)
20+ , elements
2021 )
2122import Test.QuickCheck.Property (morallyDubiousIOProperty, Result(..), result)
2223import Test.HUnit (Assertion, assertFailure)
@@ -32,23 +33,47 @@ import Data.ByteString.Char8 (pack)
3233import qualified Data.ByteString as BSS (concat)
3334
3435import Network.Transport
35−import Network.Transport.TCP (createTransport, defaultTCPParameters)
36+import Network.Transport.TCP
37+ ( createTransportExposeInternals
38+ , defaultTCPParameters
39+ , TransportInternals(socketBetween)
40+ )
41+
42+import Network.Transport.TCP.Mock.Socket (scheduleReadAction, sClose)
3643
3744--------------------------------------------------------------------------------
3845-- Script infrastructure --
3946--------------------------------------------------------------------------------
4047
48+-- | We randomly generate /scripts/ which are essentially a deep embedding of
49+-- the Transport API. These scripts are then executed and the results compared
50+-- against an abstract interpreter.
4151data ScriptCmd =
52+ -- | Create a new endpoint
4253 NewEndPoint
54+ -- | @Connect i j@ creates a connection from endpoint @i@ to endpoint @j@,
55+ -- where @i@ and @j@ are indices and refer to the @i@th and @j@th endpoint
56+ -- created by NewEndPoint
4357 | Connect Int Int
58+ -- | @Close i@ closes the @i@ connection created using 'Connect'. Note that
59+ -- closing a connection does not shift other indices; in other words, in
60+ -- @[Connect 0 0, Close 0, Connect 0 0, Close 0]@ the second 'Close'
61+ -- refers to the first (already closed) connection
4462 | Close Int
63+ -- | @Send i bs@ sends payload @bs@ on the @i@ connection created
4564 | Send Int [ByteString]
65+ -- | @BreakAfterReads n i j@ force-closes the socket between endpoints @i@
66+ -- and @j@ after @n@ reads by @i@
67+ --
68+ -- We should have @i /= j@ because the TCP transport does not use sockets
69+ -- for connections from an endpoint to itself
70+ | BreakAfterReads Int Int Int
4671 deriving Show
4772
4873type Script = [ScriptCmd]
4974
50−execScript :: Transport -> Script -> IO (Map Int [Event])
51−execScript transport script = do
75+execScript :: (Transport, TransportInternals) -> Script -> IO (Map Int [Event])
76+execScript (transport, transportInternals) script = do
5277 chan <- newChan
5378 runScript chan script
5479 collectAll chan
@@ -78,6 +103,10 @@ execScript transport script = do
78103 Right () <- send (conns !! connIx) payload
79104 threadDelay 10000
80105 go endPoints conns cmds
106+ go endPoints conns (BreakAfterReads n i j : cmds) = do
107+ sock <- socketBetween transportInternals (address (endPoints !! i)) (address (endPoints !! j))
108+ scheduleReadAction sock n (sClose sock)
109+ go endPoints conns cmds
81110
82111 forwardTo :: Chan (Maybe (Int, Event)) -> (Int, EndPoint) -> IO ()
83112 forwardTo chan (ix, endPoint) = go
@@ -136,6 +165,8 @@ verify script = go script []
136165 go cmds conns (Map.insert epIx epEvs' evs)
137166 _ ->
138167 Just $ "Missing (Received " ++ show connId ++ " " ++ show payload ++ ") event in " ++ show epEvs
168+ go (BreakAfterReads n i j : cmds) conns evs =
169+ go cmds conns evs
139170
140171--------------------------------------------------------------------------------
141172-- Script generators --
@@ -223,6 +254,25 @@ script_ConnectSendClose numEndPoints = do
223254 isOpen :: Map Int Bool -> Int -> Bool
224255 isOpen conns connIx = connIx `Map.member` conns && conns Map.! connIx
225256
257+withErrors :: Int -> Gen Script -> Gen Script
258+withErrors numErrors gen = gen >>= insertError numErrors
259+ where
260+ insertError :: Int -> Script -> Gen Script
261+ insertError _ [] = return []
262+ insertError n (Connect i j : cmds) | i /= j = do
263+ insert <- arbitrary
264+ if insert && n > 0
265+ then do
266+ -- We sometimes want big delays, but usually we want short delays
267+ numReads <- elements (concat (replicate 10 [0 .. 9]) ++ [10 ..99])
268+ return $ Connect i j : BreakAfterReads numReads i j : cmds
269+ else do
270+ cmds' <- insertError (n - 1) cmds
271+ return $ Connect i j : cmds'
272+ insertError n (cmd : cmds) = do
273+ cmds' <- insertError n cmds
274+ return $ cmd : cmds'
275+
226276--------------------------------------------------------------------------------
227277-- Individual scripts to test specific bugs --
228278--------------------------------------------------------------------------------
@@ -283,41 +333,42 @@ script_Bug1 = [
283333-- Main application driver --
284334--------------------------------------------------------------------------------
285335
286−basicTests :: Transport -> Int -> [Test]
287−basicTests transport numEndPoints = [
288− testGen "NewEndPoint" transport (script_NewEndPoint 2)
289− , testGen "Connect" transport (script_Connect 2)
290− , testGen "ConnectClose" transport (script_ConnectClose 2)
291− , testGen "ConnectSendClose" transport (script_ConnectSendClose 2)
336+basicTests :: (Transport, TransportInternals) -> Int -> (Gen Script -> Gen Script) -> [Test]
337+basicTests transport numEndPoints trans = [
338+ testGen "NewEndPoint" transport (trans (script_NewEndPoint numEndPoints))
339+ , testGen "Connect" transport (trans (script_Connect numEndPoints))
340+ , testGen "ConnectClose" transport (trans (script_ConnectClose numEndPoints))
341+ , testGen "ConnectSendClose" transport (trans (script_ConnectSendClose numEndPoints))
292342 ]
293343
294−tests :: Transport -> [Test]
344+tests :: (Transport, TransportInternals) -> [Test]
295345tests transport = [
296346 testGroup "Bugs" [
297347 testOne "Bug1" transport script_Bug1
298348 ]
299− , testGroup "One endpoint, with delays" (basicTests transport 1)
300− , testGroup "Two endpoints, with delays" (basicTests transport 2)
301− , testGroup "Three endpoints, with delays" (basicTests transport 3)
349+ , testGroup "One endpoint, with delays" (basicTests transport 1 id)
350+ , testGroup "Two endpoints, with delays" (basicTests transport 2 id)
351+ , testGroup "Three endpoints, with delays" (basicTests transport 3 id)
352+ , testGroup "Four endpoints, with delay, single error" (basicTests transport 4 (withErrors 1))
302353 ]
303354 where
304355
305−testOne :: TestName -> Transport -> Script -> Test
356+testOne :: TestName -> (Transport, TransportInternals) -> Script -> Test
306357testOne label transport script = testCase label (testScript transport script)
307358
308−testGen :: TestName -> Transport -> Gen Script -> Test
359+testGen :: TestName -> (Transport, TransportInternals) -> Gen Script -> Test
309360testGen label transport script = testProperty label (testScriptGen transport script)
310361
311362main :: IO ()
312363main = do
313− Right transport <- createTransport "127.0.0.1" "8080" defaultTCPParameters
364+ Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
314365 defaultMain (tests transport)
315366
316367--------------------------------------------------------------------------------
317368-- Test infrastructure --
318369--------------------------------------------------------------------------------
319370
320−testScriptGen :: Transport -> Gen Script -> Property
371+testScriptGen :: (Transport, TransportInternals) -> Gen Script -> Property
321372testScriptGen transport scriptGen =
322373 forAll scriptGen $ \script ->
323374 morallyDubiousIOProperty $ do
@@ -330,7 +381,7 @@ testScriptGen transport scriptGen =
330381 , reason = '\n' : err ++ "\nAll events: " ++ show evs
331382 }
332383
333−testScript :: Transport -> Script -> Assertion
384+testScript :: (Transport, TransportInternals) -> Script -> Assertion
334385testScript transport script = do
335386 logShow script
336387 evs <- execScript transport script