packages feed

weeder-2.7.0: test/Spec.hs

import qualified Weeder.Main
import qualified Weeder.Run
import qualified Weeder
import qualified TOML
import qualified UnitTests

import Algebra.Graph.Export.Dot
import GHC.Types.Name.Occurrence (occNameString)
import System.Directory
import System.Environment (getArgs, withArgs)
import System.FilePath
import System.Process
import System.IO (stderr, hPrint)
import Test.Hspec
import Control.Monad (zipWithM_, when)
import Control.Exception ( throwIO, IOException, handle )
import Data.Maybe (isJust)
import Data.List (find, sortOn)

main :: IO ()
main = do
  args <- getArgs
  testOutputFiles <- fmap sortTests discoverIntegrationTests
  let hieDirectories = map (dropExtension . snd) testOutputFiles
      drawDots = mapM_ (drawDot . (<.> ".dot")) hieDirectories
      graphviz = "--graphviz" `elem` args
  withArgs (filter (/="--graphviz") args) $
    hspec $ afterAll_ (when graphviz drawDots) $ do
      describe "Weeder.Run" $
        describe "runWeeder" $
          zipWithM_ (uncurry integrationTestSpec) testOutputFiles hieDirectories
      UnitTests.spec
  where
    -- Draw a dotfile via graphviz
    drawDot f = callCommand $ "dot -Tpng " ++ f ++ " -o " ++ (f -<.> ".png")
    -- Sort the output files such that the failing ones go last
    sortTests = sortOn (isJust . fst)

-- | Run weeder on @hieDirectory@, comparing the output to @stdoutFile@.
--
-- The directory containing @hieDirectory@ must also have a @.toml@ file
-- with the same name as @hieDirectory@.
--
-- If @failingFile@ is @Just@, it is used as the expected output instead of
-- @stdoutFile@, and a different failure message is printed if the output
-- matches @stdoutFile@.
integrationTestSpec :: Maybe FilePath -> FilePath -> FilePath -> Spec
integrationTestSpec failingFile stdoutFile hieDirectory = do
  it (integrationTestText ++ hieDirectory) $ do
    expectedOutput <- readFile stdoutFile
    actualOutput <- integrationTestOutput hieDirectory
    case failingFile of
      Just f -> do
        failingOutput <- readFile f
        actualOutput `shouldNotBe` expectedOutput
        actualOutput `shouldBe` failingOutput
      Nothing ->
        actualOutput `shouldBe` expectedOutput
  where
    integrationTestText = case failingFile of
      Nothing -> "produces the expected output for "
      Just _ -> "produces the expected (wrong) output for "

-- | Returns detected .failing and .stdout files in ./test/Spec
discoverIntegrationTests :: IO [(Maybe FilePath, FilePath)]
discoverIntegrationTests = do
  contents <- listDirectory testPath
  let stdoutFiles = map (testPath </>) $
        filter (".stdout" `isExtensionOf`) contents
  pure . map (\s -> (findFailing s contents, s)) $ stdoutFiles
    where
      findFailing s = fmap (testPath </>) . find (takeBaseName s <.> ".failing" ==)
      testPath = "./test/Spec"

-- | Run weeder on the given directory for .hie files, returning stdout
-- Also creates a dotfile containing the dependency graph as seen by Weeder
integrationTestOutput :: FilePath -> IO String
integrationTestOutput hieDirectory = do
  hieFiles <- Weeder.Main.getHieFiles ".hie" [hieDirectory] True
  weederConfig <- TOML.decodeFile configExpr >>= either throwIO pure
  let (weeds, analysis) = Weeder.Run.runWeeder weederConfig hieFiles
      graph = Weeder.dependencyGraph analysis
      graph' = export (defaultStyle (occNameString . Weeder.declOccName)) graph
  handle (\e -> hPrint stderr (e :: IOException)) $
    writeFile (hieDirectory <.> ".dot") graph'
  pure (unlines $ map Weeder.Run.formatWeed weeds)
  where
    configExpr = hieDirectory <.> ".toml"