glue-core-0.8.0.0: test/Glue/DogpileProtectionSpec.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Glue.DogpileProtectionSpec (spec) where
import Control.Concurrent (yield)
import Control.Concurrent.Async (async, wait)
import Data.IORef
import Glue.Clock
import Glue.Clock.Virtual
import Glue.DogpileProtection
import Test.Hspec
import UnliftIO.Exception (Exception, throwIO)
import UnliftIO.STM (atomically, newEmptyTMVarIO,
putTMVar, readTMVar)
data DogpileProtectionTestException = DogpileProtectionTestException
deriving (Eq, Show)
instance Exception DogpileProtectionTestException
requests :: [Int]
requests = replicate 10 1
waitForCount :: IORef Int -> Int -> IO ()
waitForCount ref n = readIORef ref >>= \c -> if c >= n then pure () else yield >> waitForCount ref n
spec :: Spec
spec = do
describe "dogpileProtect" $ do
it "With multiple calls to a slow service only one actually gets through" $ do
counter <- newIORef (0 :: Int)
gate <- newEmptyTMVarIO
let service request = do
atomicModifyIORef' counter (\n -> (n + 1, ()))
atomically $ readTMVar gate
pure (request * 2)
protectedService <- dogpileProtect service
asyncResults <- traverse (async . protectedService) requests
waitForCount counter 1
atomically $ putTMVar gate ()
traverse wait asyncResults `shouldReturn` fmap (* 2) requests
readIORef counter `shouldReturn` 1
it "With multiple calls to a slow failing service only one actually gets through" $ do
counter <- newIORef (0 :: Int)
gate <- newEmptyTMVarIO
let service _ = do
atomicModifyIORef' counter (\n -> (n + 1, ()))
atomically $ readTMVar gate
throwIO DogpileProtectionTestException :: IO Int
protectedService <- dogpileProtect service
asyncResults <- traverse (async . protectedService) requests
waitForCount counter 1
atomically $ putTMVar gate ()
traverse wait asyncResults `shouldThrow` (== DogpileProtectionTestException)
readIORef counter `shouldReturn` 1
describe "dogpileProtectWith" $
it "Retains a completed result for exactly the cache time" $ do
vc <- newVirtualClock
counter <- newIORef (0 :: Int)
let clock = virtualClock vc :: Clock IO
options = (defaultDogpileOptions :: DogpileOptions IO) { dogpileCacheTimeMs = 100, dogpileClock = clock }
service (request :: Int) = atomicModifyIORef' counter (\n -> (n + 1, request * 2))
protectedService <- dogpileProtectWith options service
protectedService 1 `shouldReturn` 2
waitForSleepers vc 1
protectedService 1 `shouldReturn` 2
readIORef counter `shouldReturn` 1
advanceClock vc (99 * 1000)
protectedService 1 `shouldReturn` 2
readIORef counter `shouldReturn` 1
advanceClock vc 1000
protectedService 1 `shouldReturn` 2
readIORef counter `shouldReturn` 2