ghc-exactprint-0.5.0.0: tests/Test/Common.hs
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Test.Common (
RoundtripReport (..)
, Report
, ParseFailure(..)
, ReportType(..)
, roundTripTest
, getModSummaryForFile
, testList
, testPrefix
, Changer
, genTest
, noChange
) where
import Language.Haskell.GHC.ExactPrint
import Language.Haskell.GHC.ExactPrint.Utils
import Language.Haskell.GHC.ExactPrint.Parsers (parseModuleApiAnnsWithCpp)
import Language.Haskell.GHC.ExactPrint.Preprocess
import GHC.Paths (libdir)
import qualified ApiAnnotation as GHC
import qualified DynFlags as GHC
import qualified FastString as GHC
import qualified GHC as GHC hiding (parseModule)
import qualified HeaderInfo as GHC
import qualified Lexer as GHC
import qualified MonadUtils as GHC
import qualified Outputable as GHC
import qualified Parser as GHC
import qualified SrcLoc as GHC
import qualified StringBuffer as GHC
import qualified Data.Map as Map
import Data.List hiding (find)
import Control.Monad
import System.Directory
import Test.Consistency
import Test.HUnit
import System.FilePath
-- import Debug.Trace
testPrefix :: FilePath
testPrefix = "tests" </> "examples"
testList :: String -> [Test] -> Test
testList s ts = TestLabel s (TestList ts)
-- ---------------------------------------------------------------------
-- Roundtrip machinery
type Report = Either ParseFailure RoundtripReport
data RoundtripReport =
Report
{ debugTxt :: String
, status :: ReportType
, cppStatus :: Maybe String -- Result of CPP if invoked
, inconsistent :: Maybe [(GHC.SrcSpan, (GHC.AnnKeywordId, [GHC.SrcSpan]))]
}
data ParseFailure = ParseFailure GHC.SrcSpan String
data ReportType =
Success
| RoundTripFailure deriving (Eq, Show)
runParser :: GHC.P a -> GHC.DynFlags -> FilePath -> String -> GHC.ParseResult a
runParser parser flags filename str = GHC.unP parser parseState
where
location = GHC.mkRealSrcLoc (GHC.mkFastString filename) 1 1
buffer = GHC.stringToStringBuffer str
parseState = GHC.mkPState flags buffer location
parseFile :: GHC.DynFlags -> FilePath -> String -> GHC.ParseResult (GHC.Located (GHC.HsModule GHC.RdrName))
parseFile = runParser GHC.parseModule
mkApiAnns :: GHC.PState -> GHC.ApiAnns
mkApiAnns pstate = (Map.fromListWith (++) . GHC.annotations $ pstate
, Map.fromList ((GHC.noSrcSpan, GHC.comment_q pstate) : (GHC.annotations_comments pstate)))
removeSpaces :: String -> String
removeSpaces = map (\case {'\160' -> ' '; s -> s})
roundTripTest :: FilePath -> IO Report
roundTripTest f = genTest noChange f f
type Changer = (Anns -> GHC.ParsedSource -> IO (Anns,GHC.ParsedSource))
noChange :: Changer
noChange ans parsed = return (ans,parsed)
genTest :: Changer -> FilePath -> FilePath -> IO Report
genTest f origFile expectedFile = do
res <- parseModuleApiAnnsWithCpp defaultCppOptions origFile
expected <- GHC.liftIO $ readFile expectedFile
orig <- GHC.liftIO $ readFile origFile
let pristine = removeSpaces expected
case res of
Left (ss, m) -> return . Left $ ParseFailure ss m
Right (apianns, injectedComments, dflags, pmod) -> do
(printed', anns, pmod') <- GHC.liftIO (runRoundTrip f apianns pmod injectedComments)
let useCpp = GHC.xopt GHC.Opt_Cpp dflags
printed = trimPrinted printed'
-- let (printed, anns) = first trimPrinted $ runRoundTrip apianns pmod injectedComments
-- Clang cpp adds an extra newline character
-- Do not remove this line!
trimPrinted p = if useCpp
then unlines $ take (length (lines pristine)) (lines p)
else p
debugTxt = mkDebugOutput origFile printed pristine apianns anns pmod'
consistency = checkConsistency apianns pmod
inconsistent = if null consistency then Nothing else Just consistency
status = if printed == pristine then Success else RoundTripFailure
cppStatus = if useCpp then Just orig else Nothing
return $ Right Report {..}
mkDebugOutput :: FilePath -> String -> String
-> GHC.ApiAnns
-> Anns
-> GHC.ParsedSource -> String
mkDebugOutput filename printed original apianns anns parsed =
intercalate sep [ printed
, filename
, "lengths:" ++ show (length printed,length original) ++ "\n"
, showAnnData anns 0 parsed
, showGhc anns
, showGhc apianns
]
where
sep = "\n==============\n"
runRoundTrip :: Changer
-> GHC.ApiAnns -> GHC.Located (GHC.HsModule GHC.RdrName)
-> [Comment]
-> IO (String, Anns, GHC.ParsedSource)
runRoundTrip f !anns !parsedOrig cs = do
let !relAnns = relativiseApiAnnsWithComments cs parsedOrig anns
(annsMod, pmod) <- f relAnns parsedOrig
let !printed = exactPrint pmod annsMod
return (printed, relAnns, pmod)
-- ---------------------------------------------------------------------`
canonicalizeGraph ::
[GHC.ModSummary] -> IO [(Maybe (FilePath), GHC.ModSummary)]
canonicalizeGraph graph = do
let mm = map (\m -> (GHC.ml_hs_file $ GHC.ms_location m, m)) graph
canon ((Just fp),m) = do
fp' <- canonicalizePath fp
return $ (Just fp',m)
canon (Nothing,m) = return (Nothing,m)
mm' <- mapM canon mm
return mm'
-- ---------------------------------------------------------------------
getModSummaryForFile :: (GHC.GhcMonad m) => FilePath -> m (Maybe GHC.ModSummary)
getModSummaryForFile fileName = do
cfileName <- GHC.liftIO $ canonicalizePath fileName
graph <- GHC.getModuleGraph
cgraph <- GHC.liftIO $ canonicalizeGraph graph
let mm = filter (\(mfn,_ms) -> mfn == Just cfileName) cgraph
case mm of
[] -> return Nothing
fs -> return (Just (snd $ head fs))