2415b2680a
Mmny of our tests require running the pandoc executable. This is problematic for a few different reasons. First, cabal-install will sometimes run the test suite after building the library but before building the executable, which means the executable isn't in place for the tests. One can work around that by first building, then building and running the tests, but that's fragile. Second, we have to find the executable. So far, we've done that using a function findPandoc that attempts to locate it relative to the test executable (which can be located using findExecutablePath). But the logic here is delicate and work with every combination of options. To solve both problems, we add an `--emulate` option to the `test-pandoc` executable. When `--emulate` occurs as the first argument passed to `test-pandoc`, the program simply emulates the regular pandoc executable, using the rest of the arguments (after `--emulate`). Thus, test-pandoc --emulate -f markdown -t latex is just like pandoc -f markdown -t latex Since all the work is done by library functions, implementing this emulation just takes a couple lines of code and should be entirely reliable. With this change, we can test the pandoc executable by running the test program itself (locatable using findExecutablePath) with the `--emulate` option. This removes the need for the fragile `findPandoc` step, and it means we can run our integration tests even when we're just building the library, not the executable. Part of this change involved simplifying some complex handling to set environment variables for dynamic library paths. I have tested a build with `--enable-dynamic-executable`, and it works, but further testing may be needed.
146 lines
5.4 KiB
Haskell
146 lines
5.4 KiB
Haskell
{-# LANGUAGE NoImplicitPrelude #-}
|
|
{-# LANGUAGE TupleSections #-}
|
|
{- |
|
|
Module : Tests.Command
|
|
Copyright : © 2006-2021 John MacFarlane
|
|
License : GNU GPL, version 2 or above
|
|
|
|
Maintainer : John MacFarlane <jgm@berkeley@edu>
|
|
Stability : alpha
|
|
Portability : portable
|
|
|
|
Run commands, and test results, defined in markdown files.
|
|
-}
|
|
module Tests.Command (runTest, tests)
|
|
where
|
|
|
|
import Prelude
|
|
import Data.Algorithm.Diff
|
|
import System.Environment.Executable (getExecutablePath)
|
|
import qualified Data.ByteString as BS
|
|
import qualified Data.Text as T
|
|
import Data.List (isSuffixOf, intercalate)
|
|
import Data.Maybe (catMaybes)
|
|
import System.Directory
|
|
import qualified System.Environment as Env
|
|
import System.Exit
|
|
import System.FilePath (joinPath, splitDirectories, takeDirectory, (</>))
|
|
import System.IO (hPutStr, stderr)
|
|
import System.IO.Unsafe (unsafePerformIO)
|
|
import System.Process
|
|
import Test.Tasty
|
|
import Test.Tasty.HUnit
|
|
import Test.Tasty.Golden.Advanced (goldenTest)
|
|
import Tests.Helpers
|
|
import Text.Pandoc
|
|
import qualified Text.Pandoc.UTF8 as UTF8
|
|
|
|
-- | Run a test with and return output.
|
|
execTest :: String -- ^ Path to test executable
|
|
-> String -- ^ Shell command
|
|
-> String -- ^ Input text
|
|
-> IO (ExitCode, String) -- ^ Exit code and actual output
|
|
execTest testExePath cmd inp = do
|
|
mldpath <- Env.lookupEnv "LD_LIBRARY_PATH"
|
|
mdyldpath <- Env.lookupEnv "DYLD_LIBRARY_PATH"
|
|
let env' = ("PATH",takeDirectory testExePath) :
|
|
("TMP",".") :
|
|
("LANG","en_US.UTF-8") :
|
|
("HOME", "./") :
|
|
("pandoc_datadir", "..") :
|
|
maybe [] ((:[]) . ("LD_LIBRARY_PATH",)) mldpath ++
|
|
maybe [] ((:[]) . ("DYLD_LIBRARY_PATH",)) mdyldpath
|
|
let pr = (shell (pandocToEmulate True cmd)){ env = Just env' }
|
|
(ec, out', err') <- readCreateProcessWithExitCode pr inp
|
|
-- filter \r so the tests will work on Windows machines
|
|
let out = filter (/= '\r') $ err' ++ out'
|
|
case ec of
|
|
ExitFailure _ -> hPutStr stderr err'
|
|
ExitSuccess -> return ()
|
|
return (ec, out)
|
|
|
|
pandocToEmulate :: Bool -> String -> String
|
|
pandocToEmulate True ('p':'a':'n':'d':'o':'c':cs) =
|
|
"test-pandoc --emulate" ++ pandocToEmulate False cs
|
|
pandocToEmulate False ('|':' ':'p':'a':'n':'d':'o':'c':cs) =
|
|
"| " ++ "test-pandoc --emulate" ++ pandocToEmulate False cs
|
|
pandocToEmulate _ (c:cs) = c : pandocToEmulate False cs
|
|
pandocToEmulate _ [] = []
|
|
|
|
-- | Run a test, return True if test passed.
|
|
runTest :: String -- ^ Path to test executable
|
|
-> String -- ^ Title of test
|
|
-> String -- ^ Shell command
|
|
-> String -- ^ Input text
|
|
-> String -- ^ Expected output
|
|
-> TestTree
|
|
runTest testExePath testname cmd inp norm = testCase testname $ do
|
|
(ec, out) <- execTest testExePath cmd inp
|
|
result <- if ec == ExitSuccess
|
|
then
|
|
if out == norm
|
|
then return TestPassed
|
|
else return
|
|
$ TestFailed cmd "expected"
|
|
$ getDiff (lines out) (lines norm)
|
|
else return $ TestError ec
|
|
assertBool (show result) (result == TestPassed)
|
|
|
|
tests :: TestTree
|
|
{-# NOINLINE tests #-}
|
|
tests = unsafePerformIO $ do
|
|
files <- filter (".md" `isSuffixOf`) <$>
|
|
getDirectoryContents "command"
|
|
testExePath <- getExecutablePath
|
|
let cmds = map (extractCommandTest testExePath) files
|
|
return $ testGroup "Command:" cmds
|
|
|
|
isCodeBlock :: Block -> Bool
|
|
isCodeBlock (CodeBlock _ _) = True
|
|
isCodeBlock _ = False
|
|
|
|
extractCode :: Block -> String
|
|
extractCode (CodeBlock _ code) = T.unpack code
|
|
extractCode _ = ""
|
|
|
|
dropPercent :: String -> String
|
|
dropPercent ('%':xs) = dropWhile (== ' ') xs
|
|
dropPercent xs = xs
|
|
|
|
runCommandTest :: FilePath -> FilePath -> Int -> String -> TestTree
|
|
runCommandTest testExePath fp num code =
|
|
goldenTest testname getExpected getActual compareValues updateGolden
|
|
where
|
|
testname = "#" <> show num
|
|
codelines = lines code
|
|
(continuations, r1) = span ("\\" `isSuffixOf`) codelines
|
|
cmd = dropPercent (unwords (map init continuations ++ take 1 r1))
|
|
r2 = drop 1 r1
|
|
(inplines, r3) = break (=="^D") r2
|
|
normlines = takeWhile (/=".") (drop 1 r3)
|
|
input = unlines inplines
|
|
norm = unlines normlines
|
|
getExpected = return norm
|
|
getActual = snd <$> execTest testExePath cmd input
|
|
compareValues expected actual
|
|
| actual == expected = return Nothing
|
|
| otherwise = return $ Just $ "--- test/command/" ++ fp ++ "\n+++ " ++
|
|
cmd ++ "\n" ++ showDiff (1,1)
|
|
(getDiff (lines actual) (lines expected))
|
|
updateGolden newnorm = do
|
|
let fp' = "command" </> fp
|
|
raw <- UTF8.readFile fp'
|
|
let cmdline = "% " <> cmd
|
|
let x = cmdline <> "\n" <> input <> "^D\n" <> norm
|
|
let y = cmdline <> "\n" <> input <> "^D\n" <> newnorm
|
|
let updated = T.unpack $ T.replace (T.pack x) (T.pack y) (T.pack raw)
|
|
UTF8.writeFile fp' updated
|
|
|
|
extractCommandTest :: FilePath -> FilePath -> TestTree
|
|
extractCommandTest testExePath fp = unsafePerformIO $ do
|
|
contents <- UTF8.toText <$> BS.readFile ("command" </> fp)
|
|
Pandoc _ blocks <- runIOorExplode (readMarkdown
|
|
def{ readerExtensions = pandocExtensions } contents)
|
|
let codeblocks = map extractCode $ filter isCodeBlock blocks
|
|
let cases = zipWith (runCommandTest testExePath fp) [1..] codeblocks
|
|
return $ testGroup fp cases
|