{-# LANGUAGE ViewPatterns #-}
-- | Use "runhaskell Setup.hs test" or "cabal test" to run these tests.
module Main where
import Language.Haskell.Exts.Annotated
import qualified Language.Haskell.Exts as S -- S for "Simple", i.e. not annotated
import Test.Tasty hiding (defaultMain)
import Test.Tasty.Golden
import Test.Tasty.Golden.Manage
import System.FilePath
import System.FilePath.Find
import System.IO
import Control.Monad.Trans
import Control.Applicative
import Data.Generics
import Extensions
main :: IO ()
main = do
sources <- getTestFiles examplesDir
defaultMain $ testGroup "Tests" $
[ parserTests sources
, exactPrinterTests sources
, prettyPrinterTests sources
, prettyParserTests sources
, extensionProperties
]
-- | Where all the tests are to be found
examplesDir :: FilePath
examplesDir = "tests" </> "examples"
getTestFiles :: MonadIO m => FilePath -> m [FilePath]
getTestFiles dir = liftIO $ find (return True) (extension ==? ".hs" ||? extension ==? ".lhs") dir
parserTests :: [FilePath] -> TestTree -- {{{
parserTests sources = testGroup "Parser tests" $ do
-- list monad
file <- sources
let
out = file <.> "parser" <.> "out"
golden = file <.> "parser" <.> "golden"
run = do
ast <-
parseUTF8FileWithComments
(defaultParseMode { parseFilename = file })
file
writeBinaryFile out $ show ast ++ "\n"
return $ goldenVsFile (takeBaseName file) golden out run
-- }}}
exactPrinterTests :: [FilePath] -> TestTree -- {{{
exactPrinterTests sources = testGroup "Exact printer tests" $ do
-- list monad
file <- sources
let
out = file <.> "exactprinter" <.> "out"
golden = file <.> "exactprinter" <.> "golden"
run = do
contents <- readUTF8File file
let
-- parse
mbAst =
parseFileContentsWithComments
(defaultParseMode { parseFilename = file })
contents
-- try to pretty-print; summarize the test result
result =
case mbAst of
f@ParseFailed{} -> show f
ParseOk ast ->
let
printed = uncurry exactPrint ast
in
if printed == contents
then "Match"
else printed
writeBinaryFile out $ result ++ "\n"
return $ goldenVsFile (takeBaseName file) golden out run
-- }}}
prettyPrinterTests :: [FilePath] -> TestTree -- {{{
prettyPrinterTests sources = testGroup "Pretty printer tests" $ do
-- list monad
file <- sources
let
out = file <.> "prettyprinter" <.> "out"
golden = file <.> "prettyprinter" <.> "golden"
run = do
contents <- readUTF8File file
let
-- parse
mbAst =
S.parseFileContentsWithMode
(defaultParseMode { parseFilename = file })
contents
-- try to pretty-print; summarize the test result
result =
case mbAst of
f@ParseFailed{} -> show f
ParseOk ast -> prettyPrint ast
writeBinaryFile out $ result ++ "\n"
return $ goldenVsFile (takeBaseName file) golden out run
-- }}}
prettyParserTests :: [FilePath] -> TestTree -- {{{
prettyParserTests sources = testGroup "Pretty-parser tests" $ do
-- list monad
file <- sources
let
out = file <.> "prettyparser" <.> "out"
golden = file <.> "prettyparser" <.> "golden"
run = do
contents <- readUTF8File file
let
-- parse
parse1Result :: ParseResult S.Module
parse1Result =
S.parseFileContentsWithMode
(defaultParseMode { parseFilename = file })
contents
prettyResult :: ParseResult String
prettyResult = prettyPrint <$> parse1Result
parse2Result :: ParseResult (ParseResult S.Module)
parse2Result = S.parseFileContents <$> prettyResult
-- Even the un-annotated AST contains certain locations.
-- Obviously, they may differ, so we have to erase them.
eraseLocs :: S.Module -> S.Module
eraseLocs = everywhere $ mkT $ const noLoc
summary =
case liftA3 (,,) parse1Result prettyResult parse2Result of
f@ParseFailed{} -> show f
ParseOk (eraseLocs -> ast1, pretty, mbAst2) ->
case mbAst2 of
f@ParseFailed{} ->
"Failed to parse output of pretty-printer:\n" ++
show f ++ "\n" ++
"The pretty-printer output follows.\n\n" ++
pretty
ParseOk (eraseLocs -> ast2) ->
if ast1 == ast2
then "Match"
else
"Roundtrip test failed\n\n" ++
"AST 1:\n\n" ++
show ast1 ++ "\n\n" ++
"AST 2:\n\n" ++
show ast2 ++ "\n"
writeBinaryFile out $ summary ++ "\n"
return $ goldenVsFile (takeBaseName file) golden out run
-- }}}
-- UTF8 utils {{{
readUTF8File :: FilePath -> IO String
readUTF8File fp = openFile fp ReadMode >>= \h -> do
hSetEncoding h utf8
hGetContents h
parseUTF8FileWithComments :: ParseMode -> FilePath -> IO (ParseResult (Module SrcSpanInfo, [Comment]))
parseUTF8FileWithComments p fp = readUTF8File fp >>= (return . parseFileContentsWithComments p)
-- }}}