packages feed

dejafu 1.3.1.0 → 1.3.2.0

raw patch · 17 files changed

+564/−100 lines, 17 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Test.DejaFu.Conc.Internal: DCSnapshot :: Context n r () -> (Threads n r -> n ()) -> r (Maybe (Either Failure a)) -> DCSnapshot r n a
+ Test.DejaFu.Conc.Internal: [dcsContext] :: DCSnapshot r n a -> Context n r ()
+ Test.DejaFu.Conc.Internal: [dcsRef] :: DCSnapshot r n a -> r (Maybe (Either Failure a))
+ Test.DejaFu.Conc.Internal: [dcsRestore] :: DCSnapshot r n a -> Threads n r -> n ()
+ Test.DejaFu.Conc.Internal: data DCSnapshot r n a
+ Test.DejaFu.Conc.Internal.Memory: commitThreadId :: ThreadId -> Maybe CRefId -> ThreadId
+ Test.DejaFu.Internal: [_debugFatal] :: Settings n a -> Bool
+ Test.DejaFu.Internal: [_equality] :: Settings n a -> Maybe (a -> a -> Bool)
+ Test.DejaFu.Internal: [_simplify] :: Settings n a -> Bool
+ Test.DejaFu.SCT.Internal: dropCommits :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]
+ Test.DejaFu.SCT.Internal: fromId :: Coercible a Id => a -> Int
+ Test.DejaFu.SCT.Internal: lexicoNormalForm :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]
+ Test.DejaFu.SCT.Internal: permuteBy :: MemType -> [ThreadId -> ThreadId -> Bool] -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]
+ Test.DejaFu.SCT.Internal: pullBack :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]
+ Test.DejaFu.SCT.Internal: pushForward :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]
+ Test.DejaFu.SCT.Internal: renumber :: MemType -> Int -> Int -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]
+ Test.DejaFu.SCT.Internal: replay :: (MonadConc n, MonadRef r n) => (forall x. Scheduler x -> x -> n (Either Failure a, x, Trace)) -> [(ThreadId, ThreadAction)] -> n (Either Failure a, [(ThreadId, ThreadAction)], Trace)
+ Test.DejaFu.SCT.Internal: sct :: (MonadConc n, MonadRef r n) => Settings n a -> ([ThreadId] -> s) -> (s -> Maybe t) -> ((Scheduler g -> g -> n (Either Failure a, g, Trace)) -> s -> t -> n (s, Maybe (Either Failure a, Trace))) -> ConcT r n a -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT.Internal: sct' :: (MonadConc n, MonadRef r n) => Settings n a -> s -> (s -> Maybe t) -> (s -> t -> n (s, Maybe (Either Failure a, Trace))) -> (forall x. Scheduler x -> x -> n (Either Failure a, x, Trace)) -> ThreadId -> CRefId -> n [(Either Failure a, Trace)]
+ Test.DejaFu.SCT.Internal: simplify :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]
+ Test.DejaFu.SCT.Internal: simplifyExecution :: (MonadConc n, MonadRef r n) => Settings n a -> (forall x. Scheduler x -> x -> n (Either Failure a, x, Trace)) -> ThreadId -> CRefId -> Either Failure a -> Trace -> n (Either Failure a, Trace)
+ Test.DejaFu.SCT.Internal: toId :: Coercible Id a => Int -> a
+ Test.DejaFu.Settings: ldebugFatal :: Lens' (Settings n a) Bool
+ Test.DejaFu.Settings: lequality :: Lens' (Settings n a) (Maybe (a -> a -> Bool))
+ Test.DejaFu.Settings: lsimplify :: Lens' (Settings n a) Bool
+ Test.DejaFu.Utils: toTIdTrace :: Trace -> [(ThreadId, ThreadAction)]
- Test.DejaFu.Internal: Settings :: Way -> MemType -> Maybe (Either Failure a -> Maybe Discard) -> Maybe (a -> String) -> Maybe (String -> n ()) -> Maybe (Either Failure a -> Bool) -> Settings n a
+ Test.DejaFu.Internal: Settings :: Way -> MemType -> Maybe (Either Failure a -> Maybe Discard) -> Maybe (a -> String) -> Maybe (String -> n ()) -> Bool -> Maybe (Either Failure a -> Bool) -> Maybe (a -> a -> Bool) -> Bool -> Settings n a
- Test.DejaFu.SCT.Internal.DPOR: dporSched :: IncrementalBoundFunc k -> Scheduler (DPORSchedState k)
+ Test.DejaFu.SCT.Internal.DPOR: dporSched :: MemType -> IncrementalBoundFunc k -> Scheduler (DPORSchedState k)
- Test.DejaFu.SCT.Internal.DPOR: findBacktrackSteps :: BacktrackFunc -> Bool -> Seq ([(ThreadId, Lookahead)], [ThreadId]) -> Trace -> [BacktrackStep]
+ Test.DejaFu.SCT.Internal.DPOR: findBacktrackSteps :: MemType -> BacktrackFunc -> Bool -> Seq ([(ThreadId, Lookahead)], [ThreadId]) -> Trace -> [BacktrackStep]
- Test.DejaFu.SCT.Internal.DPOR: incorporateTrace :: Bool -> Trace -> DPOR -> DPOR
+ Test.DejaFu.SCT.Internal.DPOR: incorporateTrace :: MemType -> Bool -> Trace -> DPOR -> DPOR
- Test.DejaFu.SCT.Internal.DPOR: updateCRState :: ThreadAction -> Map CRefId Bool -> Map CRefId Bool
+ Test.DejaFu.SCT.Internal.DPOR: updateCRState :: MemType -> ThreadAction -> Map CRefId Bool -> Map CRefId Bool
- Test.DejaFu.SCT.Internal.DPOR: updateDepState :: DepState -> ThreadId -> ThreadAction -> DepState
+ Test.DejaFu.SCT.Internal.DPOR: updateDepState :: MemType -> DepState -> ThreadId -> ThreadAction -> DepState

