{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-orphans #-}


{- | This modules provides concurrency abstractions for hedgehog tests. Using "lifted-base" one can execute
expensive test actions concurrently.

For example, the actions invoked inside 'mapConcurrently_' are invoked in the same 'MonadTest' as the outer
monad of 'mapConcurrently_'.

@
import qualified Hedgehog.Extras.Test.Concurrent as H

setUpEnvironment = H.mapConcurrently_ id
  [ H.threadDelay 100 >> pure 1
  , H.threadDelay 200 >> pure 2
  , H.threadDelay 300 >> pure 3
  ]
@


__Warning: Do not use this module for running concurrent checks!__ The 'MonadBaseControl' instance does not
aggregate effects for 'PropertyT'. Consider the following code:

@
  LA.mapConcurrently_ id
    [ do
      H.note_ \"FAIL1\"
      success
    , do
      IO.threadDelay 1_000_000
      H.note_ \"FAIL2\"
      failure
    , do
      H.note_ \"FAIL3\"
      failure
    ]
@

Executing this code will give you the following output in the test report:

@
66 ┃   LA.mapConcurrently_ id
67 ┃     [ do
68 ┃       H.note_ \"FAIL1\"
   ┃       │ FAIL1
69 ┃       success
70 ┃     , do
71 ┃       IO.threadDelay 1_000_000
72 ┃       H.note_ \"FAIL2\"
   ┃       │ FAIL2
73 ┃       failure
   ┃       ^^^^^^^
74 ┃     , do
75 ┃       H.note_ \"FAIL3\"
76 ┃       failure
77 ┃     ]
@
Please note that only @FAIL1@ and @FAIL2@ annotations were reported - @FAIL3@ annotation and the failure
below was swallowed without any information.

__Don't use concurrency abstractions from this module, when you need to aggregate and report failures!__

-}
module Hedgehog.Extras.Test.Concurrent
  ( threadDelay
  -- * Re-exports of concurrency abstractions from @lifted-base@
  , module Control.Concurrent.Async.Lifted
  , module System.Timeout.Lifted
  ) where

import           Control.Applicative
import           Control.Concurrent.Async.Lifted
import qualified Control.Concurrent.Lifted as IO
import           Control.Monad.Base
import           Control.Monad.IO.Class
import           Control.Monad.Trans.Control
import           Control.Monad.Trans.Resource
import           Data.Function
import           Data.Int
import qualified GHC.Stack as GHC
import           System.IO (IO)
import           System.Timeout.Lifted
import qualified UnliftIO

import           Hedgehog
import qualified Hedgehog as H

-- | Delay the thread by 'n' milliseconds.
threadDelay :: (MonadTest m, MonadIO m) => Int -> m ()
threadDelay :: forall (m :: * -> *). (MonadTest m, MonadIO m) => Int -> m ()
threadDelay Int
n = m () -> m ()
(HasCallStack => m ()) -> m ()
forall a. HasCallStack => (HasCallStack => a) -> a
GHC.withFrozenCallStack (m () -> m ()) -> (IO () -> m ()) -> IO () -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> m ()
forall (m :: * -> *) a.
(MonadTest m, MonadIO m, HasCallStack) =>
IO a -> m a
H.evalIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Int -> IO ()
forall (m :: * -> *). MonadBase IO m => Int -> m ()
IO.threadDelay Int
n

instance MonadBase IO (ResourceT IO) where
  liftBase :: forall α. IO α -> ResourceT IO α
liftBase = IO α -> ResourceT IO α
forall α. IO α -> ResourceT IO α
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO

instance MonadBaseControl IO (ResourceT IO) where
  type StM (ResourceT IO) a = a
  liftBaseWith :: forall a. (RunInBase (ResourceT IO) IO -> IO a) -> ResourceT IO a
liftBaseWith = ((forall a. ResourceT IO a -> IO a) -> IO a) -> ResourceT IO a
(RunInBase (ResourceT IO) IO -> IO a) -> ResourceT IO a
forall b.
((forall a. ResourceT IO a -> IO a) -> IO b) -> ResourceT IO b
forall (m :: * -> *) b.
MonadUnliftIO m =>
((forall a. m a -> IO a) -> IO b) -> m b
UnliftIO.withRunInIO
  restoreM :: forall a. StM (ResourceT IO) a -> ResourceT IO a
restoreM = a -> ResourceT IO a
StM (ResourceT IO) a -> ResourceT IO a
forall a. a -> ResourceT IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure