packages feed

snap-core-0.2.13: test/suite/Snap/Util/GZip/Tests.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Snap.Util.GZip.Tests
  ( tests ) where

import qualified Codec.Compression.GZip as GZip
import qualified Codec.Compression.Zlib as Zlib
import           Control.Exception hiding (assert)
import qualified Data.ByteString.Lazy.Char8 as L
import           Data.Digest.Pure.MD5
import           Data.IORef
import           Data.Iteratee
import qualified Data.Map as Map
import           Data.Serialize
import           Test.Framework
import           Test.Framework.Providers.QuickCheck2
import           Test.QuickCheck
import qualified Test.QuickCheck.Monadic as QC
import           Test.QuickCheck.Monadic hiding (run)
import           Test.Framework.Providers.HUnit
import qualified Test.HUnit as H

import           Snap.Types
import           Snap.Internal.Http.Types
import           Snap.Iteratee
import           Snap.Test.Common ()
import           Snap.Util.GZip


------------------------------------------------------------------------------
tests :: [Test]
tests = [ testIdentity1
        , testIdentity2
        , testIdentity3
        , testIdentity4
        , testIdentity5
        , testNoHeaders
        , testNoAcceptEncoding
        , testNopWhenContentEncodingSet
        , testCompositionDoesn'tExplode
        , testGzipLotsaChunks
        , testBadHeaders
        , testIterateeException ]


------------------------------------------------------------------------------
expectException :: IO a -> PropertyM IO ()
expectException m = do
    e <- liftQ $ try m
    case e of
      Left (z::SomeException)  -> (show z) `seq` return ()
      Right _ -> fail "expected exception, didn't get one"


------------------------------------------------------------------------------
liftQ :: forall a m . (Monad m) => m a -> PropertyM m a
liftQ = QC.run


------------------------------------------------------------------------------
gzipHdrs, xGzipHdrs, badHdrs, compressHdrs, xCompressHdrs, emptyHdrs :: Headers
emptyHdrs = Map.empty
gzipHdrs = setHeader "Accept-Encoding" "froz,gzip, x-gzip" emptyHdrs
xGzipHdrs = setHeader "Accept-Encoding" "x-gzip;q=1.0" emptyHdrs
badHdrs = setHeader "Accept-Encoding" "*&%^&^$%&%&*^\023" emptyHdrs
compressHdrs = setHeader "Accept-Encoding" "compress" emptyHdrs
xCompressHdrs = setHeader "Accept-Encoding" "x-compress" emptyHdrs



------------------------------------------------------------------------------
mkNoHeaders :: IO Request
mkNoHeaders = do
    enum <- newIORef $ SomeEnumerator return

    return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False emptyHdrs
                 enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty


mkGzipRq :: IO Request
mkGzipRq = do
    enum <- newIORef $ SomeEnumerator return

    return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False gzipHdrs
                 enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty

mkXGzipRq :: IO Request
mkXGzipRq = do
    enum <- newIORef $ SomeEnumerator return

    return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False xGzipHdrs
                 enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty



------------------------------------------------------------------------------
mkCompressRq :: IO Request
mkCompressRq = do
    enum <- newIORef $ SomeEnumerator return

    return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False compressHdrs
                 enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty

mkXCompressRq :: IO Request
mkXCompressRq = do
    enum <- newIORef $ SomeEnumerator return

    return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False xCompressHdrs
                 enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty



------------------------------------------------------------------------------
mkBadRq :: IO Request
mkBadRq = do
    enum <- newIORef $ SomeEnumerator return

    return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False badHdrs
                  enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty

------------------------------------------------------------------------------
seqSnap :: Snap a -> Snap a
seqSnap m = do
    !x <- m
    return $! x `seq` x


------------------------------------------------------------------------------
goGeneric :: IO Request -> Snap a -> IO (Request, Response)
goGeneric mkRq m = do
    rq <- mkRq
    run $! runSnap (seqSnap m) (const $ return ()) rq

