packages feed

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
  ]