Drop memory-mapped files, since ghc doesn't play well with those

All files are now "redirected", either user-created, or created by ghc-mod itself.
This commit is contained in:
Nikolay Yakimov 2015-08-16 23:20:00 +03:00
parent 20d6d4bae7
commit a5dae2a82d
9 changed files with 151 additions and 128 deletions

View File

@ -66,6 +66,7 @@ module Language.Haskell.GhcMod (
, gmUnsafeErrStrLn , gmUnsafeErrStrLn
-- * FileMapping -- * FileMapping
, loadMappedFile , loadMappedFile
, loadMappedFileSource
, unloadMappedFile , unloadMappedFile
) where ) where

View File

@ -1,5 +1,6 @@
module Language.Haskell.GhcMod.FileMapping module Language.Haskell.GhcMod.FileMapping
( loadMappedFile ( loadMappedFile
, loadMappedFileSource
, unloadMappedFile , unloadMappedFile
, mapFile , mapFile
, fileModSummaryWithMapping , fileModSummaryWithMapping
@ -11,43 +12,55 @@ import Language.Haskell.GhcMod.Gap
import Language.Haskell.GhcMod.HomeModuleGraph import Language.Haskell.GhcMod.HomeModuleGraph
import Language.Haskell.GhcMod.Utils import Language.Haskell.GhcMod.Utils
import Data.Time import System.IO
import System.FilePath
import System.Directory
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import GHC import GHC
import Control.Monad
import Control.Monad.Trans (lift)
loadMappedFile :: IOish m => FilePath -> FileMapping -> GhcModT m () loadMappedFile :: IOish m => FilePath -> FilePath -> GhcModT m ()
loadMappedFile from fm = loadMappedFile from to =
getCanonicalFileNameSafe from >>= (`addMMappedFile` fm) getCanonicalFileNameSafe from >>= (`addMMappedFile` FileMapping to False)
mapFile :: (IOish m, GmState m, GhcMonad m) => loadMappedFileSource :: IOish m => FilePath -> String -> GhcModT m ()
loadMappedFileSource from src = do
tmpdir <- cradleTempDir `fmap` cradle
to <- liftIO $ do
(fn, h) <- openTempFile tmpdir (takeFileName from)
hPutStr h src
hClose h
return fn
getCanonicalFileNameSafe from >>= (`addMMappedFile` FileMapping to True)
mapFile :: (IOish m, GmState m, GhcMonad m, GmEnv m) =>
HscEnv -> Target -> m Target HscEnv -> Target -> m Target
mapFile _ (Target tid@(TargetFile filePath _) taoc _) = do mapFile _ (Target tid@(TargetFile filePath _) taoc _) = do
mapping <- lookupMMappedFile filePath mapping <- lookupMMappedFile filePath
mkMappedTarget tid taoc mapping mkMappedTarget (Just filePath) tid taoc mapping
mapFile env (Target tid@(TargetModule moduleName) taoc _) = do mapFile env (Target tid@(TargetModule moduleName) taoc _) = do
mapping <- runMaybeT $ do (fp, mapping) <- do
filePath <- MaybeT $ liftIO $ findModulePath env moduleName filePath <- fmap (fmap mpPath) (liftIO $ findModulePath env moduleName)
MaybeT $ lookupMMappedFile $ mpPath filePath mmf <- runMaybeT $ MaybeT (return filePath) >>= MaybeT . lookupMMappedFile
mkMappedTarget tid taoc mapping return (filePath, mmf)
mkMappedTarget fp tid taoc mapping
mkMappedTarget :: (IOish m, GmState m, GhcMonad m) => mkMappedTarget :: (IOish m, GmState m, GmEnv m, GhcMonad m) =>
TargetId -> Bool -> Maybe FileMapping -> m Target Maybe FilePath -> TargetId -> Bool -> Maybe FileMapping -> m Target
mkMappedTarget _ taoc (Just (RedirectedMapping to)) = mkMappedTarget _ _ taoc (Just to) =
return $ mkTarget (TargetFile to Nothing) taoc Nothing return $ mkTarget (TargetFile (fmPath to) Nothing) taoc Nothing
mkMappedTarget tid taoc (Just (MemoryMapping (Just src))) = do mkMappedTarget _ tid taoc _ = return $ mkTarget tid taoc Nothing
sb <- toStringBuffer [src]
ct <- liftIO getCurrentTime
return $ mkTarget tid taoc $ Just (sb, ct)
mkMappedTarget tid taoc _ = return $ mkTarget tid taoc Nothing
unloadMappedFile :: IOish m => FilePath -> GhcModT m () unloadMappedFile :: IOish m => FilePath -> GhcModT m ()
unloadMappedFile = (delMMappedFile =<<) . getCanonicalFileNameSafe unloadMappedFile what = void $ runMaybeT $ do
cfn <- lift $ getCanonicalFileNameSafe what
fm <- MaybeT $ lookupMMappedFile cfn
liftIO $ when (fmTemp fm) $ removeFile (fmPath fm)
delMMappedFile cfn
fileModSummaryWithMapping :: (IOish m, GmState m, GhcMonad m, GmEnv m) => fileModSummaryWithMapping :: (IOish m, GmState m, GhcMonad m, GmEnv m) =>
FilePath -> m ModSummary FilePath -> m ModSummary
fileModSummaryWithMapping fn = do fileModSummaryWithMapping fn =
mmf <- getCanonicalFileNameSafe fn >>= lookupMMappedFile withMappedFile fn $ \fn' -> fileModSummary fn'
case mmf of
Just (RedirectedMapping to) -> fileModSummary to
_ -> fileModSummary fn

View File

@ -44,6 +44,7 @@ import Control.Arrow ((&&&))
import Control.Applicative import Control.Applicative
import Control.Monad import Control.Monad
import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT) import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)
import Control.Monad.Trans (lift)
import Control.Monad.State.Strict (execStateT) import Control.Monad.State.Strict (execStateT)
import Control.Monad.State.Class import Control.Monad.State.Class
import Data.Maybe import Data.Maybe
@ -62,7 +63,7 @@ import Language.Haskell.GhcMod.Logging
import Language.Haskell.GhcMod.Logger import Language.Haskell.GhcMod.Logger
import Language.Haskell.GhcMod.Monad.Types import Language.Haskell.GhcMod.Monad.Types
import Language.Haskell.GhcMod.Types import Language.Haskell.GhcMod.Types
import Language.Haskell.GhcMod.Utils (getMappedFileSource) import Language.Haskell.GhcMod.Utils (withMappedFile)
import Language.Haskell.GhcMod.Gap (parseModuleHeader) import Language.Haskell.GhcMod.Gap (parseModuleHeader)
-- | Turn module graph into a graphviz dot file -- | Turn module graph into a graphviz dot file
@ -247,19 +248,9 @@ preprocessFile :: (IOish m, GmEnv m, GmState m) =>
HscEnv -> FilePath -> m (Either [String] ([String], (DynFlags, FilePath))) HscEnv -> FilePath -> m (Either [String] ([String], (DynFlags, FilePath)))
preprocessFile env file = preprocessFile env file =
withLogger' env $ \setDf -> do withLogger' env $ \setDf -> do
src <- runMaybeT $ getMappedFileSource file withMappedFile file $ \fn -> do
let env' = env { hsc_dflags = setDf (hsc_dflags env) } let env' = env { hsc_dflags = setDf (hsc_dflags env) }
maybe liftIO $ preprocess env' (fn, Nothing)
(liftIO $ preprocess env' (file, Nothing))
(preprocessWithTemp env' file)
src
where
preprocessWithTemp env' fn src = do
tmpdir <- cradleTempDir <$> cradle
liftIO $ withTempFile tmpdir fn $ \fn' hndl -> do
hPutStr hndl src
hClose hndl
preprocess env' (fn', Nothing)
fileModuleName :: (IOish m, GmEnv m, GmState m) => fileModuleName :: (IOish m, GmEnv m, GmState m) =>
HscEnv -> FilePath -> m (Either [String] (Maybe ModuleName)) HscEnv -> FilePath -> m (Either [String] (Maybe ModuleName))
@ -269,11 +260,12 @@ fileModuleName env fn = do
case ep of case ep of
Left errs -> do Left errs -> do
return $ Left errs return $ Left errs
Right (_warns, (dflags, procdFile)) -> handler $ do Right (_warns, (dflags, procdFile)) -> leftM (errBagToStrList env) =<< handler (do
src <- readFile procdFile src <- readFile procdFile
case parseModuleHeader src dflags procdFile of case parseModuleHeader src dflags procdFile of
Left errs -> do Left errs -> return $ Left errs
return $ Left $ errBagToStrList env errs
Right (_, lmdl) -> do Right (_, lmdl) -> do
let HsModule {..} = unLoc lmdl let HsModule {..} = unLoc lmdl
return $ Right $ unLoc <$> hsmodName return $ Right $ unLoc <$> hsmodName)
where
leftM f = either (return . Left <=< f) (return . Right)

View File

@ -108,12 +108,16 @@ withLogger' env action = do
return ((,) ls <$> a) return ((,) ls <$> a)
errBagToStrList :: HscEnv -> Bag ErrMsg -> [String] errBagToStrList :: (Functor m, GmState m, GmEnv m) => HscEnv -> Bag ErrMsg -> m [String]
errBagToStrList env errs = let errBagToStrList env errs = let
dflags = hsc_dflags env dflags = hsc_dflags env
pu = icPrintUnqual dflags (hsc_IC env) pu = icPrintUnqual dflags (hsc_IC env)
st = mkUserStyle pu AllTheWay st = mkUserStyle pu AllTheWay
in runReader (errsToStr (bagToList errs)) GmPprEnv{gpeDynFlags=dflags, gpePprStyle=st} in do
rfm <- mkRevRedirMapFunc
return $ runReader
(errsToStr (bagToList errs))
GmPprEnv{gpeDynFlags=dflags, gpePprStyle=st, gpeMapFile=rfm}
---------------------------------------------------------------- ----------------------------------------------------------------

View File

@ -151,7 +151,7 @@ runGmlTWith efnmns' mdf wrapper action = do
let (fns, mns) = partitionEithers efnmns' let (fns, mns) = partitionEithers efnmns'
ccfns = map (cradleCurrentDir crdl </>) fns ccfns = map (cradleCurrentDir crdl </>) fns
cfns <- liftIO $ mapM canonicalizePath ccfns cfns <- mapM getCanonicalFileNameSafe ccfns
let serfnmn = Set.fromList $ map Right mns ++ map Left cfns let serfnmn = Set.fromList $ map Right mns ++ map Left cfns
opts <- targetGhcOptions crdl serfnmn opts <- targetGhcOptions crdl serfnmn
let opts' = opts ++ ["-O0"] ++ ghcUserOptions let opts' = opts ++ ["-O0"] ++ ghcUserOptions

View File

@ -69,8 +69,7 @@ data OutputStyle = LispStyle -- ^ S expression style.
-- | The type for line separator. Historically, a Null string is used. -- | The type for line separator. Historically, a Null string is used.
newtype LineSeparator = LineSeparator String deriving (Show) newtype LineSeparator = LineSeparator String deriving (Show)
data FileMapping = RedirectedMapping FilePath data FileMapping = FileMapping {fmPath :: FilePath, fmTemp :: Bool}
| MemoryMapping (Maybe String)
deriving Show deriving Show
type FileMappingMap = Map FilePath FileMapping type FileMappingMap = Map FilePath FileMapping
@ -99,7 +98,7 @@ data Options = Options {
-- | If 'True', 'browse' will return fully qualified name -- | If 'True', 'browse' will return fully qualified name
, qualified :: Bool , qualified :: Bool
, hlintOpts :: [String] , hlintOpts :: [String]
, fileMappings :: [(FilePath,FileMapping)] , fileMappings :: [(FilePath, Maybe FilePath)]
} deriving (Show) } deriving (Show)
-- | A default 'Options'. -- | A default 'Options'.

