a simple doctest implementation for functional/shell testing
This commit is contained in:
parent
7fba880fef
commit
5fb256b582
78
Tests.hs
78
Tests.hs
@ -1,4 +1,80 @@
|
|||||||
-- see also
|
{- |
|
||||||
|
hledger's test suite. Most tests are HUnit-based, and defined in the
|
||||||
|
@tests@ list below. These tests are built in to hledger and can be run at
|
||||||
|
any time with @hledger test@.
|
||||||
|
|
||||||
|
In addition, we have tests in doctest format, which can be run with @make
|
||||||
|
doctest@ in the hledger source tree. These have some advantages:
|
||||||
|
|
||||||
|
- easier to read and write than hunit, for functional/shell tests
|
||||||
|
|
||||||
|
- easier to read multi-line output from failing tests
|
||||||
|
|
||||||
|
- can also appear in, and test, docs
|
||||||
|
|
||||||
|
and disadvantages:
|
||||||
|
|
||||||
|
- not included in hledger's built-in tests
|
||||||
|
|
||||||
|
- not platform independent
|
||||||
|
|
||||||
|
All doctests are included below. Some of these may also appear in other
|
||||||
|
modules as examples within the api docs.
|
||||||
|
|
||||||
|
Run a few with c++ ledger first:
|
||||||
|
|
||||||
|
@
|
||||||
|
$ ledger -f sample.ledger balance
|
||||||
|
$-1 assets
|
||||||
|
$1 bank:saving
|
||||||
|
$-2 cash
|
||||||
|
$2 expenses
|
||||||
|
$1 food
|
||||||
|
$1 supplies
|
||||||
|
$-2 income
|
||||||
|
$-1 gifts
|
||||||
|
$-1 salary
|
||||||
|
$1 liabilities:debts
|
||||||
|
@
|
||||||
|
|
||||||
|
@
|
||||||
|
$ ledger -f sample.ledger balance o
|
||||||
|
$1 expenses:food
|
||||||
|
$-2 income
|
||||||
|
$-1 gifts
|
||||||
|
$-1 salary
|
||||||
|
--------------------
|
||||||
|
$-1
|
||||||
|
@
|
||||||
|
|
||||||
|
Then hledger:
|
||||||
|
|
||||||
|
@
|
||||||
|
$ hledger -f sample.ledger balance
|
||||||
|
$-1 assets
|
||||||
|
$1 bank:saving
|
||||||
|
$-2 cash
|
||||||
|
$2 expenses
|
||||||
|
$1 food
|
||||||
|
$1 supplies
|
||||||
|
$-2 income
|
||||||
|
$-1 gifts
|
||||||
|
$-1 salary
|
||||||
|
$1 liabilities:debts
|
||||||
|
@
|
||||||
|
|
||||||
|
@
|
||||||
|
$ hledger -f sample.ledger balance o
|
||||||
|
$1 expenses:food
|
||||||
|
$-2 income
|
||||||
|
$-1 gifts
|
||||||
|
$-1 salary
|
||||||
|
--------------------
|
||||||
|
$-1
|
||||||
|
@
|
||||||
|
|
||||||
|
-}
|
||||||
|
-- other test tools:
|
||||||
-- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/test-framework
|
-- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/test-framework
|
||||||
-- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/HTF
|
-- http://hackage.haskell.org/cgi-bin/hackage-scripts/package/HTF
|
||||||
|
|
||||||
|
|||||||
86
tools/doctest.hs
Normal file
86
tools/doctest.hs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env runhaskell
|
||||||
|
{- |
|
||||||
|
Extract (shell) tests from haddock comments in Haskell code, run them and
|
||||||
|
verify expected output, like Python's doctest system.
|
||||||
|
|
||||||
|
Here, a doctest is a haddock literal block whose first line begins with a
|
||||||
|
$ (leading whitespace ignored). The rest of the line is a shell command
|
||||||
|
and the remaining lines are the expected output.
|
||||||
|
|
||||||
|
Usage example: $ doctest.hs doctest.hs
|
||||||
|
|
||||||
|
Doctest examples:
|
||||||
|
|
||||||
|
@
|
||||||
|
$ ls doctest.hs
|
||||||
|
This doctest will fail.
|
||||||
|
@
|
||||||
|
|
||||||
|
@
|
||||||
|
$ ls doctest.hs
|
||||||
|
doctest.hs
|
||||||
|
@
|
||||||
|
|
||||||
|
After writing this I found the doctest on hackage; that one runs haskell
|
||||||
|
expressions in comments, converting them to hunit tests. We might add this
|
||||||
|
to that, and/or add this to hledger's built-in test runner.
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
import Data.List (isPrefixOf)
|
||||||
|
import System (getArgs)
|
||||||
|
import System.Exit (ExitCode(ExitSuccess))
|
||||||
|
import System.IO (hGetContents)
|
||||||
|
import System.Process (runInteractiveCommand, waitForProcess)
|
||||||
|
import Text.Printf (printf)
|
||||||
|
|
||||||
|
main = do
|
||||||
|
f <- head `fmap` getArgs
|
||||||
|
s <- readFile f
|
||||||
|
let tests = doctests s
|
||||||
|
putStrLn $ printf "running %d doctests from %s" (length tests) f
|
||||||
|
mapM_ runShellDocTest $ doctests s
|
||||||
|
|
||||||
|
runShellDocTest :: String -> IO Bool
|
||||||
|
runShellDocTest s = do
|
||||||
|
let (cmd, expected) = splitDocTest s
|
||||||
|
putStr $ printf "testing: %s .. " cmd
|
||||||
|
(_, out, _, h) <- runInteractiveCommand cmd
|
||||||
|
exit <- waitForProcess h
|
||||||
|
output <- hGetContents out
|
||||||
|
if exit == ExitSuccess
|
||||||
|
then
|
||||||
|
if output == expected
|
||||||
|
then do
|
||||||
|
putStrLn "ok"
|
||||||
|
return True
|
||||||
|
else do
|
||||||
|
putStr $ printf "FAILED\nexpected:\n%sgot:\n%s" expected output
|
||||||
|
return False
|
||||||
|
else do
|
||||||
|
putStrLn $ printf "ERROR: %s" (show exit)
|
||||||
|
return False
|
||||||
|
|
||||||
|
splitDocTest s = (strip $ drop 1 $ strip $ head ls, unlines $ tail ls)
|
||||||
|
where ls = lines s
|
||||||
|
|
||||||
|
-- extract doctests from haskell source code
|
||||||
|
doctests :: String -> [String]
|
||||||
|
doctests s = filter isDocTest $ haddockLiterals s
|
||||||
|
where
|
||||||
|
isDocTest s = (("$" `isPrefixOf`) . dropws) $ head $ lines s
|
||||||
|
|
||||||
|
-- extract haddock literal blocks from haskell source code
|
||||||
|
haddockLiterals :: String -> [String]
|
||||||
|
haddockLiterals "" = []
|
||||||
|
haddockLiterals s | null lit = []
|
||||||
|
| otherwise = [lit] ++ haddockLiterals rest
|
||||||
|
where
|
||||||
|
ls = drop 1 $ dropWhile (not . isLiteralBoundary) $ lines s
|
||||||
|
lit = unlines $ takeWhile (not . isLiteralBoundary) ls
|
||||||
|
rest = unlines $ drop 1 $ dropWhile (not . isLiteralBoundary) ls
|
||||||
|
isLiteralBoundary = (== "@") . strip
|
||||||
|
|
||||||
|
strip = dropws . reverse . dropws . reverse
|
||||||
|
dropws = dropWhile (`elem` " \t")
|
||||||
Loading…
Reference in New Issue
Block a user