{-# LANGUAGE FlexibleInstances #-} {-| This module defines types (and small auxiliary functions) for organizing tests, for configuring the execution of tests, and for representing and reporting their results. -} module Test.Framework.TestTypes ( -- * Organizing tests TestID, Assertion, Test(..), TestOptions(..), AssertionWithTestOptions(..), WithTestOptions(..), TestSuite(..), TestSort(..), TestPath(..), GenFlatTest(..), FlatTest, TestFilter, testPathToList, flatName, finalName, prefixName, defaultTestOptions, withOptions, -- * Executing tests TR, TestState(..), initTestState, TestConfig(..), TestOutput(..), -- * Reporting results ReportAllTests, ReportGlobalStart, ReportTestStart, ReportTestResult, ReportGlobalResults, TestReporter(..), emptyTestReporter, attachCallStack, CallStack, -- * Specifying results. TestResult(..), FlatTestResult, Milliseconds, RunResult(..) ) where import Test.Framework.Location import Test.Framework.Colors import Control.Monad.RWS import System.IO import Data.Maybe import qualified Data.List as List -- | An assertion is just an 'IO' action. type Assertion = IO () -- | Type for naming tests. type TestID = String -- | Type for distinguishing different sorts of tests. data TestSort = UnitTest | QuickCheckTest | BlackBoxTest deriving (Eq,Show,Read) -- | General options for tests data TestOptions = TestOptions { to_parallel :: Bool } deriving (Eq,Show,Read) -- | The default 'TestOptions' defaultTestOptions :: TestOptions defaultTestOptions = TestOptions { to_parallel = True } -- | Something with 'TestOptions' data WithTestOptions a = WithTestOptions { wto_options :: TestOptions , wto_payload :: a } deriving (Eq,Show,Read) -- | Shortcut for constructing a 'WithTestOptions' value. withOptions :: (TestOptions -> TestOptions) -> a -> WithTestOptions a withOptions f x = WithTestOptions (f defaultTestOptions) x -- | A type class for an assertion with 'TestOptions'. class AssertionWithTestOptions a where testOptions :: a -> TestOptions assertion :: a -> Assertion instance AssertionWithTestOptions (IO a) where testOptions _ = defaultTestOptions assertion io = io >> return () instance AssertionWithTestOptions (WithTestOptions (IO a)) where testOptions (WithTestOptions opts _) = opts assertion (WithTestOptions _ io) = io >> return () -- | Abstract type for tests and their results. data Test = BaseTest TestSort TestID (Maybe Location) TestOptions Assertion | CompoundTest TestSuite -- | Abstract type for test suites and their results. data TestSuite = TestSuite TestID [Test] | AnonTestSuite [Test] -- | A type denoting the hierarchical name of a test. data TestPath = TestPathBase TestID | TestPathCompound (Maybe TestID) TestPath -- | Splits a 'TestPath' into a list of test identifiers. testPathToList :: TestPath -> [Maybe TestID] testPathToList (TestPathBase i) = [Just i] testPathToList (TestPathCompound mi p) = mi : testPathToList p -- | Creates a string representation from a 'TestPath'. flatName :: TestPath -> String flatName p = flatNameFromList (testPathToList p) flatNameFromList :: [Maybe TestID] -> String flatNameFromList l = List.intercalate ":" (map (fromMaybe "") l) -- | Returns the final name of a 'TestPath' finalName :: TestPath -> String finalName (TestPathBase i) = i finalName (TestPathCompound _ p) = finalName p -- | Returns the name of the prefix of a test path. The prefix is everything except the -- last element. prefixName :: TestPath -> String prefixName path = let l = case reverse (testPathToList path) of [] -> [] (_:xs) -> reverse xs in flatNameFromList l -- | Generic type for flattened tests and their results. data GenFlatTest a = FlatTest { ft_sort :: TestSort -- ^ The sort of the test. , ft_path :: TestPath -- ^ Hierarchival path. , ft_location :: Maybe Location -- ^ Place of definition. , ft_payload :: a -- ^ A generic payload. } -- | Flattened representation of tests. type FlatTest = GenFlatTest (WithTestOptions Assertion) -- | A filter is a predicate on 'FlatTest'. If the predicate is 'True', the flat test is run. type TestFilter = FlatTest -> Bool -- | The summary result of a test. data TestResult = Pass | Pending | Fail | Error deriving (Show, Read, Eq) -- | A type synonym for time in milliseconds. type Milliseconds = Int -- | A type for call-stacks type CallStack = [(Maybe String, Location)] -- | The result of a test run. data RunResult = RunResult { rr_result :: TestResult -- ^ The summary result of the test. , rr_location :: Maybe Location -- ^ The location where the test failed (if applicable). , rr_callers :: CallStack -- ^ Information about the callers of the location where the test failed , rr_message :: ColorString -- ^ A message describing the result. , rr_wallTimeMs :: Milliseconds -- ^ Execution time in milliseconds. } attachCallStack :: ColorString -> CallStack -> ColorString attachCallStack msg callStack = case reverse callStack of [] -> msg l -> ensureNewlineColorString msg +++ noColor (unlines (map formatCallStackElem l)) where formatCallStackElem (mMsg, loc) = " called from " ++ showLoc loc ++ (case mMsg of Nothing -> "" Just s -> " (" ++ s ++ ")") -- | The result of running a 'FlatTest' type FlatTestResult = GenFlatTest RunResult -- | The state type for the 'TR' monad. data TestState = TestState { ts_results :: [FlatTestResult] -- ^ Results collected so far. , ts_index :: Int -- ^ Current index for splitted output. } -- | The initial test state. initTestState :: TestState initTestState = TestState [] 0 -- | The 'TR' (test runner) monad. type TR = RWST TestConfig () TestState IO -- | The destination of progress and result messages from HTF. data TestOutput = TestOutputHandle Handle Bool -- ^ Output goes to 'Handle', boolean flag indicates whether the handle should be closed at the end. | TestOutputSplitted FilePath -- ^ Output goes to files whose names are derived from 'FilePath' by appending a number to it. Numbering starts at zero. deriving (Show, Eq) -- | Configuration of test execution. data TestConfig = TestConfig { tc_quiet :: Bool -- ^ If set, displays messages only for failed tests. , tc_threads :: Maybe Int -- ^ Use @Just i@ for parallel execution with @i@ threads, @Nothing@ for sequential execution. , tc_shuffle :: Bool -- ^ Shuffle tests before parallel execution , tc_output :: TestOutput -- ^ Output destination of progress and result messages. , tc_outputXml :: Maybe FilePath -- ^ Output destination of XML result summary , tc_filter :: TestFilter -- ^ Filter for the tests to run. , tc_reporters :: [TestReporter] -- ^ Test reporters to use. , tc_useColors :: Bool -- ^ Whether to use colored output } instance Show TestConfig where showsPrec prec tc = showParen (prec > 0) $ showString "TestConfig { " . showString "tc_quiet=" . showsPrec 1 (tc_quiet tc) . -- showString ", tc_threads=" . showsPrec 1 (tc_threads tc) . showString ", tc_output=" . showsPrec 1 (tc_output tc) . showString ", tc_filter=" . showString ", tc_reporters=" . showsPrec 1 (tc_reporters tc) . showString " }" -- | A 'TestReporter' provides hooks to customize the output of HTF. data TestReporter = TestReporter { tr_id :: String , tr_reportAllTests :: ReportAllTests -- ^ Called to report the IDs of all tests available. , tr_reportGlobalStart :: ReportGlobalStart -- ^ Called to report the start of test execution. , tr_reportTestStart :: ReportTestStart -- ^ Called to report the start of a single test. , tr_reportTestResult :: ReportTestResult -- ^ Called to report the result of a single test. , tr_reportGlobalResults :: ReportGlobalResults -- ^ Called to report the overall results of all tests. } emptyTestReporter :: String -> TestReporter emptyTestReporter id = TestReporter { tr_id = id , tr_reportAllTests = \_ -> return () , tr_reportGlobalStart = \_ -> return () , tr_reportTestStart = \_ -> return () , tr_reportTestResult = \_ -> return () , tr_reportGlobalResults = \_ _ _ _ _ -> return () } instance Show TestReporter where showsPrec _ x = showString (tr_id x) instance Eq TestReporter where x == y = (tr_id x) == (tr_id y) -- | Reports the IDs of all tests available. type ReportAllTests = [FlatTest] -> TR () -- | Signals that test execution is about to start. type ReportGlobalStart = [FlatTest] -> TR () -- | Reports the start of a single test. type ReportTestStart = FlatTest -> TR () -- | Reports the result of a single test. type ReportTestResult = FlatTestResult -> TR () -- | Reports the overall results of all tests. type ReportGlobalResults = Milliseconds -- ^ wall time in ms -> [FlatTestResult] -- ^ passed tests -> [FlatTestResult] -- ^ pending tests -> [FlatTestResult] -- ^ failed tests -> [FlatTestResult] -- ^ erroneous tests -> TR ()