2017-11-04 05:10:29 +01:00
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
{-# LANGUAGE ConstraintKinds #-}
|
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE DeriveGeneric #-}
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE FunctionalDependencies #-}
|
|
|
|
{-# LANGUAGE GADTs #-}
|
|
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE PolyKinds #-}
|
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
{-# LANGUAGE TypeOperators #-}
|
|
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
|
|
#if __GLASGOW_HASKELL__ >= 800
|
|
|
|
{-# OPTIONS_GHC -freduction-depth=100 #-}
|
|
|
|
#else
|
|
|
|
{-# OPTIONS_GHC -fcontext-stack=100 #-}
|
|
|
|
#endif
|
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
|
|
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
|
|
|
|
|
|
|
|
#include "overlapping-compat.h"
|
|
|
|
module Servant.StreamSpec (spec) where
|
|
|
|
|
2018-06-29 21:08:26 +02:00
|
|
|
import Control.Monad
|
|
|
|
(replicateM_, void)
|
2018-02-27 15:31:41 +01:00
|
|
|
import qualified Data.ByteString as BS
|
2017-11-04 05:10:29 +01:00
|
|
|
import Data.Proxy
|
2018-02-27 15:31:41 +01:00
|
|
|
import qualified Network.HTTP.Client as C
|
2018-06-29 21:08:26 +02:00
|
|
|
import Prelude ()
|
2018-02-27 15:31:41 +01:00
|
|
|
import Prelude.Compat
|
2018-06-29 21:08:26 +02:00
|
|
|
import System.IO
|
|
|
|
(IOMode (ReadMode), withFile)
|
|
|
|
import System.IO.Unsafe
|
|
|
|
(unsafePerformIO)
|
2017-11-04 05:10:29 +01:00
|
|
|
import Test.Hspec
|
2018-03-19 18:22:49 +01:00
|
|
|
import Test.QuickCheck
|
2017-11-04 05:10:29 +01:00
|
|
|
|
2018-06-29 21:08:26 +02:00
|
|
|
import Servant.API
|
|
|
|
((:<|>) ((:<|>)), (:>), JSON, NetstringFraming,
|
|
|
|
NewlineFraming, NoFraming, OctetStream, ResultStream (..),
|
|
|
|
StreamGenerator (..), StreamGet)
|
2017-11-04 05:10:29 +01:00
|
|
|
import Servant.Client
|
2018-06-29 21:08:26 +02:00
|
|
|
import Servant.ClientSpec
|
|
|
|
(Person (..))
|
2018-02-27 15:31:41 +01:00
|
|
|
import qualified Servant.ClientSpec as CS
|
2017-11-04 05:10:29 +01:00
|
|
|
import Servant.Server
|
|
|
|
|
2018-03-15 09:46:30 +01:00
|
|
|
#if MIN_VERSION_base(4,10,0)
|
2018-06-29 21:08:26 +02:00
|
|
|
import GHC.Stats
|
|
|
|
(gc, gcdetails_mem_in_use_bytes, getRTSStats)
|
2018-03-15 09:46:30 +01:00
|
|
|
#else
|
2018-06-29 21:08:26 +02:00
|
|
|
import GHC.Stats
|
|
|
|
(currentBytesUsed, getGCStats)
|
2018-03-15 09:46:30 +01:00
|
|
|
#endif
|
2017-11-04 05:10:29 +01:00
|
|
|
|
|
|
|
spec :: Spec
|
|
|
|
spec = describe "Servant.Stream" $ do
|
|
|
|
streamSpec
|
|
|
|
|
|
|
|
type StreamApi f =
|
|
|
|
"streamGetNewline" :> StreamGet NewlineFraming JSON (f Person)
|
|
|
|
:<|> "streamGetNetstring" :> StreamGet NetstringFraming JSON (f Person)
|
2018-05-23 11:28:12 +02:00
|
|
|
:<|> "streamALot" :> StreamGet NoFraming OctetStream (f BS.ByteString)
|
2017-11-04 05:10:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
capi :: Proxy (StreamApi ResultStream)
|
|
|
|
capi = Proxy
|
|
|
|
|
|
|
|
sapi :: Proxy (StreamApi StreamGenerator)
|
|
|
|
sapi = Proxy
|
|
|
|
|
2018-02-27 15:31:41 +01:00
|
|
|
getGetNL, getGetNS :: ClientM (ResultStream Person)
|
|
|
|
getGetALot :: ClientM (ResultStream BS.ByteString)
|
|
|
|
getGetNL :<|> getGetNS :<|> getGetALot = client capi
|
2017-11-04 05:10:29 +01:00
|
|
|
|
|
|
|
alice :: Person
|
|
|
|
alice = Person "Alice" 42
|
|
|
|
|
|
|
|
bob :: Person
|
|
|
|
bob = Person "Bob" 25
|
|
|
|
|
|
|
|
server :: Application
|
2018-02-27 15:31:41 +01:00
|
|
|
server = serve sapi
|
|
|
|
$ return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
|
|
|
|
:<|> return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
|
|
|
|
:<|> return (StreamGenerator lotsGenerator)
|
|
|
|
where
|
|
|
|
lotsGenerator f r = do
|
2018-03-15 09:46:30 +01:00
|
|
|
void $ f ""
|
|
|
|
void $ withFile "/dev/urandom" ReadMode $
|
2018-03-19 18:22:49 +01:00
|
|
|
\handle -> streamFiveMBNTimes handle 1000 r
|
2018-02-27 15:31:41 +01:00
|
|
|
return ()
|
|
|
|
|
2018-03-19 18:22:49 +01:00
|
|
|
streamFiveMBNTimes handle left sink
|
2018-03-15 09:46:30 +01:00
|
|
|
| left <= (0 :: Int) = return ()
|
2018-02-27 15:31:41 +01:00
|
|
|
| otherwise = do
|
2018-03-19 18:22:49 +01:00
|
|
|
msg <- BS.hGet handle (megabytes 5)
|
2018-03-15 09:46:30 +01:00
|
|
|
_ <- sink msg
|
2018-03-19 18:22:49 +01:00
|
|
|
streamFiveMBNTimes handle (left - 1) sink
|
2018-02-27 15:31:41 +01:00
|
|
|
|
2017-11-04 05:10:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
{-# NOINLINE manager' #-}
|
|
|
|
manager' :: C.Manager
|
|
|
|
manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
|
|
|
|
|
|
|
|
runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a)
|
2017-12-31 02:48:44 +01:00
|
|
|
runClient x baseUrl' = runClientM x (mkClientEnv manager' baseUrl')
|
2017-11-04 05:10:29 +01:00
|
|
|
|
2018-06-23 22:09:28 +02:00
|
|
|
testRunResultStream :: ResultStream a
|
2018-02-27 15:31:41 +01:00
|
|
|
-> IO ( Maybe (Either String a)
|
|
|
|
, Maybe (Either String a)
|
|
|
|
, Maybe (Either String a)
|
|
|
|
, Maybe (Either String a))
|
2018-06-23 22:09:28 +02:00
|
|
|
testRunResultStream (ResultStream k)
|
2018-02-27 15:31:41 +01:00
|
|
|
= k $ \act -> (,,,) <$> act <*> act <*> act <*> act
|
2017-11-04 05:10:29 +01:00
|
|
|
|
|
|
|
streamSpec :: Spec
|
|
|
|
streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
|
|
|
|
|
2018-02-27 15:31:41 +01:00
|
|
|
it "works with Servant.API.StreamGet.Newline" $ \(_, baseUrl) -> do
|
2017-11-04 05:10:29 +01:00
|
|
|
Right res <- runClient getGetNL baseUrl
|
|
|
|
let jra = Just (Right alice)
|
|
|
|
jrb = Just (Right bob)
|
2018-06-23 22:09:28 +02:00
|
|
|
testRunResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
|
2017-11-04 05:10:29 +01:00
|
|
|
|
2018-02-27 15:31:41 +01:00
|
|
|
it "works with Servant.API.StreamGet.Netstring" $ \(_, baseUrl) -> do
|
2017-11-04 05:10:29 +01:00
|
|
|
Right res <- runClient getGetNS baseUrl
|
|
|
|
let jra = Just (Right alice)
|
|
|
|
jrb = Just (Right bob)
|
2018-06-23 22:09:28 +02:00
|
|
|
testRunResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
|
2018-02-27 15:31:41 +01:00
|
|
|
|
2018-07-04 23:27:57 +02:00
|
|
|
{-
|
2018-02-27 15:31:41 +01:00
|
|
|
it "streams in constant memory" $ \(_, baseUrl) -> do
|
|
|
|
Right (ResultStream res) <- runClient getGetALot baseUrl
|
|
|
|
let consumeNChunks n = replicateM_ n (res void)
|
|
|
|
consumeNChunks 900
|
2018-03-15 09:46:30 +01:00
|
|
|
#if MIN_VERSION_base(4,10,0)
|
|
|
|
memUsed <- gcdetails_mem_in_use_bytes . gc <$> getRTSStats
|
|
|
|
#else
|
2018-02-27 15:31:41 +01:00
|
|
|
memUsed <- currentBytesUsed <$> getGCStats
|
2018-03-15 09:46:30 +01:00
|
|
|
#endif
|
|
|
|
memUsed `shouldSatisfy` (< megabytes 22)
|
2018-07-04 23:27:57 +02:00
|
|
|
-}
|
2018-02-27 15:31:41 +01:00
|
|
|
|
|
|
|
megabytes :: Num a => a -> a
|
2018-03-15 09:46:30 +01:00
|
|
|
megabytes n = n * (1000 ^ (2 :: Int))
|