Skip to content

Commit 2c75711

Browse files
committed
New randomPort, reserveRandomPort and portInUse functions
1 parent 968ef14 commit 2c75711

File tree

4 files changed

+103
-1
lines changed

4 files changed

+103
-1
lines changed

hedgehog-extras.cabal

+23-1
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,21 @@ common directory { build-depends: directory
2727
common exceptions { build-depends: exceptions }
2828
common filepath { build-depends: filepath }
2929
common hedgehog { build-depends: hedgehog }
30+
common hedgehog-quickcheck { build-depends: hedgehog-quickcheck }
3031
common http-conduit { build-depends: http-conduit }
3132
common lifted-async { build-depends: lifted-async }
3233
common lifted-base { build-depends: lifted-base }
33-
common monad-control { build-depends: monad-control }
3434
common mmorph { build-depends: mmorph }
35+
common monad-control { build-depends: monad-control }
3536
common mtl { build-depends: mtl }
3637
common network { build-depends: network }
3738
common process { build-depends: process }
3839
common resourcet { build-depends: resourcet }
3940
common stm { build-depends: stm }
4041
common tar { build-depends: tar < 0.6 }
42+
common tasty { build-depends: tasty }
43+
common tasty-hedgehog { build-depends: tasty-hedgehog }
44+
common tasty-quickcheck { build-depends: tasty-quickcheck }
4145
common temporary { build-depends: temporary }
4246
common text { build-depends: text }
4347
common time { build-depends: time >= 1.9.1 }
@@ -47,6 +51,8 @@ common unliftio { build-depends: unliftio
4751
common yaml { build-depends: yaml }
4852
common zlib { build-depends: zlib }
4953

54+
common hedgehog-extras { build-depends: hedgehog-extras }
55+
5056
common Win32
5157
if os(windows)
5258
build-depends: Win32 >= 2.5.4.1
@@ -109,6 +115,7 @@ library
109115
Hedgehog.Extras.Stock.CallStack
110116
Hedgehog.Extras.Stock.IO.File
111117
Hedgehog.Extras.Stock.IO.Network.NamedPipe
118+
Hedgehog.Extras.Stock.IO.Network.Port
112119
Hedgehog.Extras.Stock.IO.Network.Socket
113120
Hedgehog.Extras.Stock.IO.Network.Sprocket
114121
Hedgehog.Extras.Stock.IO.Process
@@ -124,3 +131,18 @@ library
124131
Hedgehog.Extras.Test.MonadAssertion
125132
Hedgehog.Extras.Test.Network
126133
Hedgehog.Extras.Test.Process
134+
135+
test-suite hedgehog-extras-test
136+
import: base, project-config,
137+
hedgehog,
138+
hedgehog-extras,
139+
network,
140+
tasty,
141+
tasty-hedgehog,
142+
hs-source-dirs: test
143+
main-is: hedgehog-extras-test.hs
144+
type: exitcode-stdio-1.0
145+
146+
other-modules: Hedgehog.Extras.Stock.IO.Network.PortSpec
147+
148+
build-tool-depends: tasty-discover:tasty-discover
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{-# LANGUAGE TypeApplications #-}
2+
3+
module Hedgehog.Extras.Stock.IO.Network.Port
4+
( randomPort
5+
, reserveRandomPort
6+
, portInUse
7+
) where
8+
9+
import Control.Exception
10+
import Control.Monad (Monad (..), MonadFail (..))
11+
import Control.Monad.IO.Class
12+
import Control.Monad.Trans.Resource
13+
import Data.Bool
14+
import Data.Either
15+
import Data.Function
16+
import Network.Socket
17+
18+
-- | Return a random available port on a specified host address
19+
randomPort :: ()
20+
=> MonadIO m
21+
=> MonadFail m
22+
=> HostAddress
23+
-> m PortNumber
24+
randomPort hostAddress = do
25+
sock <- liftIO $ socket AF_INET Stream defaultProtocol
26+
liftIO $ bind sock $ SockAddrInet defaultPort hostAddress
27+
SockAddrInet port _ <- liftIO $ getSocketName sock
28+
liftIO $ close sock
29+
return port
30+
31+
reserveRandomPort :: ()
32+
=> MonadFail m
33+
=> MonadResource m
34+
=> HostAddress
35+
-> m (ReleaseKey, PortNumber)
36+
reserveRandomPort hostAddress = do
37+
sock <- liftIO $ socket AF_INET Stream defaultProtocol
38+
liftIO $ bind sock $ SockAddrInet defaultPort hostAddress
39+
SockAddrInet port _ <- liftIO $ getSocketName sock
40+
releaseKey <- register $ close sock
41+
return (releaseKey, port)
42+
43+
-- | Check if a port is in use on a specified host address
44+
portInUse :: ()
45+
=> MonadIO m
46+
=> HostAddress
47+
-> PortNumber
48+
-> m Bool
49+
portInUse hostAddress pn = do
50+
sock <- liftIO $ socket AF_INET Stream defaultProtocol
51+
result <- liftIO $ try @SomeException $ bind sock (SockAddrInet pn hostAddress)
52+
liftIO $ close sock
53+
return $ either (const False) (const True) result
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module Hedgehog.Extras.Stock.IO.Network.PortSpec
2+
( hprop_randomPort
3+
) where
4+
5+
import Data.Function
6+
import Data.Semigroup
7+
import Hedgehog (Property)
8+
import qualified Hedgehog as H
9+
import qualified Hedgehog.Extras as H
10+
import qualified Hedgehog.Extras.Stock.IO.Network.Port as IO
11+
import qualified Network.Socket as N
12+
import Text.Show
13+
14+
hprop_randomPort :: Property
15+
hprop_randomPort =
16+
H.propertyOnce $ do
17+
let hostAddress = N.tupleToHostAddress (0, 0, 0, 0)
18+
19+
pn <- H.evalIO $ IO.randomPort hostAddress
20+
21+
H.note_ $ "Allocated port: " <> show pn
22+
23+
-- Check that the port is available and can be bound to a socket.
24+
sock <- H.evalIO $ N.socket N.AF_INET N.Stream N.defaultProtocol
25+
H.evalIO $ N.bind sock $ N.SockAddrInet pn hostAddress
26+
H.evalIO $ N.close sock

test/hedgehog-extras-test.hs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --hide-successes #-}

0 commit comments

Comments
 (0)