{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE ScopedTypeVariables #-} module Main where -- import Control.Exception (SomeException) import Control.Concurrent.MVar (MVar, newMVar, takeMVar, putMVar, newEmptyMVar) import Control.Concurrent.STM (atomically) import Control.Concurrent.STM.TChan import Control.Distributed.Process hiding (monitor) import Control.Distributed.Process.Closure (remotable, mkStaticClosure) import Control.Distributed.Process.Node import Control.Distributed.Process.Extras hiding (__remoteTable) import qualified Control.Distributed.Process.Extras.SystemLog as Log (Logger, error) import Control.Distributed.Process.Extras.SystemLog hiding (Logger, error) import Control.Distributed.Process.Tests.Internal.Utils import Control.Distributed.Process.Extras.Time import Control.Distributed.Process.Extras.Timer import Control.Monad (void) import Data.List (delete) #if ! MIN_VERSION_base(4,6,0) import Prelude hiding (catch, drop, Read) #else import Prelude hiding (drop, read, Read) #endif import Test.Framework (Test, testGroup, defaultMain) import Test.Framework.Providers.HUnit (testCase) import Network.Transport.TCP import qualified Network.Transport as NT import GHC.Read import Text.ParserCombinators.ReadP as P import Text.ParserCombinators.ReadPrec import qualified Network.Transport as NT logLevelFormatter :: Message -> Process (Maybe String) logLevelFormatter m = handleMessage m showLevel where showLevel :: LogLevel -> Process String showLevel = return . show $(remotable ['logLevelFormatter]) logFormat :: Closure (Message -> Process (Maybe String)) logFormat = $(mkStaticClosure 'logLevelFormatter) testLoggingProcess :: Process (ProcessId, TChan String) testLoggingProcess = do chan <- liftIO $ newTChanIO let cleanup = return () let format = return pid <- systemLog (writeLog chan) cleanup Debug format addFormatter pid logFormat sleep $ seconds 1 return (pid, chan) where writeLog chan = liftIO . atomically . writeTChan chan testLogLevels :: (Log.Logger logger, ToLog tL) => MVar () -> TChan String -> logger -> LogLevel -> LogLevel -> (LogLevel -> tL) -> TestResult Bool -> Process () testLogLevels lck chan logger from to fn result = do void $ liftIO $ takeMVar lck let lvls = enumFromTo from to logIt logger fn lvls testHarness lvls chan result liftIO $ putMVar lck () where logIt _ _ [] = return () logIt lc f (l:ls) = sendLog lc (f l) l >> logIt lc f ls testHarness :: [LogLevel] -> TChan String -> TestResult Bool -> Process () testHarness [] chan result = do liftIO (atomically (isEmptyTChan chan)) >>= stash result testHarness levels chan result = do msg <- liftIO $ atomically $ readTChan chan -- liftIO $ putStrLn $ "testHarness handling " ++ msg let item = readEither msg case item of Right i -> testHarness (delete i levels) chan result Left _ -> testHarness levels chan result where readEither :: String -> Either String LogLevel readEither s = case [ x | (x,"") <- readPrec_to_S read' minPrec s ] of [x] -> Right x _ -> Left "read: ambiguous parse" read' = do x <- readPrec lift P.skipSpaces return x tests :: NT.Transport -> IO [Test] tests transport = do let ch = logChannel localNode <- newLocalNode transport $ __remoteTable initRemoteTable lock <- newMVar () ex <- newEmptyMVar void $ forkProcess localNode $ do (_, chan) <- testLoggingProcess liftIO $ putMVar ex chan chan <- takeMVar ex return [ testGroup "Log Reports / LogText" (map (mkTestCase lock chan ch simpleShowToLog localNode) (enumFromTo Debug Emergency)) , testGroup "Logging Raw Messages" (map (mkTestCase lock chan ch messageToLog localNode) (enumFromTo Debug Emergency)) , testGroup "Custom Formatters" (map (mkTestCase lock chan ch messageRaw localNode) (enumFromTo Debug Emergency)) ] where mkTestCase lck chan ch' rdr ln lvl = do let l = show lvl testCase l (delayedAssertion ("Expected up to " ++ l) ln True $ testLogLevels lck chan ch' Debug lvl rdr) simpleShowToLog = (LogText . show) messageToLog = unsafeWrapMessage . show messageRaw = unsafeWrapMessage -- | Given a @builder@ function, make and run a test suite on a single transport testMain :: (NT.Transport -> IO [Test]) -> IO () testMain builder = do Right (transport, _) <- createTransportExposeInternals "127.0.0.1" "0" defaultTCPParameters testData <- builder transport defaultMain testData main :: IO () main = testMain $ tests