module Test.Runner.Driver ( runTests, runTestsParallel,
runTestsWithArgs,
runTestsParallelWithArgs,
Result(..),
) where
import Data.Maybe ( isJust, isNothing, fromJust )
import Control.Concurrent ( forkIO )
import Control.Concurrent.STM ( newTVar, readTVar, writeTVar, atomically,
retry, TVar )
import Test.QuickCheck ( Args(..), stdArgs )
import Test.Runner.Backends
run_showing_name :: Args -> (String, TestRunnerTest) -> IO (Maybe String)
run_showing_name qcArgs (name, TestRunnerTest t) = do
putStr (name ++ ": ")
r <- runWithArgs qcArgs t
putStrLn (if isNothing r then "OK" else "FAIL!")
return r
data Result = Result { numPassed :: Int
, failures :: [(String, String)]
} deriving (Show, Eq, Ord)
runTests :: [(String, TestRunnerTest)] -> IO Result
runTests = runTestsWithArgs stdArgs
runTestsWithArgs :: Args -> [(String, TestRunnerTest)] -> IO Result
runTestsWithArgs qcArgs namedTests = do
results <- mapM (run_showing_name qcArgs) namedTests
let namedResults = zip names results
passed = length (filter isNothing results)
failed = map sndFromJust $ filter (isJust . snd) namedResults
return (Result passed failed)
where (names, _) = unzip namedTests
sndFromJust (name, result) = (name, fromJust result)
data RunnerState = RunnerState
{ testsToDo :: [(String, TestRunnerTest)]
, passedTests :: [String]
, failedTests :: [(String, String)]
, numDone :: Int
}
initial_runner_state :: [(String, TestRunnerTest)] -> RunnerState
initial_runner_state ts = RunnerState ts [] [] 0
runTestsParallel :: Int
-> [(String, TestRunnerTest)]
-> IO Result
runTestsParallel n namedTests = runTestsParallelWithArgs n stdArgs namedTests
runTestsParallelWithArgs :: Int
-> Args
-> [(String, TestRunnerTest)]
-> IO Result
runTestsParallelWithArgs n qcArgs namedTests = do
let numToDo = length namedTests
stateRef <- atomically (newTVar (initial_runner_state namedTests))
sequence_ (replicate n (forkIO (test_runner_thread qcArgs stateRef)))
atomically $ do
state <- readTVar stateRef
if numDone state == numToDo
then return $ Result (length (passedTests state)) (failedTests state)
else retry
test_runner_thread :: Args -> TVar RunnerState -> IO ()
test_runner_thread qcArgs stateRef = do
nextTest <- getNextTest
case nextTest of
Just t -> run_one_test qcArgs stateRef t >> test_runner_thread qcArgs stateRef
Nothing -> return ()
where getNextTest = atomically $ do
state <- readTVar stateRef
let tests_to_do = testsToDo state
case tests_to_do of
doNow:doLater -> do writeTVar stateRef (state { testsToDo = doLater })
return (Just doNow)
[] -> return Nothing
run_one_test :: Args -> TVar RunnerState -> (String, TestRunnerTest) -> IO ()
run_one_test qcArgs stateRef (name, TestRunnerTest t) = do
result <- runWithArgs qcArgs t
putStrLn (name ++ ": " ++ (if isNothing result then "OK" else "FAIL!"))
atomically $ do
state <- readTVar stateRef
let ps = passedTests state
fs = failedTests state
state' = state { numDone = numDone state + 1 }
state'' = case result of
Nothing -> state' { passedTests = name : ps }
Just msg -> state' { failedTests = (name, msg) : fs }
writeTVar stateRef state''