@@ -1,23 +1,26 @@
1+module Main (main, logShow) where
1−import Test.Framework (defaultMain, testGroup)
3+import Test.Framework (Test, defaultMain, testGroup)
2import Test.Framework.Providers.QuickCheck2 (testProperty)
4import Test.Framework.Providers.QuickCheck2 (testProperty)
3−import Test.QuickCheck (Gen, choose, suchThatMaybe, forAll)
5+import Test.QuickCheck (Gen, choose, suchThatMaybe, forAllShrink, Property)
4import Test.QuickCheck.Property (morallyDubiousIOProperty, Result(..), result)
6import Test.QuickCheck.Property (morallyDubiousIOProperty, Result(..), result)
5import Data.Map (Map)
7import Data.Map (Map)
6import qualified Data.Map as Map
8import qualified Data.Map as Map
7−import Control.Applicative ((<$>))
9+import Control.Exception (Exception, throwIO)
8import Control.Concurrent (forkIO, threadDelay)
10import Control.Concurrent (forkIO, threadDelay)
9import Control.Concurrent.Chan (Chan, newChan, writeChan, readChan)
11import Control.Concurrent.Chan (Chan, newChan, writeChan, readChan)
10−import Control.Monad (forM_, replicateM)
12+import Data.List (inits)
11−import Data.Either (rights)
13import Network.Transport
14import Network.Transport
14import Network.Transport.TCP (createTransport, defaultTCPParameters)
15import Network.Transport.TCP (createTransport, defaultTCPParameters)
16data ScriptCmd =
17data ScriptCmd =
17− Connect Int Int Reliability ConnectHints
19+ | Connect Int Int Reliability ConnectHints
18 | Close Int
20 | Close Int
20instance Show ScriptCmd where
22instance Show ScriptCmd where
23+ show NewEndPoint = "NewEndPoint"
21 show (Connect fr to _ _) = "Connect " ++ show fr ++ " " ++ show to
24 show (Connect fr to _ _) = "Connect " ++ show fr ++ " " ++ show to
22 show (Close i) = "Close " ++ show i
25 show (Close i) = "Close " ++ show i
@@ -26,8 +29,37 @@ type Script = [ScriptCmd]
26logShow :: Show a => a -> IO ()
29logShow :: Show a => a -> IO ()
27logShow = appendFile "log" . (++ "\n") . show
30logShow = appendFile "log" . (++ "\n") . show
29−connectCloseScript :: Int -> Gen Script
32+throwIfLeft :: Exception a => IO (Either a b) -> IO b
30−connectCloseScript numEndPoints = go Map.empty
33+throwIfLeft p = do
39+script_NewEndPoint :: Int -> Gen Script
40+script_NewEndPoint numEndPoints = return (replicate numEndPoints NewEndPoint)
42+script_Connect :: Int -> Gen Script
43+script_Connect numEndPoints = do
45+ return (replicate numEndPoints NewEndPoint ++ script)
49+ next <- choose (0, 1) :: Gen Int
52+ fr <- choose (0, numEndPoints - 1)
53+ to <- choose (0, numEndPoints - 1)
55+ return (Connect fr to ReliableOrdered defaultConnectHints : cmds)
59+script_ConnectClose :: Int -> Gen Script
60+script_ConnectClose numEndPoints = do
61+ script <- go Map.empty
62+ return (replicate numEndPoints NewEndPoint ++ script)
32 go :: Map Int Bool -> Gen Script
64 go :: Map Int Bool -> Gen Script
33 go conns = do
65 go conns = do
@@ -51,85 +83,98 @@ connectCloseScript numEndPoints = go Map.empty
51 isOpen :: Map Int Bool -> Int -> Bool
83 isOpen :: Map Int Bool -> Int -> Bool
52 isOpen conns connIx = connIx `Map.member` conns && conns Map.! connIx
84 isOpen conns connIx = connIx `Map.member` conns && conns Map.! connIx
54−execScript :: Transport -> Int -> Script -> IO (Map Int [Event])
86+execScript :: Transport -> Script -> IO (Map Int [Event])
55−execScript transport numEndPoints script = do
87+execScript transport script = do
56− endPoints <- rights <$> replicateM numEndPoints (newEndPoint transport)
57 chan <- newChan
88 chan <- newChan
58− forM_ (zip [0..] endPoints) $ forkIO . forwardTo chan
59− forkIO $ runScript endPoints [] script
89+ runScript chan script
60− collectAll chan 0
90+ collectAll chan
62− runScript :: [EndPoint] -> [Connection] -> Script -> IO ()
92+ runScript :: Chan (Maybe (Int, Event)) -> Script -> IO ()
63− runScript endPoints = go
93+ runScript chan = go [] []
65− go :: [Connection] -> Script -> IO ()
95+ go :: [EndPoint] -> [Connection] -> Script -> IO ()
66− go _ [] = threadDelay 500000 >> mapM_ closeEndPoint endPoints
96+ go _endPoints _conns [] = do
98+ writeChan chan Nothing
99+ go endPoints conns (NewEndPoint : cmds) = do
100+ endPoint <- throwIfLeft $ newEndPoint transport
101+ let endPointIx = length endPoints
102+ _tid <- forkIO $ forwardTo chan (endPointIx, endPoint)
104+ go (endPoints ++ [endPoint]) conns cmds
67− go conns cmd@(Connect fr to rel hints : cmds) = do
105+ go endPoints conns (Connect fr to rel hints : cmds) = do
68− Right conn <- connect (endPoints !! fr) (address (endPoints !! to)) rel hints
106+ conn <- throwIfLeft $ connect (endPoints !! fr) (address (endPoints !! to)) rel hints
69− go (conns ++ [conn]) cmds
108+ go endPoints (conns ++ [conn]) cmds
70− go conns cmd@(Close connIx : cmds) = do
109+ go endPoints conns (Close connIx : cmds) = do
71 close (conns !! connIx)
110 close (conns !! connIx)
72− go conns cmds
112+ go endPoints conns cmds
74− forwardTo :: Chan (Int, Event) -> (Int, EndPoint) -> IO ()
114+ forwardTo :: Chan (Maybe (Int, Event)) -> (Int, EndPoint) -> IO ()
75 forwardTo chan (ix, endPoint) = go
115 forwardTo chan (ix, endPoint) = go
77 go :: IO ()
117 go :: IO ()
79 ev <- receive endPoint
119 ev <- receive endPoint
80− writeChan chan (ix, ev)
81 case ev of
120 case ev of
82 EndPointClosed -> return ()
121 EndPointClosed -> return ()
83− _ -> go
122+ _ -> writeChan chan (Just (ix, ev)) >> go
85− collectAll :: Chan (Int, Event) -> Int -> IO (Map Int [Event])
124+ collectAll :: Chan (Maybe (Int, Event)) -> IO (Map Int [Event])
86− collectAll chan = go (Map.fromList (zip [0 .. numEndPoints - 1] (repeat [])))
125+ collectAll chan = go Map.empty
88− go :: Map Int [Event] -> Int -> IO (Map Int [Event])
127+ go :: Map Int [Event] -> IO (Map Int [Event])
89− go acc numDone | numDone == numEndPoints = return $ Map.map reverse acc
128+ go acc = do
92− (ix, ev) <- readChan chan
129+ mEv <- readChan chan
93− let acc' = Map.adjust (ev :) ix acc
130+ case mEv of
94− numDone' = case ev of EndPointClosed -> numDone + 1
131+ Nothing -> return $ Map.map reverse acc
95− _ -> numDone
132+ Just (ix, ev) -> go (Map.alter (insertEvent ev) ix acc)
97−
134+ insertEvent :: Event -> Maybe [Event] -> Maybe [Event]
135+ insertEvent ev Nothing = Just [ev]
136+ insertEvent ev (Just evs) = Just (ev : evs)
98−verify :: Int -> Script -> Map Int [Event] -> Result
138+verify :: Script -> Map Int [Event] -> Result
99−verify numEndPoints script evs =
100− case go script [] evs of
139+verify script = \evs -> case go script [] evs of
101− Nothing -> result { ok = Just True
140+ Nothing -> result { ok = Just True
103− Just err -> result { ok = Just False
142+ Just err -> result { ok = Just False
104− , reason = err
143+ , reason = '\n' : err ++ "\nAll events: " ++ show evs
107 go :: Script -> [(Int, ConnectionId)] -> Map Int [Event] -> Maybe String
146 go :: Script -> [(Int, ConnectionId)] -> Map Int [Event] -> Maybe String
108− go [] conns evs =
147+ go [] _conns evs =
109− let closed = Map.fromList (zip [0 .. numEndPoints - 1] (repeat [EndPointClosed])) in
148+ if concat (Map.elems evs) == []
110− if evs == closed then Nothing
149+ then Nothing
111− else Just $ "Expected " ++ show closed ++ "; got " ++ show evs
150+ else Just $ "Unexpected events: " ++ show evs
151+ go (NewEndPoint : cmds) conns evs =
112− go (Connect fr to rel hints : cmds) conns evs =
153+ go (Connect _fr to rel _hints : cmds) conns evs =
113 case evs Map.! to of
154 case evs Map.! to of
114− (ConnectionOpened connId rel' addr : epEvs) | rel' == rel ->
155+ (ConnectionOpened connId rel' _addr : epEvs) | rel' == rel ->
115 go cmds (conns ++ [(to, connId)]) (Map.insert to epEvs evs)
156 go cmds (conns ++ [(to, connId)]) (Map.insert to epEvs evs)
116− _ -> Just $ "Missing ConnectionOpened event in " ++ show evs
157+ _ -> Just $ "Missing (ConnectionOpened <<connId>> " ++ show rel ++ " <<addr>>) event in " ++ show evs
117 go (Close connIx : cmds) conns evs =
158 go (Close connIx : cmds) conns evs =
118 let (epIx, connId) = conns !! connIx in
159 let (epIx, connId) = conns !! connIx in
119 case evs Map.! epIx of
160 case evs Map.! epIx of
120 (ConnectionClosed connId' : epEvs) | connId' == connId ->
161 (ConnectionClosed connId' : epEvs) | connId' == connId ->
121 go cmds conns (Map.insert epIx epEvs evs)
162 go cmds conns (Map.insert epIx epEvs evs)
122− _ -> Just $ "Missing ConnectionClosed event in " ++ show evs
163+ _ -> Just $ "Missing (ConnectionClosed " ++ show connId ++ ") event in " ++ show evs
124−prop_connect_close transport = forAll (connectCloseScript 2) $ \script ->
165+genericProp :: Transport -> Int -> (Int -> Gen Script) -> Property
166+genericProp transport numEndPoints scriptGen =
167+ forAllShrink (scriptGen numEndPoints) inits $ \script ->
125− morallyDubiousIOProperty $ do
168+ morallyDubiousIOProperty $ do
127− evs <- execScript transport 2 script
169+ evs <- execScript transport script
128− return (verify 2 script evs)
170+ return (verify script evs)
172+tests :: Transport -> [Test]
130tests transport = [
173tests transport = [
131 testGroup "Unidirectional" [
174 testGroup "Unidirectional" [
132− testProperty "ConnectClose" (prop_connect_close transport)
175+ testProperty "NewEndPoint" (genericProp transport 2 script_NewEndPoint)
176+ , testProperty "Connect" (genericProp transport 2 script_Connect)
177+ , testProperty "ConnectClose" (genericProp transport 2 script_ConnectClose)