on master help sign in
Merge pull request #516 from soenkehahn/withApplication

warp: add withApplication and openFreePort

commit2659c06d23a8237541169f6c09e3b033b1acb77c authorMichael Snoyman <michael@snoyman.com> dateWed 9 Mar 2016, 10:31 (11 years ago) parent0e166c08beae2b
4 files changed+137
warp/Network/Wai/Handler/Warp.hs+4 warp/Network/Wai/Handler/Warp/WithApplication.hs+84 warp/test/WithApplicationSpec.hs+45 warp/warp.cabal+4
warp/Network/Wai/Handler/Warp.hs+4
@@ -90,6 +90,9 @@ module Network.Wai.Handler.Warp (
9090 , pauseTimeout
9191 , FileInfo(..)
9292 , getFileInfo
93+ , withApplication
94+ , testWithApplication
95+ , openFreePort
9396 -- * Version
9497 , warpVersion
9598 ) where
@@ -109,6 +112,7 @@ import Network.Wai.Handler.Warp.Run
109112import Network.Wai.Handler.Warp.Settings
110113import Network.Wai.Handler.Warp.Timeout
111114import Network.Wai.Handler.Warp.Types hiding (getFileInfo)
115+import Network.Wai.Handler.Warp.WithApplication
112116
113117-- | Port to listen on. Default value: 3000
114118--
warp/Network/Wai/Handler/Warp/WithApplication.hsnew file+84
@@ -0,0 +1,84 @@
1+
2+module Network.Wai.Handler.Warp.WithApplication (
3+ withApplication,
4+ testWithApplication,
5+ openFreePort,
6+ withFreePort,
7+) where
8+
9+import Control.Concurrent
10+import Control.Concurrent.Async
11+import Control.Exception
12+import Network.Socket
13+import Network.Wai
14+import Network.Wai.Handler.Warp.Run
15+import Network.Wai.Handler.Warp.Settings
16+import Network.Wai.Handler.Warp.Types
17+
18+-- | Runs the given 'Application' on a free port. Passes the port to the given
19+-- operation and executes it, while the 'Application' is running. Shuts down the
20+-- server before returning.
21+withApplication :: IO Application -> (Port -> IO a) -> IO a
22+withApplication mkApp action = do
23+ app <- mkApp
24+ withFreePort $ \ (port, sock) -> do
25+ started <- mkWaiter
26+ let settings =
27+ defaultSettings{
28+ settingsBeforeMainLoop = notify started ()
29+ }
30+ result <- race
31+ (runSettingsSocket settings sock app)
32+ (waitFor started >> action port)
33+ case result of
34+ Left () -> throwIO $ ErrorCall "Unexpected: runSettingsSocket exited"
35+ Right x -> return x
36+
37+-- | Same as 'withApplication' but with different exception handling: If the
38+-- given 'Application' throws an exception, 'testWithApplication' will re-throw
39+-- the exception to the calling thread, possibly interrupting the execution of
40+-- the given operation.
41+--
42+-- This is handy for running tests against an 'Application' over a real network
43+-- port. When running tests, it's useful to let exceptions thrown by your
44+-- 'Application' propagate to the main thread of the test-suite.
45+--
46+-- __The exception handling makes this function unsuitable for use in production.__
47+-- Use 'withApplication' instead.
48+testWithApplication :: IO Application -> (Port -> IO a) -> IO a
49+testWithApplication mkApp action = do
50+ callingThread <- myThreadId
51+ app <- mkApp
52+ let wrappedApp request respond =
53+ app request respond `catch` \ e -> do
54+ throwTo callingThread (e :: SomeException)
55+ throwIO e
56+ withApplication (return wrappedApp) action
57+
58+data Waiter a
59+ = Waiter {
60+ notify :: a -> IO (),
61+ waitFor :: IO a
62+ }
63+
64+mkWaiter :: IO (Waiter a)
65+mkWaiter = do
66+ mvar <- newEmptyMVar
67+ return $ Waiter {
68+ notify = putMVar mvar,
69+ waitFor = readMVar mvar
70+ }
71+
72+-- | Opens a socket on a free port and returns both port and socket.
73+openFreePort :: IO (Port, Socket)
74+openFreePort = do
75+ s <- socket AF_INET Stream defaultProtocol
76+ localhost <- inet_addr "127.0.0.1"
77+ bind s (SockAddrInet aNY_PORT localhost)
78+ listen s 1
79+ port <- socketPort s
80+ return (fromIntegral port, s)
81+
82+-- | Like 'openFreePort' but closes the socket before exiting.
83+withFreePort :: ((Port, Socket) -> IO a) -> IO a
84+withFreePort = bracket openFreePort (close . snd)
warp/test/WithApplicationSpec.hsnew file+45
@@ -0,0 +1,45 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module WithApplicationSpec where
4+
5+import Control.Concurrent
6+import Control.Exception
7+import Network.HTTP.Types
8+import Network.Socket
9+import Network.Wai
10+import System.IO
11+import System.IO.Silently
12+import System.Process
13+import Test.Hspec
14+
15+import Network.Wai.Handler.Warp.WithApplication
16+
17+spec :: Spec
18+spec = do
19+ describe "withApplication" $ do
20+ it "runs a wai Application while executing the given action" $ do
21+ let mkApp = return $ \ _request respond -> respond $ responseLBS ok200 [] "foo"
22+ withApplication mkApp $ \ port -> do
23+ output <- readProcess "curl" ["-s", "localhost:" ++ show port] ""
24+ output `shouldBe` "foo"
25+
26+ it "does not propagate exceptions from the server to the executing thread" $
27+ hSilence [stderr] $ do
28+ let mkApp = return $ \ _request _respond -> throwIO $ ErrorCall "foo"
29+ withApplication mkApp $ \ port -> do
30+ output <- readProcess "curl" ["-s", "localhost:" ++ show port] ""
31+ output `shouldContain` "Something went wron"
32+
33+ describe "testWithApplication" $ do
34+ it "propagates exceptions from the server to the executing thread" $
35+ hSilence [stderr] $ do
36+ let mkApp = return $ \ _request _respond -> throwIO $ ErrorCall "foo"
37+ (testWithApplication mkApp $ \ port -> do
38+ readProcess "curl" ["-s", "localhost:" ++ show port] "")
39+ `shouldThrow` (errorCall "foo")
40+
41+ describe "withFreePort" $ do
42+ it "closes the socket before exiting" $ do
43+ MkSocket _ _ _ _ statusMVar <- withFreePort $ \ (_, sock) -> do
44+ return sock
45+ readMVar statusMVar `shouldReturn` Closed
warp/warp.cabal+4
@@ -34,6 +34,7 @@ Flag warp-debug
3434Library
3535 Build-Depends: base >= 3 && < 5
3636 , array
37+ , async
3738 , auto-update >= 0.1.3 && < 0.2
3839 , blaze-builder >= 0.4
3940 , bytestring >= 0.9.1.4
@@ -93,6 +94,7 @@ Library
9394 Network.Wai.Handler.Warp.Timeout
9495 Network.Wai.Handler.Warp.Types
9596 Network.Wai.Handler.Warp.Windows
97+ Network.Wai.Handler.Warp.WithApplication
9698 Paths_warp
9799 Ghc-Options: -Wall
98100
@@ -129,6 +131,7 @@ Test-Suite spec
129131 ResponseSpec
130132 RunSpec
131133 SendFileSpec
134+ WithApplicationSpec
132135 HTTP
133136 Hs-Source-Dirs: test, .
134137 Type: exitcode-stdio-1.0
@@ -157,6 +160,7 @@ Test-Suite spec
157160 , time
158161 , text
159162 , streaming-commons >= 0.1.10
163+ , silently
160164 , async
161165 , vault
162166 , stm >= 2.3