packages feed

streamly-lz4-0.1.1: test/Main.hs

-- |
-- Module      : Main
-- Copyright   : (c) 2020 Composewell Technologies
-- License     : Apache-2.0
-- Maintainer  : streamly@composewell.com
-- Stability   : experimental
-- Portability : GHC
--
module Main (main) where

import Control.Monad (forM_)
import Control.Monad.IO.Class (MonadIO(..))
import Data.Word (Word8)
import Data.Function ((&))
import Streamly.Internal.Data.Stream.IsStream.Type (fromStreamD, toStreamD)
import System.IO (IOMode(..), openFile, hClose)
import System.IO.Temp (withSystemTempFile)
import Test.Hspec (describe, hspec, it, shouldBe)
import Test.QuickCheck (Property, forAll, property)
import Test.QuickCheck.Gen
    ( Gen, choose, elements, frequency, generate, listOf, vectorOf )
import Test.QuickCheck.Monadic (monadicIO)

import qualified Streamly.Internal.Data.Array.Foreign.Type as Array
import qualified Streamly.Internal.Data.Stream.IsStream as Stream
import qualified Streamly.Internal.FileSystem.Handle as Handle

import Streamly.Internal.LZ4.Config
import Streamly.Internal.LZ4
import Streamly.LZ4

-- Choose only 0 and 1 so that we can get good compressible sequences.
genArrayW8List :: Gen [Array.Array Word8]
genArrayW8List = listOf $ Array.fromList <$> listOf (elements [0,1])

genArrayW8Large :: Gen (Array.Array Word8)
genArrayW8Large = do
    let minArr = 1024 * 10
        maxArr = 1024 * 100
    arrS <- choose (minArr, maxArr)
    Array.fromList <$> vectorOf arrS (elements [0,1])

-- HC : Highly compressible
genArrayW8LargeHC :: (Int, Int) -> Gen (Array.Array Word8)
genArrayW8LargeHC (min_, max_) = do
    arrS <- choose (min_, max_)
    Array.fromList <$> vectorOf arrS (frequency [(9, pure 0), (1, pure 1)])

genNonEmptyListWith :: Gen (Array.Array Word8) -> Gen [Array.Array Word8]
genNonEmptyListWith gen = do
    sz <- choose (50, 100)
    vectorOf sz gen

genAcceleration :: Gen Int
genAcceleration = elements [-1..12]

decompressCompressChunk :: BlockConfig -> Int -> Array.Array Word8 -> IO ()
decompressCompressChunk conf i arr = do
    lz4Ctx <- c_createStream
    lz4CtxD <- c_createStreamDecode
    compressed <- compressChunk conf i lz4Ctx arr
    decompressed <- decompressChunk conf lz4CtxD compressed
    c_freeStream lz4Ctx
    c_freeStreamDecode lz4CtxD
    decompressed `shouldBe` arr

decompressCompressChunk2 ::
       BlockConfig -> Int -> Array.Array Word8 -> Array.Array Word8 -> IO ()
decompressCompressChunk2 conf i arr1 arr2 = do
    lz4Ctx <- c_createStream
    lz4CtxD <- c_createStreamDecode
    compressed1 <- compressChunk conf i lz4Ctx arr1
    compressed2 <- compressChunk conf i lz4Ctx arr2
    decompressed1 <- decompressChunk conf lz4CtxD compressed1
    decompressed2 <- decompressChunk conf lz4CtxD compressed2
    c_freeStream lz4Ctx
    c_freeStreamDecode lz4CtxD
    (decompressed1, decompressed2) `shouldBe` (arr1, arr2)

decompressResizedcompress :: BlockConfig -> Int -> [Array.Array Word8] -> IO ()
decompressResizedcompress conf i lst =
    let strm = Stream.fromList lst
     in do lst1 <-
               Stream.toList $ decompressChunksRaw $ compressChunks conf i strm
           lst `shouldBe` lst1

    where

    decompressChunksRaw = fromStreamD . decompressChunksRawD conf . toStreamD

