Skip to content

Commit 446623c

Browse files
committed
io-sim-por: added unit test for issue #113
1 parent 6700050 commit 446623c

File tree

1 file changed

+95
-23
lines changed

1 file changed

+95
-23
lines changed

io-sim/test/Test/Control/Monad/IOSimPOR.hs

Lines changed: 95 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,19 @@ import Test.Control.Monad.STM
5050
import Test.QuickCheck
5151
import Test.Tasty (TestTree, testGroup)
5252
import Test.Tasty.QuickCheck
53+
import qualified Data.List.Trace as Trace
5354

5455
tests :: TestTree
5556
tests =
5657
testGroup "IOSimPOR"
5758
[ testGroup "schedule exploration"
5859
[ testProperty "propSimulates" propSimulates
5960
, testProperty "propExploration" propExploration
61+
, testGroup "issue113"
62+
[ testProperty "wakeup" unit_issue113_wakeup
63+
, testProperty "racy" unit_issue113_racy
64+
, testProperty "nonDep" unit_issue113_nonDep
65+
]
6066
-- , testProperty "propPermutations" propPermutations
6167
]
6268
, testGroup "IO simulator properties"
@@ -261,25 +267,41 @@ sortTasks (x:y:xs) | x>y = [y:x:xs] ++ ((x:) <$> sortTasks (y:xs))
261267
sortTasks (x:xs) = (x:) <$> sortTasks xs
262268
sortTasks [] = []
263269

270+
data Compare = AreEqual | AreNotEqual
271+
deriving Show
272+
273+
instance Arbitrary Compare where
274+
arbitrary = frequency [(8, pure AreEqual),
275+
(2, pure AreNotEqual)]
276+
277+
shrink AreEqual = []
278+
shrink AreNotEqual = [AreEqual]
279+
280+
264281
interpret :: forall s.
265-
TVar (IOSim s) Int
282+
Compare
283+
-> TVar (IOSim s) Int
266284
-> TVar (IOSim s) [ThreadId (IOSim s)]
267-
-> Task
285+
-> (Int, Task)
268286
-> IOSim s (Async (IOSim s) ())
269-
interpret r t (Task steps) = async $ do
287+
interpret cmp r t (tlbl, Task steps) = async $ do
288+
labelThisThread (show tlbl)
270289
(ts, timer) <- atomically $ do
271290
ts <- readTVar t
272291
check (not (null ts))
273292
timer <- newTVar Nothing
274293
return (ts,timer)
275294
mapM_ (interpretStep ts timer) steps
276295
where
296+
compareFn = case cmp of
297+
AreEqual -> (==)
298+
AreNotEqual -> (/=)
277299
interpretStep :: [ThreadId (IOSim s)]
278300
-> TVar (IOSim s) (Maybe _) -- Timeout is not exported
279301
-> Step
280302
-> IOSim s ()
281303
interpretStep _ _ (WhenSet m n) = atomically $ do
282-
readTVar r >>= check . (== m)
304+
readTVar r >>= check . compareFn m
283305
writeTVar r n
284306
interpretStep ts _ (ThrowTo i) = throwTo (ts !! i) (ExitFailure 0)
285307
interpretStep _ _ (Delay i) = threadDelay (fromIntegral i)
@@ -292,13 +314,13 @@ interpret r t (Task steps) = async $ do
292314
(Just tout,AwaitTimeout) -> atomically $ awaitTimeout tout >> return ()
293315
(Nothing,_) -> return ()
294316

295-
runTasks :: [Task] -> IOSim s (Int,Int)
296-
runTasks tasks = do
317+
runTasks :: Compare -> [Task] -> IOSim s (Int,Int)
318+
runTasks cmp tasks = do
297319
let m = maximum [maxTaskValue t | Task t <- tasks]
298320
r <- newTVarIO m
299321
t <- newTVarIO []
300322
exploreRaces
301-
ts <- mapM (interpret r t) tasks
323+
ts <- mapM (interpret cmp r t) (zip [1..] tasks)
302324
atomically $ writeTVar t (asyncThreadId <$> ts)
303325
traverse_ wait ts -- allow the SUT threads to run
304326
a <- readTVarIO r
@@ -309,27 +331,83 @@ maxTaskValue (WhenSet m _:_) = m
309331
maxTaskValue (_:t) = maxTaskValue t
310332
maxTaskValue [] = 0
311333

312-
propSimulates :: Tasks -> Property
313-
propSimulates (Tasks tasks) =
334+
propSimulates :: Compare -> Shrink2 Tasks -> Property
335+
propSimulates cmp (Shrink2 (Tasks tasks)) =
314336
any (not . null . (\(Task steps)->steps)) tasks ==>
315-
let trace = runSimTrace (runTasks tasks) in
337+
let trace = runSimTrace (runTasks cmp tasks) in
316338
case traceResult False trace of
317339
Right (m,a) -> property (m >= a)
340+
Left (FailureInternal msg)
341+
-> counterexample msg False
318342
Left x -> counterexample (ppTrace trace)
319343
$ counterexample (show x) True
320344

321-
propExploration :: Tasks -> Property
322-
propExploration (Tasks tasks) =
345+
propExploration :: Compare -> (Shrink2 Tasks) -> Property
346+
propExploration cmp (Shrink2 ts@(Tasks tasks)) =
323347
any (not . null . (\(Task steps)->steps)) tasks ==>
324348
traceNoDuplicates $ \addTrace ->
325349
--traceCounter $ \addTrace ->
326-
exploreSimTrace id (runTasks tasks) $ \_ trace ->
350+
exploreSimTrace (\a -> a { explorationDebugLevel = 0 })
351+
(say (show ts) >> runTasks cmp tasks) $ \_ trace ->
327352
addTrace trace $
328-
counterexample (splitTrace . noExceptions $ show trace) $
353+
-- TODO: for now @coot is leaving `Trace.ppTrace`, but once we change all
354+
-- assertions into `FailureInternal`, we can use `ppTrace` instead
355+
counterexample (noExceptions $ Trace.ppTrace show (ppSimEvent 0 0 0) trace) $
329356
case traceResult False trace of
330357
Right (m,a) -> property (m >= a)
331-
Left e -> counterexample (ppTrace trace)
332-
$ counterexample (show e) True
358+
Left _ -> property True
359+
360+
361+
-- | This is a counterexample for a fix included in the commit: "io-sim-por:
362+
-- fixed counterexample in issue #113". There was a missing wakeup effect when
363+
-- a thread terminates, for threads that were blocked on `throwTo`.
364+
--
365+
unit_issue113_wakeup :: Property
366+
unit_issue113_wakeup =
367+
propExploration AreEqual (Shrink2 tasks)
368+
where
369+
tasks = Tasks [Task [ThrowTo 1, WhenSet 0 0],
370+
Task [Delay 1],
371+
Task [WhenSet 0 0],
372+
Task [WhenSet 1 0, ThrowTo 1]]
373+
374+
375+
-- | This test checks that we don't build a schedule which execute a racing
376+
-- step that depends on something that wasn't added to `stepInfoNonDep`.
377+
--
378+
unit_issue113_racy :: Property
379+
unit_issue113_racy =
380+
propExploration AreNotEqual (Shrink2 tasks)
381+
where
382+
tasks = Tasks [Task [WhenSet 1 0,ThrowTo 1],
383+
Task [],
384+
Task [ThrowTo 1,WhenSet 0 0],
385+
Task [ThrowTo 1]]
386+
387+
prop :: Property
388+
prop = shrinking shrink (Shrink2 tasks) (propExploration AreNotEqual)
389+
where
390+
tasks = Tasks [Task [WhenSet 1 0,ThrowTo 1],
391+
Task [],
392+
Task [ThrowTo 1,WhenSet 0 0],
393+
Task [ThrowTo 1]]
394+
395+
396+
-- | This test checks that we don't build a schedule which execute a non
397+
-- dependent step that depends on something that wasn't added to
398+
-- `stepInfoNonDep`.
399+
--
400+
unit_issue113_nonDep :: Property
401+
unit_issue113_nonDep =
402+
propExploration AreNotEqual (Shrink2 tasks)
403+
where
404+
tasks = Tasks [Task [WhenSet 0 0],
405+
Task [],
406+
Task [WhenSet 0 0],
407+
Task [ThrowTo 1,WhenSet 1 1],
408+
Task [ThrowTo 5],
409+
Task [ThrowTo 1]]
410+
333411

334412
-- Testing propPermutations n should collect every permutation of [1..n] once only.
335413
-- Test manually, and supply a small value of n.
@@ -359,12 +437,6 @@ noExceptions xs = unsafePerformIO $ try (evaluate xs) >>= \case
359437
Right (x:ys) -> return (x:noExceptions ys)
360438
Left e -> return ("\n"++show (e :: SomeException))
361439

362-
splitTrace :: [Char] -> [Char]
363-
splitTrace [] = []
364-
splitTrace (x:xs) | begins "(Trace" = "\n(" ++ splitTrace xs
365-
| otherwise = x:splitTrace xs
366-
where begins s = take (length s) (x:xs) == s
367-
368440
traceCounter :: (Testable prop1, Show a1) => ((a1 -> a2 -> a2) -> prop1) -> Property
369441
traceCounter k = r `pseq` (k addTrace .&&.
370442
tabulate "Trace repetitions" (map show $ traceCounts ()) True)
@@ -398,7 +470,7 @@ prop_stm_graph_sim g =
398470
traceNoDuplicates $ \addTrace ->
399471
exploreSimTrace id (prop_stm_graph g) $ \_ trace ->
400472
addTrace trace $
401-
counterexample (splitTrace . noExceptions $ show trace) $
473+
counterexample (noExceptions $ Trace.ppTrace show show trace) $
402474
case traceResult False trace of
403475
Right () -> property True
404476
Left e -> counterexample (show e) False

0 commit comments

Comments
 (0)