Files

CHANGELOG.rst view
@@ -7,6 +7,33 @@ .. _PVP: https://pvp.haskell.org/  +1.3.2.0 (2018-03-12)+--------------------++* Git: :tag:`dejafu-1.3.2.0`+* Hackage: :hackage:`dejafu-1.3.2.0`++Added+~~~~~++* (:issue:`183`) SCT settings for trace simplification:++    * ``Test.DejaFu.Settings.lequality``+    * ``Test.DejaFu.Settings.lsimplify``++* (:pull:`248`) ``Test.DejaFu.Utils.toTIdTrace`` to extract thread IDs+  from a trace.++* (:pull:`248`) SCT setting to make some recoverable errors fatal:+  ``Test.DejaFu.Settings.ldebugFatal``++Performance+~~~~~~~~~~~++* (:pull:`248`) Prune some unnecessary interleavings of ``CRef``+  actions in systematic testing when using sequential consistency.++ 1.3.1.0 (2018-03-11) -------------------- 
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2015--2017, Michael Walker <mike@barrucadu.co.uk>+Copyright (c) 2015--2018, Michael Walker <mike@barrucadu.co.uk>  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
Test/DejaFu/Conc.hs view
@@ -7,7 +7,7 @@  -- | -- Module      : Test.DejaFu.Conc--- Copyright   : (c) 2016--2017 Michael Walker+-- Copyright   : (c) 2016--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental@@ -359,20 +359,6 @@ --     pure r --   liftIO (readIORef r) -- @---- | A snapshot of the concurrency state immediately after 'dontCheck'--- finishes.------ @since 1.1.0.0-data DCSnapshot r n a = DCSnapshot-  { dcsContext :: Context n r ()-  -- ^ The execution context.  The scheduler state is ignored when-  -- restoring.-  , dcsRestore :: Threads n r -> n ()-  -- ^ Action to restore CRef, MVar, and TVar values.-  , dcsRef :: r (Maybe (Either Failure a))-  -- ^ Reference where the result will be written.-  }  -- | Like 'runConcurrent', but terminates immediately after running -- the 'dontCheck' action with a 'DCSnapshot' which can be used in
Test/DejaFu/Conc/Internal.hs view
@@ -4,7 +4,7 @@  -- | -- Module      : Test.DejaFu.Conc.Internal--- Copyright   : (c) 2016--2017 Michael Walker+-- Copyright   : (c) 2016--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental@@ -55,6 +55,20 @@   -- execution.   , finalTrace :: SeqTrace   , finalDecision :: Maybe (ThreadId, ThreadAction)+  }++-- | A snapshot of the concurrency state immediately after 'dontCheck'+-- finishes.+--+-- @since 1.1.0.0+data DCSnapshot r n a = DCSnapshot+  { dcsContext :: Context n r ()+  -- ^ The execution context.  The scheduler state is ignored when+  -- restoring.+  , dcsRestore :: Threads n r -> n ()+  -- ^ Action to restore CRef, MVar, and TVar values.+  , dcsRef :: r (Maybe (Either Failure a))+  -- ^ Reference where the result will be written.   }  -- | Run a concurrent computation with a given 'Scheduler' and initial
Test/DejaFu/Conc/Internal/Common.hs view
@@ -4,7 +4,7 @@  -- | -- Module      : Test.DejaFu.Conc.Internal.Common--- Copyright   : (c) 2016--2017 Michael Walker+-- Copyright   : (c) 2016--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental
Test/DejaFu/Conc/Internal/Memory.hs view
@@ -4,7 +4,7 @@  -- | -- Module      : Test.DejaFu.Conc.Internal.Memory--- Copyright   : (c) 2016--2017 Michael Walker+-- Copyright   : (c) 2016--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental@@ -131,15 +131,18 @@ -- | Add phantom threads to the thread list to commit pending writes. addCommitThreads :: WriteBuffer r -> Threads n r -> Threads n r addCommitThreads (WriteBuffer wb) ts = ts <> M.fromList phantoms where-  phantoms = [ (ThreadId (Id Nothing . negate $ commitTidOf k), mkthread c)+  phantoms = [ (uncurry commitThreadId k, mkthread c)              | (k, b) <- M.toList wb              , c <- maybeToList (go $ viewl b)              ]   go (BufferedWrite tid (CRef crid _) _ :< _) = Just $ ACommit tid crid   go EmptyL = Nothing -  commitTidOf (ThreadId (Id _ t), Nothing) = t + 1-  commitTidOf (ThreadId (Id _ t), Just (CRefId (Id _ c))) = t + 1 + c * 10000+-- | The ID of a commit thread.+commitThreadId :: ThreadId -> Maybe CRefId -> ThreadId+commitThreadId (ThreadId (Id _ t)) = ThreadId . Id Nothing . negate . go where+  go (Just (CRefId (Id _ c))) = t + 1 + c * 10000+  go Nothing = t + 1  -- | Remove phantom threads. delCommitThreads :: Threads n r -> Threads n r
Test/DejaFu/Conc/Internal/STM.hs view
@@ -8,7 +8,7 @@  -- | -- Module      : Test.DejaFu.Conc.Internal.STM--- Copyright   : (c) 2017 Michael Walker+-- Copyright   : (c) 2017--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental
Test/DejaFu/Conc/Internal/Threading.hs view
@@ -3,7 +3,7 @@  -- | -- Module      : Test.DejaFu.Conc.Internal.Threading--- Copyright   : (c) 2016--2017 Michael Walker+-- Copyright   : (c) 2016--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental
Test/DejaFu/Internal.hs view
@@ -39,7 +39,10 @@   , _discard :: Maybe (Either Failure a -> Maybe Discard)   , _debugShow :: Maybe (a -> String)   , _debugPrint :: Maybe (String -> n ())+  , _debugFatal :: Bool   , _earlyExit :: Maybe (Either Failure a -> Bool)+  , _equality :: Maybe (a -> a -> Bool)+  , _simplify  :: Bool   }  -- | How to explore the possible executions of a concurrent program.
Test/DejaFu/Refinement.hs view
@@ -7,7 +7,7 @@  -- | -- Module      : Test.DejaFu.Refinement--- Copyright   : (c) 2017 Michael Walker+-- Copyright   : (c) 2017--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental
Test/DejaFu/SCT.hs view
@@ -1,13 +1,10 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE LambdaCase #-}- -- | -- Module      : Test.DejaFu.SCT -- Copyright   : (c) 2015--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental--- Portability : BangPatterns, LambdaCase+-- Portability : portable -- -- Systematic testing for concurrent computations. module Test.DejaFu.SCT@@ -50,6 +47,7 @@  import           Test.DejaFu.Conc import           Test.DejaFu.Internal+import           Test.DejaFu.SCT.Internal import           Test.DejaFu.SCT.Internal.DPOR import           Test.DejaFu.SCT.Internal.Weighted import           Test.DejaFu.Settings@@ -199,13 +197,13 @@          check = findSchedulePrefix -        step dp (prefix, conservative, sleep) run = do+        step run dp (prefix, conservative, sleep) = do           (res, s, trace) <- run-            (dporSched (cBound cb0))+            (dporSched (_memtype settings) (cBound cb0))             (initialDPORSchedState sleep prefix) -          let bpoints = findBacktrackSteps (cBacktrack cb0) (schedBoundKill s) (schedBPoints s) trace-          let newDPOR = incorporateTrace conservative trace dp+          let bpoints = findBacktrackSteps (_memtype settings) (cBacktrack cb0) (schedBoundKill s) (schedBPoints s) trace+          let newDPOR = incorporateTrace (_memtype settings) conservative trace dp            pure $ if schedIgnore s                  then (force newDPOR, Nothing)@@ -218,7 +216,7 @@         check (_, 0) = Nothing         check s = Just s -        step _ (g, n) run = do+        step run _ (g, n) = do           (res, s, trace) <- run             (randSched $ \g' -> (1, g'))             (initialRandSchedState Nothing g)@@ -231,8 +229,8 @@         check (_, 0, _, _) = Nothing         check s = Just s -        step s (g, n, 0, _) run = step s (g, n, max 1 use0, M.empty) run-        step _ (g, n, use, ws) run = do+        step run s (g, n, 0, _) = step run s (g, n, max 1 use0, M.empty)+        step run _ (g, n, use, ws) = do           (res, s, trace) <- run             (randSched $ randomR (1, 50))             (initialRandSchedState (Just ws) g)@@ -506,45 +504,6 @@ sctWeightedRandomDiscard discard memtype g lim use = runSCTWithSettings $   set ldiscard (Just discard) (fromWayAndMemType (swarmy g lim use) memtype) {-# DEPRECATED sctWeightedRandomDiscard "Use runSCTWithSettings instead" #-}---- | General-purpose SCT function.-sct :: (MonadConc n, MonadRef r n)-  => Settings n a-  -- ^ The SCT settings ('Way' is ignored)-  -> ([ThreadId] -> s)-  -- ^ Initial state-  -> (s -> Maybe t)-  -- ^ State predicate-  -> (s -> t -> (Scheduler g -> g -> n (Either Failure a, g, Trace)) -> n (s, Maybe (Either Failure a, Trace)))-  -- ^ Run the computation and update the state-  -> ConcT r n a-  -> n [(Either Failure a, Trace)]-sct settings s0 sfun srun conc-    | canDCSnapshot conc = runForDCSnapshot conc >>= \case-        Just (Right snap, _) -> go (runSnap snap) (fst (threadsFromDCSnapshot snap))-        Just (Left f, trace) -> pure [(Left f, trace)]-        _ -> do-          debugPrint "Failed to construct snapshot, continuing without."-          go runFull [initialThread]-    | otherwise = go runFull [initialThread]-  where-    go run = go' Nothing . s0 where-      go' (Just res) _ | earlyExit res = pure []-      go' _ !s = case sfun s of-        Just t -> srun s t run >>= \case-          (s', Just (res, trace)) -> case discard res of-            Just DiscardResultAndTrace -> go' (Just res) s'-            Just DiscardTrace -> ((res, []):) <$> go' (Just res) s'-            Nothing -> ((res, trace):) <$> go' (Just res) s'-          (s', Nothing) -> go' Nothing s'-        Nothing -> pure []--    runFull sched s = runConcurrent sched (_memtype settings) s conc-    runSnap snap sched s = runWithDCSnapshot sched (_memtype settings) s snap--    debugPrint = fromMaybe (const (pure ())) (_debugPrint settings)-    earlyExit = fromMaybe (const False) (_earlyExit settings)-    discard = fromMaybe (const Nothing) (_discard settings)  ------------------------------------------------------------------------------- -- Utilities
+ Test/DejaFu/SCT/Internal.hs view
@@ -0,0 +1,389 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}++-- |+-- Module      : Test.DejaFu.SCT.Internal+-- Copyright   : (c) 2018 Michael Walker+-- License     : MIT+-- Maintainer  : Michael Walker <mike@barrucadu.co.uk>+-- Stability   : experimental+-- Portability : BangPatterns, FlexibleContexts, LambdaCase, RankNTypes+--+-- Internal types and functions for SCT.  This module is NOT+-- considered to form part of the public interface of this library.+module Test.DejaFu.SCT.Internal where++import           Control.Monad.Conc.Class         (MonadConc)+import           Control.Monad.Ref                (MonadRef)+import           Data.Coerce                      (Coercible, coerce)+import qualified Data.IntMap.Strict               as I+import           Data.List                        (find, mapAccumL)+import           Data.Maybe                       (fromMaybe)++import           Test.DejaFu.Conc+import           Test.DejaFu.Conc.Internal        (Context(..), DCSnapshot(..))+import           Test.DejaFu.Conc.Internal.Memory (commitThreadId)+import           Test.DejaFu.Internal+import           Test.DejaFu.Schedule             (Scheduler(..))+import           Test.DejaFu.SCT.Internal.DPOR+import           Test.DejaFu.Types+import           Test.DejaFu.Utils++-------------------------------------------------------------------------------+-- * Exploration++-- | General-purpose SCT function.+sct :: (MonadConc n, MonadRef r n)+  => Settings n a+  -- ^ The SCT settings ('Way' is ignored)+  -> ([ThreadId] -> s)+  -- ^ Initial state+  -> (s -> Maybe t)+  -- ^ State predicate+  -> ((Scheduler g -> g -> n (Either Failure a, g, Trace)) -> s -> t -> n (s, Maybe (Either Failure a, Trace)))+  -- ^ Run the computation and update the state+  -> ConcT r n a+  -> n [(Either Failure a, Trace)]+sct settings s0 sfun srun conc+    | canDCSnapshot conc = runForDCSnapshot conc >>= \case+        Just (Right snap, _) -> sct'Snap snap+        Just (Left f, trace) -> pure [(Left f, trace)]+        _ -> do+          debugFatal "Failed to construct snapshot, continuing without."+          sct'Full+    | otherwise = sct'Full+  where+    sct'Full = sct'+      settings+      (s0 [initialThread])+      sfun+      (srun runFull)+      runFull+      (toId 1)+      (toId 1)++    sct'Snap snap = let idsrc = cIdSource (dcsContext snap) in sct'+      settings+      (s0 (fst (threadsFromDCSnapshot snap)))+      sfun+      (srun (runSnap snap))+      (runSnap snap)+      (toId $ 1 + fst (_tids  idsrc))+      (toId $ 1 + fst (_crids idsrc))++    runFull sched s = runConcurrent sched (_memtype settings) s conc+    runSnap snap sched s = runWithDCSnapshot sched (_memtype settings) s snap++    debugFatal = if _debugFatal settings then fatal "sct" else debugPrint+    debugPrint = fromMaybe (const (pure ())) (_debugPrint settings)++-- | Like 'sct' but given a function to run the computation.+sct' :: (MonadConc n, MonadRef r n)+  => Settings n a+  -- ^ The SCT settings ('Way' is ignored)+  -> s+  -- ^ Initial state+  -> (s -> Maybe t)+  -- ^ State predicate+  -> (s -> t -> n (s, Maybe (Either Failure a, Trace)))+  -- ^ Run the computation and update the state+  -> (forall x. Scheduler x -> x -> n (Either Failure a, x, Trace))+  -- ^ Just run the computation+  -> ThreadId+  -- ^ The first available @ThreadId@+  -> CRefId+  -- ^ The first available @CRefId@+  -> n [(Either Failure a, Trace)]+sct' settings s0 sfun srun run nextTId nextCRId = go Nothing [] s0 where+  go (Just res) _ _ | earlyExit res = pure []+  go _ seen !s = case sfun s of+    Just t -> srun s t >>= \case+      (s', Just (res, trace)) -> case discard res of+        Just DiscardResultAndTrace -> go (Just res) seen s'+        Just DiscardTrace -> result res [] seen s'+        Nothing -> result res trace seen s'+      (s', Nothing) -> go Nothing seen s'+    Nothing -> pure []++  -- Sadly, we have to use a list to store the set of unique results,+  -- as we don't have an @Ord a@ dict hanging around.  I suspect that+  -- most test cases will have a relatively small number of unique+  -- results, compared to the number of executions, however.+  -- Pathological cases (like IORef ones in dejafu-tests which produce+  -- a different result on every execution) are probably uncommon.+  result = case _equality settings of+    Just f -> \res trace seen s ->+      let eq cmp (Right a1) (Right a2) = cmp a1 a2+          eq _   (Left  e1) (Left  e2) = e1 == e2+          eq _ _ _ = False+      in if any (eq f res) seen+         then go (Just res) seen s+         else dosimplify res trace (res:seen) s+    Nothing -> dosimplify++  dosimplify res [] seen s = ((res, []) :) <$> go (Just res) seen s+  dosimplify res trace seen s+    | not (_simplify settings) = ((res, trace) :) <$> go (Just res) seen s+    | otherwise = do+        shrunk <- simplifyExecution settings run nextTId nextCRId res trace+        (shrunk :) <$> go (Just res) seen s++  earlyExit = fromMaybe (const False) (_earlyExit settings)+  discard = fromMaybe (const Nothing) (_discard settings)++-- | Given a result and a trace, produce a more minimal trace.+--+-- In principle, simplification is semantics preserving and can be+-- done without needing to execute the computation again.  However,+-- there are two good reasons to do so:+--+--  * It's a sanity check that there are no bugs.+--  * It's easier to generate a reduced sequence of scheduling+--    decisions and let dejafu generate the full trace, than to+--    generate a reduced trace directly+--+-- Unlike shrinking in randomised property-testing tools like+-- QuickCheck or Hedgehog, we only run the test case /once/, at the+-- end, rather than after every simplification step.+simplifyExecution :: (MonadConc n, MonadRef r n)+  => Settings n a+  -- ^ The SCT settings ('Way' is ignored)+  -> (forall x. Scheduler x -> x -> n (Either Failure a, x, Trace))+  -- ^ Just run the computation+  -> ThreadId+  -- ^ The first available @ThreadId@+  -> CRefId+  -- ^ The first available @CRefId@+  -> Either Failure a+  -- ^ The expected result+  -> Trace+  -> n (Either Failure a, Trace)+simplifyExecution settings run nextTId nextCRId res trace+    | tidTrace == simplifiedTrace = do+        debugPrint ("Simplifying new result '" ++ p res ++ "': no simplification possible!")+        pure (res, trace)+    | otherwise = do+        debugPrint ("Simplifying new result '" ++ p res ++ "': OK!")+        (res', _, trace') <- replay run (fixup simplifiedTrace)+        case (_equality settings, res, res') of+          (Just f,  Right a1, Right a2) | f a1 a2  -> pure (res', trace')+          (_,       Left  e1, Left  e2) | e1 == e2 -> pure (res', trace')+          (Nothing, Right _,  Right _) -> pure (res', trace') -- this is a risky case!+          _ -> do+            debugFatal ("Got a different result after simplifying: '" ++ p res ++ "' /= '" ++ p res' ++ "'")+            pure (res, trace)+  where+    tidTrace = toTIdTrace trace+    simplifiedTrace = simplify (_memtype settings) tidTrace+    fixup = renumber (_memtype settings) (fromId nextTId) (fromId nextCRId)++    debugFatal = if _debugFatal settings then fatal "sct" else debugPrint+    debugPrint = fromMaybe (const (pure ())) (_debugPrint settings)+    debugShow = fromMaybe (const "_") (_debugShow settings)+    p = either show debugShow++-- | Replay an execution.+replay :: (MonadConc n, MonadRef r n)+  => (forall x. Scheduler x -> x -> n (Either Failure a, x, Trace))+  -- ^ Run the computation+  -> [(ThreadId, ThreadAction)]+  -- ^ The reduced sequence of scheduling decisions+  -> n (Either Failure a, [(ThreadId, ThreadAction)], Trace)+replay run = run (Scheduler (const sched)) where+    sched runnable ((t, Stop):ts) = case findThread t runnable of+      Just t' -> (Just t', ts)+      Nothing -> sched runnable ts+    sched runnable ((t, _):ts) = (findThread t runnable, ts)+    sched _ _ = (Nothing, [])++    -- find a thread ignoring names+    findThread tid0 =+      fmap fst . find (\(tid,_) -> fromId tid == fromId tid0)++-------------------------------------------------------------------------------+-- * Schedule simplification++-- | Simplify a trace by permuting adjacent independent actions to+-- reduce context switching.+simplify :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]+simplify memtype trc0 = loop (length trc0) (prepare trc0) where+  prepare = dropCommits memtype . lexicoNormalForm memtype+  step = pushForward memtype . pullBack memtype++  loop 0 trc = trc+  loop n trc =+    let trc' = step trc+    in if trc' /= trc then loop (n-1) trc' else trc++-- | Put a trace into lexicographic (by thread ID) normal form.+lexicoNormalForm :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]+lexicoNormalForm memtype = go where+  go trc =+    let trc' = permuteBy memtype (repeat (>)) trc+    in if trc == trc' then trc else go trc'++-- | Swap adjacent independent actions in the trace if a predicate+-- holds.+permuteBy+  :: MemType+  -> [ThreadId -> ThreadId -> Bool]+  -> [(ThreadId, ThreadAction)]+  -> [(ThreadId, ThreadAction)]+permuteBy memtype = go initialDepState where+  go ds (p:ps) (t1@(tid1, ta1):t2@(tid2, ta2):trc)+    | independent ds tid1 ta1 tid2 ta2 && p tid1 tid2 = go' ds ps t2 (t1 : trc)+    | otherwise = go' ds ps t1 (t2 : trc)+  go _ _ trc = trc++  go' ds ps t@(tid, ta) trc = t : go (updateDepState memtype ds tid ta) ps trc++-- | Throw away commit actions which are followed by a memory barrier.+dropCommits :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]+dropCommits SequentialConsistency = id+dropCommits memtype = go initialDepState where+  go ds (t1@(tid1, ta1@(CommitCRef _ _)):t2@(tid2, ta2):trc)+    | isBarrier (simplifyAction ta2) = go ds (t2:trc)+    | independent ds tid1 ta1 tid2 ta2 = t2 : go (updateDepState memtype ds tid2 ta2) (t1:trc)+  go ds (t@(tid,ta):trc) = t : go (updateDepState memtype ds tid ta) trc+  go _ [] = []++-- | Attempt to reduce context switches by \"pulling\" thread actions+-- back to a prior execution of that thread.+--+-- Simple example, say we have @[(tidA, act1), (tidB, act2), (tidA,+-- act3)]@, where @act2@ and @act3@ are independent.  In this case+-- 'pullBack' will swap them, giving the sequence @[(tidA, act1),+-- (tidA, act3), (tidB, act2)]@.  It works for arbitrary separations.+pullBack :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]+pullBack memtype = go initialDepState where+  go ds (t1@(tid1, ta1):trc@((tid2, _):_)) =+    let ds' = updateDepState memtype ds tid1 ta1+        trc' = if tid1 /= tid2+               then maybe trc (uncurry (:)) (findAction tid1 ds' trc)+               else trc+    in t1 : go ds' trc'+  go _ trc = trc++  findAction tid0 = fgo where+    fgo ds (t@(tid, ta):trc)+      | tid == tid0 = Just (t, trc)+      | otherwise = case fgo (updateDepState memtype ds tid ta) trc of+          Just (ft@(ftid, fa), trc')+            | independent ds tid ta ftid fa -> Just (ft, t:trc')+          _ -> Nothing+    fgo _ _ = Nothing++-- | Attempt to reduce context switches by \"pushing\" thread actions+-- forward to a future execution of that thread.+--+-- This is kind of the opposite of 'pullBack', but there are cases+-- where one applies but not the other.+--+-- Simple example, say we have @[(tidA, act1), (tidB, act2), (tidA,+-- act3)]@, where @act1@ and @act2@ are independent.  In this case+-- 'pushForward' will swap them, giving the sequence @[(tidB, act2),+-- (tidA, act1), (tidA, act3)]@.  It works for arbitrary separations.+pushForward :: MemType -> [(ThreadId, ThreadAction)] -> [(ThreadId, ThreadAction)]+pushForward memtype = go initialDepState where+  go ds (t1@(tid1, ta1):trc@((tid2, _):_)) =+    let ds' = updateDepState memtype ds tid1 ta1+    in if tid1 /= tid2+       then maybe (t1 : go ds' trc) (go ds) (findAction tid1 ta1 ds trc)+       else t1 : go ds' trc+  go _ trc = trc++  findAction tid0 ta0 = fgo where+    fgo ds (t@(tid, ta):trc)+      | tid == tid0 = Just ((tid0, ta0) : t : trc)+      | independent ds tid0 ta0 tid ta = (t:) <$> fgo (updateDepState memtype ds tid ta) trc+      | otherwise = Nothing+    fgo _ _ = Nothing++-- | Re-number threads and CRefs.+--+-- Permuting forks or newCRefs makes the existing numbering invalid,+-- which then causes problems for scheduling.  Just re-numbering+-- threads isn't enough, as CRef IDs are used to determine commit+-- thread IDs.+--+-- Renumbered things will not fix their names, so don't rely on those+-- at all.+renumber+  :: MemType+  -- ^ The memory model determines how commit threads are numbered.+  -> Int+  -- ^ First free thread ID.+  -> Int+  -- ^ First free @CRef@ ID.+  -> [(ThreadId, ThreadAction)]+  -> [(ThreadId, ThreadAction)]+renumber memtype tid0 crid0 = snd . mapAccumL go (I.empty, tid0, I.empty, crid0) where+  go s@(tidmap, _, cridmap, _) (_, CommitCRef tid crid) =+    let tid'  = renumbered tidmap  tid+        crid' = renumbered cridmap crid+        act' = CommitCRef tid' crid'+    in case memtype of+         PartialStoreOrder -> (s, (commitThreadId tid' (Just crid'), act'))+         _ -> (s, (commitThreadId tid' Nothing, act'))+  go s@(tidmap, _, _, _) (tid, act) =+    let (s', act') = updateAction s act+    in (s', (renumbered tidmap tid, act'))++  -- I can't help but feel there should be some generic programming+  -- solution to this sort of thing (and to the many other functions+  -- operating over @ThreadAction@s / @Lookahead@s)+  updateAction (tidmap, nexttid, cridmap, nextcrid) (Fork old) =+    let tidmap' = I.insert (fromId old) nexttid tidmap+        nexttid' = nexttid + 1+    in ((tidmap', nexttid', cridmap, nextcrid), Fork (toId nexttid))+  updateAction (tidmap, nexttid, cridmap, nextcrid) (ForkOS old) =+    let tidmap' = I.insert (fromId old) nexttid tidmap+        nexttid' = nexttid + 1+    in ((tidmap', nexttid', cridmap, nextcrid), ForkOS (toId nexttid))+  updateAction s@(tidmap, _, _, _) (PutMVar mvid olds) =+    (s, PutMVar mvid (map (renumbered tidmap) olds))+  updateAction s@(tidmap, _, _, _) (TryPutMVar mvid b olds) =+    (s, TryPutMVar mvid b (map (renumbered tidmap) olds))+  updateAction s@(tidmap, _, _, _) (TakeMVar mvid olds) =+    (s, TakeMVar mvid (map (renumbered tidmap) olds))+  updateAction s@(tidmap, _, _, _) (TryTakeMVar mvid b olds) =+    (s, TryTakeMVar mvid b (map (renumbered tidmap) olds))+  updateAction (tidmap, nexttid, cridmap, nextcrid) (NewCRef old) =+    let cridmap' = I.insert (fromId old) nextcrid cridmap+        nextcrid' = nextcrid + 1+    in ((tidmap, nexttid, cridmap', nextcrid'), NewCRef (toId nextcrid))+  updateAction s@(_, _, cridmap, _) (ReadCRef old) =+    (s, ReadCRef (renumbered cridmap old))+  updateAction s@(_, _, cridmap, _) (ReadCRefCas old) =+    (s, ReadCRefCas (renumbered cridmap old))+  updateAction s@(_, _, cridmap, _) (ModCRef old) =+    (s, ModCRef (renumbered cridmap old))+  updateAction s@(_, _, cridmap, _) (ModCRefCas old) =+    (s, ModCRefCas (renumbered cridmap old))+  updateAction s@(_, _, cridmap, _) (WriteCRef old) =+    (s, WriteCRef (renumbered cridmap old))+  updateAction s@(_, _, cridmap, _) (CasCRef old b) =+    (s, CasCRef (renumbered cridmap old) b)+  updateAction s@(tidmap, _, _, _) (STM tas olds) =+    (s, STM tas (map (renumbered tidmap) olds))+  updateAction s@(tidmap, _, _, _) (ThrowTo old) =+    (s, ThrowTo (renumbered tidmap old))+  updateAction s@(tidmap, _, _, _) (BlockedThrowTo old) =+    (s, BlockedThrowTo (renumbered tidmap old))+  updateAction s act = (s, act)++  renumbered :: (Coercible a Id, Coercible Id a) => I.IntMap Int -> a -> a+  renumbered idmap id_ = toId $ I.findWithDefault (fromId id_) (fromId id_) idmap++-------------------------------------------------------------------------------+-- * Utilities++-- | Helper function for constructing IDs of any sort.+toId :: Coercible Id a => Int -> a+toId = coerce . Id Nothing++-- | Helper function for deconstructing IDs of any sort.+fromId :: Coercible a Id => a -> Int+fromId a = let (Id _ id_) = coerce a in id_
Test/DejaFu/SCT/Internal/DPOR.hs view
@@ -152,7 +152,8 @@  -- | Add a new trace to the stack.  This won't work if to-dos aren't explored depth-first. incorporateTrace-  :: Bool+  :: MemType+  -> Bool   -- ^ Whether the \"to-do\" point which was used to create this new   -- execution was conservative or not.   -> Trace@@ -160,10 +161,10 @@   -- and the action performed.   -> DPOR   -> DPOR-incorporateTrace conservative trace dpor0 = grow initialDepState (initialDPORThread dpor0) trace dpor0 where+incorporateTrace memtype conservative trace dpor0 = grow initialDepState (initialDPORThread dpor0) trace dpor0 where   grow state tid trc@((d, _, a):rest) dpor =     let tid'   = tidOf tid d-        state' = updateDepState state tid' a+        state' = updateDepState memtype state tid' a     in case dporNext dpor of          Just (t, child)            | t == tid' ->@@ -184,7 +185,7 @@    -- Construct a new subtree corresponding to a trace suffix.   subtree state tid sleep ((_, _, a):rest) = validateDPOR "incorporateTrace (subtree)" $-    let state' = updateDepState state tid a+    let state' = updateDepState memtype state tid a         sleep' = M.filterWithKey (\t a' -> not $ dependent state' tid a t a') sleep     in DPOR         { dporRunnable = S.fromList $ case rest of@@ -218,7 +219,8 @@ -- runnable, a dependency is imposed between this final action and -- everything else. findBacktrackSteps-  :: BacktrackFunc+  :: MemType+  -> BacktrackFunc   -- ^ Backtracking function. Given a list of backtracking points, and   -- a thread to backtrack to at a specific point in that list, add   -- the new backtracking points. There will be at least one: this@@ -235,12 +237,12 @@   -> Trace   -- ^ The execution trace.   -> [BacktrackStep]-findBacktrackSteps backtrack boundKill = go initialDepState S.empty initialThread [] . F.toList where+findBacktrackSteps memtype backtrack boundKill = go initialDepState S.empty initialThread [] . F.toList where   -- Walk through the traces one step at a time, building up a list of   -- new backtracking points.   go state allThreads tid bs ((e,i):is) ((d,_,a):ts) =     let tid' = tidOf tid d-        state' = updateDepState state tid' a+        state' = updateDepState memtype state tid' a         this = BacktrackStep           { bcktThreadid   = tid'           , bcktDecision   = d@@ -438,18 +440,19 @@ -- yielded. Furthermore, threads which /will/ yield are ignored in -- preference of those which will not. dporSched-  :: IncrementalBoundFunc k+  :: MemType+  -> IncrementalBoundFunc k   -- ^ Bound function: returns true if that schedule prefix terminated   -- with the lookahead decision fits within the bound.   -> Scheduler (DPORSchedState k)-dporSched boundf = Scheduler $ \prior threads s ->+dporSched memtype boundf = Scheduler $ \prior threads s ->   let     -- The next scheduler state     nextState rest = s       { schedBPoints  = schedBPoints s |> (restrictToBound fst threads', rest)       , schedDepState = nextDepState       }-    nextDepState = let ds = schedDepState s in maybe ds (uncurry $ updateDepState ds) prior+    nextDepState = let ds = schedDepState s in maybe ds (uncurry $ updateDepState memtype ds) prior      -- Pick a new thread to run, not considering bounds. Choose the     -- current thread if available and it hasn't just yielded,@@ -688,19 +691,20 @@  -- | Update the dependency state with the action that has just -- happened.-updateDepState :: DepState -> ThreadId -> ThreadAction -> DepState-updateDepState depstate tid act = DepState-  { depCRState   = updateCRState       act $ depCRState   depstate-  , depMVState   = updateMVState       act $ depMVState   depstate-  , depMaskState = updateMaskState tid act $ depMaskState depstate+updateDepState :: MemType -> DepState -> ThreadId -> ThreadAction -> DepState+updateDepState memtype depstate tid act = DepState+  { depCRState   = updateCRState memtype act $ depCRState   depstate+  , depMVState   = updateMVState         act $ depMVState   depstate+  , depMaskState = updateMaskState tid   act $ depMaskState depstate   }  -- | Update the @CRef@ buffer state with the action that has just -- happened.-updateCRState :: ThreadAction -> Map CRefId Bool -> Map CRefId Bool-updateCRState (CommitCRef _ r) = M.delete r-updateCRState (WriteCRef    r) = M.insert r True-updateCRState ta+updateCRState :: MemType -> ThreadAction -> Map CRefId Bool -> Map CRefId Bool+updateCRState SequentialConsistency _ = const M.empty+updateCRState _ (CommitCRef _ r) = M.delete r+updateCRState _ (WriteCRef    r) = M.insert r True+updateCRState _ ta   | isBarrier $ simplifyAction ta = const M.empty   | otherwise = id 
Test/DejaFu/Schedule.hs view
@@ -1,6 +1,6 @@ -- | -- Module      : Test.DejaFu.Schedule--- Copyright   : (c) 2016--2017 Michael Walker+-- Copyright   : (c) 2016--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental
Test/DejaFu/Settings.hs view
@@ -150,6 +150,44 @@    , learlyExit +  -- ** Representative traces++  -- | There may be many different execution traces which give rise to+  -- the same result, but some traces can be more complex than others.+  --+  -- By supplying an equality predicate on results, all but the+  -- simplest trace for each distinct result can be thrown away.+  --+  -- __Slippage:__ Just comparing results can lead to different errors+  -- which happen to have the same result comparing as equal.  For+  -- example, all deadlocks have the same result (@Left Deadlock@),+  -- but may have different causes.  See issue @#241@.++  , lequality++  -- ** Trace simplification++  -- | There may be many ways to reveal the same bug, and dejafu is+  -- not guaranteed to find the simplest way first.  This is+  -- particularly problematic with random testing, where the schedules+  -- generated tend to involve a lot of context switching.+  -- Simplification produces smaller traces, which still have the same+  -- essential behaviour.+  --+  -- __Performance:__ Simplification in dejafu, unlike shrinking in+  -- most random testing tools, is quite cheap.  Simplification is+  -- guaranteed to preserve semantics, so the test case does not need+  -- to be re-run repeatedly during the simplification process.  The+  -- test case is re-run only /once/, after the process completes, for+  -- implementation reasons.+  --+  -- Concurrency tests can be rather large, however.  So+  -- simplification is disabled by default, and it is /highly/+  -- recommended to also use 'lequality', to reduce the number of+  -- traces to simplify.++  , lsimplify+   -- ** Debug output    -- | You can opt to receive debugging messages by setting debugging@@ -160,6 +198,12 @@   , ldebugShow   , ldebugPrint +  -- | The debugging output includes both recoverable errors and+  -- informative messages.  Those recoverable errors can be made fatal+  -- instead.++  , ldebugFatal+   -- * Lens helpers   , get   , set@@ -196,7 +240,10 @@   , _discard = Nothing   , _debugShow = Nothing   , _debugPrint = Nothing+  , _debugFatal = False   , _earlyExit = Nothing+  , _equality = Nothing+  , _simplify = False   }  -------------------------------------------------------------------------------@@ -368,6 +415,24 @@ learlyExit afb s = (\b -> s {_earlyExit = b}) <$> afb (_earlyExit s)  -------------------------------------------------------------------------------+-- Representative traces++-- | A lens into the equality predicate.+--+-- @since 1.3.2.0+lequality :: Lens' (Settings n a) (Maybe (a -> a -> Bool))+lequality afb s = (\b -> s {_equality = b}) <$> afb (_equality s)++-------------------------------------------------------------------------------+-- Simplification++-- | A lens into the simplify flag.+--+-- @since 1.3.2.0+lsimplify :: Lens' (Settings n a) Bool+lsimplify afb s = (\b -> s {_simplify = b}) <$> afb (_simplify s)++------------------------------------------------------------------------------- -- Debug output  -- | A lens into the debug 'show' function.@@ -381,6 +446,12 @@ -- @since 1.2.0.0 ldebugPrint :: Lens' (Settings n a) (Maybe (String -> n ())) ldebugPrint afb s = (\b -> s {_debugPrint = b}) <$> afb (_debugPrint s)++-- | A lens into the make-recoverable-errors-fatal flag.+--+-- @since 1.3.2.0+ldebugFatal :: Lens' (Settings n a) Bool+ldebugFatal afb s = (\b -> s {_debugFatal = b}) <$> afb (_debugFatal s)  ------------------------------------------------------------------------------- -- Lens helpers
Test/DejaFu/Utils.hs view
@@ -1,6 +1,6 @@ -- |--- Module      : Test.DejaFu.utils--- Copyright   : (c) 2017 Michael Walker+-- Module      : Test.DejaFu.Utils+-- Copyright   : (c) 2017--2018 Michael Walker -- License     : MIT -- Maintainer  : Michael Walker <mike@barrucadu.co.uk> -- Stability   : experimental@@ -18,6 +18,13 @@  ------------------------------------------------------------------------------- -- * Traces++-- | Turn a 'Trace' into an abbreviated form.+--+-- @since 1.3.2.0+toTIdTrace :: Trace -> [(ThreadId, ThreadAction)]+toTIdTrace =+  tail . scanl (\(t, _) (d, _, a) -> (tidOf t d, a)) (initialThread, undefined)  -- | Pretty-print a trace, including a key of the thread IDs (not -- including thread 0). Each line of the key is indented by two
dejafu.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                dejafu-version:             1.3.1.0+version:             1.3.2.0 synopsis:            A library for unit-testing concurrent programs.  description:@@ -20,7 +20,7 @@ license-file:        LICENSE author:              Michael Walker maintainer:          mike@barrucadu.co.uk-copyright:           (c) 2015--2017 Michael Walker+copyright:           (c) 2015--2018 Michael Walker category:            Concurrency build-type:          Simple extra-source-files:  README.markdown CHANGELOG.rst@@ -33,7 +33,7 @@ source-repository this   type:     git   location: https://github.com/barrucadu/dejafu.git-  tag:      dejafu-1.3.1.0+  tag:      dejafu-1.3.2.0  library   exposed-modules:     Test.DejaFu@@ -52,6 +52,7 @@                      , Test.DejaFu.Conc.Internal.STM                      , Test.DejaFu.Conc.Internal.Threading                      , Test.DejaFu.Internal+                     , Test.DejaFu.SCT.Internal                      , Test.DejaFu.SCT.Internal.DPOR                      , Test.DejaFu.SCT.Internal.Weighted