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 (Just s) <- unwrapMessage msg :: Process (Maybe String)
129 stash res (to == pid && from == self && s == "hello there")
130 stash res (to == pid && from == self)
133 send pid "hello there"
134 res' <- liftIO $ takeMVar res
137testTraceRegistration :: TestResult Bool -> Process ()
138testTraceRegistration result = do
140 pid <- spawnLocal $ do
143 register "foobar" self
147 res <- liftIO $ newEmptyMVar
148 withFlags defaultTraceFlags { traceRegistered = traceOn } $ do
153 stash res (p == pid && s == "foobar")
161 match (\(ProcessMonitorNotification _ _ _) -> return ())
163 res' <- liftIO $ takeMVar res
166testTraceUnRegistration :: TestResult Bool -> Process ()
167testTraceUnRegistration result = do
168 pid <- spawnLocal $ do
173 register "foobar" pid
174 res <- liftIO $ newEmptyMVar
175 withFlags defaultTraceFlags { traceUnregistered = traceOn } $ do
179 MxUnRegistered p n -> do
180 stash res (p == pid && n == "foobar")
187 matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
190 res' <- liftIO $ takeMVar res
193testTraceLayering :: TestResult () -> Process ()
194testTraceLayering result = do
195 pid <- spawnLocal $ do
196 getSelfPid >>= register "foobar"
198 traceMessage ("traceMsg", 123 :: Int)
200 withFlags defaultTraceFlags {
201 traceDied = traceOnly [pid]
202 , traceRecv = traceOnly ["foobar"]
203 } $ doTest pid result
206 doTest :: ProcessId -> MVar () -> Process ()
207 doTest pid result' = do
208 -- TODO: this is pretty gross, even for a test case
209 died <- liftIO $ newEmptyMVar
213 MxProcessDied _ _ -> liftIO $ putMVar died ()
216 recv <- liftIO $ newEmptyMVar
220 MxReceived _ _ -> liftIO $ putMVar recv ()
223 user <- liftIO $ newEmptyMVar
227 MxUser _ -> liftIO $ putMVar user ()
229 (send pid () >> (liftIO $ takeMVar user))
230 ; liftIO $ takeMVar recv
232 ; liftIO $ takeMVar died
234 liftIO $ putMVar result' ()
236testRemoteTraceRelay :: TestTransport -> TestResult Bool -> Process ()
237testRemoteTraceRelay TestTransport{..} result =
238 let flags = defaultTraceFlags { traceSpawned = traceOn }
240 node2 <- liftIO $ newLocalNode testTransport initRemoteTable
241 mvNid <- liftIO $ newEmptyMVar
243 -- As well as needing node2's NodeId, we want to
244 -- redirect all its logs back here, to avoid generating
245 -- garbage on stderr for the duration of the test run.
246 -- Here we set up that relay, and then wait for a signal
247 -- that the tracer (on node1) has seen the expected
248 -- MxSpawned message, at which point we're finished
249 (Just log') <- whereis "logger"
250 pid <- liftIO $ forkProcess node2 $ do
251 logRelay <- spawnLocal $ relay log'
252 reregister "logger" logRelay
253 getSelfNode >>= stash mvNid >> (expect :: Process ())
255 nid <- liftIO $ takeMVar mvNid
257 observedPid <- liftIO $ newEmptyMVar
258 spawnedPid <- liftIO $ newEmptyMVar
259 setTraceFlagsRemote flags nid
261 withFlags defaultTraceFlags { traceSpawned = traceOn } $ do
265 MxSpawned p -> stash observedPid p >> send pid ()
267 relayPid <- startTraceRelay nid
268 liftIO $ threadDelay 1000000
269 p <- liftIO $ forkProcess node2 $ do
270 expectTimeout 1000000 :: Process (Maybe ())
274 -- Now we wait for (the outer) pid to exit. This won't happen until
275 -- our tracer has seen the trace event for `p' and sent `p' the
276 -- message it's waiting for prior to exiting
278 matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
282 relayRef <- monitor relayPid
285 matchIf (\(ProcessMonitorNotification rref' _ _) -> rref' == relayRef)
288 observed <- liftIO $ takeMVar observedPid
289 expected <- liftIO $ takeMVar spawnedPid
290 stash result (observed == expected)
291 -- and just to be polite...
292 liftIO $ closeLocalNode node2
294-- | Sets the value of an environment variable while executing the given IO
295-- computation and restores the preceeding value upon completion.
296withEnv :: String -> String -> IO a -> IO a
298 IO.bracket (fmap (lookup var) getEnvironment <* setEnv var val)
299 (maybe (unsetEnv var) (setEnv var))
302-- | Tests that one and only one interesting trace message is produced when a
303-- given action is performed. A message is considered interesting when the given
304-- function return @True@.
305testSystemLoggerMsg :: TestTransport
307 -> (a -> String -> Bool)
309testSystemLoggerMsg t action interestingMessage =
310 withEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" "yes" $
311 withEnv "DISTRIBUTED_PROCESS_TRACE_FLAGS" "pdnusrl" $ do
312 n <- newLocalNode (testTransport t) initRemoteTable
316 reregister "trace.logger" self
318 let interestingMessage' (_ :: String, msg) = interestingMessage a msg
319 -- Wait for the trace message.
320 receiveWait [ matchIf interestingMessage' $ const $ return () ]
321 -- Only one interesting message should arrive.
322 Nothing <- receiveTimeout 100000
323 [ matchIf interestingMessage' $ const $ return () ]
327-- | Tests that one and only one trace message is produced when a message is
329testSystemLoggerMxReceive :: TestTransport -> IO ()
330testSystemLoggerMxReceive t = testSystemLoggerMsg t
331 (getSelfPid >>= flip send ())
332 (\_ msg -> "MxReceived" `isPrefixOf` msg
333 -- discard traces of internal messages
334 && not (":: RegisterReply" `isSuffixOf` msg)
337-- | Tests that one and only one trace message is produced when a message is
339testSystemLoggerMxSent :: TestTransport -> IO ()
340testSystemLoggerMxSent t = testSystemLoggerMsg t
341 (getSelfPid >>= flip send ())
342 (const $ isPrefixOf "MxSent")
344-- | Tests that one and only one trace message is produced when a process dies.
345testSystemLoggerMxProcessDied :: TestTransport -> IO ()
346testSystemLoggerMxProcessDied t = testSystemLoggerMsg t
347 (spawnLocal $ return ())
348 (\pid -> isPrefixOf $ "MxProcessDied " ++ show pid)
350-- | Tests that one and only one trace message appears when a process spawns.
351testSystemLoggerMxSpawned :: TestTransport -> IO ()
352testSystemLoggerMxSpawned t = testSystemLoggerMsg t
353 (spawnLocal $ return ())
354 (\pid -> isPrefixOf $ "MxSpawned " ++ show pid)
356-- | Tests that one and only one trace message appears when a process is
358testSystemLoggerMxRegistered :: TestTransport -> IO ()
359testSystemLoggerMxRegistered t = testSystemLoggerMsg t
360 (getSelfPid >>= register "a" >> getSelfPid)
361 (\self -> isPrefixOf $ "MxRegistered " ++ show self ++ " " ++ show "a")
363-- | Tests that one and only one trace message appears when a process is
365testSystemLoggerMxUnRegistered :: TestTransport -> IO ()
366testSystemLoggerMxUnRegistered t = testSystemLoggerMsg t
367 (getSelfPid >>= register "a" >> unregister "a" >> getSelfPid)
368 (\self -> isPrefixOf $ "MxUnRegistered " ++ show self ++ " " ++ show "a")
370tests :: TestTransport -> IO [Test]
371tests testtrans@TestTransport{..} = do
372 node1 <- newLocalNode testTransport initRemoteTable
373 -- if we execute the test cases in parallel, the
374 -- various tracers will race with one another and
375 -- we'll get garbage results (or worse, deadlocks)
376 lock <- liftIO $ newMVar ()
378 testGroup "Tracing" [
379 testCase "Spawn Tracing"
380 (synchronisedAssertion
381 "expected dead process-info to be ProcessInfoNone"
382 node1 True testSpawnTracing lock)
383 , testCase "Recv Tracing (Explicit Pid)"
384 (synchronisedAssertion
385 "expected a recv trace for the supplied pid"
386 node1 True testTraceRecvExplicitPid lock)
387 , testCase "Recv Tracing (Named Pid)"
388 (synchronisedAssertion
389 "expected a recv trace for the process registered as 'foobar'"
390 node1 True testTraceRecvNamedPid lock)
391 , testCase "Trace Send(er)"
392 (synchronisedAssertion
393 "expected a 'send' trace with the requisite fields set"
394 node1 True testTraceSending lock)
395 , testCase "Trace Registration"
396 (synchronisedAssertion
397 "expected a 'registered' trace"
398 node1 True testTraceRegistration lock)
399 , testCase "Trace Unregistration"
400 (synchronisedAssertion
401 "expected an 'unregistered' trace"
402 node1 True testTraceUnRegistration lock)
403 , testCase "Trace Layering"
404 (synchronisedAssertion
406 node1 () testTraceLayering lock)
407 , testCase "Remote Trace Relay"
408 (synchronisedAssertion
410 node1 True (testRemoteTraceRelay testtrans) lock)
411 , testGroup "SystemLoggerTracer"
412 [ testCase "MxReceive" $ testSystemLoggerMxReceive testtrans
413 , testCase "MxSent" $ testSystemLoggerMxSent testtrans
414 , testCase "MxProcessDied" $ testSystemLoggerMxProcessDied testtrans
415 , testCase "MxSpawned" $ testSystemLoggerMxSpawned testtrans
416 , testCase "MxRegistered" $ testSystemLoggerMxRegistered testtrans
417 , 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