|
Packit |
bc3140 |
-- | Text-based test controller for running HUnit tests and reporting
|
|
Packit |
bc3140 |
-- results as text, usually to a terminal.
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
module Test.HUnit.Text
|
|
Packit |
bc3140 |
(
|
|
Packit |
bc3140 |
PutText(..),
|
|
Packit |
bc3140 |
putTextToHandle, putTextToShowS,
|
|
Packit |
bc3140 |
runTestText,
|
|
Packit |
bc3140 |
showPath, showCounts,
|
|
Packit |
bc3140 |
runTestTT
|
|
Packit |
bc3140 |
)
|
|
Packit |
bc3140 |
where
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
import Test.HUnit.Base
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
import Data.CallStack
|
|
Packit |
bc3140 |
import Control.Monad (when)
|
|
Packit |
bc3140 |
import System.IO (Handle, stderr, hPutStr, hPutStrLn)
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
-- | As the general text-based test controller ('runTestText') executes a
|
|
Packit |
bc3140 |
-- test, it reports each test case start, error, and failure by
|
|
Packit |
bc3140 |
-- constructing a string and passing it to the function embodied in a
|
|
Packit |
bc3140 |
-- 'PutText'. A report string is known as a \"line\", although it includes
|
|
Packit |
bc3140 |
-- no line terminator; the function in a 'PutText' is responsible for
|
|
Packit |
bc3140 |
-- terminating lines appropriately. Besides the line, the function
|
|
Packit |
bc3140 |
-- receives a flag indicating the intended \"persistence\" of the line:
|
|
Packit |
bc3140 |
-- 'True' indicates that the line should be part of the final overall
|
|
Packit |
bc3140 |
-- report; 'False' indicates that the line merely indicates progress of
|
|
Packit |
bc3140 |
-- the test execution. Each progress line shows the current values of
|
|
Packit |
bc3140 |
-- the cumulative test execution counts; a final, persistent line shows
|
|
Packit |
bc3140 |
-- the final count values.
|
|
Packit |
bc3140 |
--
|
|
Packit |
bc3140 |
-- The 'PutText' function is also passed, and returns, an arbitrary state
|
|
Packit |
bc3140 |
-- value (called 'st' here). The initial state value is given in the
|
|
Packit |
bc3140 |
-- 'PutText'; the final value is returned by 'runTestText'.
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
data PutText st = PutText (String -> Bool -> st -> IO st) st
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
-- | Two reporting schemes are defined here. @putTextToHandle@ writes
|
|
Packit |
bc3140 |
-- report lines to a given handle. 'putTextToShowS' accumulates
|
|
Packit |
bc3140 |
-- persistent lines for return as a whole by 'runTestText'.
|
|
Packit |
bc3140 |
--
|
|
Packit |
bc3140 |
-- @putTextToHandle@ writes persistent lines to the given handle,
|
|
Packit |
bc3140 |
-- following each by a newline character. In addition, if the given flag
|
|
Packit |
bc3140 |
-- is @True@, it writes progress lines to the handle as well. A progress
|
|
Packit |
bc3140 |
-- line is written with no line termination, so that it can be
|
|
Packit |
bc3140 |
-- overwritten by the next report line. As overwriting involves writing
|
|
Packit |
bc3140 |
-- carriage return and blank characters, its proper effect is usually
|
|
Packit |
bc3140 |
-- only obtained on terminal devices.
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
putTextToHandle
|
|
Packit |
bc3140 |
:: Handle
|
|
Packit |
bc3140 |
-> Bool -- ^ Write progress lines to handle?
|
|
Packit |
bc3140 |
-> PutText Int
|
|
Packit |
bc3140 |
putTextToHandle handle showProgress = PutText put initCnt
|
|
Packit |
bc3140 |
where
|
|
Packit |
bc3140 |
initCnt = if showProgress then 0 else -1
|
|
Packit |
bc3140 |
put line pers (-1) = do when pers (hPutStrLn handle line); return (-1)
|
|
Packit |
bc3140 |
put line True cnt = do hPutStrLn handle (erase cnt ++ line); return 0
|
|
Packit |
bc3140 |
put line False _ = do hPutStr handle ('\r' : line); return (length line)
|
|
Packit |
bc3140 |
-- The "erasing" strategy with a single '\r' relies on the fact that the
|
|
Packit |
bc3140 |
-- lengths of successive summary lines are monotonically nondecreasing.
|
|
Packit |
bc3140 |
erase cnt = if cnt == 0 then "" else "\r" ++ replicate cnt ' ' ++ "\r"
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
-- | Accumulates persistent lines (dropping progess lines) for return by
|
|
Packit |
bc3140 |
-- 'runTestText'. The accumulated lines are represented by a
|
|
Packit |
bc3140 |
-- @'ShowS' ('String' -> 'String')@ function whose first argument is the
|
|
Packit |
bc3140 |
-- string to be appended to the accumulated report lines.
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
putTextToShowS :: PutText ShowS
|
|
Packit |
bc3140 |
putTextToShowS = PutText put id
|
|
Packit |
bc3140 |
where put line pers f = return (if pers then acc f line else f)
|
|
Packit |
bc3140 |
acc f line rest = f (line ++ '\n' : rest)
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
-- | Executes a test, processing each report line according to the given
|
|
Packit |
bc3140 |
-- reporting scheme. The reporting scheme's state is threaded through calls
|
|
Packit |
bc3140 |
-- to the reporting scheme's function and finally returned, along with final
|
|
Packit |
bc3140 |
-- count values.
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
runTestText :: PutText st -> Test -> IO (Counts, st)
|
|
Packit |
bc3140 |
runTestText (PutText put us0) t = do
|
|
Packit |
bc3140 |
(counts', us1) <- performTest reportStart reportError reportFailure us0 t
|
|
Packit |
bc3140 |
us2 <- put (showCounts counts') True us1
|
|
Packit |
bc3140 |
return (counts', us2)
|
|
Packit |
bc3140 |
where
|
|
Packit |
bc3140 |
reportStart ss us = put (showCounts (counts ss)) False us
|
|
Packit |
bc3140 |
reportError = reportProblem "Error:" "Error in: "
|
|
Packit |
bc3140 |
reportFailure = reportProblem "Failure:" "Failure in: "
|
|
Packit |
bc3140 |
reportProblem p0 p1 loc msg ss us = put line True us
|
|
Packit |
bc3140 |
where line = "### " ++ kind ++ path' ++ "\n" ++ formatLocation loc ++ msg
|
|
Packit |
bc3140 |
kind = if null path' then p0 else p1
|
|
Packit |
bc3140 |
path' = showPath (path ss)
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
formatLocation :: Maybe SrcLoc -> String
|
|
Packit |
bc3140 |
formatLocation Nothing = ""
|
|
Packit |
bc3140 |
formatLocation (Just loc) = srcLocFile loc ++ ":" ++ show (srcLocStartLine loc) ++ "\n"
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
-- | Converts test execution counts to a string.
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
showCounts :: Counts -> String
|
|
Packit |
bc3140 |
showCounts Counts{ cases = cases', tried = tried',
|
|
Packit |
bc3140 |
errors = errors', failures = failures' } =
|
|
Packit |
bc3140 |
"Cases: " ++ show cases' ++ " Tried: " ++ show tried' ++
|
|
Packit |
bc3140 |
" Errors: " ++ show errors' ++ " Failures: " ++ show failures'
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
-- | Converts a test case path to a string, separating adjacent elements by
|
|
Packit |
bc3140 |
-- the colon (\':\'). An element of the path is quoted (as with 'show') when
|
|
Packit |
bc3140 |
-- there is potential ambiguity.
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
showPath :: Path -> String
|
|
Packit |
bc3140 |
showPath [] = ""
|
|
Packit |
bc3140 |
showPath nodes = foldl1 f (map showNode nodes)
|
|
Packit |
bc3140 |
where f b a = a ++ ":" ++ b
|
|
Packit |
bc3140 |
showNode (ListItem n) = show n
|
|
Packit |
bc3140 |
showNode (Label label) = safe label (show label)
|
|
Packit |
bc3140 |
safe s ss = if ':' `elem` s || "\"" ++ s ++ "\"" /= ss then ss else s
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
-- | Provides the \"standard\" text-based test controller. Reporting is made to
|
|
Packit |
bc3140 |
-- standard error, and progress reports are included. For possible
|
|
Packit |
bc3140 |
-- programmatic use, the final counts are returned.
|
|
Packit |
bc3140 |
--
|
|
Packit |
bc3140 |
-- The \"TT\" in the name suggests \"Text-based reporting to the Terminal\".
|
|
Packit |
bc3140 |
|
|
Packit |
bc3140 |
runTestTT :: Test -> IO Counts
|
|
Packit |
bc3140 |
runTestTT t = do (counts', 0) <- runTestText (putTextToHandle stderr True) t
|
|
Packit |
bc3140 |
return counts'
|