-- Copyright (C) 2009 Reinier Lamers -- -- This program is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; either version 2, or (at your option) -- any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program; see the file COPYING. If not, write to -- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -- Boston, MA 02110-1301, USA. -- | Test.Runner.Frontend contains the code for the prefabricated unit test -- executable, like command-line argument parsing and handling. module Test.Runner.Frontend ( testRunnerMain ) where import System.IO ( hPutStrLn, stderr, hSetBuffering, stdout, BufferMode ( NoBuffering ) ) import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), getOpt, usageInfo, ArgOrder(Permute) ) import System.Environment ( getArgs ) import System.Random ( StdGen ) import System.Exit ( exitWith, ExitCode(ExitSuccess) ) import Data.Maybe ( isJust ) import Text.Regex ( mkRegex, matchRegex ) import Test.QuickCheck ( Args(..), stdArgs ) import Test.Runner.Backends ( TestRunnerTest(..) ) import Test.Runner.Driver ( runTestsParallelWithArgs, Result(..) ) -- | testRunnerMain is intended to be used as the main function of a unit test -- program. It takes as an argument the complete list of unit tests for a -- package. testRunnerMain :: [(String, TestRunnerTest)] -> IO () testRunnerMain tests = do hSetBuffering stdout NoBuffering maybeFlags <- parse_args `fmap` getArgs case maybeFlags of Nothing -> do hPutStrLn stderr "testrunner: Unrecognized arguments on command line" printUsageAndDie Just flags -> let showHelp = not (null [x | x <- flags, case x of ShowHelp -> True _ -> False]) in if showHelp then printUsageAndDie else runWithFlags flags tests printUsageAndDie :: IO () printUsageAndDie = do putStr (usageInfo "unit - run darcs unit tests" opts) exitWith ExitSuccess -- | Runs a set of tests according to the given command-line flags. The -- @ShowHelp@ flag is assumed to have been handled already and is ignored by -- this function. runWithFlags :: [UnitFlag] -> [(String, TestRunnerTest)] -> IO () runWithFlags flags tests = runAndShowTests numThreads qcArgs matchingTests where matchingTests = filter (isJust . (matchRegex matchEx) . fst) tests qcArgs = case replayValues of Nothing -> stdArgs Just (seed, size) -> stdArgs { replay = Just (seed, size) } numThreads | null jobsArgs = 1 | otherwise = last jobsArgs jobsArgs = [j | NumJobs j <- flags] matchEx | null matchArgs = mkRegex ".*" -- matches any string | otherwise = mkRegex (last matchArgs) matchArgs = [ex | Matching ex <- flags] replayValues | null replayArgs = Nothing | otherwise = Just (last replayArgs) replayArgs = [(seed, size) | QuickCheckReplay seed size <- flags] -- | Run tests in a number of threads, and give a summary after all tests have -- been run runAndShowTests :: Int -> Args -> [(String, TestRunnerTest)] -> IO () runAndShowTests numThreads qcArgs tests = do results <- runTestsParallelWithArgs numThreads qcArgs tests putStr (show (numPassed results) ++ " tests passed.") if not (null (failures results)) then do putStrLn " Failing tests:" mapM_ (putStr . formatFailure) (failures results) else putChar '\n' where formatFailure (name, output) = " " ++ name ++ ":\n" ++ ((unlines . map (" "++) . lines) output) -- | Data type describing command line flag data UnitFlag = ShowHelp | NumJobs Int | Matching String | QuickCheckReplay StdGen Int -- | Parse a string to UnitFlag that describes the number of jobs to run. Exits -- in case of malformed input. parse_numjobs :: String -> UnitFlag parse_numjobs s = case reads s of [(x,"")] -> NumJobs x _ -> error "Invalid number of Haskell threads given" parse_qc_replay :: String -> UnitFlag parse_qc_replay s = QuickCheckReplay seed size where (seedString, sizeString) = break (==',') s seed = case reads seedString :: [(StdGen, String)] of [(seed', "")] -> seed' _ -> error "Invalid QuickCheck seed given" size = case sizeString of [] -> error "Empty QuickCheck size given" sizeString' -> case reads (tail sizeString') of [(size', "")] -> size' _ -> error "Invalid QuickCheck size given" -- | List of possible command line options opts :: [OptDescr UnitFlag] opts = [Option ['j'] ["jobs"] (ReqArg parse_numjobs "NUM") "Number of Haskell threads to run unit tests (you need +RTS -N too)" ,Option ['m'] ["matching"] (ReqArg Matching "REGEX") "Run only tests matching the given POSIX regular expression" ,Option ['r'] ["quickcheck-replay"] (ReqArg parse_qc_replay "SEED,SIZE") "Run QuickCheck tests once with given seed and size" ,Option ['h'] ["help"] (NoArg ShowHelp) "Show usage information and exit" ] -- | Parses the command line options with @getOpt@ and returns @Nothing@ in the -- case of an invalid command line, or the parsed options otherwise. parse_args :: [String] -- ^ The list of command line args as from -- @getArgs@ -> Maybe [UnitFlag] -- ^ List of options represented by @UnitFlag@ -- values parse_args args | not (null nonopts) || not (null errs) = Nothing | otherwise = Just vals where (vals, nonopts, errs) = getOpt Permute opts args