@@ -50,13 +50,19 @@ import Test.Control.Monad.STM
50
50
import Test.QuickCheck
51
51
import Test.Tasty (TestTree , testGroup )
52
52
import Test.Tasty.QuickCheck
53
+ import qualified Data.List.Trace as Trace
53
54
54
55
tests :: TestTree
55
56
tests =
56
57
testGroup " IOSimPOR"
57
58
[ testGroup " schedule exploration"
58
59
[ testProperty " propSimulates" propSimulates
59
60
, 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
+ ]
60
66
-- , testProperty "propPermutations" propPermutations
61
67
]
62
68
, testGroup " IO simulator properties"
@@ -261,25 +267,41 @@ sortTasks (x:y:xs) | x>y = [y:x:xs] ++ ((x:) <$> sortTasks (y:xs))
261
267
sortTasks (x: xs) = (x: ) <$> sortTasks xs
262
268
sortTasks [] = []
263
269
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
+
264
281
interpret :: forall s .
265
- TVar (IOSim s ) Int
282
+ Compare
283
+ -> TVar (IOSim s ) Int
266
284
-> TVar (IOSim s ) [ThreadId (IOSim s )]
267
- -> Task
285
+ -> ( Int , Task )
268
286
-> 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)
270
289
(ts, timer) <- atomically $ do
271
290
ts <- readTVar t
272
291
check (not (null ts))
273
292
timer <- newTVar Nothing
274
293
return (ts,timer)
275
294
mapM_ (interpretStep ts timer) steps
276
295
where
296
+ compareFn = case cmp of
297
+ AreEqual -> (==)
298
+ AreNotEqual -> (/=)
277
299
interpretStep :: [ThreadId (IOSim s )]
278
300
-> TVar (IOSim s ) (Maybe _ ) -- Timeout is not exported
279
301
-> Step
280
302
-> IOSim s ()
281
303
interpretStep _ _ (WhenSet m n) = atomically $ do
282
- readTVar r >>= check . ( == m)
304
+ readTVar r >>= check . compareFn m
283
305
writeTVar r n
284
306
interpretStep ts _ (ThrowTo i) = throwTo (ts !! i) (ExitFailure 0 )
285
307
interpretStep _ _ (Delay i) = threadDelay (fromIntegral i)
@@ -292,13 +314,13 @@ interpret r t (Task steps) = async $ do
292
314
(Just tout,AwaitTimeout ) -> atomically $ awaitTimeout tout >> return ()
293
315
(Nothing ,_) -> return ()
294
316
295
- runTasks :: [Task ] -> IOSim s (Int ,Int )
296
- runTasks tasks = do
317
+ runTasks :: Compare -> [Task ] -> IOSim s (Int ,Int )
318
+ runTasks cmp tasks = do
297
319
let m = maximum [maxTaskValue t | Task t <- tasks]
298
320
r <- newTVarIO m
299
321
t <- newTVarIO []
300
322
exploreRaces
301
- ts <- mapM (interpret r t) tasks
323
+ ts <- mapM (interpret cmp r t) ( zip [ 1 .. ] tasks)
302
324
atomically $ writeTVar t (asyncThreadId <$> ts)
303
325
traverse_ wait ts -- allow the SUT threads to run
304
326
a <- readTVarIO r
@@ -309,27 +331,83 @@ maxTaskValue (WhenSet m _:_) = m
309
331
maxTaskValue (_: t) = maxTaskValue t
310
332
maxTaskValue [] = 0
311
333
312
- propSimulates :: Tasks -> Property
313
- propSimulates ( Tasks tasks) =
334
+ propSimulates :: Compare -> Shrink2 Tasks -> Property
335
+ propSimulates cmp ( Shrink2 ( Tasks tasks) ) =
314
336
any (not . null . (\ (Task steps)-> steps)) tasks ==>
315
- let trace = runSimTrace (runTasks tasks) in
337
+ let trace = runSimTrace (runTasks cmp tasks) in
316
338
case traceResult False trace of
317
339
Right (m,a) -> property (m >= a)
340
+ Left (FailureInternal msg)
341
+ -> counterexample msg False
318
342
Left x -> counterexample (ppTrace trace)
319
343
$ counterexample (show x) True
320
344
321
- propExploration :: Tasks -> Property
322
- propExploration ( Tasks tasks) =
345
+ propExploration :: Compare -> ( Shrink2 Tasks ) -> Property
346
+ propExploration cmp ( Shrink2 ts @ ( Tasks tasks) ) =
323
347
any (not . null . (\ (Task steps)-> steps)) tasks ==>
324
348
traceNoDuplicates $ \ addTrace ->
325
349
-- traceCounter $ \addTrace ->
326
- exploreSimTrace id (runTasks tasks) $ \ _ trace ->
350
+ exploreSimTrace (\ a -> a { explorationDebugLevel = 0 })
351
+ (say (show ts) >> runTasks cmp tasks) $ \ _ trace ->
327
352
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) $
329
356
case traceResult False trace of
330
357
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
+
333
411
334
412
-- Testing propPermutations n should collect every permutation of [1..n] once only.
335
413
-- Test manually, and supply a small value of n.
@@ -359,12 +437,6 @@ noExceptions xs = unsafePerformIO $ try (evaluate xs) >>= \case
359
437
Right (x: ys) -> return (x: noExceptions ys)
360
438
Left e -> return (" \n " ++ show (e :: SomeException ))
361
439
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
-
368
440
traceCounter :: (Testable prop1 , Show a1 ) => ((a1 -> a2 -> a2 ) -> prop1 ) -> Property
369
441
traceCounter k = r `pseq` (k addTrace .&&.
370
442
tabulate " Trace repetitions" (map show $ traceCounts () ) True )
@@ -398,7 +470,7 @@ prop_stm_graph_sim g =
398
470
traceNoDuplicates $ \ addTrace ->
399
471
exploreSimTrace id (prop_stm_graph g) $ \ _ trace ->
400
472
addTrace trace $
401
- counterexample (splitTrace . noExceptions $ show trace) $
473
+ counterexample (noExceptions $ Trace. ppTrace show show trace) $
402
474
case traceResult False trace of
403
475
Right () -> property True
404
476
Left e -> counterexample (show e) False
0 commit comments