wobsurv-0.1.0: tests/Main.hs
{-# OPTIONS_GHC -F -pgmF htfpp #-}
module Main where
import Test.Framework
import BasePrelude hiding (getEnv)
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
import Data.Text (Text)
import Data.ByteString (ByteString)
import Filesystem.Path.CurrentOS (FilePath)
import qualified Control.Concurrent.Async.Lifted as Async
import qualified Network
import qualified Network.HTTP.Client as Client
import qualified Wobsurv
import qualified Filesystem
import qualified Data.Text
import qualified Data.Text.Encoding
import qualified Data.ByteString.Lazy
import qualified Data.ByteString as ByteString
import qualified Network.HTTP.Types.Status
import qualified Filesystem.Path.CurrentOS
import qualified System.Random.MWC as MWC
type LazyByteString =
Data.ByteString.Lazy.ByteString
main =
Network.withSocketsDo $ htfMain htf_thisModulesTests
-- * Tests
-------------------------
test_notFound =
runTest env1 $ do
status <- getResponseStatus =<< resourceURI =<< missingResource
lift $ assertEqual "Not Found" status
test_ok =
runTest env1 $ do
response <- getResponse =<< resourceURI =<< existentResource
lift $ assertEqual "OK" $ responseStatus response
lift $ assertBool $ (/= "") $ responseBody response
test_corruptRequestsDontMakeTheServerThrowExceptions =
unitTestPending ""
test_corruptRequestsDontReduceTheConnectionSlots =
unitTestPending ""
test_highLoad =
runTest (env1 {envConnectionsLimit = 48}) $ do
results <-
Async.mapConcurrently id $ replicate 48 $ do
replicateM 5 $
getResponseStatus =<< resourceURI =<< someResource
lift $ assertBool $ all (/= "Service Unavailable") $ concat $ results
test_tooManyConnections =
runTest (env1 {envConnectionsLimit = 10}) $ do
results <-
Async.mapConcurrently id $ replicate 20 $ do
getResponseStatus =<< resourceURI =<< largeResource
lift $ assertBool $ (> 1) $ length $ filter (== "Service Unavailable") $ results
test_multipleClientsOnTooManyConnectionsStillGetDismissedProperly =
test_tooManyConnections
test_slotsGetReleased =
runTest (env1 {envConnectionsLimit = 10}) $ do
Async.mapConcurrently id $ replicate 20 $ do
getResponseStatus =<< resourceURI =<< someResource
results <-
Async.mapConcurrently id $ replicate 10 $ do
getResponseStatus =<< resourceURI =<< someResource
lift $ assertBool $ all (/= "Service Unavailable") $ results
test_uriDecoding =
runTest env1 $ do
let path = "Директория/Название в Юникоде с пробелами.расширение"
createFile 1 path
lift . assertEqual "OK" =<< getResponseStatus =<< resourceURI path
-- * Test monad
-------------------------
type Test =
ReaderT Setup IO
type Setup =
(Client.Manager, ThreadId, Env, MWC.GenIO)
runTest :: Env -> Test a -> IO a
runTest env test =
bracket acquire release $ runReaderT test
where
acquire =
do
manager <- Client.newManager Client.defaultManagerSettings
serverThread <-
forkIO $ Wobsurv.serve $
Wobsurv.Settings
(True)
(envPort env)
(envConnectionsLimit env)
("templates")
(envRoot env)
(mempty)
traverse_ (prepFile 10000) (envSmallFiles env)
traverse_ (prepFile 1000000) (envLargeFiles env)
rand <- MWC.create
return (manager, serverThread, env, rand)
where
prepFile size path =
do
Filesystem.createTree (Filesystem.Path.CurrentOS.directory fullPath)
Filesystem.writeFile fullPath $
ByteString.replicate size (fromIntegral $ ord 'x')
where
fullPath = envRoot env <> path
release (manager, serverThread, env, rand) =
do
Client.closeManager manager
killThread serverThread
Filesystem.removeTree (envRoot env)
createFile :: Int -> FilePath -> Test ()
createFile size path =
do
fullPath <- (<> path) . envRoot <$> getEnv
lift $ do
Filesystem.createTree (Filesystem.Path.CurrentOS.directory fullPath)
Filesystem.writeFile fullPath $
ByteString.replicate size (fromIntegral $ ord 'x')
-- | Send a "GET" request.
getResponse :: URI -> Test Response
getResponse uri =
do
(manager, _, _, _) <- ask
request <- Client.parseUrl uri
lift $ Client.httpLbs request manager
getResponseStatus :: URI -> Test Status
getResponseStatus uri =
do
(manager, _, _, _) <- ask
request <- Client.parseUrl uri
lift $ fmap Network.HTTP.Types.Status.statusMessage $ handle getHTTPExceptionStatus $
Client.responseStatus <$> Client.httpLbs request manager
where
getHTTPExceptionStatus =
\case
Client.StatusCodeException s _ _ -> return s
e -> throwIO e
random :: MWC.Variate a => (a, a) -> Test a
random range =
do
(_, _, _, gen) <- ask
lift $ MWC.uniformR range gen
oneOf :: [a] -> Test (Maybe a)
oneOf list =
case length list of
0 -> return Nothing
l -> do
i <- random (0, pred l)
return $ Just $ list !! i
someResource :: Test FilePath
someResource =
random (0 :: Int, 1) >>= \case
0 -> missingResource
1 -> existentResource
getEnv :: Test Env
getEnv =
ReaderT $ \(_, _, x, _) -> return x
largeResource :: Test FilePath
largeResource =
return . fromMaybe (error "No large files") =<< oneOf . envLargeFiles =<< getEnv
smallResource :: Test FilePath
smallResource =
return . fromMaybe (error "No small files") =<< oneOf . envSmallFiles =<< getEnv
missingResource :: Test FilePath
missingResource =
return . fromMaybe (error "No missing files") =<< oneOf . envMissingFiles =<< getEnv
existentResource :: Test FilePath
existentResource =
random (0 :: Int, 1) >>= \case
0 -> smallResource
1 -> largeResource
responseStatus :: Response -> Status
responseStatus =
Network.HTTP.Types.Status.statusMessage . Client.responseStatus
responseBody :: Response -> LazyByteString
responseBody =
Client.responseBody
resourceURI :: FilePath -> Test URI
resourceURI path = do
(_, _, env, _) <- ask
return $
"http://localhost:" <> show (envPort env) <> "/" <> pathString
where
pathString =
Filesystem.Path.CurrentOS.encodeString path
type Response =
Client.Response LazyByteString
type URI =
[Char]
type Status =
ByteString
-- ** Predefined environments
-------------------------
data Env =
Env {
envPort :: Word,
envConnectionsLimit :: Word,
envRoot :: FilePath,
envLargeFiles :: [FilePath],
envSmallFiles :: [FilePath],
envMissingFiles :: [FilePath]
}
env1 :: Env
env1 =
Env 53000 10 root largeFiles smallFiles missingFiles
where
root =
"dist/wobsurv-test-tmp/"
smallFiles =
[ "a.txt", "dir/b.txt", "Ёжик лижет мёд.jpg" ]
largeFiles =
[ "c.mp3", "d/d d d" ]
missingFiles =
[ "e.txt", "f.nfo" ]