@@ -2,7 +2,7 @@
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE ScopedTypeVariables #-}
5−module RunSpec (main, spec, withApp, connectTo) where
5+module RunSpec (main, spec, withApp, connectTo, MySocket, msWrite, msRead, msClose) where
7import Control.Concurrent (forkIO, killThread, threadDelay)
7import Control.Concurrent (forkIO, killThread, threadDelay)
8import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
8import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
@@ -11,7 +11,7 @@ import qualified Control.Exception as E
11import Control.Exception.Lifted (bracket, try, IOException, onException)
11import Control.Exception.Lifted (bracket, try, IOException, onException)
12import Control.Monad (forM_, replicateM_, unless)
12import Control.Monad (forM_, replicateM_, unless)
13import Control.Monad.IO.Class (MonadIO, liftIO)
13import Control.Monad.IO.Class (MonadIO, liftIO)
14−import Data.ByteString (ByteString, hPutStr, hGetSome)
14+import Data.ByteString (ByteString)
15import qualified Data.ByteString as S
15import qualified Data.ByteString as S
16import Data.ByteString.Builder (byteString)
16import Data.ByteString.Builder (byteString)
17import qualified Data.ByteString.Char8 as S8
17import qualified Data.ByteString.Char8 as S8
@@ -23,7 +23,6 @@ import Network.Socket
23import Network.Socket.ByteString (sendAll)
23import Network.Socket.ByteString (sendAll)
24import Network.Wai hiding (responseHeaders)
24import Network.Wai hiding (responseHeaders)
25import Network.Wai.Handler.Warp
25import Network.Wai.Handler.Warp
26−import System.IO (hFlush, hClose, Handle, IOMode(..))
27import System.IO.Unsafe (unsafePerformIO)
26import System.IO.Unsafe (unsafePerformIO)
28import System.Timeout (timeout)
27import System.Timeout (timeout)
29import Test.Hspec
28import Test.Hspec
@@ -36,13 +35,44 @@ main = hspec spec
36type Counter = I.IORef (Either String Int)
35type Counter = I.IORef (Either String Int)
37type CounterApplication = Counter -> Application
36type CounterApplication = Counter -> Application
39−connectTo :: HostName -> Int -> IO Handle
38+data MySocket = MySocket
40−connectTo host port = do
39+ { msSocket :: !Socket
41− let hints = defaultHints { addrSocketType = Stream }
40+ , _msBuffer :: !(I.IORef ByteString)
42− addr:_ <- getAddrInfo (Just hints) (Just host) (Just $ show port)
41+ }
43− sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
42+
44− connect sock $ addrAddress addr
43+msWrite :: MySocket -> ByteString -> IO ()
45− socketToHandle sock ReadWriteMode
44+msWrite ms bs = sendAll (msSocket ms) bs
46+msRead :: MySocket -> Int -> IO ByteString
47+msRead (MySocket s ref) expected = do
48+ bs <- I.readIORef ref
49+ inner (bs:) (S.length bs)
52+ case compare total expected of
54+ I.writeIORef ref mempty
55+ pure $ S.concat $ front []
57+ let bs = S.concat $ front []
58+ (x, y) = S.splitAt expected bs
62+ bs <- safeRecv s 4096
65+ I.writeIORef ref mempty
66+ pure $ S.concat $ front []
67+ else inner (front . (bs:)) (total + S.length bs)
69+msClose :: MySocket -> IO ()
70+msClose = Network.Socket.close . msSocket
72+connectTo :: Int -> IO MySocket
73+connectTo port = MySocket
74+ <$> (fst <$> getSocketTCP "127.0.0.1" port)
75+ <*> I.newIORef mempty
47incr :: MonadIO m => Counter -> m ()
77incr :: MonadIO m => Counter -> m ()
48incr icount = liftIO $ I.atomicModifyIORef icount $ \ecount ->
78incr icount = liftIO $ I.atomicModifyIORef icount $ \ecount ->
@@ -106,7 +136,13 @@ withApp settings app f = do
107 (forkIO $ runSettings settings' app `onException` putMVar baton ())
137 (forkIO $ runSettings settings' app `onException` putMVar baton ())
108 killThread
138 killThread
109− (const $ takeMVar baton >> f port)
140+ takeMVar baton
141+ -- use timeout to make sure we don't take too long
142+ mres <- timeout (60 * 1000 * 1000) (f port)
144+ Nothing -> error "Timeout triggered, too slow!"
111runTest :: Int -- ^ expected number of requests
147runTest :: Int -- ^ expected number of requests
112 -> CounterApplication
148 -> CounterApplication
@@ -115,9 +151,9 @@ runTest :: Int -- ^ expected number of requests
115runTest expected app chunks = do
151runTest expected app chunks = do
116 ref <- I.newIORef (Right 0)
152 ref <- I.newIORef (Right 0)
117 withApp defaultSettings (app ref) $ \port -> do
153 withApp defaultSettings (app ref) $ \port -> do
118− handle <- connectTo "127.0.0.1" port
154+ ms <- connectTo port
119− forM_ chunks $ \chunk -> hPutStr handle chunk >> hFlush handle
155+ forM_ chunks $ \chunk -> msWrite ms chunk
120− _ <- timeout 100000 $ replicateM_ expected $ hGetSome handle 4096
156+ _ <- timeout 100000 $ replicateM_ expected $ msRead ms 4096
121 res <- I.readIORef ref
157 res <- I.readIORef ref
122 case res of
158 case res of
123 Left s -> error s
159 Left s -> error s
@@ -133,10 +169,9 @@ runTerminateTest expected input = do
133 ref <- I.newIORef Nothing
169 ref <- I.newIORef Nothing
134 let onExc _ = I.writeIORef ref . Just
170 let onExc _ = I.writeIORef ref . Just
135 withApp (setOnException onExc defaultSettings) dummyApp $ \port -> do
171 withApp (setOnException onExc defaultSettings) dummyApp $ \port -> do
136− handle <- connectTo "127.0.0.1" port
172+ handle <- connectTo port
137− hPutStr handle input
173+ msWrite handle input
139− hClose handle
174+ msClose handle
140 threadDelay 1000
175 threadDelay 1000
141 res <- I.readIORef ref
176 res <- I.readIORef ref
142 show res `shouldBe` show (Just expected)
177 show res `shouldBe` show (Just expected)
@@ -208,13 +243,12 @@ spec = do
208 liftIO $ I.writeIORef iheaders $ requestHeaders req
243 liftIO $ I.writeIORef iheaders $ requestHeaders req
209 f $ responseLBS status200 [] ""
244 f $ responseLBS status200 [] ""
210 withApp defaultSettings app $ \port -> do
245 withApp defaultSettings app $ \port -> do
211− handle <- connectTo "127.0.0.1" port
246+ handle <- connectTo port
212 let input = S.concat
247 let input = S.concat
213 [ "GET / HTTP/1.1\r\nfoo: bar\r\n baz\r\n\tbin\r\n\r\n"
248 [ "GET / HTTP/1.1\r\nfoo: bar\r\n baz\r\n\tbin\r\n\r\n"
215− hPutStr handle input
250+ msWrite handle input
217− hClose handle
251+ msClose handle
218 threadDelay 1000
252 threadDelay 1000
219 headers <- I.readIORef iheaders
253 headers <- I.readIORef iheaders
220 headers `shouldBe`
254 headers `shouldBe`
@@ -226,13 +260,12 @@ spec = do
226 liftIO $ I.writeIORef iheaders $ requestHeaders req
260 liftIO $ I.writeIORef iheaders $ requestHeaders req
227 f $ responseLBS status200 [] ""
261 f $ responseLBS status200 [] ""
228 withApp defaultSettings app $ \port -> do
262 withApp defaultSettings app $ \port -> do
229− handle <- connectTo "127.0.0.1" port
263+ handle <- connectTo port
230 let input = S.concat
264 let input = S.concat
231 [ "GET / HTTP/1.1\r\nfoo:bar\r\n\r\n"
265 [ "GET / HTTP/1.1\r\nfoo:bar\r\n\r\n"
233− hPutStr handle input
267+ msWrite handle input
235− hClose handle
268+ msClose handle
236 threadDelay 1000
269 threadDelay 1000
237 headers <- I.readIORef iheaders
270 headers <- I.readIORef iheaders
238 headers `shouldBe`
271 headers `shouldBe`
@@ -241,28 +274,33 @@ spec = do
242 describe "chunked bodies" $ do
275 describe "chunked bodies" $ do
243 it "works" $ do
276 it "works" $ do
277+ countVar <- newTVarIO (0 :: Int)
244 ifront <- I.newIORef id
278 ifront <- I.newIORef id
245 let app req f = do
279 let app req f = do
246 bss <- consumeBody $ requestBody req
280 bss <- consumeBody $ requestBody req
247 liftIO $ I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
281 liftIO $ I.atomicModifyIORef ifront $ \front -> (front . (S.concat bss:), ())
282+ atomically $ modifyTVar countVar (+ 1)
248 f $ responseLBS status200 [] ""
283 f $ responseLBS status200 [] ""
249 withApp defaultSettings app $ \port -> do
284 withApp defaultSettings app $ \port -> do
250− handle <- connectTo "127.0.0.1" port
285+ handle <- connectTo port
251 let input = S.concat
286 let input = S.concat
252 [ "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
287 [ "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
253 , "c\r\nHello World\n\r\n3\r\nBye\r\n0\r\n\r\n"
288 , "c\r\nHello World\n\r\n3\r\nBye\r\n0\r\n\r\n"
254 , "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
289 , "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
255 , "b\r\nHello World\r\n0\r\n\r\n"
290 , "b\r\nHello World\r\n0\r\n\r\n"
257− hPutStr handle input
292+ msWrite handle input
258− hFlush handle
293+ msClose handle
259− hClose handle
294+ atomically $ do
260− threadDelay 1000
295+ count <- readTVar countVar
261 front <- I.readIORef ifront
297 front <- I.readIORef ifront
262 front [] `shouldBe`
298 front [] `shouldBe`
263 [ "Hello World\nBye"
299 [ "Hello World\nBye"
264 , "Hello World"
300 , "Hello World"
303+-- Too slow on Windows
266 it "lots of chunks" $ do
304 it "lots of chunks" $ do
267 ifront <- I.newIORef id
305 ifront <- I.newIORef id
268 countVar <- newTVarIO (0 :: Int)
306 countVar <- newTVarIO (0 :: Int)
@@ -272,13 +310,13 @@ spec = do
272 atomically $ modifyTVar countVar (+ 1)
310 atomically $ modifyTVar countVar (+ 1)
273 f $ responseLBS status200 [] ""
311 f $ responseLBS status200 [] ""
274 withApp defaultSettings app $ \port -> do
312 withApp defaultSettings app $ \port -> do
275− handle <- connectTo "127.0.0.1" port
313+ handle <- connectTo port
276 let input = concat $ replicate 2 $
314 let input = concat $ replicate 2 $
277 ["POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"] ++
315 ["POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"] ++
278 (replicate 50 "5\r\n12345\r\n") ++
316 (replicate 50 "5\r\n12345\r\n") ++
279 ["0\r\n\r\n"]
317 ["0\r\n\r\n"]
280− mapM_ (\bs -> hPutStr handle bs >> hFlush handle) input
318+ mapM_ (msWrite handle) input
281− hClose handle
319+ msClose handle
282 atomically $ do
320 atomically $ do
283 count <- readTVar countVar
321 count <- readTVar countVar
284 check $ count == 2
322 check $ count == 2
@@ -286,7 +324,6 @@ spec = do
286 front [] `shouldBe` replicate 2 (S.concat $ replicate 50 "12345")
324 front [] `shouldBe` replicate 2 (S.concat $ replicate 50 "12345")
287-- For some reason, the following test on Windows causes the socket
325-- For some reason, the following test on Windows causes the socket
288-- to be killed prematurely. Worth investigating in the future if possible.
326-- to be killed prematurely. Worth investigating in the future if possible.
290 it "in chunks" $ do
327 it "in chunks" $ do
291 ifront <- I.newIORef id
328 ifront <- I.newIORef id
292 countVar <- newTVarIO (0 :: Int)
329 countVar <- newTVarIO (0 :: Int)
@@ -296,15 +333,15 @@ spec = do
296 atomically $ modifyTVar countVar (+ 1)
333 atomically $ modifyTVar countVar (+ 1)
297 f $ responseLBS status200 [] ""
334 f $ responseLBS status200 [] ""
298 withApp defaultSettings app $ \port -> do
335 withApp defaultSettings app $ \port -> do
299− handle <- connectTo "127.0.0.1" port
336+ handle <- connectTo port
300 let input = S.concat
337 let input = S.concat
301 [ "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
338 [ "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
302 , "c\r\nHello World\n\r\n3\r\nBye\r\n0\r\n"
339 , "c\r\nHello World\n\r\n3\r\nBye\r\n0\r\n"
303 , "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
340 , "POST / HTTP/1.1\r\nTransfer-Encoding: Chunked\r\n\r\n"
304 , "b\r\nHello World\r\n0\r\n\r\n"
341 , "b\r\nHello World\r\n0\r\n\r\n"
306− mapM_ (\bs -> hPutStr handle bs >> hFlush handle) $ map S.singleton $ S.unpack input
343+ mapM_ (msWrite handle) $ map S.singleton $ S.unpack input
307− hClose handle
344+ msClose handle
308 atomically $ do
345 atomically $ do
309 count <- readTVar countVar
346 count <- readTVar countVar
310 check $ count == 2
347 check $ count == 2
@@ -330,16 +367,16 @@ spec = do
330 let bs1 = S.replicate 2048 88
367 let bs1 = S.replicate 2048 88
331 bs2 = "This is short"
368 bs2 = "This is short"
332 bs = S.append bs1 bs2
369 bs = S.append bs1 bs2
333− handle <- connectTo "127.0.0.1" port
370+ handle <- connectTo port
334− hPutStr handle "POST / HTTP/1.1\r\n"
371+ msWrite handle "POST / HTTP/1.1\r\n"
335− hPutStr handle "content-length: "
372+ msWrite handle "content-length: "
336− hPutStr handle $ S8.pack $ show $ S.length bs
373+ msWrite handle $ S8.pack $ show $ S.length bs
337− hPutStr handle "\r\n\r\n"
374+ msWrite handle "\r\n\r\n"
338 threadDelay 100000
375 threadDelay 100000
339− hPutStr handle bs1
376+ msWrite handle bs1
340 threadDelay 100000
377 threadDelay 100000
341− hPutStr handle bs2
378+ msWrite handle bs2
342− hClose handle
379+ msClose handle
343 threadDelay 5000000
380 threadDelay 5000000
344 front <- I.readIORef ifront
381 front <- I.readIORef ifront
345 S.concat (front []) `shouldBe` bs
382 S.concat (front []) `shouldBe` bs
@@ -356,30 +393,33 @@ spec = do
357 doubleBS = S.concatMap $ \w -> S.pack [w, w]
394 doubleBS = S.concatMap $ \w -> S.pack [w, w]
358 withApp defaultSettings app $ \port -> do
395 withApp defaultSettings app $ \port -> do
359− handle <- connectTo "127.0.0.1" port
396+ ms <- connectTo port
360− hPutStr handle "POST / HTTP/1.1\r\n\r\n12345"
397+ msWrite ms "POST / HTTP/1.1\r\n\r\n12345"
362− timeout 100000 (S.hGet handle 10) >>= (`shouldBe` Just "1122334455")
398+ timeout 100000 (msRead ms 10) >>= (`shouldBe` Just "1122334455")
363− hPutStr handle "67890"
399+ msWrite ms "67890"
365− timeout 100000 (S.hGet handle 10) >>= (`shouldBe` Just "6677889900")
400+ timeout 100000 (msRead ms 10) >>= (`shouldBe` Just "6677889900")
367 it "only one date and server header" $ do
402 it "only one date and server header" $ do
368 let app _ f = f $ responseLBS status200
403 let app _ f = f $ responseLBS status200
369 [ ("server", "server")
404 [ ("server", "server")
370 , ("date", "date")
405 , ("date", "date")
407+ getValues key = map snd
408+ . filter (\(key', _) -> key == key')
372 withApp defaultSettings app $ \port -> do
410 withApp defaultSettings app $ \port -> do
373 res <- sendGET $ "http://127.0.0.1:" ++ show port
411 res <- sendGET $ "http://127.0.0.1:" ++ show port
374− getHeaderValue hServer (responseHeaders res) `shouldBe` Just "server"
412+ getValues hServer res `shouldBe` ["server"]
375− getHeaderValue hDate (responseHeaders res) `shouldBe` Just "date"
413+ getValues hDate res `shouldBe` ["date"]
377 it "streaming echo #249" $ do
415 it "streaming echo #249" $ do
416+ countVar <- newTVarIO (0 :: Int)
378 let app req f = f $ responseStream status200 [] $ \write _ -> do
417 let app req f = f $ responseStream status200 [] $ \write _ -> do
379 let loop = do
418 let loop = do
380 bs <- requestBody req
419 bs <- requestBody req
381 unless (S.null bs) $ do
420 unless (S.null bs) $ do
382 write $ byteString bs
421 write $ byteString bs
422+ atomically $ modifyTVar countVar (+ 1)
385 withApp defaultSettings app $ \port -> do
425 withApp defaultSettings app $ \port -> do
@@ -387,6 +427,9 @@ spec = do
387 sendAll sock "POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n"
427 sendAll sock "POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n"
388 threadDelay 10000
428 threadDelay 10000
389 sendAll sock "5\r\nhello\r\n0\r\n\r\n"
429 sendAll sock "5\r\nhello\r\n0\r\n\r\n"
431+ count <- readTVar countVar
390 bs <- safeRecv sock 4096
433 bs <- safeRecv sock 4096
391 S.takeWhile (/= 13) bs `shouldBe` "HTTP/1.1 200 OK"
434 S.takeWhile (/= 13) bs `shouldBe` "HTTP/1.1 200 OK"