{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module RunSpec where
import Control.Exception
import Data.String.Interpolate
import GHC
import System.Environment
import System.Exit
import System.IO
import System.IO.Silently
import Test.Hspec
import Helper
import Run
spec :: Spec
spec = do
describe "run" $ around_ (hSilence [stdout, stderr]) $ do
context "when given a module containing dead code" $ do
let main = ("Main", [i|
module Main (main) where
main = used
used = return ()
unused = ()
|])
run' = withArgs (words "-i. --root Main") run
it "works" $ do
withModules [main] $ do
output <- capture_ $ swallowExceptions run'
output `shouldBe` "./Main.hs:4:1: unused\n"
it "exits with a non-zero exit-code" $ do
withModules [main] $ do
run' `shouldDie` ""
it "allows to set multiple roots" $ do
let a = ("A", [i|
module A (a) where
a = ()
|])
b = ("B", [i|
module B (b) where
b = ()
|])
withModules [a, b] $ do
output <- hCapture_ [stdout, stderr] $ swallowExceptions $
withArgs (words "-i. --root A --root B") run
output `shouldBe` ""
it "complains when it's invoked with no arguments" $ do
withArgs [] run `shouldThrow` (== ExitFailure 1)
it "has a version option" $ do
output <- capture_ $
handle (\ ExitSuccess -> return ()) $
withArgs ["--version"] run
output `shouldContain` "version: "
context "--ignore" $ do
it "ignores files if told to do so" $ do
let main = ("Main", [i|
module Main where
main = return ()
|])
b = ("B", [i|
This is some arbitrary text that is not Haskell.
|])
run' = withArgs (words "-i. -e./B.hs --root Main") run
withModules [main, b] $ run' `shouldReturn` ()
it "errors out on missing ignored files" $ do
let main = ("Main", [i|
module Main where
main = return ()
|])
run' = withArgs (words "-i. -e./B.hs --root Main") run
withModules [main] $ run' `shouldDie` "file not found: ./B.hs\n"
it "ignores files if referenced differently" $ do
let main = ("Main", [i|
module Main where
main = return ()
|])
b = ("B", [i|
This is some arbitrary text that is not Haskell.
|])
run' = withArgs (words "-i. -e B.hs --root Main") run
withModules [main, b] $ run' `shouldReturn` ()
describe "deadNamesFromFiles" $ do
it "should clearly mark ghc's output as such" $ do
let a = ("A", [i|
module A where
import B
|])
withModules [a] $ do
output <- hCapture_ [stderr] $
deadNamesFromFiles ["A.hs"] [mkModuleName "A"] False
`shouldThrow` (== ExitFailure 1)
output `shouldContain` "ghc says:"
it "can be run on multiple modules" $ do
let a = ("A", [i|
module A where
foo = ()
|])
b = ("B", [i|
module B where
bar = ()
|])
withModules [a, b] $ do
deadNamesFromFiles ["A.hs", "B.hs"] [mkModuleName "A"] False
`shouldReturn` ["B.hs:2:1: bar"]
context "names starting with an underscore" $ do
it "excludes them by default" $ do
let a = ("A", [i|
module A (foo) where
foo = ()
_bar = ()
|])
withModules [a] $ do
dead <- deadNamesFromFiles ["A.hs"] [mkModuleName "A"] False
dead `shouldMatchList` []
it "includes them if asked to" $ do
let a = ("A", [i|
module A (foo) where
foo = ()
_bar = ()
|])
withModules [a] $ do
dead <- deadNamesFromFiles ["A.hs"] [mkModuleName "A"] True
dead `shouldMatchList` ["A.hs:3:1: _bar"]
it "excludes constructor names" $ do
let a = ("A", [i|
module A () where
data A = A
|])
withModules [a] $ do
dead <- deadNamesFromFiles ["A.hs"] [mkModuleName "A"] True
dead `shouldMatchList` []
it "only considers exported top-level declarations as roots" $ do
let a = ("A", [i|
module A (foo) where
import B
foo = ()
bar = B.baz
|])
b = ("B", [i|
module B where
baz = ()
|])
withModules [a, b] $ do
dead <- deadNamesFromFiles ["A.hs", "B.hs"] [mkModuleName "A"] False
dead `shouldMatchList` ["A.hs:4:1: bar", "B.hs:2:1: baz"]
shouldDie :: IO a -> String -> IO ()
shouldDie action err = do
(output, exception) <- hCapture [stderr] $ catch
(action >> return Nothing)
(\ (e :: ExitCode) -> return $ Just e)
case exception of
Nothing -> throwIO $ ErrorCall "shouldDie: didn't receive ExitCode exception"
Just ExitSuccess -> throwIO $ ErrorCall "shouldDie: received ExitSuccess exception"
Just (ExitFailure _) ->
output `shouldBe` err