decompressCompress :: BlockConfig -> Int -> Int -> [Array.Array Word8] -> IO ()
decompressCompress conf bufsize i lst = do
    let strm = Stream.fromList lst
    withSystemTempFile "LZ4" $ \tmp tmpH -> do
        compressChunks conf i strm & Handle.putChunks tmpH
        hClose tmpH
        lst1 <-
            Stream.toList
                $ Stream.bracket_ (openFile tmp ReadMode) hClose
                $ \h ->
                      Stream.unfold Handle.readChunksWithBufferOf (bufsize, h)
                          & decompressChunks conf
        lst1 `shouldBe` lst

{-# INLINE endMarkArr #-}
endMarkArr :: Array.Array Word8
endMarkArr = Array.fromListN 4 [0,0,0,0]

decompressCompressFrame ::
       BlockConfig -> FrameConfig -> Int -> Int -> [Array.Array Word8] -> IO ()
decompressCompressFrame bf ff bufsize i lst = do
    let strm = Stream.fromList lst
    withSystemTempFile "LZ4" $ \tmp tmpH -> do
        compressChunksFrame strm & Handle.putChunks tmpH
        hClose tmpH
        lst1 <-
            Stream.toList
                $ Stream.bracket_ (openFile tmp ReadMode) hClose
                $ \h ->
                      Stream.unfold Handle.readChunksWithBufferOf (bufsize, h)
                          & decompressChunksFrame
        lst1 `shouldBe` lst

    where

    decompressChunksFrame
        :: Stream.SerialT IO (Array.Array Word8)
        -> Stream.SerialT IO (Array.Array Word8)
    decompressChunksFrame =
        fromStreamD . decompressChunksRawD bf . resizeChunksD bf ff . toStreamD

    compressChunksFrame
        :: Stream.SerialT IO (Array.Array Word8)
        -> Stream.SerialT IO (Array.Array Word8)
    compressChunksFrame strm =
        if hasEndMark ff
        then (fromStreamD . compressChunksD bf i . toStreamD) strm
                 `Stream.append` Stream.fromPure endMarkArr
        else (fromStreamD . compressChunksD bf i . toStreamD) strm

decompressWithCompress :: Int -> Int -> [Array.Array Word8] -> IO ()
decompressWithCompress bufsize i lst = do
    -- Stream with header that returns the config
    -- Magic Little endian (4 bytes) = 407708164
    let magicLE = [4, 34, 77, 24]
    -- flg with everything unset
        flg = 64
    -- bd with 64KB block max size
        bd = 64
        headerChk = 0
        headerList = magicLE ++ [flg, bd, headerChk]
        header = Stream.fromList headerList
    headerArr <- Stream.fold (Array.writeN (length headerList)) header
    (bf, ff) <- Stream.parseD simpleFrameParserD header
    let strm = Stream.fromList lst
    withSystemTempFile "LZ4" $ \tmp tmpH -> do
        compressChunksFrame bf ff i strm
            & Stream.cons headerArr
            & Handle.putChunks tmpH
        hClose tmpH
        lst1 <-
            Stream.toList
                $ Stream.bracket_ (openFile tmp ReadMode) hClose
                $ \h ->
                      Stream.unfold Handle.readChunksWithBufferOf (bufsize, h)
                          & decompressWith_
        lst1 `shouldBe` lst

    where

    compressChunksFrame
        :: BlockConfig
        -> FrameConfig
        -> Int
        -> Stream.SerialT IO (Array.Array Word8)
        -> Stream.SerialT IO (Array.Array Word8)
    compressChunksFrame bf ff i_ strm =
        if hasEndMark ff
        then (fromStreamD . compressChunksD bf i_ . toStreamD) strm
                 `Stream.append` Stream.fromPure endMarkArr
        else (fromStreamD . compressChunksD bf i_ . toStreamD) strm

    decompressWith_ ::
           Stream.SerialT IO (Array.Array Word8)
        -> Stream.SerialT IO (Array.Array Word8)
    decompressWith_ =
        fromStreamD . decompressChunksWithD simpleFrameParserD . toStreamD

resizeIdempotence :: BlockConfig -> Property
resizeIdempotence conf =
    forAll ((,) <$> genAcceleration <*> genArrayW8List)
        $ \(acc, w8List) -> do
              let strm = compressChunks conf acc $ Stream.fromList w8List
              f1 <- Stream.toList $ resizeChunks strm
              f2 <- Stream.toList $ foldr ($) strm $ replicate acc resizeChunks
              f1 `shouldBe` f2

    where

    resizeChunks =
        fromStreamD . resizeChunksD conf defaultFrameConfig . toStreamD

main :: IO ()
main = do
    large <- generate $ genNonEmptyListWith genArrayW8Large
    largeHC <-
        generate
            $ genNonEmptyListWith
            $ genArrayW8LargeHC (1024 * 10, 1024 * 64)
    hspec $ do
        describe "Idempotence" $
            it "resize" (resizeIdempotence defaultBlockConfig)
        describe "Identity" $ do
            propsChunk defaultBlockConfig
            propsChunk2 defaultBlockConfig
            propsSimple defaultBlockConfig
            forM_ [-1, 5, 12, 100]
                $ \i ->
                      forM_ [1, 512, 32 * 1024, 256 * 1024]
                          $ \bufsize -> do
                                propsBig defaultBlockConfig bufsize i large
                                describe "Highly compressible"
                                    $ propsBig
                                          defaultBlockConfig bufsize i largeHC
            describe "setBlockMaxSize BlockMax256KB defaultBlockConfig" $ do
                let config = setBlockMaxSize BlockMax256KB defaultBlockConfig
                propsChunk config
                propsChunk2 config
                propsBig config 512 5 largeHC
            describe "defaultBlockConfig" $ do
                propsSimpleDecompressCompress defaultBlockConfig
                propsBigDecompressCompress defaultBlockConfig 512 5 large
                propsBigDecompressCompress defaultBlockConfig 512 5 largeHC
            describe "setFrameEndMark True defaultFrameConfig" $ do
                let ff = setFrameEndMark True defaultFrameConfig
                propsFrame defaultBlockConfig ff 512 5 large
                propsFrame defaultBlockConfig ff 512 5 largeHC
            describe "setFrameEndMark True . setBlockMaxSize BlockMax256KB"
                $ do
                    let bf = setBlockMaxSize BlockMax256KB defaultBlockConfig
                        ff = setFrameEndMark True defaultFrameConfig
                    propsFrame bf ff 512 5 large
                    propsFrame bf ff 512 5 largeHC
            describe "parse config (decompressWith)" $ do
                propsDecompressWithCompress 512 5 largeHC

    where

    propsSimpleDecompressCompress conf = do
        it "decompress . compress == id"
            $ property
            $ forAll
                  ((,) <$> genAcceleration <*> genArrayW8List)
                  (monadicIO . liftIO . uncurry (decompressCompress conf 512))

    propsBigDecompressCompress conf bufsize i l = do
        it
            ("decompress . compress ("
                 ++ show i ++ "/" ++ show bufsize ++ ") == id (big)")
            $ decompressCompress conf bufsize i l

    propsDecompressWithCompress bufsize i l = do
        it
            "decompressWith . compress == id"
            (decompressWithCompress bufsize i l)

    propsFrame bf ff bufsize i l = do
        it ("decompressFrame . compressFrame (" ++ show i ++ ") == id")
            $ decompressCompressFrame bf ff bufsize i l

    propsSimple conf = do
        it "decompressResized . compress == id"
            $ property
            $ forAll
                  ((,) <$> genAcceleration <*> genArrayW8List)
                  (monadicIO . liftIO . uncurry (decompressResizedcompress conf))
        it "decompress . compress == id"
            $ property
            $ forAll
                  ((,) <$> genAcceleration <*> genArrayW8List)
                  (monadicIO . liftIO . uncurry (decompressCompress conf 512))

    propsBig conf bufsize i l = do
        it ("decompressResized . compress (" ++ show i ++ ") == id (big)")
            $ decompressResizedcompress conf i l
        it
            ("decompress . compress ("
                 ++ show i ++ "/" ++ show bufsize ++ ") == id (big)")
            $ decompressCompress conf bufsize i l

    propsChunk2 conf = do
        it "decompressChunk . compressChunk (x2) == id (big)"
            $ property
            $ forAll
                  ((,,)
                       <$> genAcceleration
                       <*> genArrayW8Large
                       <*> genArrayW8Large)
                  (\(i, arr1, arr2) -> decompressCompressChunk2 conf i arr1 arr2)

    propsChunk conf = do
        it "decompressChunk . compressChunk == id (big)"
            $ property
            $ forAll
                  ((,) <$> genAcceleration <*> genArrayW8Large)
                  (uncurry (decompressCompressChunk conf))