git.iko.soy/distributed-process
on master help sign in
Improved tracing
ebec48fEdsko de Vries
network-transport-2/tests/TestTCP.hs+19−26
@@ -11,7 +11,7 @@ import Data.Int (Int32)
11import Data.Maybe (fromJust)
11import Data.Maybe (fromJust)
12import Control.Concurrent (threadDelay)
12import Control.Concurrent (threadDelay)
13import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar, readMVar, isEmptyMVar)
13import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar, readMVar, isEmptyMVar)
14−import Control.Monad (replicateM)
14+import Control.Monad (replicateM, guard)
15import Control.Applicative ((<$>))
15import Control.Applicative ((<$>))
16import Network.Transport.TCP ( decodeEndPointAddress
16import Network.Transport.TCP ( decodeEndPointAddress
17 , EndPointId
17 , EndPointId
@@ -49,9 +49,6 @@ instance Show Transport where
49instance Show Connection where
49instance Show Connection where
50 show _ = "<<connection>>"
50 show _ = "<<connection>>"
51
51
52−trlog :: String -> Traced ()
53−trlog = liftIO . tlog
54−
55-- Test that the server gets a ConnectionClosed message when the client closes
52-- Test that the server gets a ConnectionClosed message when the client closes
56-- the socket without sending an explicit control message to the server first
53-- the socket without sending an explicit control message to the server first
57testEarlyDisconnect :: IO ()
54testEarlyDisconnect :: IO ()
@@ -74,22 +71,18 @@ testEarlyDisconnect = do
74 putMVar serverAddr (fromJust . decodeEndPointAddress . address $ endpoint)
71 putMVar serverAddr (fromJust . decodeEndPointAddress . address $ endpoint)
75 theirAddr <- readMVar clientAddr
72 theirAddr <- readMVar clientAddr
76
73
77− runTraced $ do
78− -- First test: they connect to us, then drop the connection
74+ -- First test: they connect to us, then drop the connection
79− ConnectionOpened cid _ addr <- liftIO $ receive endpoint
75+ ConnectionOpened cid _ addr <- receive endpoint
80− True <- return $ addr == theirAddr
76+ True <- return $ addr == theirAddr
81−
77+
82− ErrorEvent (ErrorEventConnectionLost addr' [cid']) <- liftIO $ receive endpoint
78+ ErrorEvent (ErrorEventConnectionLost addr' [cid']) <- receive endpoint
83− True <- return $ addr' == theirAddr && cid' == cid
79+ True <- return $ addr' == theirAddr && cid' == cid
84−
85− -- Second test: after they dropped their connection to us, we now try to
86− -- establish a connection to them. This should re-establish the broken
87− -- TCP connection.
88− trlog "Trying to connect to client"
89− Right _ <- liftIO $ connect endpoint theirAddr ReliableOrdered
90−
91
80
92− return ()
81+ -- Second test: after they dropped their connection to us, we now try to
82+ -- establish a connection to them. This should re-establish the broken
83+ -- TCP connection.
84+ tlog "Trying to connect to client"
85+ Right _ <- connect endpoint theirAddr ReliableOrdered
93
86
94 putMVar serverDone ()
87 putMVar serverDone ()
95
88
@@ -145,24 +138,24 @@ testInvalidAddress = do
145
138
146-- | Test connecting to invalid or non-existing endpoints
139-- | Test connecting to invalid or non-existing endpoints
147testInvalidConnect :: IO ()
140testInvalidConnect :: IO ()
148−testInvalidConnect = runTraced $ do
141+testInvalidConnect = do
149− Right transport <- liftIO $ createTransport "127.0.0.1" "8083"
142+ Right transport <- createTransport "127.0.0.1" "8083"
150− Right endpoint <- liftIO $ newEndPoint transport
143+ Right endpoint <- newEndPoint transport
151
144
152 -- Syntax error in the endpoint address
145 -- Syntax error in the endpoint address
153− Left (FailedWith ConnectInvalidAddress _) <- liftIO $
146+ Left (FailedWith ConnectInvalidAddress _) <-
154 connect endpoint (EndPointAddress "InvalidAddress") ReliableOrdered
147 connect endpoint (EndPointAddress "InvalidAddress") ReliableOrdered
155
148
156 -- Syntax connect, but invalid hostname (TCP address lookup failure)
149 -- Syntax connect, but invalid hostname (TCP address lookup failure)
157− Left (FailedWith ConnectInvalidAddress _) <- liftIO $
150+ Left (FailedWith ConnectInvalidAddress _) <-
158 connect endpoint (EndPointAddress "invalidHost:port:0") ReliableOrdered
151 connect endpoint (EndPointAddress "invalidHost:port:0") ReliableOrdered
159
152
160 -- TCP address correct, but nobody home at that address
153 -- TCP address correct, but nobody home at that address
161− Left (FailedWith ConnectFailed _) <- liftIO $
154+ Left (FailedWith ConnectFailed _) <-
162 connect endpoint (EndPointAddress "127.0.0.1:9000:0") ReliableOrdered
155 connect endpoint (EndPointAddress "127.0.0.1:9000:0") ReliableOrdered
163
156
164 -- Valid TCP address but invalid endpoint number
157 -- Valid TCP address but invalid endpoint number
165− Left (FailedWith ConnectInvalidAddress _) <- liftIO $
158+ Left (FailedWith ConnectInvalidAddress _) <-
166 connect endpoint (EndPointAddress "127.0.0.1:8083:1") ReliableOrdered
159 connect endpoint (EndPointAddress "127.0.0.1:8083:1") ReliableOrdered
167
160
168 return ()
161 return ()
network-transport-2/tests/Traced.hs+70−123
@@ -1,161 +1,108 @@
1−{-# LANGUAGE RebindableSyntax #-}
1+{-# LANGUAGE RebindableSyntax, FlexibleInstances, UndecidableInstances, ScopedTypeVariables, DeriveDataTypeable #-}
2−-- | Monad to trace all terms that get bound
2+-- | Add "tracing" to the IO monad (see examples).
3--
3--
4−-- Examples:
4+-- [Usage]
5−--
5+--
6−-- > test1 :: IO Int
6+-- > {-# LANGUAGE RebindableSyntax #-}
7−-- > test1 = runTraced $ do
7+-- > import Prelude hiding (catch, (>>=), (>>), return, fail)
8−-- > Left x <- return (Left 1 :: Either Int Int)
8+-- > import Traced
9−-- > Left y <- return (Left 2 :: Either Int Int)
10−-- > return (x + y)
11−--
12−-- outputs
13−--
14−-- > 3
15--
9--
16−-- as expected. Things get more interesting when we have a pattern match failure:
10+-- [Example]
17--
11--
18−-- > test2 :: IO Int
12+-- > test1 :: IO Int
19−-- > test2 = runTraced $ do
13+-- > test1 = do
20-- > Left x <- return (Left 1 :: Either Int Int)
14-- > Left x <- return (Left 1 :: Either Int Int)
15+-- > putStrLn "Hello world"
21-- > Right y <- return (Left 2 :: Either Int Int)
16-- > Right y <- return (Left 2 :: Either Int Int)
22-- > return (x + y)
17-- > return (x + y)
23--
18--
24−-- outputs
19+-- outputs
25−--
26−-- > *** Exception: user error (Pattern match failure in do expression at Traced.hs:96:3-9
27−-- > Trace:
28−-- > Left 1
29−-- > Left 2
30−-- > )
31−--
32−-- The module also exports a 'guard' function which is used in similar fashion
33−-- to the 'guard' from "Control.Monad":
34−--
35−-- > test3 :: IO Int
36−-- > test3 = runTraced $ do
37−-- > Left x <- return (Left 1 :: Either Int Int)
38−-- > Left y <- return (Left 2 :: Either Int Int)
39−-- > guard (x == 3)
40−-- > return (x + y)
41−--
42−-- outputs
43--
20--
44−-- > *** Exception: user error (Guard failed
21+-- > Hello world
22+-- > *** Exception: user error (Pattern match failure in do expression at tests/Traced.hs:107:3-9)
45-- > Trace:
23-- > Trace:
46−-- > Left 1
47−-- > Left 2
24+-- > 0 Left 2
48−-- > )
25+-- > 1 Left 1
49−--
50−-- Note that we get no line-number info this way; we can fix this by saying
51−--
52−-- > test4 :: IO Int
53−-- > test4 = runTraced $ do
54−-- > Left x <- return (Left 1 :: Either Int Int)
55−-- > Left y <- return (Left 2 :: Either Int Int)
56−-- > True <- return (x == 3)
57−-- > return (x + y)
58−--
59−-- which outputs
60--
26--
61−-- > *** Exception: user error (Pattern match failure in do expression at Traced.hs:110:3-6
27+-- [Guards]
62−-- > Trace:
63−-- > Left 1
64−-- > Left 2
65−-- > False
66−-- > )
67--
28--
68−-- The module also exports a 'liftIO', and does not trace ignored unit values (from '>>'):
29+-- Use the following idiom instead of using 'Control.Monad.guard':
69--
30--
70−-- > test5 :: IO Int
31+-- > test2 :: IO Int
71−-- > test5 = runTraced $ do
32+-- > test2 = do
72-- > Left x <- return (Left 1 :: Either Int Int)
33-- > Left x <- return (Left 1 :: Either Int Int)
73−-- > liftIO $ putStrLn "hi"
74−-- > Left y <- return (Left 2 :: Either Int Int)
75−-- > True <- return (y == 1)
34+-- > True <- return (x == 3)
76−-- > return (x + y)
35+-- > return x
77−--
78−-- outputs
79--
36--
80−-- > hi
37+-- The advantage of this idiom is that it gives you line number information when the guard fails:
81−-- > *** Exception: user error (Pattern match failure in do expression at Traced.hs:137:3-6
82−-- > Trace:
83−-- > Left 1
84−-- > Left 2
85−-- > False
86−-- > )
87--
38--
88−-- Every Monad is also a MonadS, but not vice versa.
39+-- > *Traced> test3
89−
40+-- > *** Exception: user error (Pattern match failure in do expression at tests/Traced.hs:103:3-6)
90−module Traced ( -- Subclass of monads where bind is defined only for showable arguments
41+-- > Trace:
42+-- > 0 False
43+-- > 1 Left 1
91− MonadS(..)
44+module Traced ( MonadS(..)
92− , return
45+ , return
93− , (>>=)
46+ , (>>=)
94− , (>>)
47+ , (>>)
95− , fail
48+ , fail
96− , guard
97− -- MonadS instance that shows a trace of all bound terms when an error occurs
98− , Traced
99− , runTraced
100− , liftIO
101− ) where
49+ ) where
102
50
103import Prelude hiding ((>>=), return, fail, catch, (>>))
51import Prelude hiding ((>>=), return, fail, catch, (>>))
104import qualified Prelude
52import qualified Prelude
105import Data.String (fromString)
53import Data.String (fromString)
54+import Control.Exception (catches, Handler(..), SomeException, throw, Exception(..))
55+import Data.Typeable (Typeable)
106
56
57+--------------------------------------------------------------------------------
58+-- MonadS class --
59+--------------------------------------------------------------------------------
60+
61+-- | Like 'Monad' but bind is only defined for 'Showable' instances
107class MonadS m where
62class MonadS m where
108 returnS :: a -> m a
63 returnS :: a -> m a
109 bindS :: Show a => m a -> (a -> m b) -> m b
64 bindS :: Show a => m a -> (a -> m b) -> m b
110 failS :: String -> m a
65 failS :: String -> m a
111 seqS :: m a -> m b -> m b
66 seqS :: m a -> m b -> m b
112
67
113−instance MonadS IO where
68+-- | Redefinition of 'Prelude.>>='
114− returnS = Prelude.return
115− bindS = (Prelude.>>=)
116− failS = Prelude.fail
117− seqS = (Prelude.>>)
118−
119−newtype Traced a = Traced { unTraced :: IO ([String], Either String a) }
120−
121−instance MonadS Traced where
122− returnS x = Traced $ Prelude.return ([], Right x)
123− x `bindS` f = Traced $ unTraced x Prelude.>>= \(trace, result) ->
124− case result of
125− Left err -> Prelude.return (trace, Left err)
126− Right a -> unTraced (f a) Prelude.>>= \(trace', result') ->
127− Prelude.return (trace ++ show a : trace', result')
128− x `seqS` y = Traced $ unTraced x Prelude.>>= \(trace, result) ->
129− case result of
130− Left err -> Prelude.return (trace, Left err)
131− Right _ -> unTraced y Prelude.>>= \(trace', result') ->
132− Prelude.return (trace ++ trace', result')
133− failS err = Traced $ Prelude.return ([], Left err)
134−
135−runTraced :: Traced a -> IO a
136−runTraced (Traced x) = x Prelude.>>= \(trace, ma) ->
137− case ma of
138− Left err -> Prelude.fail $ err ++ "\nTrace:\n" ++ showTrace trace
139− Right a -> Prelude.return a
140−
141−showTrace :: [String] -> String
142−showTrace = unlines . map (\xs -> ' ' : ' ' : xs)
143−
144−guard :: MonadS m => Bool -> m ()
145−guard True = returnS ()
146−guard False = failS "Guard failed"
147−
148(>>=) :: (MonadS m, Show a) => m a -> (a -> m b) -> m b
69(>>=) :: (MonadS m, Show a) => m a -> (a -> m b) -> m b
149(>>=) = bindS
70(>>=) = bindS
150
71
72+-- | Redefinition of 'Prelude.>>'
151(>>) :: (MonadS m, Show a) => m a -> m b -> m b
73(>>) :: (MonadS m, Show a) => m a -> m b -> m b
152(>>) = seqS
74(>>) = seqS
153
75
76+-- | Redefinition of 'Prelude.return'
154return :: MonadS m => a -> m a
77return :: MonadS m => a -> m a
155return = returnS
78return = returnS
156
79
80+-- | Redefinition of 'Prelude.fail'
157fail :: MonadS m => String -> m a
81fail :: MonadS m => String -> m a
158fail = failS
82fail = failS
159
83
160−liftIO :: IO a -> Traced a
84+--------------------------------------------------------------------------------
161−liftIO x = Traced $ x Prelude.>>= \a -> Prelude.return ([], Right a)
85+-- IO instance for MonadS --
86+--------------------------------------------------------------------------------
87+
88+data TracedException = TracedException [String] SomeException
89+ deriving Typeable
90+
91+instance Exception TracedException
92+
93+-- | Add tracing to 'IO' (see examples)
94+instance MonadS IO where
95+ returnS = Prelude.return
96+ bindS = \x f -> x Prelude.>>= \a -> catches (f a) (traceHandlers a)
97+ failS = Prelude.fail
98+ seqS = (Prelude.>>)
99+
100+instance Show TracedException where
101+ show (TracedException trace ex) =
102+ show ex ++ "\nTrace:\n" ++ unlines (map (\(i, t) -> show i ++ "\t" ++ t) (zip ([0..] :: [Int]) (reverse trace)))
103+
104+traceHandlers :: Show a => a -> [Handler b]
105+traceHandlers a =
106+ [ Handler $ \(TracedException trace ex) -> throw $ TracedException (show a : trace) ex
107+ , Handler $ \(ex :: SomeException) -> throw $ TracedException [show a] ex
108+ ]