@@ -17,6 +17,7 @@ import Test.QuickCheck
17 , forAllShrink
17 , forAllShrink
18 , Property
18 , Property
19 , Arbitrary(arbitrary)
19 , Arbitrary(arbitrary)
21import Test.QuickCheck.Property (morallyDubiousIOProperty, Result(..), result)
22import Test.QuickCheck.Property (morallyDubiousIOProperty, Result(..), result)
22import Test.HUnit (Assertion, assertFailure)
23import Test.HUnit (Assertion, assertFailure)
@@ -32,23 +33,47 @@ import Data.ByteString.Char8 (pack)
32import qualified Data.ByteString as BSS (concat)
33import qualified Data.ByteString as BSS (concat)
34import Network.Transport
35import Network.Transport
35−import Network.Transport.TCP (createTransport, defaultTCPParameters)
36+import Network.Transport.TCP
37+ ( createTransportExposeInternals
38+ , defaultTCPParameters
39+ , TransportInternals(socketBetween)
42+import Network.Transport.TCP.Mock.Socket (scheduleReadAction, sClose)
37--------------------------------------------------------------------------------
44--------------------------------------------------------------------------------
38-- Script infrastructure --
45-- Script infrastructure --
39--------------------------------------------------------------------------------
46--------------------------------------------------------------------------------
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.
41data ScriptCmd =
51data ScriptCmd =
52+ -- | Create a new endpoint
42 NewEndPoint
53 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
43 | Connect Int Int
57 | 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
44 | Close Int
62 | Close Int
63+ -- | @Send i bs@ sends payload @bs@ on the @i@ connection created
45 | Send Int [ByteString]
64 | Send Int [ByteString]
65+ -- | @BreakAfterReads n i j@ force-closes the socket between endpoints @i@
66+ -- and @j@ after @n@ reads by @i@
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
46 deriving Show
71 deriving Show
48type Script = [ScriptCmd]
73type Script = [ScriptCmd]
50−execScript :: Transport -> Script -> IO (Map Int [Event])
75+execScript :: (Transport, TransportInternals) -> Script -> IO (Map Int [Event])
51−execScript transport script = do
76+execScript (transport, transportInternals) script = do
52 chan <- newChan
77 chan <- newChan
53 runScript chan script
78 runScript chan script
54 collectAll chan
79 collectAll chan
@@ -78,6 +103,10 @@ execScript transport script = do
78 Right () <- send (conns !! connIx) payload
103 Right () <- send (conns !! connIx) payload
79 threadDelay 10000
104 threadDelay 10000
80 go endPoints conns cmds
105 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
82 forwardTo :: Chan (Maybe (Int, Event)) -> (Int, EndPoint) -> IO ()
111 forwardTo :: Chan (Maybe (Int, Event)) -> (Int, EndPoint) -> IO ()
83 forwardTo chan (ix, endPoint) = go
112 forwardTo chan (ix, endPoint) = go
@@ -136,6 +165,8 @@ verify script = go script []
136 go cmds conns (Map.insert epIx epEvs' evs)
165 go cmds conns (Map.insert epIx epEvs' evs)
138 Just $ "Missing (Received " ++ show connId ++ " " ++ show payload ++ ") event in " ++ show epEvs
167 Just $ "Missing (Received " ++ show connId ++ " " ++ show payload ++ ") event in " ++ show epEvs
168+ go (BreakAfterReads n i j : cmds) conns evs =
140--------------------------------------------------------------------------------
171--------------------------------------------------------------------------------
141-- Script generators --
172-- Script generators --
@@ -223,6 +254,25 @@ script_ConnectSendClose numEndPoints = do
223 isOpen :: Map Int Bool -> Int -> Bool
254 isOpen :: Map Int Bool -> Int -> Bool
224 isOpen conns connIx = connIx `Map.member` conns && conns Map.! connIx
255 isOpen conns connIx = connIx `Map.member` conns && conns Map.! connIx
257+withErrors :: Int -> Gen Script -> Gen Script
258+withErrors numErrors gen = gen >>= insertError numErrors
260+ insertError :: Int -> Script -> Gen Script
261+ insertError _ [] = return []
262+ insertError n (Connect i j : cmds) | i /= j = 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
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'
226--------------------------------------------------------------------------------
276--------------------------------------------------------------------------------
227-- Individual scripts to test specific bugs --
277-- Individual scripts to test specific bugs --
228--------------------------------------------------------------------------------
278--------------------------------------------------------------------------------
@@ -283,41 +333,42 @@ script_Bug1 = [
283-- Main application driver --
333-- Main application driver --
284--------------------------------------------------------------------------------
334--------------------------------------------------------------------------------
286−basicTests :: Transport -> Int -> [Test]
336+basicTests :: (Transport, TransportInternals) -> Int -> (Gen Script -> Gen Script) -> [Test]
287−basicTests transport numEndPoints = [
337+basicTests transport numEndPoints trans = [
288− testGen "NewEndPoint" transport (script_NewEndPoint 2)
338+ testGen "NewEndPoint" transport (trans (script_NewEndPoint numEndPoints))
289− , testGen "Connect" transport (script_Connect 2)
339+ , testGen "Connect" transport (trans (script_Connect numEndPoints))
290− , testGen "ConnectClose" transport (script_ConnectClose 2)
340+ , testGen "ConnectClose" transport (trans (script_ConnectClose numEndPoints))
291− , testGen "ConnectSendClose" transport (script_ConnectSendClose 2)
341+ , testGen "ConnectSendClose" transport (trans (script_ConnectSendClose numEndPoints))
294−tests :: Transport -> [Test]
344+tests :: (Transport, TransportInternals) -> [Test]
295tests transport = [
345tests transport = [
296 testGroup "Bugs" [
346 testGroup "Bugs" [
297 testOne "Bug1" transport script_Bug1
347 testOne "Bug1" transport script_Bug1
299− , testGroup "One endpoint, with delays" (basicTests transport 1)
349+ , testGroup "One endpoint, with delays" (basicTests transport 1 id)
300− , testGroup "Two endpoints, with delays" (basicTests transport 2)
350+ , testGroup "Two endpoints, with delays" (basicTests transport 2 id)
301− , testGroup "Three endpoints, with delays" (basicTests transport 3)
351+ , testGroup "Three endpoints, with delays" (basicTests transport 3 id)
352+ , testGroup "Four endpoints, with delay, single error" (basicTests transport 4 (withErrors 1))
305−testOne :: TestName -> Transport -> Script -> Test
356+testOne :: TestName -> (Transport, TransportInternals) -> Script -> Test
306testOne label transport script = testCase label (testScript transport script)
357testOne label transport script = testCase label (testScript transport script)
308−testGen :: TestName -> Transport -> Gen Script -> Test
359+testGen :: TestName -> (Transport, TransportInternals) -> Gen Script -> Test
309testGen label transport script = testProperty label (testScriptGen transport script)
360testGen label transport script = testProperty label (testScriptGen transport script)
311main :: IO ()
362main :: IO ()
313− Right transport <- createTransport "127.0.0.1" "8080" defaultTCPParameters
364+ Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
314 defaultMain (tests transport)
365 defaultMain (tests transport)
316--------------------------------------------------------------------------------
367--------------------------------------------------------------------------------
317-- Test infrastructure --
368-- Test infrastructure --
318--------------------------------------------------------------------------------
369--------------------------------------------------------------------------------
320−testScriptGen :: Transport -> Gen Script -> Property
371+testScriptGen :: (Transport, TransportInternals) -> Gen Script -> Property
321testScriptGen transport scriptGen =
372testScriptGen transport scriptGen =
322 forAll scriptGen $ \script ->
373 forAll scriptGen $ \script ->
323 morallyDubiousIOProperty $ do
374 morallyDubiousIOProperty $ do
@@ -330,7 +381,7 @@ testScriptGen transport scriptGen =
330 , reason = '\n' : err ++ "\nAll events: " ++ show evs
381 , reason = '\n' : err ++ "\nAll events: " ++ show evs
333−testScript :: Transport -> Script -> Assertion
384+testScript :: (Transport, TransportInternals) -> Script -> Assertion
334testScript transport script = do
385testScript transport script = do
335 logShow script
386 logShow script
336 evs <- execScript transport script
387 evs <- execScript transport script