goGZip, goCompress, goXGZip     :: Snap a -> IO (Request,Response)
goNoHeaders, goXCompress, goBad :: Snap a -> IO (Request,Response)

goGZip      = goGeneric mkGzipRq
goCompress  = goGeneric mkCompressRq
goXGZip     = goGeneric mkXGzipRq
goXCompress = goGeneric mkXCompressRq
goBad       = goGeneric mkBadRq
goNoHeaders = goGeneric mkNoHeaders

------------------------------------------------------------------------------
noContentType :: L.ByteString -> Snap ()
noContentType s = modifyResponse $ setResponseBody (enumLBS s)


------------------------------------------------------------------------------
textPlainErr :: L.ByteString -> Snap ()
textPlainErr s = modifyResponse $
                 setResponseBody (enumLBS s >. enumErr "blah") .
                 setContentType "text/plain"


textPlain :: L.ByteString -> Snap ()
textPlain s = modifyResponse $
              setResponseBody (enumLBS s) .
              setContentType "text/plain"


------------------------------------------------------------------------------
binary :: L.ByteString -> Snap ()
binary s = modifyResponse $
           setResponseBody (enumLBS s) .
           setContentType "application/octet-stream"


------------------------------------------------------------------------------
testNoHeaders :: Test
testNoHeaders = testProperty "gzip/noheaders" $
                monadicIO $
                forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = do
        -- if there's no content-type, withCompression should be a no-op
        (!_,!rsp) <- liftQ $ goNoHeaders (seqSnap $ withCompression
                                                  $ noContentType s)
        assert $ getHeader "Content-Encoding" rsp == Nothing
        assert $ getHeader "Vary" rsp == Nothing
        let body = rspBodyToEnum $ rspBody rsp

        c <- liftQ $
             body stream2stream >>= run >>= return . fromWrap

        assert $ s == c


------------------------------------------------------------------------------
testNoAcceptEncoding :: Test
testNoAcceptEncoding = testProperty "gzip/noAcceptEncoding" $
                       monadicIO $
                       forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = do
        -- if there's no content-type, withCompression should be a no-op
        (!_,!rsp) <- liftQ $ goNoHeaders (seqSnap $ withCompression
                                                  $ textPlain s)
        assert $ getHeader "Content-Encoding" rsp == Nothing
        assert $ getHeader "Vary" rsp == Nothing
        let body = rspBodyToEnum $ rspBody rsp

        c <- liftQ $
             body stream2stream >>= run >>= return . fromWrap

        assert $ s == c


------------------------------------------------------------------------------
testIdentity1 :: Test
testIdentity1 = testProperty "gzip/identity1" $ monadicIO $ forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = do
        (!_,!rsp) <- liftQ $ goGZip (seqSnap $ withCompression $ textPlain s)
        assert $ getHeader "Content-Encoding" rsp == Just "gzip"
        assert $ getHeader "Vary" rsp == Just "Accept-Encoding"
        let body = rspBodyToEnum $ rspBody rsp

        c <- liftQ $
             body stream2stream >>= run >>= return . fromWrap

        let s1 = GZip.decompress c
        assert $ s == s1


------------------------------------------------------------------------------
testIdentity2 :: Test
testIdentity2 = testProperty "gzip/identity2" $ monadicIO $ forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = do
        (!_,!rsp) <- liftQ $ goCompress (seqSnap $ withCompression $ textPlain s)

        assert $ getHeader "Content-Encoding" rsp == Just "compress"
        assert $ getHeader "Vary" rsp == Just "Accept-Encoding"
        let body = rspBodyToEnum $ rspBody rsp

        c <- liftQ $
              body stream2stream >>= run >>= return . fromWrap

        let s' = Zlib.decompress c
        assert $ s == s'


------------------------------------------------------------------------------
testIdentity3 :: Test
testIdentity3 = testProperty "gzip/identity3" $ monadicIO $ forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = do
        (!_,!rsp3) <- liftQ $ goGZip (seqSnap $ withCompression $ binary s)
        let body3 = rspBodyToEnum $ rspBody rsp3

        s3 <- liftQ $
              body3 stream2stream >>= run >>= return . fromWrap

        assert $ s == s3


