Blame test/Test.hs

Packit 4cd534
{-# LANGUAGE CPP #-}
Packit 4cd534
Packit 4cd534
module Main where
Packit 4cd534
Packit 4cd534
import Codec.Compression.Zlib.Internal
Packit 4cd534
import qualified Codec.Compression.Zlib     as Zlib
Packit 4cd534
import qualified Codec.Compression.GZip     as GZip
Packit 4cd534
import qualified Codec.Compression.Zlib.Raw as Raw
Packit 4cd534
Packit 4cd534
import Test.Codec.Compression.Zlib.Internal ()
Packit 4cd534
import Test.Codec.Compression.Zlib.Stream ()
Packit 4cd534
Packit 4cd534
import Test.QuickCheck
Packit 4cd534
import Test.Tasty
Packit 4cd534
import Test.Tasty.QuickCheck
Packit 4cd534
import Test.Tasty.HUnit
Packit 4cd534
import Utils ()
Packit 4cd534
Packit 4cd534
import Control.Monad
Packit 4cd534
import Control.Exception
Packit 4cd534
import qualified Data.ByteString.Char8 as BS.Char8
Packit 4cd534
import qualified Data.ByteString.Lazy as BL
Packit 4cd534
import qualified Data.ByteString      as BS
Packit 4cd534
import System.IO
Packit 4cd534
#if !(MIN_VERSION_base(4,6,0))
Packit 4cd534
import Prelude hiding (catch)
Packit 4cd534
#endif
Packit 4cd534
Packit 4cd534
Packit 4cd534
main :: IO ()
Packit 4cd534
main = defaultMain $
Packit 4cd534
  testGroup "zlib tests" [
Packit 4cd534
    testGroup "property tests" [
Packit 4cd534
      testProperty "decompress . compress = id (standard)"           prop_decompress_after_compress,
Packit 4cd534
      testProperty "decompress . compress = id (Zlib -> GZipOrZLib)" prop_gziporzlib1,
Packit 4cd534
      testProperty "decompress . compress = id (GZip -> GZipOrZlib)" prop_gziporzlib2,
Packit 4cd534
      testProperty "concatenated gzip members"                       prop_gzip_concat,
Packit 4cd534
      testProperty "multiple gzip members, boundaries (all 2-chunks)" prop_multiple_members_boundary2,
Packit 4cd534
      testProperty "multiple gzip members, boundaries (all 3-chunks)" prop_multiple_members_boundary3,
Packit 4cd534
      testProperty "prefixes of valid stream detected as truncated"  prop_truncated
Packit 4cd534
    ],
Packit 4cd534
    testGroup "unit tests" [
Packit 4cd534
      testCase "simple gzip case"          test_simple_gzip,
Packit 4cd534
      testCase "detect bad crc"            test_bad_crc,
Packit 4cd534
      testCase "detect non-gzip"           test_non_gzip,
Packit 4cd534
      testCase "detect custom dictionary"  test_custom_dict,
Packit 4cd534
      testCase "dectect inflate with wrong dict"   test_wrong_dictionary,
Packit 4cd534
      testCase "dectect inflate with right dict"   test_right_dictionary,
Packit 4cd534
      testCase "handle trailing data"      test_trailing_data,
Packit 4cd534
      testCase "multiple gzip members"     test_multiple_members,
Packit 4cd534
      testCase "check small input chunks"  test_small_chunks,
Packit 4cd534
      testCase "check empty input"         test_empty,
Packit 4cd534
      testCase "check exception raised"    test_exception
Packit 4cd534
    ]
Packit 4cd534
  ]
Packit 4cd534
Packit 4cd534
Packit 4cd534
prop_decompress_after_compress :: Format
Packit 4cd534
                               -> CompressParams
Packit 4cd534
                               -> DecompressParams
Packit 4cd534
                               -> Property
Packit 4cd534
prop_decompress_after_compress w cp dp =
Packit 4cd534
   (w /= zlibFormat || decompressWindowBits dp >= compressWindowBits cp) &&
Packit 4cd534
   -- Zlib decompression has been observed to fail with both compress and decompress
Packit 4cd534
   -- window bits = 8. This seems to be contrary to the docs and to a quick reading
Packit 4cd534
   -- of the zlib source code.
Packit 4cd534
   (decompressWindowBits dp > compressWindowBits cp || decompressWindowBits dp > WindowBits 8) &&
Packit 4cd534
   decompressBufferSize dp > 0 && compressBufferSize cp > 0 ==>
Packit 4cd534
   liftM2 (==) (decompress w dp . compress w cp) id
Packit 4cd534
Packit 4cd534
Packit 4cd534
prop_gziporzlib1 :: CompressParams
Packit 4cd534
                 -> DecompressParams
Packit 4cd534
                 -> Property
Packit 4cd534
prop_gziporzlib1 cp dp =
Packit 4cd534
   decompressWindowBits dp > compressWindowBits cp &&
Packit 4cd534
   decompressBufferSize dp > 0 && compressBufferSize cp > 0 ==>
Packit 4cd534
   liftM2 (==) (decompress gzipOrZlibFormat dp . compress zlibFormat cp) id
Packit 4cd534
Packit 4cd534
Packit 4cd534
prop_gziporzlib2 :: CompressParams
Packit 4cd534
                 -> DecompressParams
Packit 4cd534
                 -> Property
Packit 4cd534
prop_gziporzlib2 cp dp =
Packit 4cd534
   decompressWindowBits dp >= compressWindowBits cp &&
Packit 4cd534
   decompressBufferSize dp > 0 && compressBufferSize cp > 0 ==>
Packit 4cd534
   liftM2 (==) (decompress gzipOrZlibFormat dp . compress gzipFormat cp) id
Packit 4cd534
Packit 4cd534
prop_gzip_concat :: CompressParams
Packit 4cd534
                 -> DecompressParams
Packit 4cd534
                 -> BL.ByteString
Packit 4cd534
                 -> Property
Packit 4cd534
prop_gzip_concat cp dp input =
Packit 4cd534
   decompressWindowBits dp >= compressWindowBits cp &&
Packit 4cd534
   decompressBufferSize dp > 0 && compressBufferSize cp > 0 ==>
Packit 4cd534
   let catComp = BL.concat (replicate 5 (compress gzipFormat cp input))
Packit 4cd534
       compCat = compress gzipFormat cp (BL.concat (replicate 5 input))
Packit 4cd534
Packit 4cd534
    in decompress gzipFormat dp { decompressAllMembers = True } catComp
Packit 4cd534
    == decompress gzipFormat dp { decompressAllMembers = True } compCat
Packit 4cd534
Packit 4cd534
prop_multiple_members_boundary2 :: Property
Packit 4cd534
prop_multiple_members_boundary2 =
Packit 4cd534
    forAll shortStrings $ \bs ->
Packit 4cd534
      all (\c -> decomp c == BL.append bs bs)
Packit 4cd534
          (twoChunkSplits (comp bs `BL.append` comp bs))
Packit 4cd534
  where
Packit 4cd534
    comp   = compress gzipFormat defaultCompressParams
Packit 4cd534
    decomp = decompress gzipFormat defaultDecompressParams
Packit 4cd534
Packit 4cd534
    shortStrings = fmap BL.pack $ listOf arbitrary
Packit 4cd534
Packit 4cd534
prop_multiple_members_boundary3 :: Property
Packit 4cd534
prop_multiple_members_boundary3 =
Packit 4cd534
    forAll shortStrings $ \bs ->
Packit 4cd534
      all (\c -> decomp c == BL.append bs bs)
Packit 4cd534
          (threeChunkSplits (comp bs `BL.append` comp bs))
Packit 4cd534
  where
Packit 4cd534
    comp   = compress gzipFormat defaultCompressParams
Packit 4cd534
    decomp = decompress gzipFormat defaultDecompressParams
Packit 4cd534
Packit 4cd534
    shortStrings = sized $ \sz -> resize (sz `div` 10) $
Packit 4cd534
                   fmap BL.pack $ listOf arbitrary
Packit 4cd534
Packit 4cd534
prop_truncated :: Format -> Property
Packit 4cd534
prop_truncated format =
Packit 4cd534
   forAll shortStrings $ \bs ->
Packit 4cd534
     all (truncated decomp)
Packit 4cd534
         (init (BL.inits (comp bs)))
Packit 4cd534
  -- All the initial prefixes of a valid compressed stream should be detected
Packit 4cd534
  -- as truncated.
Packit 4cd534
  where
Packit 4cd534
    comp   = compress format defaultCompressParams
Packit 4cd534
    decomp = decompressST format defaultDecompressParams
Packit 4cd534
    truncated = foldDecompressStreamWithInput (\_ r -> r) (\_ -> False)
Packit 4cd534
                  (\err -> case err of TruncatedInput -> True; _ -> False)
Packit 4cd534
Packit 4cd534
    shortStrings = sized $ \sz -> resize (sz `div` 6) arbitrary
Packit 4cd534
Packit 4cd534
Packit 4cd534
test_simple_gzip :: Assertion
Packit 4cd534
test_simple_gzip =
Packit 4cd534
  withSampleData "hello.gz" $ \hnd ->
Packit 4cd534
    let decomp = decompressIO gzipFormat defaultDecompressParams
Packit 4cd534
     in assertDecompressOk hnd decomp
Packit 4cd534
Packit 4cd534
test_bad_crc :: Assertion
Packit 4cd534
test_bad_crc =
Packit 4cd534
  withSampleData "bad-crc.gz" $ \hnd -> do
Packit 4cd534
    let decomp = decompressIO gzipFormat defaultDecompressParams
Packit 4cd534
    err <- assertDecompressError hnd decomp
Packit 4cd534
    msg <- assertDataFormatError err
Packit 4cd534
    msg @?= "incorrect data check"
Packit 4cd534
Packit 4cd534
test_non_gzip :: Assertion
Packit 4cd534
test_non_gzip = do
Packit 4cd534
  withSampleData "not-gzip" $ \hnd -> do
Packit 4cd534
    let decomp = decompressIO gzipFormat defaultDecompressParams
Packit 4cd534
    err <- assertDecompressError hnd decomp
Packit 4cd534
    msg <- assertDataFormatError err
Packit 4cd534
    msg @?= "incorrect header check"
Packit 4cd534
Packit 4cd534
  withSampleData "not-gzip" $ \hnd -> do
Packit 4cd534
    let decomp = decompressIO zlibFormat defaultDecompressParams
Packit 4cd534
    err <- assertDecompressError hnd decomp
Packit 4cd534
    msg <- assertDataFormatError err
Packit 4cd534
    msg @?= "incorrect header check"
Packit 4cd534
Packit 4cd534
  withSampleData "not-gzip" $ \hnd -> do
Packit 4cd534
    let decomp = decompressIO rawFormat defaultDecompressParams
Packit 4cd534
    err <- assertDecompressError hnd decomp
Packit 4cd534
    msg <- assertDataFormatError err
Packit 4cd534
    msg @?= "invalid code lengths set"
Packit 4cd534
Packit 4cd534
  withSampleData "not-gzip" $ \hnd -> do
Packit 4cd534
    let decomp = decompressIO gzipOrZlibFormat defaultDecompressParams
Packit 4cd534
    err <- assertDecompressError hnd decomp
Packit 4cd534
    msg <- assertDataFormatError err
Packit 4cd534
    msg @?= "incorrect header check"
Packit 4cd534
Packit 4cd534
test_custom_dict :: Assertion
Packit 4cd534
test_custom_dict =
Packit 4cd534
  withSampleData "custom-dict.zlib" $ \hnd -> do
Packit 4cd534
    let decomp = decompressIO zlibFormat defaultDecompressParams
Packit 4cd534
    err <- assertDecompressError hnd decomp
Packit 4cd534
    err @?= DictionaryRequired
Packit 4cd534
Packit 4cd534
test_wrong_dictionary :: Assertion
Packit 4cd534
test_wrong_dictionary = do
Packit 4cd534
  withSampleData "custom-dict.zlib" $ \hnd -> do
Packit 4cd534
    let decomp = decompressIO zlibFormat defaultDecompressParams {
Packit 4cd534
                                           decompressDictionary = -- wrong dict!
Packit 4cd534
                                             Just (BS.pack [65,66,67])
Packit 4cd534
                                         }
Packit 4cd534
Packit 4cd534
    err <- assertDecompressError hnd decomp
Packit 4cd534
    err @?= DictionaryMismatch
Packit 4cd534
Packit 4cd534
test_right_dictionary :: Assertion
Packit 4cd534
test_right_dictionary = do
Packit 4cd534
  withSampleData "custom-dict.zlib" $ \hnd -> do
Packit 4cd534
    dict <- readSampleData "custom-dict.zlib-dict"
Packit 4cd534
    let decomp = decompressIO zlibFormat defaultDecompressParams {
Packit 4cd534
                                           decompressDictionary =
Packit 4cd534
                                             Just (toStrict dict)
Packit 4cd534
                                         }
Packit 4cd534
    assertDecompressOk hnd decomp
Packit 4cd534
Packit 4cd534
test_trailing_data :: Assertion
Packit 4cd534
test_trailing_data =
Packit 4cd534
  withSampleData "two-files.gz" $ \hnd -> do
Packit 4cd534
    let decomp = decompressIO gzipFormat defaultDecompressParams {
Packit 4cd534
                   decompressAllMembers = False
Packit 4cd534
                 }
Packit 4cd534
    chunks <- assertDecompressOkChunks hnd decomp
Packit 4cd534
    case chunks of
Packit 4cd534
      [chunk] -> chunk @?= BS.Char8.pack "Test 1"
Packit 4cd534
      _       -> assertFailure "expected single chunk"
Packit 4cd534
Packit 4cd534
test_multiple_members :: Assertion
Packit 4cd534
test_multiple_members =
Packit 4cd534
  withSampleData "two-files.gz" $ \hnd -> do
Packit 4cd534
    let decomp = decompressIO gzipFormat defaultDecompressParams {
Packit 4cd534
                   decompressAllMembers = True
Packit 4cd534
                 }
Packit 4cd534
    chunks <- assertDecompressOkChunks hnd decomp
Packit 4cd534
    case chunks of
Packit 4cd534
      [chunk1,
Packit 4cd534
       chunk2] -> do chunk1 @?= BS.Char8.pack "Test 1"
Packit 4cd534
                     chunk2 @?= BS.Char8.pack "Test 2"
Packit 4cd534
      _       -> assertFailure "expected two chunks"
Packit 4cd534
Packit 4cd534
test_small_chunks :: Assertion
Packit 4cd534
test_small_chunks = do
Packit 4cd534
  uncompressedFile <- readSampleData "not-gzip"
Packit 4cd534
  GZip.compress (smallChunks uncompressedFile) @?= GZip.compress uncompressedFile
Packit 4cd534
  Zlib.compress (smallChunks uncompressedFile) @?= Zlib.compress uncompressedFile
Packit 4cd534
  Raw.compress  (smallChunks uncompressedFile) @?= Raw.compress uncompressedFile
Packit 4cd534
Packit 4cd534
  GZip.decompress (smallChunks (GZip.compress uncompressedFile)) @?= uncompressedFile
Packit 4cd534
  Zlib.decompress (smallChunks (Zlib.compress uncompressedFile)) @?= uncompressedFile
Packit 4cd534
  Raw.decompress  (smallChunks (Raw.compress  uncompressedFile)) @?= uncompressedFile
Packit 4cd534
Packit 4cd534
  compressedFile   <- readSampleData "hello.gz"
Packit 4cd534
  (GZip.decompress . smallChunks) compressedFile @?= GZip.decompress compressedFile
Packit 4cd534
Packit 4cd534
test_empty :: Assertion
Packit 4cd534
test_empty = do
Packit 4cd534
  -- Regression test to make sure we only ask for input once in the case of
Packit 4cd534
  -- initially empty input. We previously asked for input twice before
Packit 4cd534
  -- returning the error.
Packit 4cd534
  let decomp = decompressIO zlibFormat defaultDecompressParams
Packit 4cd534
  case decomp of
Packit 4cd534
    DecompressInputRequired next -> do
Packit 4cd534
      decomp' <- next BS.empty
Packit 4cd534
      case decomp' of
Packit 4cd534
        DecompressStreamError TruncatedInput -> return ()
Packit 4cd534
        _ -> assertFailure "expected truncated error"
Packit 4cd534
Packit 4cd534
    _ -> assertFailure "expected input"
Packit 4cd534
Packit 4cd534
test_exception :: Assertion
Packit 4cd534
test_exception =
Packit 4cd534
 (do
Packit 4cd534
    compressedFile <- readSampleData "bad-crc.gz"
Packit 4cd534
    _ <- evaluate (BL.length (GZip.decompress compressedFile))
Packit 4cd534
    assertFailure "expected exception")
Packit 4cd534
Packit 4cd534
  `catch` \err -> do
Packit 4cd534
      msg <- assertDataFormatError err
Packit 4cd534
      msg @?= "incorrect data check"
Packit 4cd534
Packit 4cd534
toStrict :: BL.ByteString -> BS.ByteString
Packit 4cd534
#if MIN_VERSION_bytestring(0,10,0)
Packit 4cd534
toStrict = BL.toStrict
Packit 4cd534
#else
Packit 4cd534
toStrict = BS.concat . BL.toChunks
Packit 4cd534
#endif
Packit 4cd534
Packit 4cd534
-----------------------
Packit 4cd534
-- Chunk boundary utils
Packit 4cd534
Packit 4cd534
smallChunks :: BL.ByteString -> BL.ByteString
Packit 4cd534
smallChunks = BL.fromChunks . map (\c -> BS.pack [c]) . BL.unpack
Packit 4cd534
Packit 4cd534
twoChunkSplits :: BL.ByteString -> [BL.ByteString]
Packit 4cd534
twoChunkSplits bs = zipWith (\a b -> BL.fromChunks [a,b]) (BS.inits sbs) (BS.tails sbs)
Packit 4cd534
  where
Packit 4cd534
    sbs = toStrict bs
Packit 4cd534
Packit 4cd534
threeChunkSplits :: BL.ByteString -> [BL.ByteString]
Packit 4cd534
threeChunkSplits bs =
Packit 4cd534
    [ BL.fromChunks [a,b,c]
Packit 4cd534
    | (a,x) <- zip (BS.inits sbs) (BS.tails sbs)
Packit 4cd534
    , (b,c) <- zip (BS.inits x) (BS.tails x) ]
Packit 4cd534
  where
Packit 4cd534
    sbs = toStrict bs
Packit 4cd534
Packit 4cd534
--------------
Packit 4cd534
-- HUnit Utils
Packit 4cd534
Packit 4cd534
readSampleData :: FilePath -> IO BL.ByteString
Packit 4cd534
readSampleData file = BL.readFile ("test/data/" ++ file)
Packit 4cd534
Packit 4cd534
withSampleData :: FilePath -> (Handle -> IO a) -> IO a
Packit 4cd534
withSampleData file = withFile ("test/data/" ++ file) ReadMode
Packit 4cd534
Packit 4cd534
expected :: String -> String -> IO a
Packit 4cd534
expected e g = assertFailure ("expected: " ++ e ++ "\nbut got: " ++ g)
Packit 4cd534
            >> fail ""
Packit 4cd534
Packit 4cd534
assertDecompressOk :: Handle -> DecompressStream IO -> Assertion
Packit 4cd534
assertDecompressOk hnd =
Packit 4cd534
    foldDecompressStream
Packit 4cd534
      (BS.hGet hnd 4000 >>=)
Packit 4cd534
      (\_ r -> r)
Packit 4cd534
      (\_ -> return ())
Packit 4cd534
      (\err -> expected "decompress ok" (show err))
Packit 4cd534
Packit 4cd534
assertDecompressOkChunks :: Handle -> DecompressStream IO -> IO [BS.ByteString]
Packit 4cd534
assertDecompressOkChunks hnd =
Packit 4cd534
    foldDecompressStream
Packit 4cd534
      (BS.hGet hnd 4000 >>=)
Packit 4cd534
      (\chunk -> liftM (chunk:))
Packit 4cd534
      (\_ -> return [])
Packit 4cd534
      (\err -> expected "decompress ok" (show err))
Packit 4cd534
Packit 4cd534
assertDecompressError :: Handle -> DecompressStream IO -> IO DecompressError
Packit 4cd534
assertDecompressError hnd =
Packit 4cd534
    foldDecompressStream
Packit 4cd534
      (BS.hGet hnd 4000 >>=)
Packit 4cd534
      (\_ r -> r)
Packit 4cd534
      (\_ -> expected "StreamError" "StreamEnd")
Packit 4cd534
      return
Packit 4cd534
Packit 4cd534
assertDataFormatError :: DecompressError -> IO String
Packit 4cd534
assertDataFormatError (DataFormatError detail) = return detail
Packit 4cd534
assertDataFormatError _                        = assertFailure "expected DataError"
Packit 4cd534
                                              >> return ""