packages feed

streamly-0.8.0: test/Streamly/Test/FileSystem/Handle.hs

-- |
-- Module      : Streamly.Test.FileSystem.Handle
-- Copyright   : (c) 2020 Composewell technologies
-- License     : BSD-3-Clause
-- Maintainer  : streamly@composewell.com
-- Stability   : experimental
-- Portability : GHC

module Streamly.Test.FileSystem.Handle (main) where

import Data.Functor.Identity (runIdentity)
import Data.Word (Word8)
import Foreign.Storable (Storable(..))
import Streamly.Internal.Data.Stream.IsStream (IsStream, SerialT)
import System.FilePath ((</>))
import System.IO
    ( Handle
    , IOMode(..)
    , SeekMode(..)
    , hClose
    , hFlush
    , hSeek
    , openFile
    )
import System.IO.Temp (withSystemTempDirectory)
import Test.QuickCheck (Property, forAll, Gen, vectorOf, choose)
import Test.QuickCheck.Monadic (monadicIO, assert, run)

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

import Test.Hspec as H
import Test.Hspec.QuickCheck

allocOverhead :: Int
allocOverhead = 2 * sizeOf (undefined :: Int)

defaultChunkSize :: Int
defaultChunkSize = 32 * k - allocOverhead
   where k = 1024

maxArrLen :: Int
maxArrLen = defaultChunkSize * 8

maxTestCount :: Int
maxTestCount = 10

chooseWord8 :: (Word8, Word8) -> Gen Word8
chooseWord8 = choose

utf8ToString :: Array.Array Word8 -> String
utf8ToString =
    runIdentity . Stream.toList . Unicode.decodeUtf8' . Array.toStream

testData :: String
testData = "This is the test data for FileSystem.Handle ??`!@#$%^&*~~))`]"

testDataLarge :: String
testDataLarge = concat $ replicate 6000 testData

executor :: (Handle -> SerialT IO Char) -> IO (SerialT IO Char)
executor f =
    withSystemTempDirectory "fs_handle" $ \fp -> do
        let fpath = fp </> "tmp_read.txt"
        writeFile fpath testDataLarge
        h <- openFile fpath ReadMode
        return $ f h

readFromHandle :: IO (SerialT IO Char)
readFromHandle =
    let f = Unicode.decodeUtf8 . Stream.unfold Handle.read
    in executor f

readWithBufferFromHandle :: IO (SerialT IO Char)
readWithBufferFromHandle =
    let f1 = (\h -> (1024, h))
        f2 = Unicode.decodeUtf8 . Stream.unfold Handle.readWithBufferOf . f1
    in executor f2

readChunksFromHandle :: IO (SerialT IO Char)
readChunksFromHandle =
    let f =   Unicode.decodeUtf8
            . Stream.concatMap Array.toStream
            . Stream.unfold Handle.readChunks
    in executor f

readChunksWithBuffer :: IO (SerialT IO Char)
readChunksWithBuffer =
    let f1 = (\h -> (1024, h))
        f2 =
              Unicode.decodeUtf8
            . Stream.concatMap Array.toStream
            . Stream.unfold Handle.readChunksWithBufferOf
            . f1
    in executor f2

testRead :: (IsStream t) => IO (t IO Char) -> Property
testRead fn = monadicIO $ do
    let v2 = Stream.fromList testDataLarge
    v1 <- run fn
    res <- run $ Stream.eqBy (==) v1 v2
    assert res

testWrite :: (Handle -> Fold.Fold IO Word8 ()) -> Property
testWrite hfold =
    forAll (choose (0, maxArrLen)) $ \len ->
        forAll (vectorOf len $ chooseWord8 (0, 255)) $ \list0 ->
            monadicIO $ do
                res <- run $ go list0
                assert res

                where

                go list =
                    withSystemTempDirectory "fs_handle" $ \fp -> do
                        let fpathWrite = fp </> "tmp_write.txt"
                        writeFile fpathWrite ""
                        h <- openFile fpathWrite ReadWriteMode
                        hSeek h AbsoluteSeek 0
                        _ <- Stream.fold (hfold h) $ Stream.fromList list
                        hFlush h
                        hSeek h AbsoluteSeek 0
                        ls <- Stream.toList $ Stream.unfold Handle.read h
                        hClose h
                        return (ls == list)

testWriteWithChunk :: Property
testWriteWithChunk =
    monadicIO $ do
        res <- run go
        assert res

        where

        go =
            withSystemTempDirectory "fs_handle" $ \fp -> do
                let fpathRead = fp </> "tmp_read.txt"
                    fpathWrite = fp </> "tmp_write.txt"
                writeFile fpathRead testDataLarge
                writeFile fpathWrite ""
                hr <- openFile fpathRead ReadMode
                hw <- openFile fpathWrite ReadWriteMode
                hSeek hw AbsoluteSeek 0
                _ <- Stream.fold (Handle.writeChunks hw)
                    $ Stream.unfold Handle.readChunksWithBufferOf (1024, hr)
                hFlush hw
                hSeek hw AbsoluteSeek 0
                ls <- Stream.toList $ Stream.unfold Handle.read hw
                let arr = Array.fromList ls
                return (testDataLarge == utf8ToString arr)

moduleName :: String
moduleName = "FileSystem.Handle"

main :: IO ()
main =
    hspec $
    H.parallel $
    modifyMaxSuccess (const maxTestCount) $ do
      describe moduleName $ do
        describe "Read From Handle" $ do
            prop "read" $ testRead readFromHandle
            prop "readWithBufferOf" $ testRead readWithBufferFromHandle
            prop "readChunks" $ testRead readChunksFromHandle
            prop "readChunksWithBufferOf" $ testRead readChunksWithBuffer
        describe "Write To Handle" $ do
            prop "write" $ testWrite Handle.write
            prop "writeWithBufferOf" $ testWrite $ Handle.writeWithBufferOf 1024
            -- XXX This test needs a lot of stack when built with -O0
            prop "writeChunks" testWriteWithChunk