mirror of
https://github.com/simonmichael/hledger.git
synced 2024-09-18 17:57:11 +03:00
add a shell test runner like ledger's, and "make functest" rule
This commit is contained in:
parent
163ba5de93
commit
2ae609fee6
9
Makefile
9
Makefile
@ -87,6 +87,10 @@ tools/bench: tools/bench.hs
|
||||
tools/doctest: tools/doctest.hs
|
||||
ghc --make tools/doctest.hs
|
||||
|
||||
# build the shell test runner
|
||||
tools/shelltest: tools/shelltest.hs
|
||||
ghc --make -threaded tools/shelltest.hs
|
||||
|
||||
# build the generateledger tool
|
||||
tools/generateledger: tools/generateledger.hs
|
||||
ghc --make tools/generateledger.hs
|
||||
@ -112,6 +116,11 @@ unittest:
|
||||
@(runghc hledger.hs test \
|
||||
&& echo $@ passed) || echo $@ FAILED
|
||||
|
||||
# run functional tests
|
||||
functest: tools/shelltest
|
||||
@(tools/shelltest tests/*.test \
|
||||
&& echo $@ passed) || echo $@ FAILED
|
||||
|
||||
# run doc tests
|
||||
doctest: tools/doctest
|
||||
@(tools/doctest Commands/Add.hs >/dev/null \
|
||||
|
122
tools/shelltest.hs
Normal file
122
tools/shelltest.hs
Normal file
@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env runhaskell
|
||||
{-
|
||||
|
||||
Run one or more hledger command-line tests, specified by .test files like
|
||||
those used in the ledger project. A ledger-style .test file contains a
|
||||
partial command line, input, expected output, expected error output, and
|
||||
expected exit code separated by delimiters.
|
||||
|
||||
Usage: $ shelltest *.test
|
||||
|
||||
Here is the .test file format:
|
||||
@
|
||||
--option1 arg1 arg2
|
||||
<<<
|
||||
lines of
|
||||
input
|
||||
>>>
|
||||
expected
|
||||
output
|
||||
>>>2
|
||||
expected
|
||||
error output
|
||||
===0
|
||||
;
|
||||
; Lines whose first non-whitespace character is ; are ignored.
|
||||
; The first line is the command line. "hledger" is prepended, and "-f-" is
|
||||
; appended unless there is a -f or <... argument (in which case the
|
||||
; provided input is ignored.)
|
||||
; Then there is a line containing <<< and 0 or more lines of input.
|
||||
; Then a line containing >>> (or >>>1 for ledger testrunner compatibility)
|
||||
; and 0 or more lines of expected output.
|
||||
; Then a line containing >>>2 and 0 or more lines of expected stderr output.
|
||||
; Then === and the expected exit code (on the same line).
|
||||
@
|
||||
-}
|
||||
|
||||
module Main where
|
||||
import System (getArgs)
|
||||
import System.Exit (exitFailure, exitWith, ExitCode(..))
|
||||
import System.IO (hGetContents, hPutStr, hFlush, stderr, stdout)
|
||||
import System.Process (runInteractiveCommand, waitForProcess)
|
||||
import Text.Printf (printf)
|
||||
import Text.ParserCombinators.Parsec
|
||||
import Control.Monad (liftM,when)
|
||||
|
||||
exe :: String
|
||||
exe = "hledger"
|
||||
|
||||
data ShellTest = ShellTest {
|
||||
command :: String
|
||||
,stdin :: String
|
||||
,stdoutExpected :: String
|
||||
,stderrExpected :: String
|
||||
,exitCodeExpected :: ExitCode
|
||||
} deriving (Show)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
files <- getArgs
|
||||
ok <- mapM (\f -> parseShellTest f >>= runShellTest) files
|
||||
if any not ok then exitFailure else exitWith ExitSuccess
|
||||
|
||||
parseShellTest :: FilePath -> IO ShellTest
|
||||
parseShellTest = liftM (either (error.show) id) . parseFromFile shelltest
|
||||
|
||||
shelltest :: Parser ShellTest
|
||||
shelltest = do
|
||||
c <- line; string "<<<\n"
|
||||
i <- line `manyTill` (string ">>>" >> optional (char '1') >> char '\n')
|
||||
o <- line `manyTill` (string ">>>2\n")
|
||||
e <- line `manyTill` (string "===")
|
||||
x <- line
|
||||
let x' = read x -- `catch` (\e -> fail (show e))
|
||||
eof
|
||||
return ShellTest{command=c,stdin=unlines i,stdoutExpected=unlines o,stderrExpected=unlines e,exitCodeExpected=toExitCode x'}
|
||||
|
||||
line :: Parser String
|
||||
line = do
|
||||
l <- anyChar `manyTill` newline
|
||||
if take 1 (strip l) == ";"
|
||||
then line
|
||||
else return l
|
||||
|
||||
runShellTest :: ShellTest -> IO Bool
|
||||
runShellTest ShellTest{
|
||||
command=c,stdin=i,stdoutExpected=o,stderrExpected=e,exitCodeExpected=x} = do
|
||||
let cmd = unwords [exe,c,if (any isinputarg $ words c) then "" else "-f-"]
|
||||
where isinputarg a = take 2 a == "-f" || (take 1 a == "<")
|
||||
printf "Testing: %s" cmd; hFlush stdout
|
||||
(ih,oh,eh,ph) <- runInteractiveCommand cmd
|
||||
hPutStr ih i
|
||||
out <- hGetContents oh
|
||||
err <- hGetContents eh
|
||||
exit <- waitForProcess ph
|
||||
let (outputok, errorok, exitok) = (out==o, err==e, exit==x)
|
||||
if outputok && errorok && exitok
|
||||
then do
|
||||
putStrLn " .. ok"
|
||||
return True
|
||||
else do
|
||||
hPutStr stderr $ printf " .. FAIL\n"
|
||||
when (not outputok) $ printExpectedActual "stdout" o out
|
||||
when (not errorok) $ printExpectedActual "stderr" e err
|
||||
when (not exitok) $ printExpectedActual "exit code" (show (fromExitCode x)++"\n") (show (fromExitCode exit)++"\n")
|
||||
return False
|
||||
|
||||
printExpectedActual :: String -> String -> String -> IO ()
|
||||
printExpectedActual f e a = hPutStr stderr $ printf "**Expected %s:\n%s**Got:\n%s" f e a
|
||||
|
||||
toExitCode :: Int -> ExitCode
|
||||
toExitCode 0 = ExitSuccess
|
||||
toExitCode n = ExitFailure n
|
||||
|
||||
fromExitCode :: ExitCode -> Int
|
||||
fromExitCode ExitSuccess = 0
|
||||
fromExitCode (ExitFailure n) = n
|
||||
|
||||
strip,lstrip,rstrip,dropws :: String -> String
|
||||
strip = lstrip . rstrip
|
||||
lstrip = dropws
|
||||
rstrip = reverse . dropws . reverse
|
||||
dropws = dropWhile (`elem` " \t")
|
Loading…
Reference in New Issue
Block a user