2015-01-23 18:11:37 +03:00
|
|
|
-- Modification (to introduce bug) of an example in Parallel and
|
|
|
|
-- Concurrent Programming in Haskell, chapter 7.
|
|
|
|
module Tests.Logger
|
|
|
|
( Logger
|
|
|
|
, initLogger
|
|
|
|
, logMessage
|
|
|
|
, logStop
|
|
|
|
, bad
|
|
|
|
, testLA, testLP, testLE
|
|
|
|
) where
|
|
|
|
|
2015-01-28 20:48:26 +03:00
|
|
|
import Control.Concurrent.CVar
|
2015-01-23 18:11:37 +03:00
|
|
|
import Control.Monad.Conc.Class
|
|
|
|
import Control.Monad.Conc.SCT.Tests
|
|
|
|
|
2015-01-28 15:10:19 +03:00
|
|
|
data Logger m = Logger (CVar m LogCommand) (CVar m [String])
|
2015-01-23 18:11:37 +03:00
|
|
|
|
|
|
|
data LogCommand = Message String | Stop
|
|
|
|
|
|
|
|
-- | Create a new logger with no internal log.
|
2015-01-28 15:10:19 +03:00
|
|
|
initLogger :: MonadConc m => m (Logger m)
|
2015-01-23 18:11:37 +03:00
|
|
|
initLogger = do
|
|
|
|
cmd <- newEmptyCVar
|
|
|
|
log <- newCVar []
|
|
|
|
let l = Logger cmd log
|
|
|
|
fork $ logger l
|
|
|
|
return l
|
|
|
|
|
2015-01-28 15:10:19 +03:00
|
|
|
logger :: MonadConc m => Logger m -> m ()
|
2015-01-23 18:11:37 +03:00
|
|
|
logger (Logger cmd log) = loop where
|
|
|
|
loop = do
|
|
|
|
command <- takeCVar cmd
|
|
|
|
case command of
|
|
|
|
Message str -> do
|
|
|
|
strs <- takeCVar log
|
|
|
|
putCVar log (strs ++ [str])
|
|
|
|
loop
|
|
|
|
Stop -> return ()
|
|
|
|
|
|
|
|
-- | Add a string to the log.
|
2015-01-28 15:10:19 +03:00
|
|
|
logMessage :: MonadConc m => Logger m -> String -> m ()
|
2015-01-23 18:11:37 +03:00
|
|
|
logMessage (Logger cmd _) str = putCVar cmd $ Message str
|
|
|
|
|
|
|
|
-- | Stop the logger and return the contents of the log.
|
2015-01-28 15:10:19 +03:00
|
|
|
logStop :: MonadConc m => Logger m -> m [String]
|
2015-01-23 18:11:37 +03:00
|
|
|
logStop (Logger cmd log) = do
|
|
|
|
putCVar cmd Stop
|
|
|
|
readCVar log
|
|
|
|
|
|
|
|
-- | Race condition! Can you see where?
|
2015-01-28 15:10:19 +03:00
|
|
|
bad :: MonadConc m => m [String]
|
2015-01-23 18:11:37 +03:00
|
|
|
bad = do
|
|
|
|
l <- initLogger
|
|
|
|
logMessage l "Hello"
|
|
|
|
logMessage l "World"
|
|
|
|
logMessage l "Foo"
|
|
|
|
logMessage l "Bar"
|
|
|
|
logMessage l "Baz"
|
|
|
|
logStop l
|
|
|
|
|
|
|
|
-- | Test that the result is always in the set of allowed values, and
|
|
|
|
-- doesn't deadlock.
|
2015-01-23 19:48:38 +03:00
|
|
|
testLA :: Result [String]
|
2015-01-23 18:11:37 +03:00
|
|
|
testLA = runTest (alwaysTrue listContents) bad where
|
|
|
|
listContents (Just strs) = strs `elem` [ ["Hello", "World", "Foo", "Bar", "Baz"]
|
|
|
|
, ["Hello", "World", "Foo", "Bar"]
|
|
|
|
]
|
|
|
|
listContents Nothing = False
|
|
|
|
|
|
|
|
-- | Test that the "proper" result occurs at least once.
|
2015-01-23 19:48:38 +03:00
|
|
|
testLP :: Result [String]
|
2015-01-23 18:11:37 +03:00
|
|
|
testLP = runTest (somewhereTrue loggedAll) bad where
|
|
|
|
loggedAll (Just a) = length a == 5
|
|
|
|
loggedAll Nothing = False
|
|
|
|
|
|
|
|
-- | Test that the erroneous result occurs at least once.
|
2015-01-23 19:48:38 +03:00
|
|
|
testLE :: Result [String]
|
2015-01-23 18:11:37 +03:00
|
|
|
testLE = runTest (somewhereTrue loggedAlmostAll) bad where
|
|
|
|
loggedAlmostAll (Just a) = length a == 4
|
|
|
|
loggedAlmostAll Nothing = False
|