texmath-0.12.4: tests/test-texmath.hs
{-# LANGUAGE OverloadedStrings #-}
import System.Directory
import System.FilePath
import Text.XML.Light
import System.IO
import System.IO.Temp (withTempDirectory)
import System.Process
import Text.TeXMath
import System.Exit
import Control.Monad
import GHC.IO.Encoding (setLocaleEncoding)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import System.Environment (getArgs)
tshow :: Show a => a -> T.Text
tshow = T.pack . show
data Status = Pass FilePath FilePath
| Fail FilePath FilePath
| Error FilePath FilePath
deriving (Eq, Show)
type Ext = String
readers :: [(Ext, T.Text -> Either T.Text [Exp])]
readers = [ (".tex", readTeX)
, (".mml", readMathML)
, (".omml", readOMML)
]
writers :: [(Ext, [Exp] -> T.Text)]
writers = [ (".mml", T.pack . ppTopElement . writeMathML DisplayBlock)
, (".tex", writeTeX)
, (".omml", T.pack . ppTopElement . writeOMML DisplayBlock)
, (".eqn", writeEqn DisplayBlock)
]
-- when called with --round-trip, JUST do round trip tests.
-- otherwise omit them.
main :: IO ()
main = do
args <- getArgs
let roundTrip = "--round-trip" `elem` args
let regen = "--regenerate-tests" `elem` args || "--accept" `elem` args
setLocaleEncoding utf8
setCurrentDirectory "tests"
statuses <- if roundTrip
then do
texs <- runRoundTrip "tex" writeTeX readTeX
ommls <- runRoundTrip "omml"
(T.pack . ppTopElement . writeOMML DisplayBlock) readOMML
mathmls <- runRoundTrip "mml"
(T.pack . ppTopElement . writeMathML DisplayBlock)
readMathML
return $ texs <> ommls <> mathmls
else (<>) <$> (concat <$> mapM (uncurry (runReader regen)) readers)
<*> (concat <$> mapM (uncurry (runWriter regen)) writers)
let passes = length $ filter isPass statuses
let failures = length statuses - passes
T.putStrLn $ tshow passes <> " tests passed, " <> tshow failures <> " failed."
if all isPass statuses
then exitWith ExitSuccess
else exitWith $ ExitFailure failures
printPass :: FilePath -> FilePath -> IO ()
printPass _inp _out = return () -- putStrLn $ "PASSED: " ++ inp ++ " ==> " ++ out
-- Second parameter is Left format (for round-trip) or Right output file.
printFail :: FilePath -> Either T.Text FilePath -> T.Text -> IO ()
printFail inp out actual =
withTempDirectory "." ((either (const inp) id out) ++ ".") $ \tmpDir -> do
-- break native files at commas for easier diffing
let breakAtCommas = case out of
Left _ -> T.intercalate ",\n" . T.split (==',')
Right f | takeExtension f == ".native" ->
T.intercalate ",\n" . T.split (==',')
_ -> id
T.putStrLn $ "FAILED: " <> T.pack inp <> " ==> " <>
either (\x -> "round trip via " <> x) T.pack out
T.readFile (either (const inp) id out) >>=
T.writeFile (tmpDir </> "expected") . ensureFinalNewline . breakAtCommas
T.writeFile (tmpDir </> "actual") $ ensureFinalNewline $ breakAtCommas actual
hFlush stdout
_ <- runProcess "diff" ["-u", tmpDir </> "expected", tmpDir </> "actual"]
Nothing Nothing Nothing Nothing Nothing >>= waitForProcess
putStrLn ""
printError :: FilePath -> FilePath -> T.Text -> IO ()
printError inp out msg =
T.putStrLn $ "ERROR: " <> T.pack inp <> " ==> " <> T.pack out <> "\n" <> msg
ensureFinalNewline :: T.Text -> T.Text
ensureFinalNewline xs = case T.unsnoc xs of
Nothing -> xs
Just (_, '\n') -> xs
_ -> xs <> "\n"
isPass :: Status -> Bool
isPass (Pass _ _) = True
isPass _ = False
runReader :: Bool -> Ext -> (T.Text -> Either T.Text [Exp]) -> IO [Status]
runReader regen ext f = do
input <- filter (\x -> takeExtension x == ext) <$> getDirectoryContents "./src"
let input' = map ("src" </>) input
mapM (\inp ->
runReaderTest regen (\x -> tshow <$> f x) inp (rename inp)) input'
where
rename x = replaceExtension (replaceDirectory x ("./readers" </> (tail ext))) "native"
runWriter :: Bool -> Ext -> ([Exp] -> T.Text) -> IO [Status]
runWriter regen ext f = do
mmls <- map ("./readers/mml/"++) <$> getDirectoryContents "./readers/mml"
texs <- map ("./readers/tex/"++) <$> getDirectoryContents "./readers/tex"
let sources = mmls ++ texs
let inputs = [x | x <- sources, takeExtension x == ".native"]
mapM (\inp ->
runWriterTest regen f inp ("./writers/" ++ takeBaseName inp ++ ext))
inputs
runRoundTrip :: String
-> ([Exp] -> T.Text)
-> (T.Text -> Either T.Text [Exp])
-> IO [Status]
runRoundTrip fmt writer reader = do
inps <- filter (\x -> takeExtension x == ".native") <$>
map (("./readers/" <> fmt <> "/") <>) <$>
getDirectoryContents ("./readers/" <> fmt)
mapM (runRoundTripTest fmt writer reader) inps
runWriterTest :: Bool -> ([Exp] -> T.Text) -> FilePath -> FilePath -> IO Status
runWriterTest regen f input output = do
rawnative <- T.readFile input
native <- case reads $ T.unpack rawnative of
((x,_):_) -> return x
[] -> error $ "Could not read " ++ input
let result = ensureFinalNewline $ f native
when regen $ T.writeFile output result
out_t <- ensureFinalNewline <$> T.readFile output
if result == out_t
then printPass input output >> return (Pass input output)
else printFail input (Right output) result >> return (Fail input output)
runReaderTest :: Bool
-> (T.Text -> Either T.Text T.Text)
-> FilePath
-> FilePath
-> IO Status
runReaderTest regen fn input output = do
inp_t <- T.readFile input
let result = ensureFinalNewline <$> fn inp_t
let errfile = dropExtension output ++ ".error"
errorExpected <- doesFileExist errfile
if errorExpected
then
case result of
Left _ -> do
printPass input errfile
return (Pass input errfile)
Right _ -> do
printError input errfile "error expected but not raised"
return (Error input errfile)
else do
when regen $
T.writeFile output (either (const "") id result)
out_t <- ensureFinalNewline <$> T.readFile output
case result of
Left msg -> do
printError input output msg
return (Error input output)
Right r
| r == out_t -> do
printPass input output
return (Pass input output)
| otherwise -> do
printFail input (Right output) r
return (Fail input output)
runRoundTripTest :: String
-> ([Exp] -> T.Text)
-> (T.Text -> Either T.Text [Exp])
-> FilePath
-> IO Status
runRoundTripTest fmt writer reader input = do
rawnative <- T.readFile input
native <- case reads $ T.unpack rawnative of
((x,_):_) -> return x
[] -> error $ "Could not read " ++ input
let rendered = ensureFinalNewline $ writer native
case reader rendered of
Left msg -> printError input input msg >>
return (Error input input)
Right r
| r == native -> printPass input input >>
return (Pass input input)
| otherwise -> printFail input (Left $ T.pack fmt) (tshow r) >>
return (Fail input input)