packages feed

haskell-bee-tests-0.1.0.0: tests/Test/Integration/Utils.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.Integration.Utils
  ( randomQueueName
  , waitUntil
  , waitUntilTVarEq
  , waitUntilTVarPred
  , waitUntilQueueSizeIs
  , waitUntilQueueEmpty )
where

import Async.Worker.Broker qualified as B
import Control.Concurrent (threadDelay)
import Control.Concurrent.STM.TVar (TVar, readTVarIO)
import Control.Monad (unless)
import Data.Maybe (fromMaybe)
import Data.String (fromString)
import System.Environment (lookupEnv)
import System.Timeout qualified as Timeout
import Test.Hspec (expectationFailure, shouldBe, shouldSatisfy, Expectation, HasCallStack)
import Test.RandomStrings (randomASCII, randomString, onlyLower)



-- | Timeout for 'wait' jobs, in ms.
newtype TimeoutMs = TimeoutMs Int
  deriving (Eq, Show, Num, Integral, Real, Enum, Ord)


-- | Given a queue prefix, add a random suffix to create a queue name
randomQueueName :: B.Queue -> IO B.Queue
randomQueueName prefix = do
  postfix <- fromString <$> randomString (onlyLower randomASCII) 10
  return $ prefix <> "_" <> postfix


waitThreadDelay :: Int
waitThreadDelay = 50 * 1000

-- | Given a predicate IO action, test it for given number of
-- milliseconds or fail
waitUntil :: HasCallStack => IO Bool -> TimeoutMs -> Expectation
waitUntil pred' (TimeoutMs timeoutMs) = do
  _mTimeout <- Timeout.timeout (timeoutMs * 1000) performTest
  -- shortcut for testing mTimeout
  p <- pred'
  unless p (expectationFailure "Predicate test failed")
  
  where
    performTest = do
      p <- pred'
      if p
        then return ()
        else do
          threadDelay waitThreadDelay
          performTest

-- | Similar to 'waitUntil' but specialized to 'TVar' equality checking
waitUntilTVarEq :: (HasCallStack, Show a, Eq a) => TVar a -> a -> TimeoutMs -> Expectation
waitUntilTVarEq tvar expected (TimeoutMs timeoutMs) = do
  _mTimeout <- Timeout.timeout (timeoutMs * 1000) performTest
  -- shortcut for testing mTimeout
  val <- readTVarIO tvar
  val `shouldBe` expected
  
  where
    performTest = do
      val <- readTVarIO tvar
      if val == expected
        then return ()
        else do
          threadDelay waitThreadDelay
          performTest

-- | Similar to 'waitUntilTVarEq' but with predicate checking
waitUntilTVarPred :: (HasCallStack, Show a) => TVar a -> (a -> Bool) -> TimeoutMs -> Expectation
waitUntilTVarPred tvar predicate (TimeoutMs timeoutMs) = do
  _mTimeout <- Timeout.timeout (timeoutMs * 1000) performTest
  -- shortcut for testing mTimeout
  val <- readTVarIO tvar
  val `shouldSatisfy` predicate
  
  where
    performTest = do
      val <- readTVarIO tvar
      if predicate val
        then return ()
        else do
          threadDelay waitThreadDelay
          performTest

waitUntilQueueSizeIs :: (B.MessageBroker b a) => B.Broker b a -> B.Queue -> Int -> TimeoutMs -> Expectation
waitUntilQueueSizeIs b queue size (TimeoutMs timeoutMs) = do
  _mTimeout <- Timeout.timeout (timeoutMs * 1000) performTest

  qSize <- B.getQueueSize b queue
  qSize `shouldBe` size

  where
    performTest = do
      qSize <- B.getQueueSize b queue
      if qSize == size
        then return ()
        else do
          threadDelay waitThreadDelay
          performTest

waitUntilQueueEmpty :: (B.MessageBroker b a) => B.Broker b a -> B.Queue -> TimeoutMs -> Expectation
waitUntilQueueEmpty b queue timeoutMs = waitUntilQueueSizeIs b queue 0 timeoutMs