View File

@ -26,25 +26,23 @@ module Language.Haskell.GhcMod.Utils (
import Control.Applicative import Control.Applicative
import Data.Char import Data.Char
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe (mapMaybe, fromMaybe) import Data.Maybe (fromMaybe)
import Exception import Exception
import Language.Haskell.GhcMod.Error import Language.Haskell.GhcMod.Error
import Language.Haskell.GhcMod.Types import Language.Haskell.GhcMod.Types
import Language.Haskell.GhcMod.Monad.Types import Language.Haskell.GhcMod.Monad.Types
import System.Directory (getCurrentDirectory, setCurrentDirectory, doesFileExist, import System.Directory (getCurrentDirectory, setCurrentDirectory, doesFileExist,
getTemporaryDirectory, canonicalizePath, removeFile) getTemporaryDirectory, canonicalizePath)
import System.Environment import System.Environment
import System.FilePath (splitDrive, takeDirectory, takeFileName, pathSeparators, import System.FilePath (splitDrive, takeDirectory, takeFileName, pathSeparators,
(</>), makeRelative) (</>), makeRelative)
import System.IO.Temp (createTempDirectory, openTempFile) import System.IO.Temp (createTempDirectory)
import System.IO (hPutStr, hClose)
import System.Process (readProcess) import System.Process (readProcess)
import Text.Printf import Text.Printf
import Paths_ghc_mod (getLibexecDir) import Paths_ghc_mod (getLibexecDir)
import Utils import Utils
import Prelude import Prelude
import Control.Monad.Trans.Maybe
-- dropWhileEnd is not provided prior to base 4.5.0.0. -- dropWhileEnd is not provided prior to base 4.5.0.0.
dropWhileEnd :: (a -> Bool) -> [a] -> [a] dropWhileEnd :: (a -> Bool) -> [a] -> [a]
@ -167,44 +165,26 @@ withMappedFile :: (IOish m, GmState m, GmEnv m) =>
forall a. FilePath -> (FilePath -> m a) -> m a forall a. FilePath -> (FilePath -> m a) -> m a
withMappedFile file action = getCanonicalFileNameSafe file >>= lookupMMappedFile >>= runWithFile withMappedFile file action = getCanonicalFileNameSafe file >>= lookupMMappedFile >>= runWithFile
where where
runWithFile (Just (RedirectedMapping to)) = action to runWithFile (Just to) = action $ fmPath to
runWithFile (Just (MemoryMapping (Just src))) = do
crdl <- cradle
(fp,hndl) <- liftIO $ openTempFile (cradleTempDir crdl) (takeFileName file)
liftIO $ hPutStr hndl src
liftIO $ hClose hndl
result <- action fp
liftIO $ removeFile fp
return result
runWithFile _ = action file runWithFile _ = action file
getCanonicalFileNameSafe :: (IOish m, GmEnv m) => FilePath -> m FilePath getCanonicalFileNameSafe :: (IOish m, GmEnv m) => FilePath -> m FilePath
getCanonicalFileNameSafe fn = do getCanonicalFileNameSafe fn = do
crdl <- cradle crdl <- cradle
let ccfn = cradleCurrentDir crdl </> fn let ccfn = cradleRootDir crdl </> fn
fex <- liftIO $ doesFileExist ccfn fex <- liftIO $ doesFileExist ccfn
if fex if fex
then liftIO $ canonicalizePath ccfn then liftIO $ canonicalizePath ccfn
else return ccfn else return ccfn
getMappedFileSource :: (IOish m, GmEnv m, GmState m) => FilePath -> MaybeT m String
getMappedFileSource fn = do
mf <- MaybeT $ getCanonicalFileNameSafe fn >>= lookupMMappedFile
case mf of
RedirectedMapping fn' -> liftIO $ readFile fn'
MemoryMapping (Just src) -> return src
_ -> mzero
mkRevRedirMapFunc :: (Functor m, GmState m, GmEnv m) => m (FilePath -> FilePath) mkRevRedirMapFunc :: (Functor m, GmState m, GmEnv m) => m (FilePath -> FilePath)
mkRevRedirMapFunc = do mkRevRedirMapFunc = do
rm <- M.fromList <$> mapMaybe (uncurry mf) <$> M.toList <$> getMMappedFiles rm <- M.fromList <$> map (uncurry mf) <$> M.toList <$> getMMappedFiles
crdl <- cradle crdl <- cradle
return $ \key -> return $ \key ->
fromMaybe key fromMaybe key
$ makeRelative (cradleRootDir crdl) $ makeRelative (cradleRootDir crdl)
<$> M.lookup key rm <$> M.lookup key rm
where where
mf :: FilePath -> FileMapping -> Maybe (FilePath, FilePath) mf :: FilePath -> FileMapping -> (FilePath, FilePath)
mf from (RedirectedMapping to) mf from to = (fmPath to, from)
= Just (to, from)
mf _ _ = Nothing

View File

@ -274,8 +274,8 @@ globalArgSpec =
, option "" ["map-file"] "Redirect one file to another, --map-file \"file1.hs=file2.hs\"" $ , option "" ["map-file"] "Redirect one file to another, --map-file \"file1.hs=file2.hs\"" $
reqArg "OPT" $ \g o -> reqArg "OPT" $ \g o ->
let m = case second (drop 1) $ span (/='=') g of let m = case second (drop 1) $ span (/='=') g of
(s,"") -> (s, MemoryMapping Nothing) (s,"") -> (s, Nothing)
(f,t) -> (f, RedirectedMapping t) (f,t) -> (f, Just t)
in in
Right $ o { fileMappings = m : fileMappings o } Right $ o { fileMappings = m : fileMappings o }
@ -359,16 +359,10 @@ main = do
progMain :: (Options,[String]) -> IO () progMain :: (Options,[String]) -> IO ()
progMain (globalOptions,cmdArgs) = hndle $ runGhcModT globalOptions $ handler $ do progMain (globalOptions,cmdArgs) = hndle $ runGhcModT globalOptions $ handler $ do
let
loadMMappedFiles from (MemoryMapping Nothing) = do
src <- liftIO getFileSourceFromStdin
return (from, MemoryMapping $ Just src)
loadMMappedFiles from x = return (from, x)
fileMappings' <- forM (reverse $ fileMappings globalOptions) $ uncurry loadMMappedFiles
case globalCommands cmdArgs of case globalCommands cmdArgs of
Just s -> gmPutStr s Just s -> gmPutStr s
Nothing -> do Nothing -> do
mapM_ (uncurry loadMappedFile) fileMappings' forM_ (reverse $ fileMappings globalOptions) $ uncurry loadMMappedFiles
ghcCommands cmdArgs ghcCommands cmdArgs
where where
hndle action = do hndle action = do
@ -378,6 +372,10 @@ progMain (globalOptions,cmdArgs) = hndle $ runGhcModT globalOptions $ handler $
return () return ()
Left ed -> Left ed ->
exitError' globalOptions $ renderStyle ghcModStyle (gmeDoc ed) exitError' globalOptions $ renderStyle ghcModStyle (gmeDoc ed)
loadMMappedFiles from (Just to) = loadMappedFile from to
loadMMappedFiles from (Nothing) = do
src <- liftIO getFileSourceFromStdin
loadMappedFileSource from src
globalCommands :: [String] -> Maybe String globalCommands :: [String] -> Maybe String
globalCommands (cmd:_) globalCommands (cmd:_)
@ -447,7 +445,7 @@ legacyInteractiveLoop symdbreq world = do
"browse" -> browseCmd args "browse" -> browseCmd args
"map-file" -> liftIO getFileSourceFromStdin "map-file" -> liftIO getFileSourceFromStdin
>>= loadMappedFile arg . MemoryMapping . Just >>= loadMappedFileSource arg
>> return "" >> return ""
"unmap-file" -> unloadMappedFile arg "unmap-file" -> unloadMappedFile arg

View File

@ -7,6 +7,7 @@ import TestUtils
import qualified Data.Map as M import qualified Data.Map as M
import Dir import Dir
import System.IO.Temp import System.IO.Temp
import System.Directory
import Language.Haskell.GhcMod import Language.Haskell.GhcMod
@ -16,45 +17,74 @@ spec = do
it "inserts a given FilePath FileMapping into state with canonicalized path" $ do it "inserts a given FilePath FileMapping into state with canonicalized path" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
mappedFiles <- runD $ do mappedFiles <- runD $ do
loadMappedFile "File.hs" (MemoryMapping Nothing) loadMappedFile "File.hs" "File.hs"
getMMappedFiles getMMappedFiles
dir <- getCurrentDirectory dir <- getCurrentDirectory
show mappedFiles `shouldBe` show (M.fromList [(dir </> "File.hs", MemoryMapping Nothing)]) show mappedFiles `shouldBe` show (M.fromList [(dir </> "File.hs", FileMapping "File.hs" False)])
it "should try to guess a canonical name if file doesn't exist" $ do it "should try to guess a canonical name if file doesn't exist" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
mappedFiles <- runD $ do mappedFiles <- runD $ do
loadMappedFile "NonExistantFile.hs" (MemoryMapping Nothing) loadMappedFile "NonExistantFile.hs" "File.hs"
getMMappedFiles getMMappedFiles
dir <- getCurrentDirectory dir <- getCurrentDirectory
show mappedFiles `shouldBe` show (M.fromList [(dir </> "NonExistantFile.hs", MemoryMapping Nothing)]) show mappedFiles `shouldBe` show (M.fromList [(dir </> "NonExistantFile.hs", FileMapping "File.hs" False)])
describe "loadMappedFileSource" $ do
it "inserts a given FilePath FileMapping into state with canonicalized path" $ do
withDirectory_ "test/data/file-mapping" $ do
mappedFiles <- runD $ do
loadMappedFileSource "File.hs" "main :: IO ()"
getMMappedFiles
dir <- getCurrentDirectory
-- TODO
M.toList mappedFiles `shouldSatisfy` \[(fn, FileMapping _to True)] ->
fn == dir </> "File.hs"
it "should try to guess a canonical name if file doesn't exist" $ do
withDirectory_ "test/data/file-mapping" $ do
mappedFiles <- runD $ do
loadMappedFileSource "NonExistantFile.hs" "main :: IO ()"
getMMappedFiles
dir <- getCurrentDirectory
-- TODO
M.toList mappedFiles `shouldSatisfy` \[(fn, FileMapping _to True)] ->
fn == dir </> "NonExistantFile.hs"
describe "unloadMappedFile" $ do describe "unloadMappedFile" $ do
it "removes a given FilePath from state" $ do it "removes a given FilePath from state" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
mappedFiles <- runD $ do mappedFiles <- runD $ do
loadMappedFile "File.hs" (MemoryMapping Nothing) loadMappedFile "File.hs" "File2.hs"
unloadMappedFile "File.hs" unloadMappedFile "File.hs"
getMMappedFiles getMMappedFiles
show mappedFiles `shouldBe` show (M.fromList ([] :: [(FilePath, FileMapping)])) show mappedFiles `shouldBe` show (M.fromList ([] :: [(FilePath, FileMapping)]))
it "should work even if file does not exist" $ do it "should work even if file does not exist" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
mappedFiles <- runD $ do mappedFiles <- runD $ do
loadMappedFile "NonExistantFile.hs" (MemoryMapping Nothing) loadMappedFile "NonExistantFile.hs" "File2.hs"
unloadMappedFile "NonExistantFile.hs" unloadMappedFile "NonExistantFile.hs"
getMMappedFiles getMMappedFiles
show mappedFiles `shouldBe` show (M.fromList ([] :: [(FilePath, FileMapping)])) show mappedFiles `shouldBe` show (M.fromList ([] :: [(FilePath, FileMapping)]))
it "should remove created temporary files" $ do
withDirectory_ "test/data/file-mapping" $ do
dir <- getCurrentDirectory
fileExists <- runD $ do
loadMappedFileSource "NonExistantFile.hs" "main :: IO ()"
fp <- maybe undefined fmPath `fmap` lookupMMappedFile (dir </> "NonExistantFile.hs")
unloadMappedFile "NonExistantFile.hs"
liftIO $ doesFileExist fp
not fileExists `shouldBe` True
describe "withMappedFile" $ do describe "withMappedFile" $ do
it "checks if there is a redirected file and calls and action with its FilePath" $ do it "checks if there is a redirected file and calls and action with its FilePath" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
res <- runD $ do res <- runD $ do
loadMappedFile "File.hs" (RedirectedMapping "File_Redir.hs") loadMappedFile "File.hs" "File_Redir.hs"
withMappedFile "File.hs" return withMappedFile "File.hs" return
res `shouldBe` "File_Redir.hs" res `shouldBe` "File_Redir.hs"
it "checks if there is an in-memory file and calls and action with temporary file" $ do it "checks if there is an in-memory file and calls and action with temporary file" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
(fn, src) <- runD $ do (fn, src) <- runD $ do
loadMappedFile "File.hs" (MemoryMapping $ Just "main = test") loadMappedFileSource "File.hs" "main = test"
withMappedFile "File.hs" $ \fn -> do withMappedFile "File.hs" $ \fn -> do
src <- liftIO $ readFile fn src <- liftIO $ readFile fn
return (fn, src) return (fn, src)
@ -69,101 +99,107 @@ spec = do
describe "integration tests" $ do describe "integration tests" $ do
it "checks redirected file if one is specified and outputs original filename" $ do it "checks redirected file if one is specified and outputs original filename" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
let fm = [("File.hs", RedirectedMapping "File_Redir.hs")] let fm = [("File.hs", "File_Redir.hs")]
res <- run defaultOptions $ do res <- run defaultOptions $ do
mapM_ (uncurry loadMappedFile) fm mapM_ (uncurry loadMappedFile) fm
checkSyntax ["File.hs"] checkSyntax ["File.hs"]
res `shouldBe` "File.hs:1:1:Warning: Top-level binding with no type signature: main :: IO ()\n" res `shouldBe` "File.hs:1:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
it "checks in-memory file if one is specified and outputs original filename" $ do it "checks in-memory file if one is specified and outputs original filename" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
let fm = [("File.hs", MemoryMapping $ Just "main = putStrLn \"Hello World!\"\n")] let fm = [("File.hs", "main = putStrLn \"Hello World!\"\n")]
res <- run defaultOptions $ do res <- run defaultOptions $ do
mapM_ (uncurry loadMappedFile) fm mapM_ (uncurry loadMappedFileSource) fm
checkSyntax ["File.hs"] checkSyntax ["File.hs"]
res `shouldBe` "File.hs:1:1:Warning: Top-level binding with no type signature: main :: IO ()\n" res `shouldBe` "File.hs:1:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
it "should work even if file doesn't exist" $ do
withDirectory_ "test/data/file-mapping" $ do
let fm = [("Nonexistent.hs", "main = putStrLn \"Hello World!\"\n")]
res <- run defaultOptions{logLevel=GmDebug} $ do
mapM_ (uncurry loadMappedFileSource) fm
checkSyntax ["Nonexistent.hs"]
res `shouldBe` "Nonexistent.hs:1:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
it "lints redirected file if one is specified and outputs original filename" $ do it "lints redirected file if one is specified and outputs original filename" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
res <- runD $ do res <- runD $ do
loadMappedFile "File.hs" (RedirectedMapping "File_Redir_Lint.hs") loadMappedFile "File.hs" "File_Redir_Lint.hs"
lint "File.hs" lint "File.hs"
res `shouldBe` "File.hs:4:1: Error: Eta reduce\NULFound:\NUL func a b = (*) a b\NULWhy not:\NUL func = (*)\n" res `shouldBe` "File.hs:4:1: Error: Eta reduce\NULFound:\NUL func a b = (*) a b\NULWhy not:\NUL func = (*)\n"
it "lints in-memory file if one is specified and outputs original filename" $ do it "lints in-memory file if one is specified and outputs original filename" $ do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
res <- runD $ do res <- runD $ do
loadMappedFile "File.hs" (MemoryMapping $ Just "func a b = (++) a b\n") loadMappedFileSource "File.hs" "func a b = (++) a b\n"
lint "File.hs" lint "File.hs"
res `shouldBe` "File.hs:1:1: Error: Eta reduce\NULFound:\NUL func a b = (++) a b\NULWhy not:\NUL func = (++)\n" res `shouldBe` "File.hs:1:1: Error: Eta reduce\NULFound:\NUL func a b = (++) a b\NULWhy not:\NUL func = (++)\n"
it "shows types of the expression for redirected files" $ do it "shows types of the expression for redirected files" $ do
let tdir = "test/data/file-mapping" let tdir = "test/data/file-mapping"
res <- runD' tdir $ do res <- runD' tdir $ do
loadMappedFile "File.hs" (RedirectedMapping "File_Redir_Lint.hs") loadMappedFile "File.hs" "File_Redir_Lint.hs"
types "File.hs" 4 12 types "File.hs" 4 12
res `shouldBe` "4 12 4 15 \"a -> a -> a\"\n4 12 4 17 \"a -> a\"\n4 12 4 19 \"a\"\n4 1 4 19 \"a -> a -> a\"\n" res `shouldBe` "4 12 4 15 \"a -> a -> a\"\n4 12 4 17 \"a -> a\"\n4 12 4 19 \"a\"\n4 1 4 19 \"a -> a -> a\"\n"
it "shows types of the expression for in-memory files" $ do it "shows types of the expression for in-memory files" $ do
let tdir = "test/data/file-mapping" let tdir = "test/data/file-mapping"
res <- runD' tdir $ do res <- runD' tdir $ do
loadMappedFile "File.hs" (MemoryMapping $ Just "main = putStrLn \"Hello!\"") loadMappedFileSource "File.hs" "main = putStrLn \"Hello!\""
types "File.hs" 1 14 types "File.hs" 1 14
res `shouldBe` "1 8 1 16 \"String -> IO ()\"\n1 8 1 25 \"IO ()\"\n1 1 1 25 \"IO ()\"\n" res `shouldBe` "1 8 1 16 \"String -> IO ()\"\n1 8 1 25 \"IO ()\"\n1 1 1 25 \"IO ()\"\n"
it "shows info for the expression for redirected files" $ do it "shows info for the expression for redirected files" $ do
let tdir = "test/data/file-mapping" let tdir = "test/data/file-mapping"
res <- runD' tdir $ do res <- runD' tdir $ do
loadMappedFile "File.hs" (RedirectedMapping "File_Redir_Lint.hs") loadMappedFile "File.hs" "File_Redir_Lint.hs"
info "File.hs" $ Expression "func" info "File.hs" $ Expression "func"
res `shouldBe` "func :: Num a => a -> a -> a \t-- Defined at File.hs:4:1\n" res `shouldBe` "func :: Num a => a -> a -> a \t-- Defined at File.hs:4:1\n"
it "shows info for the expression for in-memory files" $ do it "shows info for the expression for in-memory files" $ do
let tdir = "test/data/file-mapping" let tdir = "test/data/file-mapping"
res <- runD' tdir $ do res <- runD' tdir $ do
loadMappedFile "File.hs" (MemoryMapping $ Just "module File where\n\ntestfun = putStrLn \"Hello!\"") loadMappedFileSource "File.hs" "module File where\n\ntestfun = putStrLn \"Hello!\""
info "File.hs" $ Expression "testfun" info "File.hs" $ Expression "testfun"
res `shouldBe` "testfun :: IO () \t-- Defined at File.hs:3:1\n" res `shouldBe` "testfun :: IO () \t-- Defined at File.hs:3:1\n"
describe "preprocessor tests" $ do describe "preprocessor tests" $ do
it "checks redirected file if one is specified and outputs original filename" $ do it "checks redirected file if one is specified and outputs original filename" $ do
withDirectory_ "test/data/file-mapping/preprocessor" $ do withDirectory_ "test/data/file-mapping/preprocessor" $ do
let fm = [("File.hs", RedirectedMapping "File_Redir.hs")] let fm = [("File.hs", "File_Redir.hs")]
res <- run defaultOptions $ do res <- run defaultOptions $ do
mapM_ (uncurry loadMappedFile) fm mapM_ (uncurry loadMappedFile) fm
checkSyntax ["File.hs"] checkSyntax ["File.hs"]
res `shouldBe` "File.hs:3:1:Warning: Top-level binding with no type signature: main :: IO ()\n" res `shouldBe` "File.hs:3:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
it "doesn't check in-memory file" $ do it "checks in-memory file" $ do
withDirectory_ "test/data/file-mapping/preprocessor" $ do withDirectory_ "test/data/file-mapping/preprocessor" $ do
src <- readFile "File_Redir.hs" src <- readFile "File_Redir.hs"
let fm = [("File.hs", MemoryMapping $ Just src)] let fm = [("File.hs", src)]
res <- run defaultOptions $ do res <- run defaultOptions $ do
mapM_ (uncurry loadMappedFile) fm mapM_ (uncurry loadMappedFileSource) fm
checkSyntax ["File.hs"] checkSyntax ["File.hs"]
res `shouldBe` "buffer needs preprocesing; interactive check disabled\n" res `shouldBe` "File.hs:3:1:Warning: Top-level binding with no type signature: main :: IO ()\n"
it "lints redirected file if one is specified and outputs original filename" $ do it "lints redirected file if one is specified and outputs original filename" $ do
withDirectory_ "test/data/file-mapping/preprocessor" $ do withDirectory_ "test/data/file-mapping/preprocessor" $ do
res <- runD $ do res <- runD $ do
loadMappedFile "File.hs" (RedirectedMapping "File_Redir_Lint.hs") loadMappedFile "File.hs" "File_Redir_Lint.hs"
lint "File.hs" lint "File.hs"
res `shouldBe` "File.hs:6:1: Error: Eta reduce\NULFound:\NUL func a b = (*) a b\NULWhy not:\NUL func = (*)\n" res `shouldBe` "File.hs:6:1: Error: Eta reduce\NULFound:\NUL func a b = (*) a b\NULWhy not:\NUL func = (*)\n"
it "lints in-memory file if one is specified and outputs original filename" $ do it "lints in-memory file if one is specified and outputs original filename" $ do
withDirectory_ "test/data/file-mapping/preprocessor" $ do withDirectory_ "test/data/file-mapping/preprocessor" $ do
src <- readFile "File_Redir_Lint.hs" src <- readFile "File_Redir_Lint.hs"
res <- runD $ do res <- runD $ do
loadMappedFile "File.hs" (MemoryMapping $ Just src) loadMappedFileSource "File.hs" src
lint "File.hs" lint "File.hs"
res `shouldBe` "File.hs:6:1: Error: Eta reduce\NULFound:\NUL func a b = (*) a b\NULWhy not:\NUL func = (*)\n" res `shouldBe` "File.hs:6:1: Error: Eta reduce\NULFound:\NUL func a b = (*) a b\NULWhy not:\NUL func = (*)\n"
describe "literate haskell tests" $ do describe "literate haskell tests" $ do
it "checks redirected file if one is specified and outputs original filename" $ do it "checks redirected file if one is specified and outputs original filename" $ do
withDirectory_ "test/data/file-mapping/lhs" $ do withDirectory_ "test/data/file-mapping/lhs" $ do
let fm = [("File.lhs", RedirectedMapping "File_Redir.lhs")] let fm = [("File.lhs", "File_Redir.lhs")]
res <- run defaultOptions $ do res <- run defaultOptions $ do
mapM_ (uncurry loadMappedFile) fm mapM_ (uncurry loadMappedFile) fm
checkSyntax ["File.lhs"] checkSyntax ["File.lhs"]
res `shouldBe` "File.lhs:1:3:Warning: Top-level binding with no type signature: main :: IO ()\n" res `shouldBe` "File.lhs:1:3:Warning: Top-level binding with no type signature: main :: IO ()\n"
-- NOTE: GHC can't 'unliterate' a file in-memory, so this won't work it "checks in-memory file if one is specified and outputs original filename" $ do
-- it "checks in-memory file if one is specified and outputs original filename" $ do withDirectory_ "test/data/file-mapping/lhs" $ do
-- withDirectory_ "test/data/file-mapping/lhs" $ do src <- readFile "File_Redir.lhs"
-- src <- readFile "File_Redir.lhs" let fm = [("File.lhs", src)]
-- let fm = [("File.lhs", MemoryMapping $ Just src)] res <- run defaultOptions $ do
-- res <- run defaultOptions $ do mapM_ (uncurry loadMappedFileSource) fm
-- mapM_ (uncurry loadMappedFile) fm checkSyntax ["File.lhs"]
-- checkSyntax ["File.lhs"] res `shouldBe` "File.lhs:1:3:Warning: Top-level binding with no type signature: main :: IO ()\n"
-- res `shouldBe` "File.lhs:1:3:Warning: Top-level binding with no type signature: main :: IO ()\n"
-- NOTE: There is a bug in hlint that prevents it from linting lhs files. -- NOTE: There is a bug in hlint that prevents it from linting lhs files.
-- it "lints redirected file if one is specified and outputs original filename" $ do -- it "lints redirected file if one is specified and outputs original filename" $ do
-- withDirectory_ "test/data/file-mapping/lhs" $ do -- withDirectory_ "test/data/file-mapping/lhs" $ do
@ -186,9 +222,9 @@ spec = do
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
writeFile (tmpdir </> "Foo_Redir.hs") srcFoo writeFile (tmpdir </> "Foo_Redir.hs") srcFoo
writeFile (tmpdir </> "Bar_Redir.hs") srcBar writeFile (tmpdir </> "Bar_Redir.hs") srcBar
let fm = [("Foo.hs", RedirectedMapping $ tmpdir </> "Foo_Redir.hs") let fm = [("Foo.hs", tmpdir </> "Foo_Redir.hs")
,("Bar.hs", RedirectedMapping $ tmpdir </> "Bar_Redir.hs")] ,("Bar.hs", tmpdir </> "Bar_Redir.hs")]
res <- run defaultOptions $ do res <- run defaultOptions{logLevel = GmDebug} $ do
mapM_ (uncurry loadMappedFile) fm mapM_ (uncurry loadMappedFile) fm
types "Bar.hs" 5 1 types "Bar.hs" 5 1
res `shouldBe` unlines ["5 1 5 20 \"[Char]\""] res `shouldBe` unlines ["5 1 5 20 \"[Char]\""]
@ -196,9 +232,9 @@ spec = do
srcFoo <- readFile "test/data/template-haskell/Foo.hs" srcFoo <- readFile "test/data/template-haskell/Foo.hs"
srcBar <- readFile "test/data/template-haskell/Bar.hs" srcBar <- readFile "test/data/template-haskell/Bar.hs"
withDirectory_ "test/data/file-mapping" $ do withDirectory_ "test/data/file-mapping" $ do
let fm = [("Foo.hs", MemoryMapping $ Just srcFoo) let fm = [("Foo.hs", srcFoo)
,("Bar.hs", MemoryMapping $ Just srcBar)] ,("Bar.hs", srcBar)]
res <- run defaultOptions $ do res <- run defaultOptions{logLevel = GmDebug} $ do
mapM_ (uncurry loadMappedFile) fm mapM_ (uncurry loadMappedFileSource) fm
types "Bar.hs" 5 1 types "Bar.hs" 5 1
res `shouldBe` unlines ["5 1 5 20 \"[Char]\""] res `shouldBe` unlines ["5 1 5 20 \"[Char]\""]