attoparsec-framer-0.1.0.0: src/Data/Attoparsec/Framer/Testing.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_HADDOCK prune not-home #-}
{- |
Module : Data.Attoparsec.Framer.Testing
Copyright : (c) 2022 Tim Emiola
Maintainer : Tim Emiola <adetokunbo@emio.la>
SPDX-License-Identifier: BSD3
This module provides combinators that simplify unit tests of code that
use @'Framer's@.
-}
module Data.Attoparsec.Framer.Testing (
-- * testing combinators
parsesFromFramerOk,
chunksOfN,
) where
import Control.Exception (catch)
import qualified Data.Attoparsec.ByteString as A
import Data.Attoparsec.Framer
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.IORef (
IORef,
modifyIORef',
newIORef,
readIORef,
writeIORef,
)
import Data.List (unfoldr)
import Data.Word (Word32)
{- | Creates a 'Framer' and uses 'runFramer to confirm that the expect frames
are received '
-}
parsesFromFramerOk :: Eq a => (a -> ByteString) -> A.Parser a -> Word32 -> [a] -> IO Bool
parsesFromFramerOk asBytes parser chunkSize' wanted = do
chunkStore <- newIORef Nothing
dst <- newIORef []
let updateDst x = modifyIORef' dst ((:) x)
mkChunks n = mconcat $ map (chunksOfN n . asBytes) wanted
src = nextFrom' mkChunks chunkStore
frames = setChunkSize chunkSize' $ mkFramer parser updateDst src
runFramer frames `catch` (\(_e :: NoMoreInput) -> pure ())
got <- readIORef dst
pure $ got == reverse wanted
-- | Split a 'ByteString' into chunks of given size
chunksOfN :: Int -> ByteString -> [ByteString]
chunksOfN x b =
let go y =
let taken = BS.take x y
in if BS.null taken then Nothing else Just (taken, BS.drop x y)
in unfoldr go b
nextFrom' ::
(Int -> [ByteString]) -> IORef (Maybe [ByteString]) -> Word32 -> IO ByteString
nextFrom' initChunks chunkStore chunkSize' = do
readIORef chunkStore >>= \case
Nothing -> do
writeIORef chunkStore $ Just $ initChunks $ fromIntegral chunkSize'
nextFrom' initChunks chunkStore chunkSize'
Just [] -> pure BS.empty
Just (x : xs) -> do
writeIORef chunkStore $ Just xs
pure x