123 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
			
		
		
	
	
			123 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
| #!/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")
 |