------------------------------------------------------------------------------
testIdentity4 :: Test
testIdentity4 = testProperty "gzip/identity4" $ monadicIO $ forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = do
        (!_,!rsp) <- liftQ $ goXGZip (seqSnap $ withCompression $ textPlain s)
        assert $ getHeader "Content-Encoding" rsp == Just "x-gzip"
        let body = rspBodyToEnum $ rspBody rsp

        c <- liftQ $
             body stream2stream >>= run >>= return . fromWrap

        let s1 = GZip.decompress c
        assert $ s == s1


------------------------------------------------------------------------------
testIdentity5 :: Test
testIdentity5 = testProperty "gzip/identity5" $ monadicIO $ forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = do
        (!_,!rsp2) <- liftQ $ goXCompress (seqSnap $ withCompression $ textPlain s)

        assert $ getHeader "Content-Encoding" rsp2 == Just "x-compress"
        let body2 = rspBodyToEnum $ rspBody rsp2

        c2 <- liftQ $
              body2 stream2stream >>= run >>= return . fromWrap

        let s2 = Zlib.decompress c2
        assert $ s == s2


------------------------------------------------------------------------------
testBadHeaders :: Test
testBadHeaders = testProperty "gzip/bad headers" $ monadicIO $ forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = expectException $ do
        (!_,!rsp) <- goBad (seqSnap $ withCompression $ textPlain s)
        let body = rspBodyToEnum $ rspBody rsp

        body stream2stream >>= run >>= return . fromWrap


------------------------------------------------------------------------------
testNopWhenContentEncodingSet :: Test
testNopWhenContentEncodingSet =
    testProperty "gzip/testNopWhenContentEncodingSet" $
                 monadicIO $
                 forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = do
        (!_,!rsp) <- liftQ $ goGZip $ f s
        assert $ getHeader "Content-Encoding" rsp == Just "identity"

    f !s = seqSnap $ withCompression $ do
            modifyResponse $ setHeader "Content-Encoding" "identity"
            textPlain s


------------------------------------------------------------------------------
testCompositionDoesn'tExplode :: Test
testCompositionDoesn'tExplode =
    testProperty "gzip/testCompositionDoesn'tExplode" $
                 monadicIO $
                 forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = do
        (!_,!rsp) <- liftQ $ goGZip (seqSnap $
                                     withCompression $
                                     withCompression $
                                     withCompression $ textPlain s)

        assert $ getHeader "Content-Encoding" rsp == Just "gzip"

        let body = rspBodyToEnum $ rspBody rsp

        c <- liftQ $
             body stream2stream >>= run >>= return . fromWrap

        let s1 = GZip.decompress c
        assert $ s == s1


------------------------------------------------------------------------------
testGzipLotsaChunks :: Test
testGzipLotsaChunks = testCase "gzip/lotsOfChunks" prop
  where
    prop = do
        let s = L.take 120000 $ L.fromChunks $ frobnicate "dshflkahdflkdhsaflk"
        (!_,!rsp) <- goGZip (seqSnap $ withCompression $ textPlain s)
        let body = rspBodyToEnum $ rspBody rsp

        c <- body stream2stream >>= run >>= return . fromWrap

        let s1 = GZip.decompress c
        H.assertBool "streams equal" $ s == s1

    
    -- in order to get incompressible text (so that we can test whether the
    -- gzip thread is streaming properly!) we'll iteratively md5 the source
    -- string
    frobnicate s = let s' = encode $ md5 $ L.fromChunks [s]
                   in (s:frobnicate s')


------------------------------------------------------------------------------
testIterateeException :: Test
testIterateeException = testProperty "gzip/iterateeException" $
                        monadicIO $ forAllM arbitrary prop
  where
    prop :: L.ByteString -> PropertyM IO ()
    prop s = expectException $ do
        (!_,!rsp) <- goGZip (seqSnap $ withCompression $ textPlainErr s)
        let body = rspBodyToEnum $ rspBody rsp
        body stream2stream >>= run >>= return . fromWrap