module Report (
runModules
, Summary(..)
#ifdef TEST
, Report
, ReportState (..)
, report
, report_
, reportFailure
, runProperty
, DocTestResult (..)
#endif
) where
import Prelude hiding (putStr, putStrLn, error)
import Data.Monoid
import Control.Monad
import Text.Printf (printf)
import System.IO (hPutStrLn, hPutStr, stderr, hIsTerminalDevice)
import Data.Char
import Control.Monad.Trans.State
import Control.Monad.IO.Class
import Interpreter (Interpreter)
import qualified Interpreter
import Parse
import Location
import Type
import Property
data Summary = Summary {
sExamples :: Int
, sTried :: Int
, sErrors :: Int
, sFailures :: Int
} deriving Eq
instance Show Summary where
show (Summary examples tried errors failures) =
printf "Examples: %d Tried: %d Errors: %d Failures: %d" examples tried errors failures
instance Monoid Summary where
mempty = Summary 0 0 0 0
(Summary x1 x2 x3 x4) `mappend` (Summary y1 y2 y3 y4) = Summary (x1 + y1) (x2 + y2) (x3 + y3) (x4 + y4)
runModules :: Int -> Interpreter -> [Module DocTest] -> IO Summary
runModules exampleCount repl modules = do
isInteractive <- hIsTerminalDevice stderr
ReportState _ _ s <- (`execStateT` ReportState 0 isInteractive mempty {sExamples = exampleCount}) $ do
forM_ modules $ runModule repl
gets (show . reportStateSummary) >>= report
return s
type Report = StateT ReportState IO
data ReportState = ReportState {
reportStateCount :: Int
, reportStateInteractive :: Bool
, reportStateSummary :: Summary
}
report :: String -> Report ()
report msg = do
overwrite msg
liftIO $ hPutStrLn stderr ""
modify (\st -> st {reportStateCount = 0})
report_ :: String -> Report ()
report_ msg = do
f <- gets reportStateInteractive
when f $ do
overwrite msg
modify (\st -> st {reportStateCount = length msg})
overwrite :: String -> Report ()
overwrite msg = do
n <- gets reportStateCount
let str | 0 < n = "\r" ++ msg ++ replicate (n length msg) ' '
| otherwise = msg
liftIO (hPutStr stderr str)
runModule :: Interpreter -> Module DocTest -> Report ()
runModule repl (Module name examples) = do
forM_ examples $ \e -> do
gets (show . reportStateSummary) >>= report_
r <- liftIO $ runDocTest repl name e
case r of
Success ->
success
Error (Located loc expression) err -> do
report (printf "### Error in %s: expression `%s'" (show loc) expression)
report err
error
InteractionFailure (Located loc (Interaction expression expected)) actual -> do
report (printf "### Failure in %s: expression `%s'" (show loc) expression)
reportFailure expected actual
failure
PropertyFailure (Located loc expression) msg -> do
report (printf "### Failure in %s: expression `%s'" (show loc) expression)
report msg
failure
where
success = updateSummary (Summary 0 1 0 0)
failure = updateSummary (Summary 0 1 0 1)
error = updateSummary (Summary 0 1 1 0)
updateSummary summary = do
ReportState n f s <- get
put (ReportState n f $ s `mappend` summary)
reportFailure :: [String] -> [String] -> Report ()
reportFailure expected actual = do
outputLines "expected: " expected
outputLines " but got: " actual
where
printQuotes = any isSpace (map last . filter (not . null) $ expected ++ actual)
escapeOutput = any (not . isSafe) (concat $ expected ++ actual)
isSafe :: Char -> Bool
isSafe c = c == ' ' || (isPrint c && (not . isSpace) c)
outputLines message l_ = case l of
x:xs -> do
report (message ++ x)
let padding = replicate (length message) ' '
forM_ xs $ \y -> report (padding ++ y)
[] ->
report message
where
l | printQuotes || escapeOutput = map show l_
| otherwise = l_
runDocTest :: Interpreter -> String -> DocTest -> IO DocTestResult
runDocTest repl module_ docTest = do
_ <- Interpreter.eval repl ":reload"
_ <- Interpreter.eval repl $ ":m *" ++ module_
case docTest of
Example xs -> runExample repl xs
Property p -> runProperty repl p
runExample :: Interpreter -> [Located Interaction] -> IO DocTestResult
runExample repl = go
where
go (i@(Located loc (Interaction expression expected)) : xs) = do
r <- fmap lines `fmap` Interpreter.safeEval repl expression
case r of
Left err -> do
return (Error (Located loc expression) err)
Right actual -> do
if expected /= actual
then
return (InteractionFailure i actual)
else
go xs
go [] = return Success