{-# LANGUAGE ScopedTypeVariables #-}
import Control.Applicative ( (<$>) )
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BLC
import Data.Char ( chr )
import Data.Int ( Int64 )
import System.IO
( Handle, hGetContents, hSeek, SeekMode(..), hPutStr
, TextEncoding, hSetEncoding, utf8, latin1
)
import System.IO.Unsafe ( unsafePerformIO )
import Test.HUnit
import Test.Framework ( defaultMain )
import Test.Framework.Providers.HUnit
import Test.Framework.Providers.QuickCheck2 ( testProperty )
import Test.QuickCheck ( (==>), Property )
import Test.QuickCheck.Arbitrary ( Arbitrary(..) )
import Data.ByteString.Handle ( readHandle, writeHandle )
instance Arbitrary BL.ByteString where
arbitrary = fmap BL.pack arbitrary
readViaHandle :: Bool -> BL.ByteString -> (Handle -> IO a) -> a
readViaHandle binary bs f = unsafePerformIO $ readHandle binary bs >>= f
writeViaHandle :: Bool -> (Handle -> IO ()) -> BL.ByteString
writeViaHandle binary f = fst $ unsafePerformIO $ writeHandle binary f
simpleBinaryReadRoundTrip :: BL.ByteString -> Bool
simpleBinaryReadRoundTrip bs =
-- ironically, BL.hGetContents doesn't like ByteString-backed handles - fails with "Not an FD"
bs == readViaHandle True bs (\h -> BLC.pack <$> hGetContents h)
prop_SimpleBinaryReadRoundTrip :: BL.ByteString -> Bool
prop_SimpleBinaryReadRoundTrip bs = simpleBinaryReadRoundTrip bs
simpleBinaryWriteRoundTrip :: BL.ByteString -> Bool
simpleBinaryWriteRoundTrip bs =
bs == writeViaHandle True (\h -> hPutStr h (BLC.unpack bs))
prop_SimpleBinaryWriteRoundTrip :: BL.ByteString -> Bool
prop_SimpleBinaryWriteRoundTrip bs = simpleBinaryWriteRoundTrip bs
-- testing segments of bytestrings is important because they are internally represented
-- using offsets into the original bytestrings
tailBinaryReadRoundTrip :: BL.ByteString -> Bool
tailBinaryReadRoundTrip bs =
BL.tail bs == readViaHandle True (BL.tail bs) (\h -> BLC.pack <$> hGetContents h)
prop_TailBinaryReadRoundTrip :: BL.ByteString -> Property
prop_TailBinaryReadRoundTrip bs = not (BL.null bs) ==> tailBinaryReadRoundTrip bs
seekBinaryReadRoundTrip :: BL.ByteString -> Bool
seekBinaryReadRoundTrip bs =
BL.tail bs == readViaHandle True bs (\h -> do { hSeek h AbsoluteSeek 1 ; BLC.pack <$> hGetContents h })
prop_SeekBinaryReadRoundTrip :: BL.ByteString -> Property
prop_SeekBinaryReadRoundTrip bs = not (BL.null bs) ==> seekBinaryReadRoundTrip bs
initBinaryReadRoundTrip :: BL.ByteString -> Bool
initBinaryReadRoundTrip bs =
BL.init bs == readViaHandle True (BL.init bs) (\h -> BLC.pack <$> hGetContents h)
prop_InitBinaryReadRoundTrip :: BL.ByteString -> Property
prop_InitBinaryReadRoundTrip bs = not (BL.null bs) ==> initBinaryReadRoundTrip bs
takeBinaryReadRoundTrip :: Int64 -> BL.ByteString -> Bool
takeBinaryReadRoundTrip n bs =
BL.take n bs == BL.take n (readViaHandle True bs (\h -> BLC.pack <$> hGetContents h))
takeSeekBinaryReadRoundTrip :: Int64 -> BL.ByteString -> Bool
takeSeekBinaryReadRoundTrip n bs =
BL.take n (BL.tail bs) == BL.take n (readViaHandle True bs (\h -> do { hSeek h AbsoluteSeek 1 ; BLC.pack <$> hGetContents h }))
-- several times the default 32K chunk size
bigByteString :: BL.ByteString
bigByteString = BL.pack $ concat $ replicate 1000 $ [0..255] ++ [1..100]
infiniteByteString :: BL.ByteString
infiniteByteString = BL.cycle $ BL.pack $ [0..255] ++ [1..100]
encode :: TextEncoding -> String -> BL.ByteString
encode te str = writeViaHandle True (\h -> do { hSetEncoding h te ; hPutStr h str })
decode :: TextEncoding -> BL.ByteString -> String
decode te bs = readViaHandle True bs (\h -> do { hSetEncoding h te ; hGetContents h })
tests =
[ testProperty "Simple binary read roundtrip" prop_SimpleBinaryReadRoundTrip
, testProperty "Tail binary read roundtrip" prop_TailBinaryReadRoundTrip
, testProperty "Init binary read roundtrip" prop_InitBinaryReadRoundTrip
, testProperty "Seek binary read roundtrip" prop_SeekBinaryReadRoundTrip
, testCase "Big bytestring binary read roundtrip" $
assertBool "roundtrip succeeded" (simpleBinaryReadRoundTrip bigByteString)
, testCase "Big bytestring tail binary read roundtrip" $
assertBool "roundtrip succeeded" (tailBinaryReadRoundTrip bigByteString)
, testCase "Big bytestring init binary read roundtrip" $
assertBool "roundtrip succeeded" (initBinaryReadRoundTrip bigByteString)
, testCase "Big bytestring seek binary read roundtrip" $
assertBool "roundtrip succeeded" (seekBinaryReadRoundTrip bigByteString)
, testCase "Infinite bytestring read roundtrip" $
assertBool "roundtrip succeeded" (takeBinaryReadRoundTrip 100000 infiniteByteString)
, testCase "Infinite bytestring seek read roundtrip" $
assertBool "roundtrip succeeded" (takeSeekBinaryReadRoundTrip 100000 infiniteByteString)
, testProperty "Simple binary write roundtrip" prop_SimpleBinaryWriteRoundTrip
, testCase "Sterling symbol latin1 encode" $
assertEqual "latin1 sterling" (BL.pack [163]) (encode latin1 [chr 163])
, testCase "Sterling symbol utf8 encode" $
assertEqual "latin1 sterling" (BL.pack [194,163]) (encode utf8 [chr 163])
, testCase "Sterling symbol latin1 decode" $
assertEqual "latin1 sterling" [chr 163] (decode latin1 (BL.pack [163]))
, testCase "Sterling symbol utf8 decode" $
assertEqual "latin1 sterling" [chr 163] (decode utf8 (BL.pack [194,163]))
]
main = defaultMain tests