Blame src/Test/HUnit/Text.hs

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'