streaming-commons 0.1.9.1 → 0.1.10.0
raw patch · 8 files changed
+548/−26 lines, 8 filesdep ~basedep ~blaze-builderdep ~textPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: base, blaze-builder, text
API changes (from Hackage documentation)
+ Data.Streaming.ByteString.Builder: allNewBuffersStrategy :: Int -> BufferAllocStrategy
+ Data.Streaming.ByteString.Builder: allocBuffer :: Int -> IO Buffer
+ Data.Streaming.ByteString.Builder: bufferSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder: data Buffer
+ Data.Streaming.ByteString.Builder: defaultStrategy :: BufferAllocStrategy
+ Data.Streaming.ByteString.Builder: freeSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder: newByteStringBuilderRecv :: BufferAllocStrategy -> IO (BuilderRecv, BuilderFinish)
+ Data.Streaming.ByteString.Builder: nextSlice :: Int -> Buffer -> Maybe Buffer
+ Data.Streaming.ByteString.Builder: reuseBuffer :: Buffer -> Buffer
+ Data.Streaming.ByteString.Builder: reuseBufferStrategy :: IO Buffer -> BufferAllocStrategy
+ Data.Streaming.ByteString.Builder: sliceSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder: type BufferAllocStrategy = (IO Buffer, Int -> Buffer -> IO (IO Buffer))
+ Data.Streaming.ByteString.Builder: type BuilderFinish = IO (Maybe ByteString)
+ Data.Streaming.ByteString.Builder: type BuilderPopper = IO ByteString
+ Data.Streaming.ByteString.Builder: type BuilderRecv = Builder -> IO BuilderPopper
+ Data.Streaming.ByteString.Builder: unsafeFreezeBuffer :: Buffer -> ByteString
+ Data.Streaming.ByteString.Builder: unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe ByteString
+ Data.Streaming.ByteString.Builder.Buffer: Buffer :: {-# UNPACK #-} !(ForeignPtr Word8) -> {-# UNPACK #-} !(Ptr Word8) -> {-# UNPACK #-} !(Ptr Word8) -> {-# UNPACK #-} !(Ptr Word8) -> Buffer
+ Data.Streaming.ByteString.Builder.Buffer: allNewBuffersStrategy :: Int -> BufferAllocStrategy
+ Data.Streaming.ByteString.Builder.Buffer: allocBuffer :: Int -> IO Buffer
+ Data.Streaming.ByteString.Builder.Buffer: bufferSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder.Buffer: data Buffer
+ Data.Streaming.ByteString.Builder.Buffer: defaultStrategy :: BufferAllocStrategy
+ Data.Streaming.ByteString.Builder.Buffer: freeSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder.Buffer: nextSlice :: Int -> Buffer -> Maybe Buffer
+ Data.Streaming.ByteString.Builder.Buffer: reuseBuffer :: Buffer -> Buffer
+ Data.Streaming.ByteString.Builder.Buffer: reuseBufferStrategy :: IO Buffer -> BufferAllocStrategy
+ Data.Streaming.ByteString.Builder.Buffer: sliceSize :: Buffer -> Int
+ Data.Streaming.ByteString.Builder.Buffer: type BufferAllocStrategy = (IO Buffer, Int -> Buffer -> IO (IO Buffer))
+ Data.Streaming.ByteString.Builder.Buffer: unsafeFreezeBuffer :: Buffer -> ByteString
+ Data.Streaming.ByteString.Builder.Buffer: unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe ByteString
+ Data.Streaming.ByteString.Builder.Buffer: updateEndOfSlice :: Buffer -> Ptr Word8 -> Buffer
+ Data.Streaming.ByteString.Builder.Class: builderFlush :: StreamingBuilder b => b
+ Data.Streaming.ByteString.Builder.Class: class Monoid b => StreamingBuilder b
+ Data.Streaming.ByteString.Builder.Class: instance StreamingBuilder Builder
+ Data.Streaming.ByteString.Builder.Class: newBuilderRecv :: StreamingBuilder b => BufferAllocStrategy -> IO (b -> IO BuilderPopper, BuilderFinish)
- Data.Streaming.Blaze: data Buffer :: *
+ Data.Streaming.Blaze: data Buffer
Files
- ChangeLog.md +4/−0
- Data/Streaming/Blaze.hs +29/−14
- Data/Streaming/ByteString/Builder.hs +123/−6
- Data/Streaming/ByteString/Builder/Buffer.hs +208/−0
- Data/Streaming/ByteString/Builder/Class.hs +43/−0
- streaming-commons.cabal +6/−3
- test/Data/Streaming/BlazeSpec.hs +16/−0
- test/Data/Streaming/ByteString/BuilderSpec.hs +119/−3
ChangeLog.md view
@@ -1,3 +1,7 @@+## 0.1.10++Support blaze-builder >= 0.4. Add `newByteStringBuilderRecv` to Data.Streaming.ByteString.Builder; add modules Data.Streaming.ByteString.Builder.Buffer and Data.Streaming.ByteString.Builder.Class.+ ## 0.1.9 Add Data.Streaming.ByteString.Builder
Data/Streaming/Blaze.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE RankNTypes #-}@@ -5,6 +6,9 @@ -- -- Adapted from blaze-builder-enumerator, written by myself and Simon Meier. --+-- Note: if you have blaze-builder >= 0.4, 'newBlazeRecv' just calls+-- 'Data.Streaming.ByteString.Builder.newByteStringBuilderRecv'+ -- Note that the functions here can work in any monad built on top of @IO@ or -- @ST@. module Data.Streaming.Blaze@@ -37,26 +41,25 @@ , defaultStrategy ) where -import Data.IORef+import Blaze.ByteString.Builder+import qualified Data.ByteString as S -import qualified Data.ByteString as S+#if MIN_VERSION_blaze_builder(0,4,0) -import Blaze.ByteString.Builder.Internal-import Blaze.ByteString.Builder.Internal.Types-import Blaze.ByteString.Builder.Internal.Buffer+import Data.Streaming.ByteString.Builder --- | Provides a series of @ByteString@s until empty, at which point it provides--- an empty @ByteString@.------ Since 0.1.2-type BlazePopper = IO S.ByteString+newBlazeRecv :: BufferAllocStrategy -> IO (BlazeRecv, BlazeFinish)+newBlazeRecv = newByteStringBuilderRecv+{-# INLINE newBlazeRecv #-} -type BlazeRecv = Builder -> IO BlazePopper+#else /* !MIN_VERSION_blaze_builder(0,4,0) */ -type BlazeFinish = IO (Maybe S.ByteString)+import Blaze.ByteString.Builder.Internal hiding (insertByteString)+import Blaze.ByteString.Builder.Internal.Types hiding (insertByteString)+import Blaze.ByteString.Builder.Internal.Buffer (execBuildStep)+import Data.IORef -defaultStrategy :: BufferAllocStrategy-defaultStrategy = allNewBuffersStrategy defaultBufferSize+import Data.Streaming.ByteString.Builder.Buffer newBlazeRecv :: BufferAllocStrategy -> IO (BlazeRecv, BlazeFinish) newBlazeRecv (ioBufInit, nextBuf) = do@@ -133,3 +136,15 @@ buf <- lift $ unsafeLiftIO $ ioBuf maybe (return ()) (yield' . Chunk) (unsafeFreezeNonEmptyBuffer buf) -}++#endif /* !MIN_VERSION_blaze_builder(0,4,0) */++-- | Provides a series of @ByteString@s until empty, at which point it provides+-- an empty @ByteString@.+--+-- Since 0.1.2+type BlazePopper = IO S.ByteString++type BlazeRecv = Builder -> IO BlazePopper++type BlazeFinish = IO (Maybe S.ByteString)
Data/Streaming/ByteString/Builder.hs view
@@ -1,23 +1,139 @@--- | Provides @toByteStringIO*@ like "Blaze.ByteString.Builder"s, for "Data.ByteString.Builder".+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE RankNTypes #-}++-- | Convert a stream of bytestring @Builder@s into a stream of @ByteString@s. --+-- Adapted from blaze-builder-enumerator, written by Michael Snoyman and Simon Meier.+--+-- Note that the functions here can work in any monad built on top of @IO@ or+-- @ST@.+--+-- Also provides @toByteStringIO*@ like "Blaze.ByteString.Builder"s, for+-- "Data.ByteString.Builder".+-- -- Since 0.1.9+-- module Data.Streaming.ByteString.Builder- ( toByteStringIO+ ( BuilderRecv+ , BuilderPopper+ , BuilderFinish+ , newByteStringBuilderRecv++ -- * toByteStringIO+ , toByteStringIO , toByteStringIOWith- , toByteStringIOWithBuffer )+ , toByteStringIOWithBuffer++ -- * Buffers+ , Buffer++ -- ** Status information+ , freeSize+ , sliceSize+ , bufferSize++ -- ** Creation and modification+ , allocBuffer+ , reuseBuffer+ , nextSlice++ -- ** Conversion to bytestings+ , unsafeFreezeBuffer+ , unsafeFreezeNonEmptyBuffer++ -- * Buffer allocation strategies+ , BufferAllocStrategy+ , allNewBuffersStrategy+ , reuseBufferStrategy+ , defaultStrategy+ ) where -import Control.Monad (when)+import Control.Monad (when,unless)+import qualified Data.ByteString as S import Data.ByteString.Builder (Builder) import Data.ByteString.Builder.Extra (runBuilder, BufferWriter, Next(Done, More, Chunk)) import Data.ByteString.Internal (mallocByteString, ByteString(PS)) import Data.ByteString.Lazy.Internal (defaultChunkSize)+import Data.IORef (newIORef, writeIORef, readIORef) import Data.Word (Word8) import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)+import Foreign.Ptr (plusPtr, minusPtr) +import Data.Streaming.ByteString.Builder.Buffer++-- | Provides a series of @ByteString@s until empty, at which point it provides+-- an empty @ByteString@.+--+-- Since 0.1.10.0+--+type BuilderPopper = IO S.ByteString++type BuilderRecv = Builder -> IO BuilderPopper++type BuilderFinish = IO (Maybe S.ByteString)++newByteStringBuilderRecv :: BufferAllocStrategy -> IO (BuilderRecv, BuilderFinish)+newByteStringBuilderRecv (ioBufInit, nextBuf) = do+ refBuf <- newIORef ioBufInit+ return (push refBuf, finish refBuf)+ where+ finish refBuf = do+ ioBuf <- readIORef refBuf+ buf <- ioBuf+ return $ unsafeFreezeNonEmptyBuffer buf++ push refBuf builder = do+ refWri <- newIORef $ Left $ runBuilder builder+ return $ popper refBuf refWri++ popper refBuf refWri = do+ ioBuf <- readIORef refBuf+ ebWri <- readIORef refWri+ case ebWri of+ Left bWri -> do+ !buf@(Buffer _ _ op ope) <- ioBuf+ (bytes, next) <- bWri op (ope `minusPtr` op)+ let op' = op `plusPtr` bytes+ case next of+ Done -> do+ writeIORef refBuf $ return $ updateEndOfSlice buf op'+ return S.empty+ More minSize bWri' -> do+ let buf' = updateEndOfSlice buf op'+ {-# INLINE cont #-}+ cont mbs = do+ -- sequencing the computation of the next buffer+ -- construction here ensures that the reference to the+ -- foreign pointer `fp` is lost as soon as possible.+ ioBuf' <- nextBuf minSize buf'+ writeIORef refBuf ioBuf'+ writeIORef refWri $ Left bWri'+ case mbs of+ Just bs | not $ S.null bs -> return bs+ _ -> popper refBuf refWri+ cont $ unsafeFreezeNonEmptyBuffer buf'+ Chunk bs bWri' -> do+ let buf' = updateEndOfSlice buf op'+ let yieldBS = do+ nextBuf 1 buf' >>= writeIORef refBuf+ writeIORef refWri $ Left bWri'+ if S.null bs+ then popper refBuf refWri+ else return bs+ case unsafeFreezeNonEmptyBuffer buf' of+ Nothing -> yieldBS+ Just bs' -> do+ writeIORef refWri $ Right yieldBS+ return bs'+ Right action -> action+ -- | Use a pre-existing buffer to 'toByteStringIOWith'. -- -- Since 0.1.9+-- toByteStringIOWithBuffer :: Int -> (ByteString -> IO ()) -> Builder@@ -31,7 +147,7 @@ loop :: BufferWriter -> IO () loop wr = do (len, next) <- withForeignPtr buf (flip wr bufSize)- when (len > 0) (io (PS buf 0 len))+ when (len > 0) (io $! PS buf 0 len) case next of Done -> return () More newBufSize nextWr@@ -40,7 +156,7 @@ go newBufSize newBuf nextWr | otherwise -> loop nextWr Chunk s nextWr -> do- io s+ unless (S.null s) (io s) loop nextWr -- | @toByteStringIOWith bufSize io b@ runs the builder @b@ with a buffer of@@ -67,6 +183,7 @@ -> IO () toByteStringIOWith bufSize io b = toByteStringIOWithBuffer bufSize io b =<< mallocByteString bufSize+{-# INLINE toByteStringIOWith #-} -- | Run the builder with a 'defaultChunkSize'd buffer and execute the given -- 'IO' action whenever the buffer is full or gets flushed.
+ Data/Streaming/ByteString/Builder/Buffer.hs view
@@ -0,0 +1,208 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE Rank2Types #-}++-- | Buffers for 'Builder's. This is a partial copy of blaze-builder-0.3.3.4's+-- "Blaze.ByteString.Builder.Internal.Buffer" module, which was removed in+-- blaze-builder-0.4.+--+-- If you are using blaze-builder 0.3.*, this module just re-exports from+-- "Blaze.ByteString.Builder.Internal.Buffer".+--+-- Since 0.1.10.0+--+module Data.Streaming.ByteString.Builder.Buffer+ (+ -- * Buffers+ Buffer (..)++ -- ** Status information+ , freeSize+ , sliceSize+ , bufferSize++ -- ** Creation and modification+ , allocBuffer+ , reuseBuffer+ , nextSlice+ , updateEndOfSlice++ -- ** Conversion to bytestings+ , unsafeFreezeBuffer+ , unsafeFreezeNonEmptyBuffer++ -- * Buffer allocation strategies+ , BufferAllocStrategy+ , allNewBuffersStrategy+ , reuseBufferStrategy+ , defaultStrategy+ ) where++import Data.ByteString.Lazy.Internal (defaultChunkSize)++#if MIN_VERSION_blaze_builder(0,4,0)++import qualified Data.ByteString as S+import qualified Data.ByteString.Internal as S+import Foreign (Word8, ForeignPtr, Ptr, plusPtr, minusPtr)+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)++------------------------------------------------------------------------------+-- Buffers+------------------------------------------------------------------------------++-- | A buffer @Buffer fpbuf p0 op ope@ describes a buffer with the underlying+-- byte array @fpbuf..ope@, the currently written slice @p0..op@ and the free+-- space @op..ope@.+--+-- Since 0.1.10.0+--+data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8) -- underlying pinned array+ {-# UNPACK #-} !(Ptr Word8) -- beginning of slice+ {-# UNPACK #-} !(Ptr Word8) -- next free byte+ {-# UNPACK #-} !(Ptr Word8) -- first byte after buffer++-- | The size of the free space of the buffer.+--+-- Since 0.1.10.0+--+freeSize :: Buffer -> Int+freeSize (Buffer _ _ op ope) = ope `minusPtr` op++-- | The size of the written slice in the buffer.+--+-- Since 0.1.10.0+--+sliceSize :: Buffer -> Int+sliceSize (Buffer _ p0 op _) = op `minusPtr` p0++-- | The size of the whole byte array underlying the buffer.+--+-- Since 0.1.10.0+--+bufferSize :: Buffer -> Int+bufferSize (Buffer fpbuf _ _ ope) =+ ope `minusPtr` unsafeForeignPtrToPtr fpbuf++-- | @allocBuffer size@ allocates a new buffer of size @size@.+--+-- Since 0.1.10.0+--+{-# INLINE allocBuffer #-}+allocBuffer :: Int -> IO Buffer+allocBuffer size = do+ fpbuf <- S.mallocByteString size+ let !pbuf = unsafeForeignPtrToPtr fpbuf+ return $! Buffer fpbuf pbuf pbuf (pbuf `plusPtr` size)++-- | Resets the beginning of the next slice and the next free byte such that+-- the whole buffer can be filled again.+--+-- Since 0.1.10.0+--+{-# INLINE reuseBuffer #-}+reuseBuffer :: Buffer -> Buffer+reuseBuffer (Buffer fpbuf _ _ ope) = Buffer fpbuf p0 p0 ope+ where+ p0 = unsafeForeignPtrToPtr fpbuf++-- | Convert the buffer to a bytestring. This operation is unsafe in the sense+-- that created bytestring shares the underlying byte array with the buffer.+-- Hence, depending on the later use of this buffer (e.g., if it gets reset and+-- filled again) referential transparency may be lost.+--+-- Since 0.1.10.0+--+{-# INLINE unsafeFreezeBuffer #-}+unsafeFreezeBuffer :: Buffer -> S.ByteString+unsafeFreezeBuffer (Buffer fpbuf p0 op _) =+ S.PS fpbuf (p0 `minusPtr` unsafeForeignPtrToPtr fpbuf) (op `minusPtr` p0)++-- | Convert a buffer to a non-empty bytestring. See 'unsafeFreezeBuffer' for+-- the explanation of why this operation may be unsafe.+--+-- Since 0.1.10.0+--+{-# INLINE unsafeFreezeNonEmptyBuffer #-}+unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe S.ByteString+unsafeFreezeNonEmptyBuffer buf+ | sliceSize buf <= 0 = Nothing+ | otherwise = Just $ unsafeFreezeBuffer buf++-- | Update the end of slice pointer.+--+-- Since 0.1.10.0+--+{-# INLINE updateEndOfSlice #-}+updateEndOfSlice :: Buffer -- Old buffer+ -> Ptr Word8 -- New end of slice+ -> Buffer -- Updated buffer+updateEndOfSlice (Buffer fpbuf p0 _ ope) op' = Buffer fpbuf p0 op' ope++-- | Move the beginning of the slice to the next free byte such that the+-- remaining free space of the buffer can be filled further. This operation+-- is safe and can be used to fill the remaining part of the buffer after a+-- direct insertion of a bytestring or a flush.+--+-- Since 0.1.10.0+--+{-# INLINE nextSlice #-}+nextSlice :: Int -> Buffer -> Maybe Buffer+nextSlice minSize (Buffer fpbuf _ op ope)+ | ope `minusPtr` op <= minSize = Nothing+ | otherwise = Just (Buffer fpbuf op op ope)++------------------------------------------------------------------------------+-- Buffer allocation strategies+------------------------------------------------------------------------------++-- | A buffer allocation strategy @(buf0, nextBuf)@ specifies the initial+-- buffer to use and how to compute a new buffer @nextBuf minSize buf@ with at+-- least size @minSize@ from a filled buffer @buf@. The double nesting of the+-- @IO@ monad helps to ensure that the reference to the filled buffer @buf@ is+-- lost as soon as possible, but the new buffer doesn't have to be allocated+-- too early.+--+-- Since 0.1.10.0+--+type BufferAllocStrategy = (IO Buffer, Int -> Buffer -> IO (IO Buffer))++-- | The simplest buffer allocation strategy: whenever a buffer is requested,+-- allocate a new one that is big enough for the next build step to execute.+--+-- NOTE that this allocation strategy may spill quite some memory upon direct+-- insertion of a bytestring by the builder. Thats no problem for garbage+-- collection, but it may lead to unreasonably high memory consumption in+-- special circumstances.+--+-- Since 0.1.10.0+--+allNewBuffersStrategy :: Int -- Minimal buffer size.+ -> BufferAllocStrategy+allNewBuffersStrategy bufSize =+ ( allocBuffer bufSize+ , \reqSize _ -> return (allocBuffer (max reqSize bufSize)) )++-- | An unsafe, but possibly more efficient buffer allocation strategy:+-- reuse the buffer, if it is big enough for the next build step to execute.+--+-- Since 0.1.10.0+--+reuseBufferStrategy :: IO Buffer+ -> BufferAllocStrategy+reuseBufferStrategy buf0 =+ (buf0, tryReuseBuffer)+ where+ tryReuseBuffer reqSize buf+ | bufferSize buf >= reqSize = return $ return (reuseBuffer buf)+ | otherwise = return $ allocBuffer reqSize+++#else /* !MIN_VERSION_blaze_builder(0,4,0) */++import Blaze.ByteString.Builder.Internal.Buffer++#endif /* !MIN_VERSION_blaze_builder(0,4,0) */++defaultStrategy :: BufferAllocStrategy+defaultStrategy = allNewBuffersStrategy defaultChunkSize
+ Data/Streaming/ByteString/Builder/Class.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE CPP #-}+-- | Typeclass to stream blaze-builder and bytestring(-builder) @Builder@s.+--+-- Since 0.1.10.0+--+module Data.Streaming.ByteString.Builder.Class+ ( StreamingBuilder (..)+ , module Data.Streaming.ByteString.Builder+ ) where++import qualified Data.ByteString.Builder+import qualified Data.ByteString.Builder.Internal+import Data.Monoid (Monoid)++import Data.Streaming.ByteString.Builder hiding (newByteStringBuilderRecv)+import qualified Data.Streaming.ByteString.Builder++#if !MIN_VERSION_blaze_builder(0,4,0)++import qualified Blaze.ByteString.Builder++import Data.Streaming.Blaze++instance StreamingBuilder Blaze.ByteString.Builder.Builder where+ newBuilderRecv = newBlazeRecv+ builderFlush = Blaze.ByteString.Builder.flush++#endif /* !MIN_VERSION_blaze_builder(0,4,0) */++-- | Typeclass to stream blaze-builder (< 0.4) and bytestring(-builder) @Builder@s.+-- This is primarily to aid the transition from blaze-builder to bytestring @Builder@s+-- (if using blaze-builder >= 0.4, there is only one instance, since the @Builder@+-- type is shared).+--+-- Since 0.1.10.0+--+class Monoid b => StreamingBuilder b where+ newBuilderRecv :: BufferAllocStrategy -> IO (b -> IO BuilderPopper, BuilderFinish)+ builderFlush :: b++instance StreamingBuilder Data.ByteString.Builder.Builder where+ newBuilderRecv = Data.Streaming.ByteString.Builder.newByteStringBuilderRecv+ builderFlush = Data.ByteString.Builder.Internal.flush
streaming-commons.cabal view
@@ -1,11 +1,11 @@ name: streaming-commons-version: 0.1.9.1+version: 0.1.10.0 synopsis: Common lower-level functions needed by various streaming data libraries description: Provides low-dependency functionality commonly needed by various streaming data libraries, such as conduit and pipes. homepage: https://github.com/fpco/streaming-commons license: MIT license-file: LICENSE-author: Michael Snoyman+author: Michael Snoyman, Emanuel Borsboom maintainer: michael@snoyman.com -- copyright: category: Data@@ -27,6 +27,8 @@ library exposed-modules: Data.Streaming.Blaze Data.Streaming.ByteString.Builder+ Data.Streaming.ByteString.Builder.Buffer+ Data.Streaming.ByteString.Builder.Class Data.Streaming.FileRead Data.Streaming.Filesystem Data.Streaming.Network@@ -47,7 +49,7 @@ build-depends: base >= 4.4 && < 5 , array- , blaze-builder >= 0.3 && < 0.4+ , blaze-builder >= 0.3 && < 0.5 , bytestring , directory , network@@ -82,6 +84,7 @@ type: exitcode-stdio-1.0 ghc-options: -Wall -threaded other-modules: Data.Streaming.ByteString.BuilderSpec+ Data.Streaming.BlazeSpec Data.Streaming.FileReadSpec Data.Streaming.FilesystemSpec Data.Streaming.NetworkSpec
+ test/Data/Streaming/BlazeSpec.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}+module Data.Streaming.BlazeSpec (spec) where++import Test.Hspec+import qualified Blaze.ByteString.Builder as B+import Data.Streaming.ByteString.BuilderSpec hiding (spec)++spec :: Spec+spec = do+ describe "Data.Streaming.Blaze" $ builderSpec BuilderFunctions+ { bfFromByteString = B.fromByteString+ , bfInsertLazyByteString = B.insertLazyByteString+ , bfToLazyByteString = B.toLazyByteString+ , bfInsertByteString = B.insertByteString+ , bfCopyByteString = B.copyByteString+ }
test/Data/Streaming/ByteString/BuilderSpec.hs view
@@ -1,20 +1,136 @@ {-# LANGUAGE OverloadedStrings #-}-module Data.Streaming.ByteString.BuilderSpec (spec) where+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Data.Streaming.ByteString.BuilderSpec+ ( spec+ , builderSpec+ , BuilderFunctions(..)+ ) where -import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString as S+import Data.ByteString.Char8 () import qualified Data.ByteString.Unsafe as S import qualified Data.ByteString.Builder as B-import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.ByteString.Builder.Internal as B+import qualified Data.ByteString.Lazy as L+import Data.ByteString.Lazy.Char8 () import Data.IORef+import Data.Maybe import Data.Monoid import Test.Hspec import Test.Hspec.QuickCheck (prop) import Data.Streaming.ByteString.Builder+import Data.Streaming.ByteString.Builder.Class +data BuilderFunctions b = BuilderFunctions+ { bfFromByteString :: S.ByteString -> b+ , bfInsertLazyByteString :: L.ByteString -> b+ , bfToLazyByteString :: b -> L.ByteString+ , bfInsertByteString :: S.ByteString -> b+ , bfCopyByteString :: S.ByteString -> b+ }++tester :: StreamingBuilder b => BufferAllocStrategy -> [b] -> IO [S.ByteString]+tester strat builders0 = do+ (recv, finish) <- newBuilderRecv strat+ let loop front [] = do+ mbs <- finish+ return $ front $ maybe [] return mbs+ loop front0 (bu:bus) = do+ popper <- recv bu+ let go front = do+ bs <- popper+ if S.null bs+ then loop front bus+ else go (front . (bs:))+ go front0+ loop id builders0++testerFlush :: StreamingBuilder b+ => BufferAllocStrategy -> [Maybe b] -> IO [Maybe S.ByteString]+testerFlush strat builders0 = do+ (recv, finish) <- newBuilderRecv strat+ let loop front [] = do+ mbs <- finish+ return $ front $ maybe [] (return . Just) mbs+ loop front0 (mbu:bus) = do+ popper <- recv $ fromMaybe builderFlush mbu+ let go front = do+ bs <- popper+ if S.null bs+ then+ case mbu of+ Nothing -> loop (front . (Nothing:)) bus+ Just _ -> loop front bus+ else go (front . (Just bs:))+ go front0+ loop id builders0++builderSpec :: forall b. StreamingBuilder b => BuilderFunctions b -> Spec+builderSpec BuilderFunctions{..} = do+ prop "idempotent to toLazyByteString" $ \bss' -> do+ let bss = map S.pack bss'+ let builders :: [b]+ builders = map bfFromByteString bss+ let lbs = bfToLazyByteString $ mconcat builders+ outBss <- tester defaultStrategy builders+ L.fromChunks outBss `shouldBe` lbs++ it "works for large input" $ do+ let builders :: [b]+ builders = replicate 10000 (bfFromByteString "hello world!" :: b)+ let lbs = bfToLazyByteString $ mconcat builders+ outBss <- tester defaultStrategy builders+ L.fromChunks outBss `shouldBe` lbs++ it "works for lazy bytestring insertion" $ do+ let builders :: [b]+ builders = replicate 10000 (bfInsertLazyByteString "hello world!")+ let lbs = bfToLazyByteString $ mconcat builders+ outBss <- tester defaultStrategy builders+ L.fromChunks outBss `shouldBe` lbs++ prop "works for strict bytestring insertion" $ \bs' -> do+ let bs = S.pack bs'+ let builders :: [b]+ builders = replicate 10000 (bfCopyByteString bs `mappend` bfInsertByteString bs)+ let lbs = bfToLazyByteString $ mconcat builders+ outBss <- tester defaultStrategy builders+ L.fromChunks outBss `shouldBe` lbs++ it "flush shouldn't bring in empty strings." $ do+ let dat = ["hello", "world"]+ builders :: [b]+ builders = map ((`mappend` builderFlush) . bfFromByteString) dat+ out <- tester defaultStrategy builders+ dat `shouldBe` out++ prop "flushing" $ \bss' -> do+ let bss = concatMap (\bs -> [Just $ S.pack bs, Nothing]) $ filter (not . null) bss'+ let builders :: [Maybe b]+ builders = map (fmap bfFromByteString) bss+ outBss <- testerFlush defaultStrategy builders+ outBss `shouldBe` bss+ it "large flush input" $ do+ let lbs = L.pack $ concat $ replicate 100000 [0..255]+ chunks :: [Maybe b]+ chunks = map (Just . bfFromByteString) (L.toChunks lbs)+ bss <- testerFlush defaultStrategy chunks+ L.fromChunks (catMaybes bss) `shouldBe` lbs+ spec :: Spec spec = describe "Data.Streaming.ByteString.Builder" $ do++ builderSpec BuilderFunctions+ { bfFromByteString = B.byteString+ , bfInsertLazyByteString = B.lazyByteStringInsert+ , bfToLazyByteString = B.toLazyByteString+ , bfInsertByteString = B.byteStringInsert+ , bfCopyByteString = B.byteStringCopy+ }+ prop "toByteStringIO idempotent to toLazyByteString" $ \bss' -> do let bss = mconcat (map (B.byteString . S.pack) bss') ior <- newIORef []