{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} module Utils ( untilJust, untilM, withCallIds, allEqOrdShowMethodsImplemented, allEnumMethodsImplemented, MockBoxInit(..), MockBox(..), NoOpArg (..), NoOpBox (..), NoOpInput (..), ) where import Control.Monad.Reader (ReaderT (runReaderT)) import Data.Foldable (traverse_) import Data.Maybe (fromMaybe) import qualified UnliftIO.MessageBox.Util.CallId as CallId import UnliftIO.MessageBox.Util.Fresh (CounterVar) import UnliftIO.MessageBox.Util.Future (Future (Future)) import UnliftIO.MessageBox.Class ( IsInput (..), IsMessageBox (Input, newInput, receive, tryReceive), IsMessageBoxArg (..), ) import Test.QuickCheck ( Arbitrary, Property, Testable (property), (.&&.), (.||.), (=/=), (===), (==>), ) import UnliftIO ( MonadIO, MonadUnliftIO, readTVarIO, registerDelay, ) import UnliftIO.Concurrent (threadDelay) untilJust :: (Monad m) => m (Maybe a) -> m a untilJust loopBody = do !res <- loopBody case res of Nothing -> untilJust loopBody Just r -> return r untilM :: Monad m => m Bool -> m () untilM loopBody = do !isOk <- loopBody if isOk then return () else untilM loopBody withCallIds :: MonadIO m => ReaderT (CounterVar CallId.CallId) m b -> m b withCallIds f = CallId.newCallIdCounter >>= runReaderT f allEqOrdShowMethodsImplemented :: forall a proxy. (Arbitrary a, Ord a, Show a) => proxy a -> Property allEqOrdShowMethodsImplemented _ = property $ \(x :: a) (y :: a) -> ( x < y ==> ( compare x y === LT .&&. compare y x === GT .&&. x /= y .&&. (y >= x) .&&. x < y .&&. (y > x) .&&. x <= y .&&. max x y === y .&&. min x y === x .&&. show x /= show y .&&. show (Just x) /= show (Just y) ) ) .&&. ( (x =/= y) .||. ( compare x y === EQ .&&. compare y x === EQ .&&. x == y .&&. y == x .&&. show x === show y .&&. show (Just x) === show (Just y) .&&. x == y .&&. x >= y .&&. y >= x .&&. x <= y .&&. y <= x .&&. x <= y .&&. x >= y .&&. max x y === x .&&. min x y === x ) ) allEnumMethodsImplemented :: forall a proxy. (Show a, Ord a, Enum a, Bounded a, Arbitrary a) => proxy a -> Property allEnumMethodsImplemented _ = property $ \(x :: a) (y :: a) -> ( not (x > minBound && x < maxBound) .||. succ (pred x) === pred (succ x) ) .&&. ( x /= y .||. ( fromEnum x === fromEnum y .&&. ( x >= maxBound .||. succ x === succ y ) .&&. ( x <= minBound .||. pred x === pred y ) ) ) .&&. toEnum (fromEnum x) === x .&&. head (enumFrom x) === x .&&. (x >= maxBound .||. enumFrom x !! 1 === succ x) .&&. ( x < y .||. fromEnum x >= fromEnum y ) .&&. head (enumFromThen x y) === x .&&. if x > y then enumFromTo x y === [] else head (enumFromTo x y) === x .&&. last (enumFromTo x y) === y .&&. ( x >= maxBound .||. enumFromThenTo x (succ x) y === enumFromTo x y ) -- message box implementation -- NOTE: Because of parametricity and the existential quantification -- of the message payload, the receive and deliver methods -- are only capable of throwing exceptions or bottoming out data MockBoxInit msgBox = MkMockBoxInit { mockBoxNew :: forall m a. MonadUnliftIO m => m (msgBox a) , mockBoxLimit :: !(Maybe Int) } instance IsMessageBox msgBox => IsMessageBoxArg (MockBoxInit msgBox) where type MessageBox (MockBoxInit msgBox) = msgBox getConfiguredMessageLimit = mockBoxLimit newMessageBox b = mockBoxNew b data MockBox input a = MkMockBox { mockBoxNewInput :: forall m . MonadUnliftIO m => m (input a) , mockBoxReceive :: forall m . MonadUnliftIO m => m (Maybe a) , mockBoxTryReceive :: forall m . MonadUnliftIO m => m (Future a) } instance IsInput input => IsMessageBox (MockBox input) where type Input (MockBox input) = input newInput m = mockBoxNewInput m receive m = mockBoxReceive m tryReceive m = mockBoxTryReceive m data NoOpArg = NoOpArg deriving stock (Show) newtype NoOpInput a = OnDeliver (forall m. MonadUnliftIO m => a -> m Bool) data NoOpBox a = OnReceive (Maybe Int) (Maybe a) deriving stock (Show) instance IsMessageBoxArg NoOpArg where type MessageBox NoOpArg = NoOpBox newMessageBox NoOpArg = return (OnReceive Nothing Nothing) getConfiguredMessageLimit _ = Nothing instance IsMessageBox NoOpBox where type Input NoOpBox = NoOpInput newInput _ = return $ OnDeliver (const (return False)) receive (OnReceive t r) = do traverse_ threadDelay t return r tryReceive (OnReceive t r) = do timeoutVar <- traverse registerDelay t return ( Future ( do isOver <- fromMaybe True <$> traverse readTVarIO timeoutVar if isOver then return r else return Nothing ) ) instance IsInput NoOpInput where deliver (OnDeliver react) m = react m