packages feed

glue-core-0.7.0: test/Glue/RetrySpec.hs

module Glue.RetrySpec (spec) where

import           Control.Concurrent     (forkIO, myThreadId, threadDelay)
import           Control.Exception.Base (AsyncException (UserInterrupt),
                                         throwTo)
import           Control.Monad.IO.Class (liftIO)
import           Data.IORef
import           Glue.Retry
import           Test.Hspec
import           Test.QuickCheck
import           UnliftIO.Exception     (Exception, throwIO)

newtype SmallInt = SmallInt Int
  deriving (Eq, Show)

instance Arbitrary SmallInt where
  arbitrary = sized $ \s -> do
    n <- choose (0, s `min` 10)
    pure $ SmallInt n

data RetryTestException = RetryTestException
  deriving (Eq, Show)

instance Exception RetryTestException

spec :: Spec
spec = do
  describe "retryingService" $ do
    it "Attempts a service call multiple times" $
      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 pure req else throwIO RetryTestException
            options      = defaultRetryOptions { maximumRetries = retries }
            retryService = retryingService options service
            successCase  = retryService request `shouldReturn` (request :: Int)
            failureCase  = retryService request `shouldThrow` (== RetryTestException)
        if retries >= failures then successCase else failureCase

    it "Asynchronous exceptions are rethrown" $
      property $ \(request, retries) -> do
        ref <- liftIO $ newIORef (0 :: Int)
        let service req = do
              atomicModifyIORef' ref (\c -> (c + 1, ()))
              threadId <- myThreadId
              _ <- forkIO (threadDelay 2000 >> throwTo threadId UserInterrupt)
              threadDelay 1000000
              pure (req * 2 :: Int)
            options      = defaultRetryOptions { maximumRetries = retries }
            retryService = retryingService options service
        retryService request `shouldThrow` (== UserInterrupt)
        readIORef ref `shouldReturn` 1