-- Copyright (c) 2020, Shayne Fletcher. All rights reserved.
-- SPDX-License-Identifier: BSD-3-Clause.
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
#include "ghclib_api.h"
import Test.Tasty
import Test.Tasty.HUnit
import System.Directory as Directory
import System.Environment
import qualified System.FilePath as FilePath
import System.IO.Extra
import Control.Monad
import Language.Haskell.GhclibParserEx.Config
import Language.Haskell.GhclibParserEx.DynFlags
import Language.Haskell.GhclibParserEx.HsExtendInstances
import Language.Haskell.GhclibParserEx.Parse
import Language.Haskell.GhclibParserEx.Dump
import Language.Haskell.GhclibParserEx.Fixity
#if defined (GHCLIB_API_811) || defined (GHCLIB_API_810)
import GHC.Hs
import RdrHsSyn
#endif
import DynFlags
import Lexer
import Outputable
import ErrUtils
#if defined (GHCLIB_API_808)
import Bag
#endif
main :: IO ()
main = do
setEnv "TASTY_NUM_THREADS" "1"
setUnsafeGlobalDynFlags (defaultDynFlags fakeSettings fakeLlvmConfig)
defaultMain tests
tests :: TestTree
tests = testGroup " All tests" [parseTests, fixityTests, extendInstancesTests]
chkParseResult :: (DynFlags -> WarningMessages -> String) -> DynFlags -> ParseResult a -> IO ()
chkParseResult report flags = \case
POk s _ -> do
let (wrns, errs) = getMessages s flags
when (not (null errs) || not (null wrns)) $
assertFailure (report flags wrns ++ report flags errs)
#if defined (GHCLIB_API_811) || defined (GHCLIB_API_810)
PFailed s -> assertFailure (report flags $ snd (getMessages s flags))
#else
PFailed _ loc err -> assertFailure (report flags $ unitBag $ mkPlainErrMsg flags loc err)
#endif
parseTests :: TestTree
parseTests = testGroup "Parse tests"
[
testCase "Module" $
chkParseResult report flags $
parseModule (unlines
[ "module Foo (readMany) where"
, "import Data.List"
, "import Data.Maybe"
, "readMany = unfoldr $ listToMaybe . concatMap reads . tails"
]) flags
, testCase "Signature" $
chkParseResult report flags $
parseSignature (unlines
[ "signature Str where"
, "data Str"
, "empty :: Str"
, "append :: Str -> Str -> Str"
]) flags
, testCase "Import" $
chkParseResult report flags $
parseImport "import qualified \"foo-lib\" Foo as Bar hiding ((<.>))" flags
, testCase "Statement" $
chkParseResult report flags $
parseStatement "Foo foo <- bar" flags
, testCase "Backpack" $
chkParseResult report flags $
parseBackpack (unlines
[ "unit main where"
, " module Main where"
, " main = putStrLn \"Hello world!\""
]) flags
, testCase "Expression" $
chkParseResult report flags $
parseExpression "unfoldr $ listToMaybe . concatMap reads . tails" flags
, testCase "Declaration (1)" $
chkParseResult report flags $
parseDeclaration "fact n = if n <= 1 then 1 else n * fact (n - 1)" flags
, testCase "Declaration (2)" $ -- Example from https://github.com/ndmitchell/hlint/issues/842.
chkParseResult report flags $
parseDeclaration "infixr 4 <%@~" flags
, testCase "File" $ do
foo <- makeFile "Foo.hs" $ unlines
["{-# LANGUAGE ScopedTypeVariables #-}"
, "module Foo (readMany) where"
, "import Data.List"
, "import Data.Maybe"
, "readMany = unfoldr $ listToMaybe . concatMap reads . tails"
]
s <- readFile' foo
parsePragmasIntoDynFlags flags ([], []) foo s >>= \case
Left msg -> assertFailure msg
Right flags -> chkParseResult report flags $ parseFile foo flags s
]
where
flags = unsafeGlobalDynFlags
report flags msgs = concat [ showSDoc flags msg | msg <- pprErrMsgBagWithLoc msgs ]
fixityTests :: TestTree
fixityTests = testGroup "Fixity tests"
[
testCase "Expression" $ do
let flags = defaultDynFlags fakeSettings fakeLlvmConfig
case parseExpression "1 + 2 * 3" flags of
#if defined (GHCLIB_API_811) || defined (GHCLIB_API_810)
POk s e ->
#else
POk _ e ->
#endif
#if defined (GHCLIB_API_811) || defined (GHCLIB_API_810)
case unP (runECP_P e >>= \e -> return e) s :: ParseResult (LHsExpr GhcPs) of
POk _ e ->
#endif
assertBool "parse tree not affected" $
showSDocUnsafe (showAstData BlankSrcSpan e) /=
showSDocUnsafe (showAstData BlankSrcSpan (applyFixities [] e))
#if defined (GHCLIB_API_811) || defined (GHCLIB_API_810)
PFailed{} -> assertFailure "ecp failure"
#endif
PFailed{} -> assertFailure "parse error"
, testCase "Pattern" $ do
let flags = defaultDynFlags fakeSettings fakeLlvmConfig
case parseDeclaration "f (1 : 2 :[]) = 1" flags of
POk _ d ->
assertBool "parse tree not affected" $
showSDocUnsafe (showAstData BlankSrcSpan d) /=
showSDocUnsafe (showAstData BlankSrcSpan (applyFixities [] d))
PFailed{} -> assertFailure "parse error"
]
makeFile :: FilePath -> String -> IO FilePath
makeFile relPath contents = do
Directory.createDirectoryIfMissing True $ FilePath.takeDirectory relPath
writeFile relPath contents
return relPath
extendInstancesTests :: TestTree
extendInstancesTests = testGroup "Extend instances tests"
[
testCase "Eq, Ord" $ do
let flags = defaultDynFlags fakeSettings fakeLlvmConfig
case parseExpression "1 + 2 * 3" flags of
#if defined (GHCLIB_API_811) || defined (GHCLIB_API_810)
POk s e ->
#else
POk _ e ->
#endif
#if defined (GHCLIB_API_811) || defined (GHCLIB_API_810)
case unP (runECP_P e >>= \e -> return e) s :: ParseResult (LHsExpr GhcPs) of
POk _ e ->
#endif
do
e' <- return $ applyFixities [] e
assertBool "astEq" $ astEq e e
assertBool "astEq" $ not (astEq e e')
e <- return $ extendInstances e
e' <- return $ extendInstances e'
assertBool "==" $ e == e
assertBool "/=" $ e /= e'
assertBool "< " $ e' < e
assertBool ">=" $ e >= e'
#if defined (GHCLIB_API_811) || defined (GHCLIB_API_810)
_ -> assertFailure "ecp failure"
#endif
_ -> assertFailure "parse error"
]