packages feed

port-utils-0.2.0.0: test/Tests/Network/Socket/WaitSpec.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}
module Tests.Network.Socket.WaitSpec where
import qualified Network.Socket.Wait.Internal as W
import qualified Network.Socket.Free as F
import qualified Network.Socket as N
import qualified Control.Concurrent as C
import qualified Control.Concurrent.STM as S
import qualified Control.Exception as E
import qualified Test.Hspec as H
import qualified System.Timeout as T
import qualified Control.Concurrent.Async as A
import qualified Control.Monad.Trans.State as St

instance Monad m => Semigroup (St.StateT s m ()) where
  (<>) = (>>)

instance Monad m => Monoid (St.StateT s m ()) where
  mempty = pure ()
  mappend = (<>)

main :: IO ()
main = H.hspec spec

throwTimeout :: Int -> String -> IO a -> IO a
throwTimeout delay msg action = T.timeout delay action >>= \case
  Nothing -> fail $ "timed out " ++ msg
  Just x -> pure x

spec :: H.Spec
spec = do
  H.describe "connectAction" $ H.before F.openFreePort $ H.after (N.close . snd) $ do
    H.it "returns False if it fails to connect" $ \(port, sock) -> do
      N.close sock
      -- There is a small race that another process might bind to this port
      W.connectAction "127.0.0.1" port `H.shouldReturn` False

    H.it "returns True if it connects" $ \(port, sock) -> do
      N.listen sock 128

      W.connectAction "127.0.0.1" port `H.shouldReturn` True

    H.it "returns True if connects after failing" $ \(port, sock) -> do
      N.close sock

      W.connectAction "127.0.0.1" port `H.shouldReturn` False

      E.bracket (N.socket N.AF_INET N.Stream N.defaultProtocol) N.close $ \sock' -> do
         N.bind sock' $ N.SockAddrInet (fromIntegral port) $ N.tupleToHostAddress (127,0,0,1)
         N.listen sock' 128

         W.connectAction "127.0.0.1" port `H.shouldReturn` True

    H.it "throws if the host does not exist" $ \(_, _) -> do
      W.connectAction "invalid." 3000 `H.shouldThrow` (\(_ :: IOError) -> True)

  H.describe "waitM" $ do
    H.it "returns immediantly if the action returns True" $
      flip St.execState False (W.waitM mempty (St.put True) (pure True)) `H.shouldBe` False

    H.it "loops until the action returns True" $ do
      let theAction = do
            St.get >>= \case
              0 -> St.put 1 >> pure False
              _ -> St.put 2 >> pure True

      flip St.execState 0 (W.waitM mempty (pure ()) theAction) `H.shouldBe` 2