import Test.Tasty
import Test.Tasty.SmallCheck
import Test.SmallCheck.Series
import Control.Applicative
import Data.Functor.Identity
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString.Streaming.Char8 as SBS8
import qualified Data.ByteString.Streaming.Internal as SBSI
import Text.Printf
import qualified Streaming.Prelude as S
import qualified Streaming as SM
import Data.String (fromString)
import Data.Functor.Identity
import qualified Data.List as L
listOf :: Monad m => Series m a -> Series m [a]
listOf a = decDepth $
pure [] \/ ((:) <$> a <~> listOf a)
strSeries :: Monad m => Series m String
strSeries = listOf (generate $ const ['a', 'b', '\n'])
strSeriesCrlf :: Monad m => Series m String
strSeriesCrlf = fmap L.concat $ listOf (generate $ const ["a", "b", "\r\n"])
chunksSeries :: Monad m => Series m [String]
chunksSeries = listOf strSeries
nats :: Monad m => Series m Int
nats = generate $ \d -> [1..d]
fromChunks :: [String] -> SBS8.ByteString Identity ()
fromChunks = SBS8.fromChunks . S.each . map BS8.pack
unix2dos :: String -> String
unix2dos = concatMap $ \c -> if c == '\n' then "\r\n" else [c]
unpackToString :: SBS8.ByteString Identity () -> String
unpackToString = runIdentity . S.toList_ . SBS8.unpack
s_lines :: SBS8.ByteString Identity () -> [BS8.ByteString]
s_lines
= runIdentity
. S.toList_
. S.mapped SBS8.toStrict
. SBS8.lines
noNullChunks :: S.Stream (SBS8.ByteString Identity) Identity () -> Bool
noNullChunks = SM.streamFold (\() -> True) runIdentity go
where
go :: SBS8.ByteString Identity Bool -> Bool
go (SBSI.Empty b) = b
go (SBSI.Chunk bs sbs) = not (BS8.null bs) && go sbs
go (SBSI.Go (Identity sbs)) = go sbs
main = defaultMain $ testGroup "Tests"
[ testGroup "lines" $
[ testProperty "Data.ByteString.Streaming.Char8.lines is equivalent to Prelude.lines" $ over chunksSeries $ \chunks ->
-- This only makes sure that the streaming-bytestring lines function
-- matches the Prelude lines function when no carriage returns
-- are present. They are not expected to have the same behavior
-- with dos-style line termination.
let expected = lines $ concat chunks
got = (map BS8.unpack . s_lines . fromChunks) chunks
in
if expected == got
then Right ""
else Left (printf "Expected %s; got %s" (show expected) (show got) :: String)
, testProperty "lines recognizes DOS line endings" $ over strSeries $ \str ->
s_lines (SBS8.string $ unix2dos str) == s_lines (SBS8.string str)
, testProperty "lines recognizes DOS line endings with tiny chunks" $ over strSeries $ \str ->
s_lines (mapM_ SBS8.singleton $ unix2dos str) == s_lines (mapM_ SBS8.singleton str)
, testProperty "lineSplit does not create null chunks (LF)" $ over ((,) <$> nats <~> strSeries) $ \(n,str) ->
noNullChunks (SBS8.lineSplit n (fromString str))
, testProperty "lineSplit does not create null chunks (CRLF)" $ over ((,) <$> nats <~> strSeriesCrlf) $ \(n,str) ->
noNullChunks (SBS8.lineSplit n (fromString str))
, testProperty "concat after lineSplit round trips (LF)" $ over ((,) <$> nats <~> strSeries) $ \(n,str) ->
unpackToString (SBS8.concat (SBS8.lineSplit n (fromString str))) == str
, testProperty "concat after lineSplit round trips (CRLF)" $ over ((,) <$> nats <~> strSeriesCrlf) $ \(n,str) ->
unpackToString (SBS8.concat (SBS8.lineSplit n (fromString str))) == str
]
]