socket-0.6.2.0: test/test.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Concurrent ( threadDelay )
import Control.Concurrent.Async ( async, race, poll, cancel, concurrently, wait )
import Control.Exception ( try, bracket, throwIO, catch )
import Control.Monad ( when, unless, void )
import Prelude hiding ( head )
import Data.Maybe ( isJust )
import Data.Monoid ( mempty )
import Data.Int ( Int64 )
import qualified Data.ByteString.Lazy as LBS
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck as QC
import System.Socket
import System.Socket.Family.Inet
import System.Socket.Family.Inet6
import System.Socket.Type.Stream
import System.Socket.Type.Datagram
import System.Socket.Protocol.TCP
import System.Socket.Protocol.UDP
main :: IO ()
main = defaultMain $ testGroup "socket"
[ testGroup "System.Socket"
[ group00
, group01
, group02
, group03
, group07
, group80
, group99
]
, testGroup "System.Socket.Inet" [
group200
, group201
]
]
port :: InetPort
port = 39000
port6 :: Inet6Port
port6 = 39000
group00 :: TestTree
group00 = testGroup "accept"
[ testGroup "Inet/Strem/TCP"
[ testCase "cancel operation" $
-- | This is to test interruptability of (blocking) calls like
-- accept. The implementation may either run the call "safe"
-- in another thread if it is really blocking or wait on events
-- in which case the control is at the RTS's IO manager.
-- In both cases this test should be able to cancel the accept
-- async and therefore terminate.
-- If the test hangs, this means the runtime system got sedated
-- possibly due to a blocking system call in a non-threaded
-- environment.
bracket (socket :: IO (Socket Inet Stream TCP)) close $ \s-> do
setSocketOption s (ReuseAddress True)
bind s (SocketAddressInet inetLoopback port)
listen s 5
a <- async (accept s)
threadDelay 1000000 -- make sure the async call really got enough time to start
p <- poll a
case p of
Just (Left ex) -> assertFailure "unexpected exception"
Just (Right _) -> assertFailure "unexpected connect"
Nothing -> void $ cancel a
]
]
group01 :: TestTree
group01 = testGroup "connect" [ testGroup "Inet/Stream/TCP" t1 ]
where
t1 =
[ testCase "connect to closed port on inetLoopback" $ bracket
( socket :: IO (Socket Inet Stream TCP))
close
( \s-> do
r <- try $ connect s (SocketAddressInet inetLoopback port)
case r of
Left e | e == eConnectionRefused -> return ()
| otherwise -> throwIO e
Right () -> assertFailure "connection should have failed"
)
, testCase "connect to closed port on inetNone" $ bracket
( socket :: IO (Socket Inet Stream TCP))
close
( \s-> do
r <- try $ connect s (SocketAddressInet inetNone port)
case r of
Left e | e == eNetworkUnreachable -> return ()
| e == eAddressNotAvailable -> return ()
| otherwise -> throwIO e
Right () -> assertFailure "connection should have failed"
)
, testCase "connect to open socket on localhost" $ bracket
( do server <- socket :: IO (Socket Inet Stream TCP)
client <- socket :: IO (Socket Inet Stream TCP)
return (server, client)
)
(\(server,client)-> do
close server
close client
)
(\(server,client)-> do
setSocketOption server (ReuseAddress True)
bind server (SocketAddressInet inetLoopback port)
listen server 5
connect client (SocketAddressInet inetLoopback port)
)
, testCase "connect closed socket" $ bracket
( do server <- socket :: IO (Socket Inet Stream TCP)
client <- socket :: IO (Socket Inet Stream TCP)
return (server, client)
)
(\(server,client)-> do
close server
close client
)
(\(server,client)-> do
setSocketOption server (ReuseAddress True)
bind server (SocketAddressInet inetLoopback port)
listen server 5
close client
connect client (SocketAddressInet inetLoopback port)
) `catch` \e-> case e of
_ | e == eBadFileDescriptor -> return ()
| otherwise -> assertFailure "expected eBadFileDescriptor"
, testCase "connect already connected socket" $ bracket
( do server <- socket :: IO (Socket Inet Stream TCP)
client <- socket :: IO (Socket Inet Stream TCP)
return (server, client)
)
(\(server,client)-> do
close server
close client
)
(\(server,client)-> do
setSocketOption server (ReuseAddress True)
bind server (SocketAddressInet inetLoopback port)
listen server 5
connect client (SocketAddressInet inetLoopback port)
connect client (SocketAddressInet inetLoopback port)
assertFailure "should have thrown eIsConnected"
) `catch` \e-> case e of
_ | e == eIsConnected -> return ()
| otherwise -> assertFailure "expected eIsConnected"
]
group02 :: TestTree
group02 = testGroup "listen"
[ testGroup "Inet/Datagram/UDP"
[ testCase "listen on bound socket" $ bracket
( socket :: IO (Socket Inet Datagram UDP) ) close $ \sock-> do
bind sock (SocketAddressInet inetLoopback port)
setSocketOption sock (ReuseAddress True)
listen sock 5 `catch` \e-> case e of
_ | e == eOperationNotSupported -> return ()
_ -> assertFailure "expected eOperationNotSupported"
]
, testGroup "Inet/Stream/TCP"
[ testCase "listen on bound socket" $ bracket
( socket :: IO (Socket Inet Stream TCP) ) close $ \sock-> do
bind sock (SocketAddressInet inetLoopback port)
setSocketOption sock (ReuseAddress True)
listen sock 5
]
]
group03 :: TestTree
group03 = testGroup "send/receive"
[ testGroup "Inet/Stream/TCP"
[ testCase "send and receive a chunk" $ bracket
( do
server <- socket :: IO (Socket Inet Stream TCP)
client <- socket :: IO (Socket Inet Stream TCP)
return (server, client)
)
( \(server,client)-> do
close server
close client
)
( \(server,client)-> do
let addr = SocketAddressInet inetLoopback port
let helloWorld = "Hello world!"
setSocketOption server (ReuseAddress True)
bind server addr
listen server 5
serverRecv <- async $ do
(peerSock, peerAddr) <- accept server
receive peerSock 4096 mempty
connect client addr
send client helloWorld mempty
msg <- wait serverRecv
when (msg /= helloWorld) (assertFailure "Received message was bogus.")
)
, testCase "trigger ePipe exception" $ bracket
( do
server <- socket :: IO (Socket Inet Stream TCP)
client <- socket :: IO (Socket Inet Stream TCP)
return (server, client)
)
( \(server,client)-> do
close server
close client
)
( \(server,client)-> do
let addr = SocketAddressInet inetLoopback port
setSocketOption server (ReuseAddress True)
bind server addr
listen server 5
connect client addr
(peerSock, _) <- accept server
_ <- send client "This should be received." mempty
_ <- receive peerSock 4096 mempty
close peerSock
threadDelay 1000000
e1 <- try $ send client "This might fail." mempty
case e1 of
Right _ -> return ()
Left e -> unless (e == ePipe) (throwIO e)
threadDelay 1000000
e2 <- try $ send client "This should fail." mempty
case e2 of
Right _ -> assertFailure "expected ePipe"
Left e -> unless (e == ePipe) (throwIO e)
)
]
, testGroup "Inet/Datagram/UDP"
[ testCase "send and receive a datagram" $ bracket
( do
server <- socket :: IO (Socket Inet Datagram UDP)
client <- socket :: IO (Socket Inet Datagram UDP)
return (server, client)
)
( \(server,client)-> do
close server
close client
)
( \(server,client)-> do
let addr = SocketAddressInet inetLoopback port
let helloWorld = "Hello world!"
bind server addr
((msg,peeraddr),_) <- concurrently (receiveFrom server 4096 mempty) $ do
-- This is a race condition:
-- The server must listen before the client sends his msg or the packt goes
-- to nirvana. Still, a second here should be enough. If not, there's
-- something wrong worth investigating.
threadDelay 1000000
sendTo client helloWorld mempty addr
when (msg /= helloWorld) $ assertFailure "messages not equal"
)
]
]
group07 :: TestTree
group07 = testGroup "sendAll/receiveAll"
[ testGroup "Inet/Stream/TCP"
[ testCase "send and receive a 128MB chunk" $ bracket
( do
server <- socket :: IO (Socket Inet Stream TCP)
client <- socket :: IO (Socket Inet Stream TCP)
return (server, client)
)
( \(server,client)-> do
close server
close client
)
( \(server,client)-> do
let addr = SocketAddressInet inetLoopback port
let msgSize = 128*1024*1024 + 1 :: Int64
let msg = LBS.replicate msgSize 23
setSocketOption server (ReuseAddress True)
bind server addr
listen server 5
serverRecv <- async $ do
(peerSock, peerAddr) <- accept server
receiveAll peerSock msgSize mempty
threadDelay 100000
connect client addr
sendAll client msg mempty
close client
msgReceived <- wait serverRecv
when (msgReceived /= msg) (assertFailure "Received message was bogus.")
)
]
]
group80 :: TestTree
group80 = testGroup "setSocketOption" [ testGroup "V6Only"
[ testCase "present" $ bracket
( do
server <- socket :: IO (Socket Inet6 Datagram UDP)
client <- socket :: IO (Socket Inet Datagram UDP)
return (server, client)
)
( \(server,client)-> do
close server
close client
)
( \(server,client)-> do
setSocketOption server (V6Only True)
bind server (SocketAddressInet6 inet6Any port6 0 0)
threadDelay 1000000 -- wait for the listening socket being set up
sendTo client "PING" mempty (SocketAddressInet inetLoopback port)
eith <- race
( void $ receiveFrom server 4096 mempty )
( threadDelay 1000000 )
case eith of
Left () -> assertFailure "expected timeout"
Right () -> return () -- timeout is the expected behaviour
)
, testCase "absent" $ bracket
( do
server <- socket :: IO (Socket Inet6 Datagram UDP)
client <- socket :: IO (Socket Inet Datagram UDP)
return (server, client)
)
( \(server,client)-> do
close server
close client
)
( \(server,client)-> do
setSocketOption server (V6Only False)
bind server (SocketAddressInet6 inet6Any port6 0 0)
threadDelay 1000000 -- wait for the listening socket being set up
sendTo client "PING" mempty (SocketAddressInet inetLoopback port)
eith <- race
( void $ receiveFrom server 4096 mempty )
( threadDelay 1000000 )
case eith of
Left () -> return ()
Right () -> assertFailure "expected packet"
)
]
]
group99 :: TestTree
group99 = testGroup "getAddrInfo" [
testCase "getAddrInfo \"127.0.0.1\" \"80\"" $ do
ais <- getAddressInfo
(Just "127.0.0.1")
(Just "80")
aiNumericHost :: IO [AddressInfo Inet Stream TCP]
when (length ais /= 1) $ assertFailure "expected 1 result"
let [ai] = ais
when (isJust $ canonicalName ai) $ assertFailure "expected no canonical name"
let sa = socketAddress ai
when (inetAddress sa /= inetLoopback) $ assertFailure "expected loopback address"
when (inetPort sa /= 80) $ assertFailure "expected port 80"
, testCase "getAddrInfo \"\" \"\"" $
void (getAddressInfo Nothing Nothing mempty :: IO [AddressInfo Inet Stream TCP]) `catch` \e-> case e of
_ | e == eaiNoName -> return ()
_ -> assertFailure "expected eaiNoName"
]
group200 :: TestTree
group200 = testGroup "System.Socket.Family.Inet" [
testCase "inetAddressFromTuple (127,0,0,1) == inetLoopback" $
assertEqual "" ( inetAddressFromTuple (127,0,0,1) ) inetLoopback
, QC.testProperty "inetAddressToTuple (inetAddressFromTuple x) == x" $ \x->
inetAddressToTuple (inetAddressFromTuple x) === x
]
group201 :: TestTree
group201 = testGroup "System.Socket.Family.Inet6" [
testCase "inet6AddressFromTuple (0,0,0,0,0,0,0,1) == inet6Loopback" $
assertEqual "" ( inet6AddressFromTuple (0,0,0,0,0,0,0,1) ) inet6Loopback
, QC.testProperty "inet6AddressToTuple (inet6AddressFromTuple x) == x" $ \x->
inet6AddressToTuple (inet6AddressFromTuple x) === x
]