network-run-0.6.0: test/TimeoutSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module TimeoutSpec (spec) where
import Control.Concurrent
import qualified Control.Exception as E
import Control.Monad
import Data.IORef
import Network.Socket
import Network.Socket.ByteString
import qualified System.TimeManager as T
import System.Timeout (timeout)
import Test.Hspec
import Network.Run.Core (ServerSettings (..), defaultServerSettings)
import qualified Network.Run.TCP.Timeout as Timeout
import Helper
spec :: Spec
spec = do
describe "runTCPServer" $ do
it "serves a connection" $
limited $
withTimeoutServer defaultServerSettings 2 echoServer $ \port ->
request port "hello" `shouldReturn` "hello"
it "kills a handler which exceeds the timeout" $ limited $ do
let server _ _ sock = do
threadDelay 8000000
sendAll sock "late"
withTimeoutServer defaultServerSettings 1 server $ \port ->
client port $ \sock -> do
sendAll sock "hello"
(bs, ms) <- elapsed $ recv sock 1024
-- The handler was killed, so the connection is
-- closed rather than answered.
bs `shouldBe` ""
ms `shouldSatisfy` (< 5000)
it "keeps a handler which tickles alive" $ limited $ do
-- The handler lives 2.4 seconds, longer than the timeout,
-- but it tickles every 300ms. The margin between the two
-- is what a loaded machine may eat without the test
-- becoming a lie, so it is kept wide.
let server _ th sock = do
replicateM_ 8 $ threadDelay 300000 >> T.tickle th
sendAll sock "ok"
withTimeoutServer defaultServerSettings 2 server $ \port ->
client port $ \sock -> do
sendAll sock "hello"
recv sock 1024 `shouldReturn` "ok"
it "keeps serving after a handler throws" $ limited $ do
(set, getReports) <- collecting
ref <- newIORef (0 :: Int)
let server _ _ sock = do
n <- atomicModifyIORef' ref $ \n -> (n + 1, n)
if n == 0 then E.throwIO (userError "boom") else echo sock
withTimeoutServer set 2 server $ \port -> do
ignoreAny $ request port "hello"
request port "hello" `shouldReturn` "hello"
reports <- waitFor 1 getReports
map fst reports `shouldSatisfy` all (/= Nothing)
it "stops when the listening socket is closed" $ limited $ do
done <- newEmptyMVar
withListenSocket $ \lsock _ -> do
void $
forkFinally
(Timeout.runTCPServerWithSocket 2 lsock echoServer)
(putMVar done)
threadDelay 100000
close lsock
r <- timeout 2000000 $ takeMVar done
case r of
Just (Left _) -> return ()
Just (Right _) -> expectationFailure "the accept loop returned"
Nothing -> expectationFailure "the accept loop did not stop"
----------------------------------------------------------------
echoServer :: Timeout.TimeoutServer ()
echoServer _ _ sock = echo sock
withTimeoutServer
:: ServerSettings
-> Int
-> Timeout.TimeoutServer ()
-> (PortNumber -> IO a)
-> IO a
withTimeoutServer set tm server body = withListenSocket $ \lsock port ->
withServerThread
(Timeout.runTCPServerWithSocketAndSettings set tm lsock server)
(body port)