2021-07-17 06:57:31 +03:00
|
|
|
#!/usr/bin/env stack
|
2021-12-17 02:34:59 +03:00
|
|
|
{- stack script --resolver nightly-2021-12-16 --compile
|
2021-07-17 06:57:31 +03:00
|
|
|
-}
|
|
|
|
-- add this to see packages being installed instead of a long silence:
|
|
|
|
-- --verbosity=info
|
|
|
|
--package base-prelude
|
|
|
|
--package directory
|
|
|
|
--package extra
|
|
|
|
--package process
|
|
|
|
--package regex
|
|
|
|
--package safe
|
|
|
|
--package shake
|
|
|
|
--package time
|
|
|
|
|
2009-03-15 08:06:36 +03:00
|
|
|
{- |
|
|
|
|
Extract (shell) tests from haddock comments in Haskell code, run them and
|
2021-07-17 06:57:31 +03:00
|
|
|
verify expected output. Like https://hackage.haskell.org/package/doctest,
|
|
|
|
but tests shell commands instead of GHCI commands.
|
2009-03-15 08:06:36 +03:00
|
|
|
|
2021-07-17 06:57:31 +03:00
|
|
|
A docshelltest 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. The exit code is expected
|
|
|
|
to be zero.
|
2009-03-15 08:06:36 +03:00
|
|
|
|
|
|
|
Usage example: $ doctest.hs doctest.hs
|
|
|
|
|
|
|
|
@
|
2021-07-17 06:57:31 +03:00
|
|
|
$ echo This test shall pass
|
|
|
|
This test shall pass
|
2009-03-15 08:06:36 +03:00
|
|
|
@
|
|
|
|
|
|
|
|
@
|
2021-07-17 06:57:31 +03:00
|
|
|
$ echo This test shall fail
|
2009-03-15 08:06:36 +03:00
|
|
|
|
2021-07-17 06:57:31 +03:00
|
|
|
@
|
2009-06-22 23:46:31 +04:00
|
|
|
|
2009-03-15 08:06:36 +03:00
|
|
|
-}
|
|
|
|
|
|
|
|
module Main where
|
|
|
|
import Data.List (isPrefixOf)
|
2021-07-17 06:57:31 +03:00
|
|
|
import System.Environment (getArgs)
|
2009-06-22 19:47:05 +04:00
|
|
|
import System.Exit (exitFailure, exitWith, ExitCode(ExitSuccess)) -- base 3 compatible
|
2009-06-05 22:59:59 +04:00
|
|
|
import System.IO (hGetContents, hPutStr, hPutStrLn, stderr)
|
2009-03-15 08:06:36 +03:00
|
|
|
import System.Process (runInteractiveCommand, waitForProcess)
|
|
|
|
import Text.Printf (printf)
|
|
|
|
|
|
|
|
main = do
|
|
|
|
f <- head `fmap` getArgs
|
|
|
|
s <- readFile f
|
|
|
|
let tests = doctests s
|
2009-03-15 11:47:23 +03:00
|
|
|
putStrLn $ printf "Running %d doctests from %s" (length tests) f
|
2009-06-05 21:29:20 +04:00
|
|
|
ok <- mapM runShellDocTest $ doctests s
|
2021-07-17 06:57:31 +03:00
|
|
|
putStrLn ""
|
2021-08-16 08:59:16 +03:00
|
|
|
if all ok then exitSuccess else exitFailure
|
2009-03-15 08:06:36 +03:00
|
|
|
|
|
|
|
runShellDocTest :: String -> IO Bool
|
|
|
|
runShellDocTest s = do
|
|
|
|
let (cmd, expected) = splitDocTest s
|
2009-03-15 11:47:23 +03:00
|
|
|
putStr $ printf "Testing: %s .. " cmd
|
2009-03-15 08:06:36 +03:00
|
|
|
(_, 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
|
2009-06-05 22:59:59 +04:00
|
|
|
hPutStr stderr $ printf "FAILED\nExpected:\n%sGot:\n%s" expected output
|
2009-03-15 08:06:36 +03:00
|
|
|
return False
|
|
|
|
else do
|
2009-06-05 22:59:59 +04:00
|
|
|
hPutStrLn stderr $ printf "ERROR: %s" (show exit)
|
2009-03-15 08:06:36 +03:00
|
|
|
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
|
2009-09-22 19:56:59 +04:00
|
|
|
isDocTest = (("$" `isPrefixOf`) . dropws) . head . lines
|
2009-03-15 08:06:36 +03:00
|
|
|
|
|
|
|
-- extract haddock literal blocks from haskell source code
|
|
|
|
haddockLiterals :: String -> [String]
|
|
|
|
haddockLiterals "" = []
|
|
|
|
haddockLiterals s | null lit = []
|
2009-09-23 13:22:53 +04:00
|
|
|
| otherwise = lit : haddockLiterals rest
|
2009-03-15 08:06:36 +03:00
|
|
|
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")
|