1{-# LANGUAGE DeriveDataTypeable #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
3module Control.Distributed.Process.Tests.Tracing (tests) where
5import Control.Distributed.Process.Tests.Internal.Utils
6import Network.Transport.Test (TestTransport(..))
8import Control.Applicative ((<*))
9import Control.Concurrent (threadDelay)
10import Control.Concurrent.MVar
17import Control.Distributed.Process
18import Control.Distributed.Process.Node
19import Control.Distributed.Process.Debug
20import Control.Distributed.Process.Management
23import qualified Control.Exception as IO (bracket)
24import Data.List (isPrefixOf, isSuffixOf)
26#if ! MIN_VERSION_base(4,6,0)
27import Prelude hiding (catch, log)
29import Prelude hiding ((<*))
36import Test.Framework.Providers.HUnit (testCase)
37import System.Environment (getEnvironment)
38-- These are available in System.Environment only since base 4.7
39import System.SetEnv (setEnv, unsetEnv)
42testSpawnTracing :: TestResult Bool -> Process ()
43testSpawnTracing result = do
44 setTraceFlags defaultTraceFlags {
45 traceSpawned = (Just TraceAll)
46 , traceDied = (Just TraceAll)
49 evSpawned <- liftIO $ newEmptyMVar
50 evDied <- liftIO $ newEmptyMVar
51 tracer <- startTracer $ \ev -> do
53 (MxSpawned p) -> liftIO $ putMVar evSpawned p
54 (MxProcessDied p r) -> liftIO $ putMVar evDied (p, r)
58 pid <- spawnLocal $ sendChan sp ()
61 tracedAlive <- liftIO $ takeMVar evSpawned
62 (tracedDead, tracedReason) <- liftIO $ takeMVar evDied
64 mref <- monitor tracer
65 stopTracer -- this is asynchronous, so we need to wait...
67 matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mref)
70 setTraceFlags defaultTraceFlags
71 stash result (tracedAlive == pid &&
73 tracedReason == DiedNormal)
75testTraceRecvExplicitPid :: TestResult Bool -> Process ()
76testTraceRecvExplicitPid result = do
77 res <- liftIO $ newEmptyMVar
78 pid <- spawnLocal $ do
80 expect >>= (flip sendChan) self
81 withFlags defaultTraceFlags {
82 traceRecv = traceOnly [pid]
87 (MxReceived pid' _) -> stash res (pid == pid')
92 res' <- liftIO $ takeMVar res
93 stash result (res' && (p == pid))
96testTraceRecvNamedPid :: TestResult Bool -> Process ()
97testTraceRecvNamedPid result = do
98 res <- liftIO $ newEmptyMVar
99 pid <- spawnLocal $ do
101 register "foobar" self
102 expect >>= (flip sendChan) self
103 withFlags defaultTraceFlags {
104 traceRecv = traceOnly ["foobar"]
109 (MxReceived pid' _) -> stash res (pid == pid')
114 res' <- liftIO $ takeMVar res
115 stash result (res' && (p == pid))
118testTraceSending :: TestResult Bool -> Process ()
119testTraceSending result = do
120 pid <- spawnLocal $ (expect :: Process String) >> return ()
122 res <- liftIO $ newEmptyMVar
123 withFlags defaultTraceFlags { traceSend = traceOn } $ do
127 (MxSent to from msg) -> do
128 mS <- unwrapMessage msg :: Process (Maybe String)
130 (Just s) -> do stash res (to == pid && from == self && s == "hello there")
131 stash res (to == pid && from == self)
132 _ -> die "failed state invariant, message type unmatched..."
135 send pid "hello there"
136 res' <- liftIO $ takeMVar res
139testTraceRegistration :: TestResult Bool -> Process ()
140testTraceRegistration result = do
142 pid <- spawnLocal $ do
145 register "foobar" self
149 res <- liftIO $ newEmptyMVar
150 withFlags defaultTraceFlags { traceRegistered = traceOn } $ do
155 stash res (p == pid && s == "foobar")
163 match (\(ProcessMonitorNotification _ _ _) -> return ())
165 res' <- liftIO $ takeMVar res
168testTraceUnRegistration :: TestResult Bool -> Process ()
169testTraceUnRegistration result = do
170 pid <- spawnLocal $ do
175 register "foobar" pid
176 res <- liftIO $ newEmptyMVar
177 withFlags defaultTraceFlags { traceUnregistered = traceOn } $ do
181 MxUnRegistered p n -> do
182 stash res (p == pid && n == "foobar")
189 matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
192 res' <- liftIO $ takeMVar res
195testTraceLayering :: TestResult () -> Process ()
196testTraceLayering result = do
197 pid <- spawnLocal $ do
198 getSelfPid >>= register "foobar"
200 traceMessage ("traceMsg", 123 :: Int)
202 withFlags defaultTraceFlags {
203 traceDied = traceOnly [pid]
204 , traceRecv = traceOnly ["foobar"]
205 } $ doTest pid result
208 doTest :: ProcessId -> MVar () -> Process ()
209 doTest pid result' = do
210 -- TODO: this is pretty gross, even for a test case
211 died <- liftIO $ newEmptyMVar
215 MxProcessDied _ _ -> liftIO $ putMVar died ()
218 recv <- liftIO $ newEmptyMVar
222 MxReceived _ _ -> liftIO $ putMVar recv ()
225 user <- liftIO $ newEmptyMVar
229 MxUser _ -> liftIO $ putMVar user ()
231 (send pid () >> (liftIO $ takeMVar user))
232 ; liftIO $ takeMVar recv
234 ; liftIO $ takeMVar died
236 liftIO $ putMVar result' ()
238testRemoteTraceRelay :: TestTransport -> TestResult Bool -> Process ()
239testRemoteTraceRelay TestTransport{..} result =
240 let flags = defaultTraceFlags { traceSpawned = traceOn }
242 node2 <- liftIO $ newLocalNode testTransport initRemoteTable
243 mvNid <- liftIO $ newEmptyMVar
245 -- As well as needing node2's NodeId, we want to
246 -- redirect all its logs back here, to avoid generating
247 -- garbage on stderr for the duration of the test run.
248 -- Here we set up that relay, and then wait for a signal
249 -- that the tracer (on node1) has seen the expected
250 -- MxSpawned message, at which point we're finished
252 pid <- splinchLogger node2 mvNid
253 nid <- liftIO $ takeMVar mvNid
255 observedPid <- liftIO $ newEmptyMVar
256 spawnedPid <- liftIO $ newEmptyMVar
257 setTraceFlagsRemote flags nid
259 withFlags defaultTraceFlags { traceSpawned = traceOn } $ do
263 MxSpawned p -> stash observedPid p >> send pid ()
265 relayPid <- startTraceRelay nid
266 liftIO $ threadDelay 1000000
267 p <- liftIO $ forkProcess node2 $ do
268 expectTimeout 1000000 :: Process (Maybe ())
272 -- Now we wait for (the outer) pid to exit. This won't happen until
273 -- our tracer has seen the trace event for `p' and sent `p' the
274 -- message it's waiting for prior to exiting
276 matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
280 relayRef <- monitor relayPid
283 matchIf (\(ProcessMonitorNotification rref' _ _) -> rref' == relayRef)
286 observed <- liftIO $ takeMVar observedPid
287 expected <- liftIO $ takeMVar spawnedPid
288 stash result (observed == expected)
289 -- and just to be polite...
290 liftIO $ closeLocalNode node2
293 splinchLogger n2 mv = do
294 mLog <- whereis "logger"
296 Nothing -> die "no logger registered"
297 Just log' -> do liftIO $ forkProcess n2 $ do
298 logRelay <- spawnLocal $ relay log'
299 reregister "logger" logRelay
300 getSelfNode >>= stash mv >> (expect :: Process ())
303-- | Sets the value of an environment variable while executing the given IO
304-- computation and restores the preceeding value upon completion.
305withEnv :: String -> String -> IO a -> IO a
307 IO.bracket (fmap (lookup var) getEnvironment <* setEnv var val)
308 (maybe (unsetEnv var) (setEnv var))
311-- | Tests that one and only one interesting trace message is produced when a
312-- given action is performed. A message is considered interesting when the given
313-- function return @True@.
314testSystemLoggerMsg :: TestTransport
316 -> (a -> String -> Bool)
318testSystemLoggerMsg t action interestingMessage =
319 withEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" "yes" $
320 withEnv "DISTRIBUTED_PROCESS_TRACE_FLAGS" "pdnusrl" $ do
321 n <- newLocalNode (testTransport t) initRemoteTable
325 reregister "trace.logger" self
327 let interestingMessage' (_ :: String, msg) = interestingMessage a msg
328 -- Wait for the trace message.
329 receiveWait [ matchIf interestingMessage' $ const $ return () ]
330 -- Only one interesting message should arrive.
331 expectedTimeout <- receiveTimeout
333 [ matchIf interestingMessage' $ const $ return () ]
334 case expectedTimeout of
336 Just _ -> die "Unexpected message arrived..."
339-- | Tests that one and only one trace message is produced when a message is
341testSystemLoggerMxReceive :: TestTransport -> IO ()
342testSystemLoggerMxReceive t = testSystemLoggerMsg t
343 (getSelfPid >>= flip send ())
344 (\_ msg -> "MxReceived" `isPrefixOf` msg
345 -- discard traces of internal messages
346 && not (":: RegisterReply" `isSuffixOf` msg)
349-- | Tests that one and only one trace message is produced when a message is
351testSystemLoggerMxSent :: TestTransport -> IO ()
352testSystemLoggerMxSent t = testSystemLoggerMsg t
353 (getSelfPid >>= flip send ())
354 (const $ isPrefixOf "MxSent")
356-- | Tests that one and only one trace message is produced when a process dies.
357testSystemLoggerMxProcessDied :: TestTransport -> IO ()
358testSystemLoggerMxProcessDied t = testSystemLoggerMsg t
359 (spawnLocal $ return ())
360 (\pid -> isPrefixOf $ "MxProcessDied " ++ show pid)
362-- | Tests that one and only one trace message appears when a process spawns.
363testSystemLoggerMxSpawned :: TestTransport -> IO ()
364testSystemLoggerMxSpawned t = testSystemLoggerMsg t
365 (spawnLocal $ return ())
366 (\pid -> isPrefixOf $ "MxSpawned " ++ show pid)
368-- | Tests that one and only one trace message appears when a process is
370testSystemLoggerMxRegistered :: TestTransport -> IO ()
371testSystemLoggerMxRegistered t = testSystemLoggerMsg t
372 (getSelfPid >>= register "a" >> getSelfPid)
373 (\self -> isPrefixOf $ "MxRegistered " ++ show self ++ " " ++ show "a")
375-- | Tests that one and only one trace message appears when a process is
377testSystemLoggerMxUnRegistered :: TestTransport -> IO ()
378testSystemLoggerMxUnRegistered t = testSystemLoggerMsg t
379 (getSelfPid >>= register "a" >> unregister "a" >> getSelfPid)
380 (\self -> isPrefixOf $ "MxUnRegistered " ++ show self ++ " " ++ show "a")
382tests :: TestTransport -> IO [Test]
383tests testtrans@TestTransport{..} = do
384 node1 <- newLocalNode testTransport initRemoteTable
385 -- if we execute the test cases in parallel, the
386 -- various tracers will race with one another and
387 -- we'll get garbage results (or worse, deadlocks)
388 lock <- liftIO $ newMVar ()
390 testGroup "Tracing" [
391 testCase "Spawn Tracing"
392 (synchronisedAssertion
393 "expected dead process-info to be ProcessInfoNone"
394 node1 True testSpawnTracing lock)
395 , testCase "Recv Tracing (Explicit Pid)"
396 (synchronisedAssertion
397 "expected a recv trace for the supplied pid"
398 node1 True testTraceRecvExplicitPid lock)
399 , testCase "Recv Tracing (Named Pid)"
400 (synchronisedAssertion
401 "expected a recv trace for the process registered as 'foobar'"
402 node1 True testTraceRecvNamedPid lock)
403 , testCase "Trace Send(er)"
404 (synchronisedAssertion
405 "expected a 'send' trace with the requisite fields set"
406 node1 True testTraceSending lock)
407 , testCase "Trace Registration"
408 (synchronisedAssertion
409 "expected a 'registered' trace"
410 node1 True testTraceRegistration lock)
411 , testCase "Trace Unregistration"
412 (synchronisedAssertion
413 "expected an 'unregistered' trace"
414 node1 True testTraceUnRegistration lock)
415 , testCase "Trace Layering"
416 (synchronisedAssertion
418 node1 () testTraceLayering lock)
419 , testCase "Remote Trace Relay"
420 (synchronisedAssertion
422 node1 True (testRemoteTraceRelay testtrans) lock)
423 , testGroup "SystemLoggerTracer"
424 [ testCase "MxReceive" $ testSystemLoggerMxReceive testtrans
425 , testCase "MxSent" $ testSystemLoggerMxSent testtrans
426 , testCase "MxProcessDied" $ testSystemLoggerMxProcessDied testtrans
427 , testCase "MxSpawned" $ testSystemLoggerMxSpawned testtrans
428 , testCase "MxRegistered" $ testSystemLoggerMxRegistered testtrans
429 , testCase "MxUnRegistered" $ testSystemLoggerMxUnRegistered testtrans
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list