module Test.Framework.Runners.Core (
RunTest(..), RunningTest, SomeImproving(..), FinishedTest, runTests,
TestRunner(..), runTestTree
) where
import Test.Framework.Core
import Test.Framework.Improving
import Test.Framework.Options
import Test.Framework.Runners.Options
import Test.Framework.Runners.TestPattern
import Test.Framework.Runners.ThreadPool
import Test.Framework.Seed
import Test.Framework.Utilities
import Control.Concurrent.MVar
import Control.Exception (mask, finally, onException)
import Control.Monad
import Data.Maybe
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid
#endif
import Data.Typeable
data RunTest a = RunTest TestName TestTypeName a
| RunTestGroup TestName [RunTest a]
deriving (Show)
data SomeImproving = forall i r. TestResultlike i r => SomeImproving (i :~> r)
type RunningTest = RunTest SomeImproving
type FinishedTest = RunTest (String, Bool)
runTests :: CompleteRunnerOptions
-> [Test]
-> IO [RunningTest]
runTests ropts tests = do
let test_patterns = unK $ ropt_test_patterns ropts
test_options = unK $ ropt_test_options ropts
(run_tests, actions) <- runTests' $ map (runTestTree test_options test_patterns) tests
_ <- executeOnPool (unK $ ropt_threads ropts) actions
return run_tests
class TestRunner b where
runSimpleTest :: (Testlike i r t, Typeable t) => TestOptions -> TestName -> t -> b
skipTest :: b
runIOTest :: IO (b, IO ()) -> b
runGroup :: TestName -> [b] -> b
runTestTree
:: TestRunner b
=> TestOptions
-> [TestPattern]
-> Test
-> b
runTestTree initialOpts pats topTest = go initialOpts [] topTest
where
go opts path t = case t of
Test name testlike ->
if null pats || any (`testPatternMatches` (path ++ [name])) pats
then runSimpleTest opts name testlike
else skipTest
TestGroup name tests ->
let path' = path ++ [name]
in runGroup name $ map (go opts path') tests
PlusTestOptions extra_topts test -> go (opts `mappend` extra_topts) path test
BuildTestBracketed build ->
runIOTest $ onLeft (go opts path) `fmap` build
newtype StdRunner = StdRunner { run :: IO (Maybe (RunningTest, [IO ()])) }
instance TestRunner StdRunner where
runSimpleTest topts name testlike = StdRunner $ do
(result, action) <- runTest (completeTestOptions topts) testlike
return (Just (RunTest name (testTypeName testlike) (SomeImproving result), [action]))
skipTest = StdRunner $ return Nothing
runGroup name tests = StdRunner $ do
(results, actions) <- runTests' tests
return $ if null results then Nothing else Just ((RunTestGroup name results), actions)
runIOTest ioTest = StdRunner $ mask $ \restore -> ioTest >>= \(StdRunner test, cleanup) -> do
mb_res <- restore test `onException` cleanup
case mb_res of
Nothing -> cleanup >> return Nothing
Just (run_test, actions) -> do
(mvars, actions') <- liftM unzip $ forM actions $ \action -> do
mvar <- newEmptyMVar
return (mvar, action `finally` putMVar mvar ())
return $ Just (run_test, actions' ++ [(cleanup >> mapM_ takeMVar mvars)])
runTests' :: [StdRunner] -> IO ([RunningTest], [IO ()])
runTests' = fmap (onRight concat . unzip . catMaybes) . mapM run
completeTestOptions :: TestOptions -> CompleteTestOptions
completeTestOptions to = TestOptions {
topt_seed = K $ topt_seed to `orElse` RandomSeed,
topt_maximum_generated_tests = K $ topt_maximum_generated_tests to `orElse` 100,
topt_maximum_unsuitable_generated_tests = K $ topt_maximum_unsuitable_generated_tests to `orElse` 1000,
topt_maximum_test_size = K $ topt_maximum_test_size to `orElse` 100,
topt_maximum_test_depth = K $ topt_maximum_test_depth to `orElse` 5,
topt_timeout = K $ topt_timeout to `orElse` Nothing
}