glue-core-0.6.2: test/Glue/RetrySpec.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
module Glue.RetrySpec where
import Control.Concurrent
import Control.Exception.Base hiding (throw, throwIO, throwTo)
import Control.Exception.Lifted hiding (throwTo)
import Control.Monad.IO.Class
import Data.IORef
import Data.Typeable
import Glue.Retry
import Test.Hspec
import Test.QuickCheck
newtype SmallInt = SmallInt Int deriving (Eq, Show)
instance Arbitrary SmallInt where
arbitrary = sized $ \s -> do
n <- choose (0, s `min` 10)
return $ SmallInt n
data RetryTestException = RetryTestException deriving (Eq, Show, Typeable)
instance Exception RetryTestException
spec :: Spec
spec = do
describe "retryingService" $ do
it "Attempts a service call multiple times" $ do
property $ \(request, (SmallInt failures), (SmallInt retries)) ->
do
ref <- liftIO $ newIORef 0
let service req = do
counter <- atomicModifyIORef' ref (\c -> (c + 1, c + 1))
if counter > failures then return req else throwIO RetryTestException
let options = defaultRetryOptions { maximumRetries = retries }
let retryService = retryingService options service
let successCase = (retryService request) `shouldReturn` (request :: Int)
let failureCase = (retryService request) `shouldThrow` (== RetryTestException)
if retries >= failures then successCase else failureCase
it "Asynchronous exceptions are rethrown" $ do
property $ \(request, retries) ->
do
ref <- liftIO $ newIORef (0 :: Int)
let service req = do
atomicModifyIORef' ref (\c -> (c + 1, ()))
threadId <- myThreadId
_ <- forkIO (threadDelay 10000 >> throwTo threadId UserInterrupt)
threadDelay 1000000
return (req * 2 :: Int)
let options = defaultRetryOptions { maximumRetries = retries }
let retryService = retryingService options service
(retryService request) `shouldThrow` (== UserInterrupt)
(readIORef ref) `shouldReturn` 1