2015-10-08 14:05:59 +03:00
|
|
|
{-# LANGUAGE RankNTypes #-}
|
|
|
|
|
|
|
|
-- | This module allows using Deja Fu predicates with HUnit to test
|
|
|
|
-- the behaviour of concurrent systems.
|
|
|
|
module Test.HUnit.DejaFu
|
|
|
|
( -- * Testing
|
|
|
|
testAuto
|
|
|
|
, testDejafu
|
|
|
|
, testDejafus
|
|
|
|
, testAutoIO
|
|
|
|
, testDejafuIO
|
|
|
|
, testDejafusIO
|
|
|
|
|
2015-11-03 22:20:47 +03:00
|
|
|
-- * Testing under Alternative Memory Models
|
2015-10-08 14:05:59 +03:00
|
|
|
, MemType(..)
|
|
|
|
, testAuto'
|
|
|
|
, testAutoIO'
|
2015-10-08 17:45:05 +03:00
|
|
|
, testDejafu'
|
2015-10-08 14:05:59 +03:00
|
|
|
, testDejafus'
|
2015-10-08 17:45:05 +03:00
|
|
|
, testDejafuIO'
|
2015-10-08 14:05:59 +03:00
|
|
|
, testDejafusIO'
|
|
|
|
) where
|
|
|
|
|
|
|
|
import Test.DejaFu
|
|
|
|
import Test.DejaFu.Deterministic (Conc, showFail, showTrace)
|
|
|
|
import Test.DejaFu.Deterministic.IO (ConcIO)
|
2015-10-27 01:57:35 +03:00
|
|
|
import Test.DejaFu.SCT (sctPFBound, sctPFBoundIO)
|
2015-10-08 23:15:46 +03:00
|
|
|
import Test.HUnit (Test(..), assertString)
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Automated testing
|
|
|
|
|
|
|
|
-- | Automatically test a computation. In particular, look for
|
|
|
|
-- deadlocks, uncaught exceptions, and multiple return values.
|
|
|
|
--
|
|
|
|
-- This uses the 'Conc' monad for testing, which is an instance of
|
|
|
|
-- 'MonadConc'. If you need to test something which also uses
|
|
|
|
-- 'MonadIO', use 'testAutoIO'.
|
|
|
|
testAuto :: (Eq a, Show a)
|
|
|
|
=> (forall t. Conc t a)
|
|
|
|
-- ^ The computation to test
|
2015-10-08 23:09:48 +03:00
|
|
|
-> Test
|
2015-11-03 22:20:47 +03:00
|
|
|
testAuto = testAuto' TotalStoreOrder
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Variant of 'testAuto' which tests a computation under a given
|
|
|
|
-- memory model.
|
|
|
|
testAuto' :: (Eq a, Show a)
|
|
|
|
=> MemType
|
|
|
|
-- ^ The memory model to use for non-synchronised @CRef@ operations.
|
|
|
|
-> (forall t. Conc t a)
|
|
|
|
-- ^ The computation to test
|
2015-10-08 23:09:48 +03:00
|
|
|
-> Test
|
2015-10-27 01:57:35 +03:00
|
|
|
testAuto' memtype conc = testDejafus' memtype 2 5 conc autocheckCases
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Variant of 'testAuto' for computations which do 'IO'.
|
2015-11-07 20:19:40 +03:00
|
|
|
testAutoIO :: (Eq a, Show a) => ConcIO a -> Test
|
2015-11-03 22:20:47 +03:00
|
|
|
testAutoIO = testAutoIO' TotalStoreOrder
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Variant of 'testAuto'' for computations which do 'IO'.
|
2015-11-07 20:19:40 +03:00
|
|
|
testAutoIO' :: (Eq a, Show a) => MemType -> ConcIO a -> Test
|
2015-10-27 01:57:35 +03:00
|
|
|
testAutoIO' memtype concio = testDejafusIO' memtype 2 5 concio autocheckCases
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Predicates for the various autocheck functions.
|
2015-10-08 17:15:13 +03:00
|
|
|
autocheckCases :: Eq a => [(String, Predicate a)]
|
|
|
|
autocheckCases =
|
|
|
|
[("Never Deadlocks", deadlocksNever)
|
|
|
|
, ("No Exceptions", exceptionsNever)
|
|
|
|
, ("Consistent Result", alwaysSame)
|
|
|
|
]
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Manual testing
|
|
|
|
|
|
|
|
-- | Check that a predicate holds.
|
2015-10-25 20:05:40 +03:00
|
|
|
testDejafu :: Show a
|
2015-10-08 14:05:59 +03:00
|
|
|
=> (forall t. Conc t a)
|
|
|
|
-- ^ The computation to test
|
2015-10-08 17:15:13 +03:00
|
|
|
-> String
|
|
|
|
-- ^ The name of the test.
|
2015-10-08 14:05:59 +03:00
|
|
|
-> Predicate a
|
|
|
|
-- ^ The predicate to check
|
2015-10-08 23:09:48 +03:00
|
|
|
-> Test
|
2015-11-03 22:20:47 +03:00
|
|
|
testDejafu = testDejafu' TotalStoreOrder 2 5
|
2015-10-08 17:45:05 +03:00
|
|
|
|
|
|
|
-- | Variant of 'testDejafu' which takes a memory model and
|
|
|
|
-- pre-emption bound.
|
2015-10-25 20:05:40 +03:00
|
|
|
testDejafu' :: Show a
|
2015-10-08 17:45:05 +03:00
|
|
|
=> MemType
|
|
|
|
-- ^ The memory model to use for non-synchronised @CRef@ operations.
|
|
|
|
-> Int
|
|
|
|
-- ^ The maximum number of pre-emptions to allow in a single
|
|
|
|
-- execution
|
2015-10-27 01:57:35 +03:00
|
|
|
-> Int
|
|
|
|
-- ^ The maximum difference between the number of yield operations
|
|
|
|
-- across all threads.
|
2015-10-08 17:45:05 +03:00
|
|
|
-> (forall t. Conc t a)
|
|
|
|
-- ^ The computation to test
|
|
|
|
-> String
|
|
|
|
-- ^ The name of the test.
|
|
|
|
-> Predicate a
|
|
|
|
-- ^ The predicate to check
|
2015-10-08 23:09:48 +03:00
|
|
|
-> Test
|
2015-10-27 01:57:35 +03:00
|
|
|
testDejafu' memtype pb fb conc name p = testDejafus' memtype pb fb conc [(name, p)]
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Variant of 'testDejafu' which takes a collection of predicates to
|
|
|
|
-- test. This will share work between the predicates, rather than
|
|
|
|
-- running the concurrent computation many times for each predicate.
|
2015-10-25 20:05:40 +03:00
|
|
|
testDejafus :: Show a
|
2015-10-08 14:05:59 +03:00
|
|
|
=> (forall t. Conc t a)
|
|
|
|
-- ^ The computation to test
|
2015-10-08 17:15:13 +03:00
|
|
|
-> [(String, Predicate a)]
|
|
|
|
-- ^ The list of predicates (with names) to check
|
2015-10-08 23:09:48 +03:00
|
|
|
-> Test
|
2015-11-03 22:20:47 +03:00
|
|
|
testDejafus = testDejafus' TotalStoreOrder 2 5
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Variant of 'testDejafus' which takes a memory model and pre-emption
|
|
|
|
-- bound.
|
2015-10-25 20:05:40 +03:00
|
|
|
testDejafus' :: Show a
|
2015-10-08 14:05:59 +03:00
|
|
|
=> MemType
|
|
|
|
-- ^ The memory model to use for non-synchronised @CRef@ operations.
|
|
|
|
-> Int
|
|
|
|
-- ^ The maximum number of pre-emptions to allow in a single
|
|
|
|
-- execution
|
2015-10-27 01:57:35 +03:00
|
|
|
-> Int
|
|
|
|
-- ^ The maximum difference between the number of yield operations
|
|
|
|
-- across all threads.
|
2015-10-08 14:05:59 +03:00
|
|
|
-> (forall t. Conc t a)
|
|
|
|
-- ^ The computation to test
|
2015-10-08 17:15:13 +03:00
|
|
|
-> [(String, Predicate a)]
|
|
|
|
-- ^ The list of predicates (with names) to check
|
2015-10-08 23:09:48 +03:00
|
|
|
-> Test
|
2015-10-08 23:15:46 +03:00
|
|
|
testDejafus' = test
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Variant of 'testDejafu' for computations which do 'IO'.
|
2015-11-07 20:19:40 +03:00
|
|
|
testDejafuIO :: Show a => ConcIO a -> String -> Predicate a -> Test
|
2015-11-03 22:20:47 +03:00
|
|
|
testDejafuIO = testDejafuIO' TotalStoreOrder 2 5
|
2015-10-08 17:45:05 +03:00
|
|
|
|
|
|
|
-- | Variant of 'testDejafu'' for computations which do 'IO'.
|
2015-11-07 20:19:40 +03:00
|
|
|
testDejafuIO' :: Show a => MemType -> Int -> Int -> ConcIO a -> String -> Predicate a -> Test
|
2015-10-27 01:57:35 +03:00
|
|
|
testDejafuIO' memtype pb fb concio name p = testDejafusIO' memtype pb fb concio [(name, p)]
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Variant of 'testDejafus' for computations which do 'IO'.
|
2015-11-07 20:19:40 +03:00
|
|
|
testDejafusIO :: Show a => ConcIO a -> [(String, Predicate a)] -> Test
|
2015-11-03 22:20:47 +03:00
|
|
|
testDejafusIO = testDejafusIO' TotalStoreOrder 2 5
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Variant of 'dejafus'' for computations which do 'IO'.
|
2015-11-07 20:19:40 +03:00
|
|
|
testDejafusIO' :: Show a => MemType -> Int -> Int -> ConcIO a -> [(String, Predicate a)] -> Test
|
2015-10-08 23:15:46 +03:00
|
|
|
testDejafusIO' = testio
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- HUnit integration
|
|
|
|
|
2015-10-08 23:15:46 +03:00
|
|
|
-- | Produce a HUnit 'Test' from a Deja Fu test.
|
2015-10-27 01:57:35 +03:00
|
|
|
test :: Show a => MemType -> Int -> Int -> (forall t. Conc t a) -> [(String, Predicate a)] -> Test
|
|
|
|
test memtype pb fb conc tests = case map toTest tests of
|
2015-10-08 23:15:46 +03:00
|
|
|
[t] -> t
|
|
|
|
ts -> TestList ts
|
|
|
|
|
|
|
|
where
|
|
|
|
toTest (name, p) = TestLabel name . TestCase $
|
|
|
|
assertString . showErr $ p traces
|
|
|
|
|
2015-10-27 01:57:35 +03:00
|
|
|
traces = sctPFBound memtype pb fb conc
|
2015-10-08 23:15:46 +03:00
|
|
|
|
|
|
|
-- | Produce a HUnit 'Test' from an IO-using Deja Fu test.
|
2015-11-07 20:19:40 +03:00
|
|
|
testio :: Show a => MemType -> Int -> Int -> ConcIO a -> [(String, Predicate a)] -> Test
|
2015-10-27 01:57:35 +03:00
|
|
|
testio memtype pb fb concio tests = case map toTest tests of
|
2015-10-08 23:15:46 +03:00
|
|
|
[t] -> t
|
|
|
|
ts -> TestList ts
|
|
|
|
|
|
|
|
where
|
|
|
|
toTest (name, p) = TestLabel name . TestCase $ do
|
|
|
|
-- Sharing of traces probably not possible (without something
|
|
|
|
-- really unsafe) here, as 'test' doesn't allow side-effects
|
|
|
|
-- (eg, constructing an 'MVar' to share the traces after one
|
|
|
|
-- test computed them).
|
2015-10-27 01:57:35 +03:00
|
|
|
traces <- sctPFBoundIO memtype pb fb concio
|
2015-10-08 23:15:46 +03:00
|
|
|
assertString . showErr $ p traces
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
-- | Convert a test result into an error message on failure (empty
|
|
|
|
-- string on success).
|
|
|
|
showErr :: Show a => Result a -> String
|
|
|
|
showErr res
|
|
|
|
| _pass res = ""
|
2015-10-27 18:39:52 +03:00
|
|
|
| otherwise = "Failed after " ++ show (_casesChecked res) ++ " cases:\n" ++ msg ++ unlines failures ++ rest where
|
|
|
|
|
|
|
|
msg = if null (_failureMsg res) then "" else _failureMsg res ++ "\n"
|
2015-10-08 14:05:59 +03:00
|
|
|
|
|
|
|
failures = map (\(r, t) -> "\t" ++ either showFail show r ++ " " ++ showTrace t) . take 5 $ _failures res
|
|
|
|
|
|
|
|
rest = if moreThan (_failures res) 5 then "\n\t..." else ""
|
|
|
|
|
|
|
|
-- | Check if a list has more than some number of elements.
|
|
|
|
moreThan :: [a] -> Int -> Bool
|
|
|
|
moreThan [] n = n < 0
|
|
|
|
moreThan _ 0 = True
|
|
|
|
moreThan (_:xs) n = moreThan xs (n-1)
|
|
|
|
|