test-framework-0.8.2.3: src/Test/Framework/Runners/Console.hs
module Test.Framework.Runners.Console (
defaultMain, defaultMainWithArgs, defaultMainWithOpts,
SuppliedRunnerOptions, optionsDescription,
interpretArgs, interpretArgsOrExit
) where
import Test.Framework.Core
import Test.Framework.Options
import Test.Framework.Runners.Console.Run
import Test.Framework.Runners.Core
import Test.Framework.Runners.Options
import Test.Framework.Runners.Processors
import Test.Framework.Runners.Statistics
import qualified Test.Framework.Runners.XML as XML
import Test.Framework.Seed
import Test.Framework.Utilities
import Control.Monad (when)
import System.Console.GetOpt
( getOpt,
usageInfo,
ArgDescr(NoArg, ReqArg),
ArgOrder(Permute),
OptDescr(..) )
import System.Environment ( getArgs, getProgName )
import System.Exit
( exitSuccess, exitWith, ExitCode(ExitFailure, ExitSuccess) )
import System.IO ( hIsTerminalDevice, hPutStrLn, stderr, stdout )
-- | @Nothing@ signifies that usage information should be displayed.
-- @Just@ simply gives us the contribution to overall options by the command line option.
type SuppliedRunnerOptions = Maybe RunnerOptions
-- | Options understood by test-framework. This can be used to add more
-- options to the tester executable.
optionsDescription :: [OptDescr SuppliedRunnerOptions]
optionsDescription = [
Option [] ["help"]
(NoArg Nothing)
"show this help message"
] ++ map (fmap Just) [
Option ['j'] ["threads"]
(ReqArg (\t -> mempty { ropt_threads = Just (read t) }) "NUMBER")
"number of threads to use to run tests",
Option [] ["test-seed"]
(ReqArg (\t -> mempty { ropt_test_options = Just (mempty { topt_seed = Just (read t) }) }) ("NUMBER|" ++ show RandomSeed))
"default seed for test random number generator",
Option ['a'] ["maximum-generated-tests"]
(ReqArg (\t -> mempty { ropt_test_options = Just (mempty { topt_maximum_generated_tests = Just (read t) }) }) "NUMBER")
"how many automated tests something like QuickCheck should try, by default",
Option [] ["maximum-unsuitable-generated-tests"]
(ReqArg (\t -> mempty { ropt_test_options = Just (mempty { topt_maximum_unsuitable_generated_tests = Just (read t) }) }) "NUMBER")
"how many unsuitable candidate tests something like QuickCheck should endure before giving up, by default",
Option ['s'] ["maximum-test-size"]
(ReqArg (\t -> mempty {ropt_test_options = Just (mempty { topt_maximum_test_size = Just (read t) }) }) "NUMBER")
"to what size something like QuickCheck should test the properties, by default",
Option ['d'] ["maximum-test-depth"]
(ReqArg (\t -> mempty { ropt_test_options = Just (mempty { topt_maximum_test_depth = Just (read t) }) }) "NUMBER")
"to what depth something like SmallCheck should test the properties, by default",
Option ['o'] ["timeout"]
(ReqArg (\t -> mempty { ropt_test_options = Just (mempty { topt_timeout = Just (Just (secondsToMicroseconds (read t))) }) }) "NUMBER")
"how many seconds a test should be run for before giving up, by default",
Option [] ["no-timeout"]
(NoArg (mempty { ropt_test_options = Just (mempty { topt_timeout = Just Nothing }) }))
"specifies that tests should be run without a timeout, by default",
Option ['l'] ["list-tests"]
(NoArg (mempty { ropt_list_only = Just True }))
"list available tests but don't run any; useful to guide subsequent --select-tests",
Option ['t'] ["select-tests"]
(ReqArg (\t -> mempty { ropt_test_patterns = Just [read t] }) "TEST-PATTERN")
"only tests that match at least one glob pattern given by an instance of this argument will be run",
Option [] ["jxml"]
(ReqArg (\t -> mempty { ropt_xml_output = Just (Just t) }) "FILE")
"write a JUnit XML summary of the output to FILE",
Option [] ["jxml-nested"]
(NoArg (mempty { ropt_xml_nested = Just True }))
"use nested testsuites to represent groups in JUnit XML (not standards compliant)",
Option [] ["plain"]
(NoArg (mempty { ropt_color_mode = Just ColorNever }))
"do not use any ANSI terminal features to display the test run",
Option [] ["color"]
(NoArg (mempty { ropt_color_mode = Just ColorAlways }))
"use ANSI terminal features to display the test run",
Option [] ["hide-successes"]
(NoArg (mempty { ropt_hide_successes = Just True }))
"hide sucessful tests, and only show failures"
]
-- | Parse the specified command line arguments into a 'RunnerOptions' and some remaining arguments,
-- or return a reason as to why we can't.
interpretArgs :: [String] -> IO (Either String (RunnerOptions, [String]))
interpretArgs args = do
prog_name <- getProgName
let usage_header = "Usage: " ++ prog_name ++ " [OPTIONS]"
case getOpt Permute optionsDescription args of
(oas, n, []) | Just os <- sequence oas -> return $ Right (mconcat os, n)
(_, _, errs) -> return $ Left (concat errs ++ usageInfo usage_header optionsDescription)
-- | A version of 'interpretArgs' that ends the process if it fails.
interpretArgsOrExit :: [String] -> IO RunnerOptions
interpretArgsOrExit args = do
interpreted_args <- interpretArgs args
case interpreted_args of
Right (ropts, []) -> return ropts
Right (_, leftovers) -> do
hPutStrLn stderr $ "Could not understand these extra arguments: " ++ unwords leftovers
exitWith (ExitFailure 1)
Left error_message -> do
hPutStrLn stderr error_message
exitWith (ExitFailure 1)
defaultMain :: [Test] -> IO ()
defaultMain tests = do
args <- getArgs
defaultMainWithArgs tests args
-- | A version of 'defaultMain' that lets you ignore the command line arguments
-- in favour of another list of 'String's.
defaultMainWithArgs :: [Test] -> [String] -> IO ()
defaultMainWithArgs tests args = do
ropts <- interpretArgsOrExit args
defaultMainWithOpts tests ropts
-- | A version of 'defaultMain' that lets you ignore the command line arguments
-- in favour of an explicit set of 'RunnerOptions'.
defaultMainWithOpts :: [Test] -> RunnerOptions -> IO ()
defaultMainWithOpts tests ropts = do
let ropts' = completeRunnerOptions ropts
when (unK$ ropt_list_only ropts') $ do
putStr $ listTests tests
exitSuccess
-- Get a lazy list of the test results, as executed in parallel
running_tests <- runTests ropts' tests
isplain <- case unK $ ropt_color_mode ropts' of
ColorAuto -> not `fmap` hIsTerminalDevice stdout
ColorNever -> return True
ColorAlways -> return False
-- Show those test results to the user as we get them
fin_tests <- showRunTestsTop isplain (unK $ ropt_hide_successes ropts') running_tests
let test_statistics' = gatherStatistics fin_tests
-- Output XML report (if requested)
case ropt_xml_output ropts' of
K (Just file) -> XML.produceReport (unK (ropt_xml_nested ropts')) test_statistics' fin_tests >>= writeFile file
_ -> return ()
-- Set the error code depending on whether the tests succeeded or not
exitWith $ if ts_no_failures test_statistics'
then ExitSuccess
else ExitFailure 1
-- | Print out a list of available tests.
listTests :: [Test] -> String
listTests tests = "\ntest-framework: All available tests:\n"++
"====================================\n"++
concat (map (++"\n") (concatMap (showTest "") tests))
where
showTest :: String -> Test -> [String]
showTest path (Test name _testlike) = [" "++path ++ name]
showTest path (TestGroup name gtests) = concatMap (showTest (path++":"++name)) gtests
showTest path (PlusTestOptions _ test) = showTest path test
showTest path (BuildTestBracketed _) = [" "++path ++ "<created at runtime>"]
completeRunnerOptions :: RunnerOptions -> CompleteRunnerOptions
completeRunnerOptions ro = RunnerOptions {
ropt_threads = K $ ropt_threads ro `orElse` processorCount,
ropt_test_options = K $ ropt_test_options ro `orElse` mempty,
ropt_test_patterns = K $ ropt_test_patterns ro `orElse` mempty,
ropt_xml_output = K $ ropt_xml_output ro `orElse` Nothing,
ropt_xml_nested = K $ ropt_xml_nested ro `orElse` False,
ropt_color_mode = K $ ropt_color_mode ro `orElse` ColorAuto,
ropt_hide_successes = K $ ropt_hide_successes ro `orElse` False,
ropt_list_only = K $ ropt_list_only ro `orElse` False
}