{-# LANGUAGE UndecidableInstances #-} module Test.Framework.Core where import Test.Framework.Improving import Test.Framework.Options import Control.Concurrent.MVar -- | Something like the result of a test: works in concert with 'Testlike'. -- The type parameters are the type that is used for progress reports and the -- type of the final output of the test respectively. class (Show i, Show r) => TestResultlike i r | r -> i where testSucceeded :: r -> Bool -- | Something test-like in its behaviour. The type parameters are the type that -- is used for progress reports, the type of the final output of the test and the -- data type encapsulating the whole potential to do a test respectively. class TestResultlike i r => Testlike i r t | t -> i r, r -> i where runTest :: CompleteTestOptions -> t -> IO (i :~> r, IO ()) testTypeName :: t -> TestTypeName -- | Test names or descriptions. These are shown to the user type TestName = String -- | The name of a type of test, such as "Properties" or "Test Cases". Tests of -- types of the same names will be grouped together in the test run summary. type TestTypeName = String -- | Main test data type: builds up a list of tests to be run. Users should use the -- utility functions in e.g. the test-framework-hunit and test-framework-quickcheck -- packages to create instances of 'Test', and then build them up into testsuites -- by using 'testGroup' and lists. -- -- For an example of how to use test-framework, please see -- data Test = forall i r t. Testlike i r t => Test TestName t -- ^ A single test of some particular type | TestGroup TestName [Test] -- ^ Assemble a number of tests into a cohesive group | PlusTestOptions TestOptions Test -- ^ Add some options to child tests | BuildTest (IO Test) -- ^ Convenience for creating tests from an 'IO' action -- | Assemble a number of tests into a cohesive group testGroup :: TestName -> [Test] -> Test testGroup = TestGroup -- | Add some options to child tests plusTestOptions :: TestOptions -> Test -> Test plusTestOptions = PlusTestOptions -- | Convenience for creating tests from an 'IO' action buildTest :: IO Test -> Test buildTest = BuildTest data MutuallyExcluded t = ME (MVar ()) t -- This requires UndecidableInstances, but I think it can't be made inconsistent? instance Testlike i r t => Testlike i r (MutuallyExcluded t) where runTest cto (ME mvar x) = withMVar mvar $ \() -> runTest cto x testTypeName ~(ME _ x) = testTypeName x -- | Mark all tests in this portion of the tree as mutually exclusive, so only one runs at a time {-# NOINLINE mutuallyExclusive #-} mutuallyExclusive :: Test -> Test mutuallyExclusive init_t = BuildTest $ do mvar <- newMVar () let go (Test tn t) = Test tn (ME mvar t) go (TestGroup tn ts) = TestGroup tn (map go ts) go (PlusTestOptions to t) = PlusTestOptions to (go t) go (BuildTest build) = BuildTest (fmap go build) return (go init_t)