module ProcessBehaviourTestCases where import Data.List ( sort ) import Data.Foldable ( traverse_ ) import qualified Data.Dynamic as Dynamic import Data.Typeable import Control.Exception import Control.Concurrent import Control.Concurrent.STM import Control.Eff.Concurrent.Process import Control.Eff.Concurrent.Process.Timer import Control.Eff.Concurrent.Api import Control.Eff.Concurrent.Api.Client import Control.Eff.Concurrent.Api.Server import qualified Control.Eff.Concurrent.Process.ForkIOScheduler as ForkIO import qualified Control.Eff.Concurrent.Process.SingleThreadedScheduler as SingleThreaded import Control.Eff import Control.Eff.Extend import Control.Eff.Log import Control.Eff.LogWriter.Async import Control.Eff.LogWriter.Console import Control.Eff.LogWriter.IO import Control.Eff.Loop import Control.Monad import Test.Tasty import Test.Tasty.HUnit import Common import Control.Applicative import Data.Void import Control.Lens (view) testInterruptReason :: InterruptReason testInterruptReason = ProcessError "test interrupt" test_forkIo :: TestTree test_forkIo = setTravisTestOptions $ withTestLogC (\c -> runLift $ withLogging (filteringLogWriter (lmSeverityIsAtLeast errorSeverity) consoleLogWriter) $ withAsyncLogWriter (100 :: Int) $ ForkIO.schedule c) (\factory -> testGroup "ForkIOScheduler" [allTests factory]) test_singleThreaded :: TestTree test_singleThreaded = setTravisTestOptions $ withTestLogC (\e -> let runEff :: Eff LoggingAndIo a -> IO a runEff = runLift . withLogging (mkLogWriterIO (\m -> when (view lmSeverity m < errorSeverity) (printLogMessage m))) in void $ SingleThreaded.scheduleM runEff yield e ) (\factory -> testGroup "SingleThreadedScheduler" [allTests factory]) allTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree allTests schedulerFactory = localOption (timeoutSeconds 300) (testGroup "Process" [ errorTests schedulerFactory , sendShutdownTests schedulerFactory , concurrencyTests schedulerFactory , exitTests schedulerFactory , pingPongTests schedulerFactory , yieldLoopTests schedulerFactory , selectiveReceiveTests schedulerFactory , linkingTests schedulerFactory , monitoringTests schedulerFactory , timerTests schedulerFactory ] ) data ReturnToSender deriving Typeable data instance Api ReturnToSender r where ReturnToSender :: ProcessId -> String -> Api ReturnToSender ('Synchronous Bool) StopReturnToSender :: Api ReturnToSender ('Synchronous ()) deriving instance Show (Api ReturnToSender x) deriving instance Typeable (Api ReturnToSender x) returnToSender :: forall q r . (HasCallStack, SetMember Process (Process q) r, Member Interrupts r) => Server ReturnToSender -> String -> Eff r Bool returnToSender toP msg = do me <- self _ <- call toP (ReturnToSender me msg) msgEcho <- receiveMessage @String return (msgEcho == msg) stopReturnToSender :: forall q r . (HasCallStack, SetMember Process (Process q) r, Member Interrupts r) => Server ReturnToSender -> Eff r () stopReturnToSender toP = call toP StopReturnToSender returnToSenderServer :: forall q . (HasCallStack, Member Logs q) => Eff (InterruptableProcess q) (Server ReturnToSender) returnToSenderServer = spawnApiServer (handleCalls (\m k -> k $ case m of StopReturnToSender -> do return (Nothing, StopServer testInterruptReason) ReturnToSender fromP echoMsg -> do sendMessage fromP echoMsg yieldProcess return (Just True, AwaitNext) ) ) stopServerOnInterrupt selectiveReceiveTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree selectiveReceiveTests schedulerFactory = setTravisTestOptions (testGroup "selective receive tests" [ testCase "send 10 messages (from 1..10) and receive messages from 10 to 1" $ applySchedulerFactory schedulerFactory $ do let nMax = 10 receiverLoop donePid = go nMax where go :: Int -> Eff (InterruptableProcess r) () go 0 = sendMessage donePid True go n = do void $ receiveSelectedMessage (filterMessage (== n)) go (n - 1) senderLoop destination = traverse_ (sendMessage destination) [1 .. nMax] me <- self receiverPid <- spawn (receiverLoop me) spawn_ (senderLoop receiverPid) ok <- receiveMessage @Bool lift (ok @? "selective receive failed") , testCase "receive a message while waiting for a call reply" $ applySchedulerFactory schedulerFactory $ do srv <- returnToSenderServer ok <- returnToSender srv "test" () <- stopReturnToSender srv lift (ok @? "selective receive failed") , testCase "flush messages" $ applySchedulerFactory schedulerFactory $ do me <- self spawn_ $ replicateM_ 10 (sendMessage me True) >> sendMessage me () spawn_ $ replicateM_ 10 (sendMessage me (123.23411 :: Float)) >> sendMessage me () spawn_ $ replicateM_ 10 (sendMessage me ("123"::String)) >> sendMessage me () () <- receiveMessage () <- receiveMessage () <- receiveMessage -- replicateCheapM_ 40 yieldProcess messages <- flushMessages lift (length messages @?= 30) ] ) yieldLoopTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree yieldLoopTests schedulerFactory = let maxN = 100000 in setTravisTestOptions (testGroup "yield tests" [ testCase "yield many times (replicateM_)" (applySchedulerFactory schedulerFactory (replicateM_ maxN yieldProcess) ) , testCase "yield many times (forM_)" (applySchedulerFactory schedulerFactory (forM_ [1 :: Int .. maxN] (\_ -> yieldProcess)) ) , testCase "construct an effect with an exit first, followed by many yields" (applySchedulerFactory schedulerFactory (do void exitNormally replicateM_ 1000000000000 yieldProcess ) ) ] ) data Ping = Ping ProcessId deriving (Eq, Show) data Pong = Pong deriving (Eq, Show) pingPongTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree pingPongTests schedulerFactory = testGroup "Yield Tests" [ testCase "ping pong a message between two processes, both don't yield" $ applySchedulerFactory schedulerFactory $ do let pongProc = foreverCheap $ do Ping pinger <- receiveMessage sendMessage pinger Pong pingProc ponger parent = do me <- self sendMessage ponger (Ping me) Pong <- receiveMessage sendMessage parent True pongPid <- spawn pongProc me <- self spawn_ (pingProc pongPid me) ok <- receiveMessage @Bool lift (ok @? "ping pong failed") , testCase "ping pong a message between two processes, with massive yielding" $ applySchedulerFactory schedulerFactory $ do yieldProcess let pongProc = foreverCheap $ do yieldProcess Ping pinger <- receiveMessage yieldProcess sendMessage pinger Pong yieldProcess pingProc ponger parent = do yieldProcess me <- self yieldProcess sendMessage ponger (Ping me) yieldProcess Pong <- receiveMessage yieldProcess sendMessage parent True yieldProcess yieldProcess pongPid <- spawn pongProc yieldProcess me <- self yieldProcess spawn_ (pingProc pongPid me) yieldProcess ok <- receiveMessage @Bool yieldProcess lift (ok @? "ping pong failed") yieldProcess , testCase "the first message is not delayed, not even in cooperative scheduling (because of yield)" $ applySchedulerFactory schedulerFactory $ do pongVar <- lift newEmptyMVar let pongProc = foreverCheap $ do Pong <- receiveMessage lift (putMVar pongVar Pong) ponger <- spawn pongProc sendMessage ponger Pong let waitLoop = do p <- lift (tryTakeMVar pongVar) case p of Nothing -> do yieldProcess waitLoop Just r -> return r p <- waitLoop lift (p == Pong @? "ping pong failed") ] errorTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree errorTests schedulerFactory = testGroup "causing and handling errors" [ testGroup "exitWithError" [ testCase "unhandled exitWithError" $ applySchedulerFactory schedulerFactory $ do void $ exitWithError "test error" error "This should not happen" , testCase "cannot catch exitWithError" $ applySchedulerFactory schedulerFactory $ do void $ exitWithError "test error 4" error "This should not happen" , testCase "multi process exitWithError" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self let n = 15 traverse_ (\(i :: Int) -> spawn $ foreverCheap (void ( sendMessage (888888 + fromIntegral i) ("test message" :: String) >> yieldProcess ) ) ) [0 .. n] traverse_ (\(i :: Int) -> spawn $ do void $ sendMessage me i void (exitWithError (show i ++ " died")) error "this should not be reached" ) [0 .. n] oks <- replicateM (length [0 .. n]) receiveMessage assertEff "" (sort oks == [0 .. n]) ] ] concurrencyTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree concurrencyTests schedulerFactory = let n = 100 in testGroup "concurrency tests" [ testCase "when main process exits the scheduler kills/cleans and returns" $ applySchedulerFactory schedulerFactory $ do me <- self traverse_ (const (spawn (do m <- receiveAnyMessage void (sendMessage me m) ) ) ) [1 .. n] lift (threadDelay 1000) , testCase "new processes are executed before the parent process" $ scheduleAndAssert schedulerFactory $ \assertEff -> do -- start massive amount of children that exit as soon as they are -- executed, this will only work smoothly when scheduler schedules -- the new child before the parent traverse_ (const (spawn exitNormally)) [1 .. n] assertEff "" True , testCase "two concurrent processes" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self child1 <- spawn (do m <- receiveAnyMessage void (sendAnyMessage me m) ) child2 <- spawn (foreverCheap (void (sendMessage 888888 (""::String)))) sendMessage child1 ("test" :: String) i <- receiveMessage sendInterrupt child2 testInterruptReason assertEff "" (i == ("test"::String)) , testCase "most processes send foreverCheap" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self traverse_ (\(i :: Int) -> spawn $ do when (i `rem` 5 == 0) $ void $ sendMessage me i foreverCheap $ void (sendMessage 888 ("test message to 888" :: String)) ) [0 .. n] oks <- replicateM (length [0, 5 .. n]) receiveMessage assertEff "" (sort oks == [0, 5 .. n]) , testCase "most processes self foreverCheap" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self traverse_ (\(i :: Int) -> spawn $ do when (i `rem` 5 == 0) $ void $ sendMessage me i foreverCheap $ void self ) [0 .. n] oks <- replicateM (length [0, 5 .. n]) receiveMessage assertEff "" (sort oks == [0, 5 .. n]) , testCase "most processes sendShutdown foreverCheap" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self traverse_ (\(i :: Int) -> spawn $ do when (i `rem` 5 == 0) $ void $ sendMessage me i foreverCheap $ void (sendShutdown 999 ExitNormally) ) [0 .. n] oks <- replicateM (length [0, 5 .. n]) receiveMessage assertEff "" (sort oks == [0, 5 .. n]) , testCase "most processes spawn foreverCheap" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self traverse_ (\(i :: Int) -> spawn $ do when (i `rem` 5 == 0) $ void $ sendMessage me i parent <- self foreverCheap $ void (spawn (void (sendMessage parent ("test msg from child"::String)))) ) [0 .. n] oks <- replicateM (length [0, 5 .. n]) receiveMessage assertEff "" (sort oks == [0, 5 .. n]) , testCase "most processes receive foreverCheap" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self traverse_ (\(i :: Int) -> spawn $ do when (i `rem` 5 == 0) $ void $ sendMessage me i foreverCheap $ void receiveAnyMessage ) [0 .. n] oks <- replicateM (length [0, 5 .. n]) receiveMessage assertEff "" (sort oks == [0, 5 .. n]) ] exitTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree exitTests schedulerFactory = testGroup "process exit tests" $ [ testGroup "async exceptions" [ testCase ( "a process dies immediately if a " ++ show e ++ " is thrown, while " ++ busyWith ) $ do tidVar <- newEmptyTMVarIO schedulerDoneVar <- newEmptyTMVarIO void $ forkIO $ do void $ try @SomeException $ void $ applySchedulerFactory schedulerFactory $ do tid <- lift $ myThreadId lift $ atomically $ putTMVar tidVar tid foreverCheap busyEffect atomically $ putTMVar schedulerDoneVar () tid <- atomically $ takeTMVar tidVar threadDelay 1000 throwTo tid e void $ atomically $ takeTMVar schedulerDoneVar | e <- [ThreadKilled, UserInterrupt, HeapOverflow, StackOverflow] , (busyWith, busyEffect) <- [ ( "receiving" , void (send (ReceiveSelectedMessage @r (filterMessage (== ("test message" :: String)))) ) ) , ( "sending" , void (send (SendMessage @r 44444 (Dynamic.toDyn ("test message" :: String)))) ) , ( "sending shutdown" , void (send (SendShutdown @r 44444 ExitNormally)) ) , ("selfpid-ing", void (send (SelfPid @r))) , ( "spawn-ing" , void (send (Spawn @r (void (send (ReceiveSelectedMessage @r selectAnyMessageLazy))) ) ) ) , ("sleeping", lift (threadDelay 100000)) ] ] , testGroup "main thread exit not blocked by" [ testCase ("a child process, busy with " ++ busyWith) $ applySchedulerFactory schedulerFactory $ do void $ spawn $ foreverCheap busyEffect lift (threadDelay 10000) | (busyWith, busyEffect) <- [ ( "receiving" , void (send (ReceiveSelectedMessage @r (filterMessage (== ("test message"::String)))) ) ) , ( "sending" , void (send (SendMessage @r 44444 (Dynamic.toDyn ("test message"::String)))) ) , ( "sending shutdown" , void (send (SendShutdown @r 44444 ExitNormally)) ) , ("selfpid-ing", void (send (SelfPid @r))) , ( "spawn-ing" , void (send (Spawn @r (void (send (ReceiveSelectedMessage @r selectAnyMessageLazy))) ) ) ) ] ] , testGroup "one process exits, the other continues unimpaired" [ testCase ( "process 2 exits with: " ++ howToExit ++ " - while process 1 is busy with: " ++ busyWith ) $ scheduleAndAssert schedulerFactory $ \assertEff -> do p1 <- spawn $ foreverCheap busyEffect lift (threadDelay 1000) void $ spawn $ do lift (threadDelay 1000) doExit lift (threadDelay 100000) wasRunningP1 <- isProcessAlive p1 sendShutdown p1 ExitNormally lift (threadDelay 100000) stillRunningP1 <- isProcessAlive p1 assertEff "the other process did not die still running" (not stillRunningP1 && wasRunningP1) | (busyWith , busyEffect) <- [ ( "receiving" , void (send (ReceiveSelectedMessage @r (filterMessage (== ("test message"::String)))) ) ) , ( "sending" , void (send (SendMessage @r 44444 (Dynamic.toDyn ("test message"::String)))) ) , ( "sending shutdown" , void (send (SendShutdown @r 44444 ExitNormally)) ) , ("selfpid-ing", void (send (SelfPid @r))) , ( "spawn-ing" , void (send (Spawn @r (void (send (ReceiveSelectedMessage @r selectAnyMessageLazy))) ) ) ) ] , (howToExit, doExit ) <- [ ("normally" , void exitNormally) , ("simply returning", return ()) , ("raiseError", void (interrupt (ProcessError "test error raised"))) , ("exitWithError" , void (exitWithError "test error exit")) , ( "sendShutdown to self" , do me <- self void (sendShutdown me ExitNormally) ) ] ] ] sendShutdownTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree sendShutdownTests schedulerFactory = testGroup "sendShutdown" [ testCase "... self" $ applySchedulerFactory schedulerFactory $ do me <- self void $ send (SendShutdown @r me ExitNormally) interrupt (ProcessError "sendShutdown must not return") , testCase "sendInterrupt to self" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self r <- send (SendInterrupt @r me (ProcessError "123")) assertEff "Interrupted must be returned" (case r of Interrupted (ProcessError "123") -> True _ -> False ) , testGroup "... other process" [ testCase "while it is sending" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self other <- spawn (do untilInterrupted (SendMessage @r 666666 (Dynamic.toDyn ("test"::String))) void (sendMessage me ("OK"::String)) ) void (sendInterrupt other testInterruptReason) a <- receiveMessage assertEff "" (a == ("OK"::String)) , testCase "while it is receiving" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self other <- spawn (do untilInterrupted (ReceiveSelectedMessage @r selectAnyMessageLazy) void (sendMessage me ("OK" :: String)) ) void (sendInterrupt other testInterruptReason) a <- receiveMessage assertEff "" (a == ("OK" :: String)) , testCase "while it is self'ing" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self other <- spawn (do untilInterrupted (SelfPid @r) void (sendMessage me ("OK" :: String)) ) void (sendInterrupt other (ProcessError "testError")) (a :: String) <- receiveMessage assertEff "" (a == "OK") , testCase "while it is spawning" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self other <- spawn (do untilInterrupted (Spawn @r (return ())) void (sendMessage me ("OK"::String)) ) void (sendInterrupt other testInterruptReason) a <- receiveMessage assertEff "" (a == ("OK"::String)) , testCase "while it is sending shutdown messages" $ scheduleAndAssert schedulerFactory $ \assertEff -> do me <- self other <- spawn (do untilInterrupted (SendShutdown @r 777 ExitNormally) void (sendMessage me ("OK"::String)) ) void (sendInterrupt other testInterruptReason) a <- receiveMessage assertEff "" (a == ("OK"::String)) , testCase "handleInterrupt handles my own interrupts" $ scheduleAndAssert schedulerFactory $ \assertEff -> handleInterrupts (\e -> return (ProcessError "test" == e)) (interrupt (ProcessError "test") >> return False) >>= assertEff "exception handler not invoked" ] ] linkingTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree linkingTests schedulerFactory = setTravisTestOptions (testGroup "process linking tests" [ testCase "link process with it self" $ applySchedulerFactory schedulerFactory $ do me <- self handleInterrupts (\er -> lift (False @? ("unexpected interrupt: " ++ show er))) (do linkProcess me lift (threadDelay 10000) ) , testCase "link with not running process" $ applySchedulerFactory schedulerFactory $ do let testPid = 234234234 handleInterrupts (lift . (@?= LinkedProcessCrashed testPid)) (do linkProcess testPid void (receiveMessage @Void) ) , testCase "linked process exit message is NormalExit" $ applySchedulerFactory schedulerFactory $ do foo <- spawn (void (receiveMessage @Void)) handleInterrupts (lift . (\e -> e /= LinkedProcessCrashed foo @? show e)) (do linkProcess foo sendShutdown foo ExitNormally lift (threadDelay 1000) ) , testCase "linked process exit message is Crash" $ applySchedulerFactory schedulerFactory $ do foo <- spawn (void (receiveMessage @Void)) handleInterrupts (lift . (@?= LinkedProcessCrashed foo)) (do linkProcess foo sendShutdown foo Killed void (receiveMessage @Void) ) , testCase "link multiple times" $ applySchedulerFactory schedulerFactory $ do foo <- spawn (void (receiveMessage @Void)) handleInterrupts (lift . (@?= LinkedProcessCrashed foo)) (do linkProcess foo linkProcess foo linkProcess foo linkProcess foo linkProcess foo linkProcess foo linkProcess foo sendShutdown foo Killed void (receiveMessage @Void) ) , testCase "unlink multiple times" $ applySchedulerFactory schedulerFactory $ do foo <- spawn (void (receiveMessage @Void)) handleInterrupts (lift . (False @?) . show) (do spawn_ (void receiveAnyMessage) linkProcess foo linkProcess foo linkProcess foo unlinkProcess foo unlinkProcess foo unlinkProcess foo unlinkProcess foo withMonitor foo $ \ref -> do sendShutdown foo Killed void (receiveSelectedMessage (selectProcessDown ref)) ) , testCase "spawnLink" $ applySchedulerFactory schedulerFactory $ do let foo = void (receiveMessage @Void) handleInterrupts (\er -> lift (isBecauseDown Nothing er @? show er)) $ do x <- spawnLink foo sendShutdown x Killed void (receiveMessage @Void) , testCase "ignore normal exit" $ applySchedulerFactory schedulerFactory $ do mainProc <- self let linkingServer = void $ exitOnInterrupt $ do logNotice "linker" foreverCheap $ do x <- receiveMessage linkProcess x sendMessage mainProc True linker <- spawnLink linkingServer logNotice "mainProc" do x <- spawnLink (logNotice "x 1" >> void (receiveMessage @Void)) withMonitor x $ \xRef -> do sendMessage linker x void $ receiveSelectedMessage (filterMessage id) sendShutdown x ExitNormally void (receiveSelectedMessage (selectProcessDown xRef)) do x <- spawnLink (logNotice "x 2" >> void (receiveMessage @Void)) withMonitor x $ \xRef -> do sendMessage linker x void $ receiveSelectedMessage (filterMessage id) sendShutdown x ExitNormally void (receiveSelectedMessage (selectProcessDown xRef)) handleInterrupts (lift . (LinkedProcessCrashed linker @=?)) $ do sendShutdown linker Killed void (receiveMessage @Void) , testCase "unlink" $ applySchedulerFactory schedulerFactory $ do let foo1 = void receiveAnyMessage foo2 foo1Pid = do linkProcess foo1Pid (r1, barPid) <- receiveMessage lift (("unlink foo1" :: String) @=? r1) unlinkProcess foo1Pid sendMessage barPid ("unlinked foo1" :: String, foo1Pid) receiveMessage >>= lift . (@?= ("the end":: String)) exitWithError "foo two" bar foo2Pid parentPid = do linkProcess foo2Pid me <- self sendMessage foo2Pid ("unlink foo1" :: String, me) (r1, foo1Pid) <- receiveMessage lift (("unlinked foo1" :: String) @=? r1) handleInterrupts (const (return ())) (do linkProcess foo1Pid sendShutdown foo1Pid Killed void (receiveMessage @Void) ) handleInterrupts (\er -> void (sendMessage parentPid (LinkedProcessCrashed foo2Pid == er)) ) (do sendMessage foo2Pid ("the end" :: String) void receiveAnyMessage ) foo1Pid <- spawn foo1 foo2Pid <- spawn (foo2 foo1Pid) me <- self barPid <- spawn (bar foo2Pid me) handleInterrupts (\er -> lift (LinkedProcessCrashed barPid @?= er)) (do res <- receiveMessage @Bool lift (threadDelay 100000) lift (res @?= True) ) ] ) monitoringTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree monitoringTests schedulerFactory = setTravisTestOptions (testGroup "process monitoring tests" [ testCase "monitored process not running" $ applySchedulerFactory schedulerFactory $ do let badPid = 132123 ref <- monitor badPid pd <- receiveSelectedMessage (selectProcessDown ref) lift (downReason pd @?= SomeExitReason (ProcessNotRunning badPid)) lift (threadDelay 10000) , testCase "monitored process exit normally" $ applySchedulerFactory schedulerFactory $ do target <- spawn (receiveMessage >>= exitBecause) ref <- monitor target sendMessage target ExitNormally pd <- receiveSelectedMessage (selectProcessDown ref) lift (downReason pd @?= SomeExitReason ExitNormally) lift (threadDelay 10000) , testCase "multiple monitors some demonitored" $ applySchedulerFactory schedulerFactory $ do target <- spawn (receiveMessage >>= exitBecause) ref1 <- monitor target ref2 <- monitor target ref3 <- monitor target ref4 <- monitor target ref5 <- monitor target demonitor ref3 demonitor ref5 sendMessage target ExitNormally pd1 <- receiveSelectedMessage (selectProcessDown ref1) lift (downReason pd1 @?= SomeExitReason ExitNormally) pd2 <- receiveSelectedMessage (selectProcessDown ref2) lift (downReason pd2 @?= SomeExitReason ExitNormally) pd4 <- receiveSelectedMessage (selectProcessDown ref4) lift (downReason pd4 @?= SomeExitReason ExitNormally) lift (threadDelay 10000) , testCase "monitored process killed" $ applySchedulerFactory schedulerFactory $ do target <- spawn (receiveMessage >>= exitBecause) ref <- monitor target sendMessage target Killed pd <- receiveSelectedMessage (selectProcessDown ref) lift (downReason pd @?= SomeExitReason Killed) lift (threadDelay 10000) , testCase "demonitored process killed" $ applySchedulerFactory schedulerFactory $ do target <- spawn (receiveMessage >>= exitBecause) ref <- monitor target demonitor ref sendMessage target Killed me <- self spawn_ (lift (threadDelay 10000) >> sendMessage me ()) pd <- receiveSelectedMessage (Right <$> selectProcessDown ref <|> Left <$> selectMessage @()) lift (pd @?= Left ()) lift (threadDelay 10000) ] ) timerTests :: forall r . (Lifted IO r, LogsTo IO r) => IO (Eff (InterruptableProcess r) () -> IO ()) -> TestTree timerTests schedulerFactory = setTravisTestOptions (testGroup "process timer tests" [ testCase "receiveAfter into timeout" $ applySchedulerFactory schedulerFactory $ do pd <- receiveAfter @Void 1000 lift (pd @?= Nothing) lift (threadDelay 10000) , testCase "receiveAfter no timeout" $ applySchedulerFactory schedulerFactory $ do me <- self other <- spawn (do r <- receiveMessage @() lift (r @?= ()) sendMessage me (123 :: Int) ) pd1 <- receiveAfter @() 10000 lift (pd1 @?= Nothing) sendMessage other () pd2 <- receiveAfter @Int 10000 lift (pd2 @?= Just 123) lift (threadDelay 10000) , testCase "many receiveAfters" $ applySchedulerFactory schedulerFactory $ do let n = 5 testMsg :: Float testMsg = 123 me <- self other <- spawn (do replicateM_ n $ sendMessage me ("bad message" :: String) r <- receiveMessage @() lift (r @?= ()) replicateM_ n $ sendMessage me testMsg ) receiveAfter @Float 100 >>= lift . (@?= Nothing) sendMessage other () replicateM_ n (do res <- receiveAfter @Float 10000 lift (res @?= Just testMsg) ) lift (threadDelay 100) ] )