packages feed

network-attoparsec-0.10.0: test/Network/AttoparsecSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Network.AttoparsecSpec where

import           Control.Concurrent               (ThreadId, forkIO, killThread,
                                                   threadDelay, myThreadId)

import           Control.Monad.IO.Class
import           Control.Monad.Catch

import qualified Data.Attoparsec.ByteString       as Atto

import qualified Network.Simple.TCP               as NS (Socket, accept,
                                                         connect, listen)
import qualified Network.Socket.ByteString        as NS (send)

import           Data.Attoparsec.ByteString.Char8 (decimal, endOfLine)
import qualified Network.Attoparsec               as Atto

import           Test.Hspec

pairSockets :: ( MonadIO m
               , MonadMask m)
            => (NS.Socket -> IO r0)
            -> (NS.Socket -> m r1)
            -> m r1
pairSockets writeCallback readCallback =
  let
    handleWrite :: NS.Socket -> IO ()
    handleWrite s = do
      putStrLn "handling writing"
      _ <- writeCallback s
      myTid <- myThreadId
      putStrLn ("handled write, my thread id = " ++ show myTid)
      return ()

    handleRead s = do
      result <- readCallback s
      return result

    handleServer :: IO ThreadId
    handleServer =
      return =<< forkIO $ NS.listen "*" "1234" $ \(lsock, _) ->
                                                  NS.accept lsock (handleWrite . fst)

  in do
    liftIO $ putStrLn "starting server"
    serverThread <- liftIO $ handleServer

    liftIO $ putStrLn ("started server, thread id = " ++ show serverThread)
    liftIO $ threadDelay 100000

    result <- NS.connect "127.0.0.1" "1234" (handleRead . fst)

    liftIO $ putStrLn "killing server"
    liftIO $ killThread serverThread
    liftIO $ putStrLn "killed server"

    return result

spec :: Spec
spec = do
  describe "when parsing a single object" $ do
    it "it should work with correct data" $
      let writeSocket s = NS.send s "1234"
          readSocket s  = Atto.parseOne s (Atto.parse numberParser)
          numberParser  = decimal

      in (pairSockets writeSocket readSocket) `shouldReturn` 1234

    it "it should work with partial data" $
      let writeSocket s = do
            _ <- NS.send s "12"
            threadDelay 100000
            NS.send s "34"

          readSocket s  = Atto.parseOne s (Atto.parse numberParser)
          numberParser  = decimal

      in (pairSockets writeSocket readSocket) `shouldReturn` 1234

    it "it should expect no unconsumed data" $
      let writeSocket s = NS.send s "1234ab"

          readSocket s  = Atto.parseOne s (Atto.parse numberParser)
          numberParser  = decimal

      in (pairSockets writeSocket readSocket) `shouldThrow` anyIOException

    it "it should throw an error when the provided data is incorrect" $
      let writeSocket s = NS.send s "ab"
          readSocket s  = Atto.parseOne s (Atto.parse numberParser)
          numberParser  = decimal

      in (pairSockets writeSocket readSocket) `shouldThrow` anyIOException

    it "it should throw an error when the socket is closed before parsing could be completed" $
      let writeSocket s = NS.send s "12"
          readSocket s  = Atto.parseOne s (Atto.parse numberParser)
          numberParser  = do
            _ <- "1234"
            _ <- endOfLine
            return ()

      in (pairSockets writeSocket readSocket) `shouldThrow` anyIOException

  describe "when parsing multiple matching objects" $ do
    let numberParser :: Atto.Parser Integer
        numberParser = do
          num <- decimal
          endOfLine
          return num

    it "should return error when using single object parser" $
      let writeSocket s = NS.send s "1234\n5678\n"
          readSocket s  = Atto.parseOne s (Atto.parse numberParser)

      in (pairSockets writeSocket readSocket) `shouldThrow` anyIOException

    it "should return multiple objects when using multi object parser" $
      let writeSocket s = NS.send s "1234\n5678\n"
          readSocket s  = do
            (_, xs) <- Atto.parseMany s (Atto.parse numberParser) (Atto.parse numberParser)
            return xs
      in (pairSockets writeSocket readSocket) `shouldReturn` [1234, 5678]

    it "should return single object when using multi object parser and providing partial data" $
      let writeSocket s = NS.send s "1234\n56"
          readSocket s  = do
            (_, xs) <- Atto.parseMany s (Atto.parse numberParser) (Atto.parse numberParser)
            return xs

      in (pairSockets writeSocket readSocket) `shouldReturn` [1234]

    it "should return multiple objects when using multi object parser and providing incremental data" $
      let writeSocket s = do
            _ <- NS.send s "1234\n56"
            threadDelay 100000
            NS.send s "78\n9012\n"
          readSocket s  = do
            (p, xs1) <- Atto.parseMany s (Atto.parse numberParser) (Atto.parse numberParser)
            (_, xs2) <- Atto.parseMany s (Atto.parse numberParser) p
            return (xs1 ++ xs2)

      in (pairSockets writeSocket readSocket) `shouldReturn` [1234, 5678, 9012]

    it "should return nothing objects when using multi object parser and providing not enough data" $
      let writeSocket s = NS.send s "12"
          readSocket s  = do
            (_, xs) <- Atto.parseMany s (Atto.parse numberParser) (Atto.parse numberParser)
            return xs

      in (pairSockets writeSocket readSocket) `shouldReturn` []

    it "should return one object when using multi object parser and providing incremental data" $
      let writeSocket s = do
            _ <- NS.send s "12"
            threadDelay 100000
            NS.send s "34\n"
          readSocket s  = do
            (p, xs1) <- Atto.parseMany s (Atto.parse numberParser) (Atto.parse numberParser)
            (_, xs2) <- Atto.parseMany s (Atto.parse numberParser) p
            return (xs1 ++ xs2)

      in (pairSockets writeSocket readSocket) `shouldReturn` [1234]