{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module : Control.Distributed.Process.Platform.Test -- Copyright : (c) Tim Watson, Jeff Epstein 2013 -- License : BSD3 (see the file LICENSE) -- -- Maintainer : Tim Watson -- Stability : experimental -- Portability : non-portable (requires concurrency) -- -- This module provides basic building blocks for testing Cloud Haskell programs. ----------------------------------------------------------------------------- module Control.Distributed.Process.Platform.Test ( TestResult , noop , stash -- ping ! , Ping(Ping) , ping -- test process utilities , TestProcessControl , startTestProcess , runTestProcess , testProcessGo , testProcessStop , testProcessReport -- runners , tryRunProcess , tryForkProcess ) where import Control.Concurrent ( myThreadId , throwTo ) import Control.Concurrent.MVar ( MVar , putMVar ) import Control.DeepSeq (NFData) import Control.Distributed.Process import Control.Distributed.Process.Node import Control.Distributed.Process.Serializable() import Control.Exception (SomeException) import Data.Binary import Data.Typeable (Typeable) #if ! MIN_VERSION_base(4,6,0) import Prelude hiding (catch) #endif import GHC.Generics -- | A mutable cell containing a test result. type TestResult a = MVar a -- | A simple @Ping@ signal data Ping = Ping deriving (Typeable, Generic, Eq, Show) instance Binary Ping where instance NFData Ping where ping :: ProcessId -> Process () ping pid = send pid Ping -- | Control signals used to manage /test processes/ data TestProcessControl = Stop | Go | Report ProcessId deriving (Typeable, Generic) instance Binary TestProcessControl where -- | Starts a test process on the local node. startTestProcess :: Process () -> Process ProcessId startTestProcess proc = spawnLocal $ do getSelfPid >>= register "test-process" runTestProcess proc -- | Runs a /test process/ around the supplied @proc@, which is executed -- whenever the outer process loop receives a 'Go' signal. runTestProcess :: Process () -> Process () runTestProcess proc = do ctl <- expect case ctl of Stop -> return () Go -> proc >> runTestProcess proc Report p -> receiveWait [matchAny (\m -> forward m p)] >> runTestProcess proc -- | Tell a /test process/ to continue executing testProcessGo :: ProcessId -> Process () testProcessGo pid = send pid Go -- | Tell a /test process/ to stop (i.e., 'terminate') testProcessStop :: ProcessId -> Process () testProcessStop pid = send pid Stop -- | Tell a /test process/ to send a report (message) -- back to the calling process testProcessReport :: ProcessId -> Process () testProcessReport pid = do self <- getSelfPid send pid $ Report self -- | Does exactly what it says on the tin, doing so in the @Process@ monad. noop :: Process () noop = return () -- | Stashes a value in our 'TestResult' using @putMVar@ stash :: TestResult a -> a -> Process () stash mvar x = liftIO $ putMVar mvar x tryRunProcess :: LocalNode -> Process () -> IO () tryRunProcess node p = do tid <- liftIO myThreadId runProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException)) tryForkProcess :: LocalNode -> Process () -> IO ProcessId tryForkProcess node p = do tid <- liftIO myThreadId forkProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))