module FileMappingSpec where import Language.Haskell.GhcMod.FileMapping import Language.Haskell.GhcMod.Utils (withMappedFile) import Test.Hspec import TestUtils import qualified Data.Map as M import Dir import System.IO.Temp import System.Directory import GhcMod spec :: Spec spec = do describe "loadMappedFile" $ do it "inserts a given FilePath FileMapping into state with canonicalized path" $ do withDirectory_ "test/data/file-mapping" $ do mappedFiles <- runD $ do loadMappedFile "File.hs" "File.hs" getMMappedFiles dir <- getCurrentDirectory 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 withDirectory_ "test/data/file-mapping" $ do mappedFiles <- runD $ do loadMappedFile "NonExistantFile.hs" "File.hs" getMMappedFiles dir <- getCurrentDirectory 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 it "removes a given FilePath from state" $ do withDirectory_ "test/data/file-mapping" $ do mappedFiles <- runD $ do loadMappedFile "File.hs" "File2.hs" unloadMappedFile "File.hs" getMMappedFiles show mappedFiles `shouldBe` show (M.fromList ([] :: [(FilePath, FileMapping)])) it "should work even if file does not exist" $ do withDirectory_ "test/data/file-mapping" $ do mappedFiles <- runD $ do loadMappedFile "NonExistantFile.hs" "File2.hs" unloadMappedFile "NonExistantFile.hs" getMMappedFiles 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 it "checks if there is a redirected file and calls and action with its FilePath" $ do withDirectory_ "test/data/file-mapping" $ do res <- runD $ do loadMappedFile "File.hs" "File_Redir.hs" withMappedFile "File.hs" return res `shouldBe` "File_Redir.hs" it "checks if there is an in-memory file and calls and action with temporary file" $ do withDirectory_ "test/data/file-mapping" $ do (fn, src) <- runD $ do loadMappedFileSource "File.hs" "main = test" withMappedFile "File.hs" $ \fn -> do src <- liftIO $ readFile fn return (fn, src) fn `shouldSatisfy` (/="File.hs") src `shouldBe` "main = test" it "runs action with original filename if there is no mapping" $ do withDirectory_ "test/data/file-mapping" $ do fn <- runD $ do withMappedFile "File.hs" return fn `shouldBe` "File.hs" describe "integration tests" $ do it "checks redirected file if one is specified and outputs original filename" $ do withDirectory_ "test/data/file-mapping" $ do let fm = [("File.hs", "File_Redir.hs")] res <- run defaultOptions $ do mapM_ (uncurry loadMappedFile) fm checkSyntax ["File.hs"] 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 withDirectory_ "test/data/file-mapping" $ do let fm = [("File.hs", "main = putStrLn \"Hello World!\"\n")] res <- run defaultOptions $ do mapM_ (uncurry loadMappedFileSource) fm checkSyntax ["File.hs"] 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 $ 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 withDirectory_ "test/data/file-mapping" $ do res <- runD $ do loadMappedFile "File.hs" "File_Redir_Lint.hs" lint lintOpts "File.hs" res `shouldBe` "File.hs:4:1: Warning: 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 withDirectory_ "test/data/file-mapping" $ do res <- runD $ do loadMappedFileSource "File.hs" "func a b = (++) a b\n" lint lintOpts "File.hs" res `shouldBe` "File.hs:1:1: Warning: Eta reduce\NULFound:\NUL func a b = (++) a b\NULWhy not:\NUL func = (++)\n" it "shows types of the expression for redirected files" $ do let tdir = "test/data/file-mapping" res <- runD' tdir $ do loadMappedFile "File.hs" "File_Redir_Lint.hs" types False "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" it "shows types of the expression with constraints for redirected files" $ do -- let tdir = "test/data/file-mapping" res <- runD' tdir $ do loadMappedFile "File.hs" "File_Redir_Lint.hs" types True "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 \"Num a => a -> a -> a\"\n" it "shows types of the expression for in-memory files" $ do let tdir = "test/data/file-mapping" res <- runD' tdir $ do loadMappedFileSource "File.hs" "main = putStrLn \"Hello!\"" types False "File.hs" 1 14 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 let tdir = "test/data/file-mapping" res <- runD' tdir $ do loadMappedFile "File.hs" "File_Redir_Lint.hs" info "File.hs" $ Expression "func" 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 let tdir = "test/data/file-mapping" res <- runD' tdir $ do loadMappedFileSource "File.hs" "module File where\n\ntestfun = putStrLn \"Hello!\"" info "File.hs" $ Expression "testfun" res `shouldBe` "testfun :: IO () \t-- Defined at File.hs:3:1\n" describe "preprocessor tests" $ do it "checks redirected file if one is specified and outputs original filename" $ do withDirectory_ "test/data/file-mapping/preprocessor" $ do let fm = [("File.hs", "File_Redir.hs")] res <- run defaultOptions $ do mapM_ (uncurry loadMappedFile) fm checkSyntax ["File.hs"] res `shouldBe` "File.hs:3:1:Warning: Top-level binding with no type signature: main :: IO ()\n" it "works with full path as well" $ do withDirectory_ "test/data/file-mapping/preprocessor" $ do cwd <- getCurrentDirectory let fm = [("File.hs", cwd "File_Redir.hs")] res <- run defaultOptions $ do mapM_ (uncurry loadMappedFile) fm checkSyntax ["File.hs"] res `shouldBe` "File.hs:3:1:Warning: Top-level binding with no type signature: main :: IO ()\n" it "checks in-memory file" $ do withDirectory_ "test/data/file-mapping/preprocessor" $ do src <- readFile "File_Redir.hs" let fm = [("File.hs", src)] res <- run defaultOptions $ do mapM_ (uncurry loadMappedFileSource) fm checkSyntax ["File.hs"] 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 withDirectory_ "test/data/file-mapping/preprocessor" $ do res <- runD $ do loadMappedFile "File.hs" "File_Redir_Lint.hs" lint lintOpts "File.hs" res `shouldBe` "File.hs:6:1: Warning: 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 withDirectory_ "test/data/file-mapping/preprocessor" $ do src <- readFile "File_Redir_Lint.hs" res <- runD $ do loadMappedFileSource "File.hs" src lint lintOpts "File.hs" res `shouldBe` "File.hs:6:1: Warning: Eta reduce\NULFound:\NUL func a b = (*) a b\NULWhy not:\NUL func = (*)\n" describe "literate haskell tests" $ do it "checks redirected file if one is specified and outputs original filename" $ do withDirectory_ "test/data/file-mapping/lhs" $ do let fm = [("File.lhs", "File_Redir.lhs")] res <- run defaultOptions $ do mapM_ (uncurry loadMappedFile) fm checkSyntax ["File.lhs"] res `shouldBe` "File.lhs:1:3: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 withDirectory_ "test/data/file-mapping/lhs" $ do src <- readFile "File_Redir.lhs" let fm = [("File.lhs", src)] res <- run defaultOptions $ do mapM_ (uncurry loadMappedFileSource) fm checkSyntax ["File.lhs"] 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. -- it "lints redirected file if one is specified and outputs original filename" $ do -- withDirectory_ "test/data/file-mapping/lhs" $ do -- res <- runD $ do -- loadMappedFile "File.lhs" (RedirectedMapping "File_Redir_Lint.lhs") -- lint "File.lhs" -- res `shouldBe` "File.lhs: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 -- withDirectory_ "test/data/file-mapping/lhs" $ do -- src <- readFile "File_Redir_Lint.lhs" -- res <- runD $ do -- loadMappedFile "File.lhs" (MemoryMapping $ Just src) -- lint "File.lhs" -- res `shouldBe` "File.lhs:6:1: Error: Eta reduce\NULFound:\NUL func a b = (*) a b\NULWhy not:\NUL func = (*)\n" describe "template haskell" $ do it "works with a redirected module using TemplateHaskell" $ do withSystemTempDirectory "ghc-mod-test" $ \tmpdir -> do srcFoo <- readFile "test/data/template-haskell/Foo.hs" srcBar <- readFile "test/data/template-haskell/Bar.hs" withDirectory_ "test/data/file-mapping" $ do writeFile (tmpdir "Foo_Redir.hs") srcFoo writeFile (tmpdir "Bar_Redir.hs") srcBar let fm = [("Foo.hs", tmpdir "Foo_Redir.hs") ,("Bar.hs", tmpdir "Bar_Redir.hs")] res <- run defaultOptions $ do mapM_ (uncurry loadMappedFile) fm types False "Bar.hs" 5 1 res `shouldBe` unlines ["5 1 5 20 \"[Char]\""] it "works with a memory module using TemplateHaskell" $ do srcFoo <- readFile "test/data/template-haskell/Foo.hs" srcBar <- readFile "test/data/template-haskell/Bar.hs" withDirectory_ "test/data/file-mapping" $ do let fm = [("Foo.hs", srcFoo) ,("Bar.hs", srcBar)] res <- run defaultOptions $ do mapM_ (uncurry loadMappedFileSource) fm types False "Bar.hs" 5 1 res `shouldBe` unlines ["5 1 5 20 \"[Char]\""] lintOpts :: LintOpts lintOpts = defaultLintOpts { optLintHlintOpts = ["--ignore=Use module export list"] }