packages feed

haskell-tools-cli-0.8.0.0: test/Main.hs

module Main where

import Test.Tasty
import Test.Tasty.HUnit

import System.Exit
import System.Directory
import System.FilePath
import Control.Monad
import Control.Exception
import qualified Data.List as List
import Data.Knob
import Data.ByteString.Char8 (pack, unpack)
import System.IO

import Language.Haskell.Tools.Refactor.CLI

main :: IO ()
main = do nightlyTests <- benchTests
          defaultMain $ testGroup "cli tests" (allTests ++ nightlyTests)

allTests :: [TestTree]
allTests = map makeCliTest cliTests

makeCliTest :: ([FilePath], [String], String, String) -> TestTree
makeCliTest (dirs, args, input, output)
  = let dir = joinPath $ longestCommonPrefix $ map splitDirectories dirs
        testdirs = map (\d -> if d == dir then dir ++ "_test" else (dir ++ "_test" </> makeRelative dir d)) dirs
    in testCase dir $ do
      exists <- doesDirectoryExist (dir ++ "_test")
      when exists $ removeDirectoryRecursive (dir ++ "_test")
      copyDir dir (dir ++ "_test")
      inKnob <- newKnob (pack input)
      inHandle <- newFileHandle inKnob "<input>" ReadMode
      outKnob <- newKnob (pack [])
      outHandle <- newFileHandle outKnob "<output>" WriteMode
      res <- refactorSession inHandle outHandle (args ++ testdirs)
      actualOut <- Data.Knob.getContents outKnob
      assertEqual "" (filter (/= '\r') output) (filter (/= '\r') $ unpack actualOut)
    `finally` removeDirectoryRecursive (dir ++ "_test")

cliTests :: [([FilePath], [String], String, String)]
cliTests
  = [ ( [testRoot </> "Project" </> "cpp-opt"]
      , ["-dry-run", "-one-shot", "-module-name=A"]
      , "", oneShotPrefix ["A"] ++ "-module-name or -refactoring flag not specified correctly. Not doing any refactoring.\n")
    , ( [testRoot </> "Project" </> "source-dir"]
      , ["-dry-run", "-one-shot", "-module-name=A", "-refactoring=\"GenerateSignature 3:1-3:1\""]
      , "", oneShotPrefix ["A"] ++ "### Module changed: A\n### new content:\nmodule A where\n\nx :: ()\nx = ()\n")
    , ( [testRoot </> "Project" </> "working-dir"]
      , ["-dry-run", "-one-shot", "-module-name=A", "-refactoring=\"OrganizeImports\""]
      , "", oneShotPrefix ["A"] ++ "### Module changed: A\n### new content:\n{-# LANGUAGE TemplateHaskell #-}\nmodule A where\n\nimport Language.Haskell.TH\nimport System.FilePath\n\n$(location >>= \\loc -> runIO (readFile (takeDirectory (takeDirectory (loc_filename loc)) </> \"data.txt\")) >> return [])\n\n")
    , ( [testRoot </> "Project" </> "source-dir-outside"]
      , ["-dry-run", "-one-shot", "-module-name=A", "-refactoring=\"GenerateSignature 3:1-3:1\""]
      , "", oneShotPrefix ["A"] ++ "### Module changed: A\n### new content:\nmodule A where\n\nx :: ()\nx = ()\n")
    , ( [testRoot </> "Project" </> "no-cabal"]
      , ["-dry-run", "-one-shot", "-module-name=A", "-refactoring=\"GenerateSignature 3:1-3:1\""]
      , "", oneShotPrefix ["A"] ++ "### Module changed: A\n### new content:\nmodule A where\n\nx :: ()\nx = ()\n")
    , ( [testRoot </> "Project" </> "has-cabal"]
      , ["-dry-run", "-one-shot", "-module-name=A", "-refactoring=\"GenerateSignature 3:1-3:1\""]
      , "", oneShotPrefix ["A"] ++ "### Module changed: A\n### new content:\nmodule A where\n\nx :: ()\nx = ()\n")
    , ( [testRoot </> "Project" </> "selection"], []
      , "SelectModule C\nSelectModule B\nRenameDefinition 5:1-5:2 bb\nSelectModule C\nRenameDefinition 3:1-3:2 cc\nExit"
      , prefixText ["C","B"] ++ "no-module-selected> C> B> "
          ++ reloads ["B"] ++ "B> C> "
          ++ reloads ["C", "B"] ++ "C> "
          )
    , ( [testRoot </> "Project" </> "reloading"], []
      , "SelectModule C\nRenameDefinition 3:1-3:2 cc\nSelectModule B\nRenameDefinition 5:1-5:2 bb\nExit"
      , prefixText ["C","B","A"] ++ "no-module-selected> C> "
          ++ reloads ["C", "B", "A"] ++ "C> B> "
          ++ reloads ["B", "A"] ++ "B> ")
    , ( map ((testRoot </> "Project" </> "multi-packages") </>) ["package1", "package2"]
      , ["-dry-run", "-one-shot", "-module-name=A", "-refactoring=\"RenameDefinition 3:1-3:2 xx\""], ""
      , oneShotPrefix ["B", "A"] ++ "### Module changed: A\n### new content:\nmodule A where\n\nxx = ()\n"
      )
    , ( map ((testRoot </> "Project" </> "multi-packages-flags") </>) ["package1", "package2"]
      , ["-dry-run", "-one-shot", "-module-name=A", "-refactoring=\"RenameDefinition 3:1-3:2 xx\""], ""
      , oneShotPrefix ["B", "A"] ++ "### Module changed: A\n### new content:\nmodule A where\n\nxx = \\case () -> ()\n"
      )
    , ( [testRoot </> "Project" </> "with-main"]
      , ["-dry-run", "-one-shot", "-module-name=Main", "-refactoring=\"GenerateSignature 3:1\""]
      , "", oneShotPrefix ["Main"] ++ "### Module changed: Main\n### new content:\nmodule Main where\n\nmain :: IO ()\nmain = putStrLn \"Hello World\"\n")
    , ( [testRoot </> "Project" </> "with-main-renamed"]
      , ["-dry-run", "-one-shot", "-module-name=Main", "-refactoring=\"GenerateSignature 3:1\""]
      , "", oneShotPrefix ["Main"] ++ "### Module changed: Main\n### new content:\nmodule Main where\n\nmain :: IO ()\nmain = putStrLn \"Hello World\"\n")
    , ( [testRoot </> "Project" </> "with-multi-main"], ["-dry-run", "-one-shot", "-module-name=B", "-refactoring=\"RenameDefinition 3:1 bb\""], ""
      , oneShotPrefix ["Main", "B", "Main"]
          ++ "### Module changed: B\n### new content:\nmodule B where\n\nbb = \"Hello\"\n"
          ++ "### Module changed: Main\n### new content:\nmodule Main where\n\nimport B\n\nmain = putStrLn (bb ++ \" World\")\n")
    , ( [testRoot </> "Project" </> "with-other-executable"]
      , ["-dry-run", "-one-shot", "-module-name=A", "-refactoring=\"GenerateSignature 3:1\""]
      , "", oneShotPrefix ["A"] ++ "### Module changed: A\n### new content:\nmodule A where\n\nmain :: IO ()\nmain = putStrLn \"Hello World\"\n")
    ]

benchTests :: IO [TestTree]
benchTests
  = forM ["full-1", "full-2", "full-3"] $ \id -> do
      commands <- readFile ("bench-tests" </> id <.> "txt")
      return $ makeCliTest (["examples" </> "CppHs"], [], filter (/='\r') commands, expectedOut id)

expectedOut "full-1"
  = prefixText cppHsMods ++ "no-module-selected> Language.Preprocessor.Cpphs.CppIfdef> "
      ++ concat (replicate 8 (reloads cppIfDefReloads ++ "Language.Preprocessor.Cpphs.CppIfdef> "))
expectedOut "full-2"
  = prefixText cppHsMods ++ "no-module-selected> Language.Preprocessor.Cpphs.MacroPass> "
      ++ concat (replicate 3 (reloads macroPassReloads ++ "Language.Preprocessor.Cpphs.MacroPass> "))
expectedOut "full-3"
  = prefixText cppHsMods ++ "no-module-selected> Language.Preprocessor.Cpphs.CppIfdef> "
      ++ concat (replicate 2 (reloads cppIfDefReloads ++ "Language.Preprocessor.Cpphs.CppIfdef> "))
      ++ "Language.Preprocessor.Cpphs.MacroPass> "
      ++ reloads macroPassReloads ++ "Language.Preprocessor.Cpphs.MacroPass> "
      ++ "Language.Preprocessor.Cpphs.CppIfdef> "
      ++ concat (replicate 3 (reloads cppIfDefReloads ++ "Language.Preprocessor.Cpphs.CppIfdef> "))
      ++ "Language.Preprocessor.Cpphs.MacroPass> "
      ++ reloads macroPassReloads ++ "Language.Preprocessor.Cpphs.MacroPass> "
      ++ "Language.Preprocessor.Cpphs.CppIfdef> "
      ++ concat (replicate 3 (reloads cppIfDefReloads ++ "Language.Preprocessor.Cpphs.CppIfdef> "))

cppIfDefReloads = [ "Language.Preprocessor.Cpphs.CppIfdef"
                  , "Language.Preprocessor.Cpphs.RunCpphs"
                  , "Language.Preprocessor.Cpphs" ]
macroPassReloads = "Language.Preprocessor.Cpphs.MacroPass" : cppIfDefReloads

cppHsMods = [ "Language.Preprocessor.Unlit"
            , "Language.Preprocessor.Cpphs.SymTab"
            , "Language.Preprocessor.Cpphs.Position"
            , "Language.Preprocessor.Cpphs.ReadFirst"
            , "Language.Preprocessor.Cpphs.Options"
            , "Language.Preprocessor.Cpphs.HashDefine"
            , "Language.Preprocessor.Cpphs.Tokenise"
            , "Language.Preprocessor.Cpphs.MacroPass"
            , "Language.Preprocessor.Cpphs.CppIfdef"
            , "Language.Preprocessor.Cpphs.RunCpphs"
            , "Language.Preprocessor.Cpphs" ]

testRoot = "examples"

prefixText :: [String] -> String
prefixText mods
  = "Compiling modules. This may take some time. Please wait.\n"
      ++ concatMap (\m -> "Loaded module: " ++ m ++ "\n") mods
      ++ "All modules loaded. Use 'SelectModule module-name' to select a module.\n"

oneShotPrefix :: [String] -> String
oneShotPrefix mods
  = "Compiling modules. This may take some time. Please wait.\n"
      ++ concatMap (\m -> "Loaded module: " ++ m ++ "\n") mods
      ++ "All modules loaded.\n"


reloads :: [String] -> String
reloads mods = concatMap (\m -> "Re-loaded module: " ++ m ++ "\n") mods

copyDir ::  FilePath -> FilePath -> IO ()
copyDir src dst = do
  exists <- doesDirectoryExist dst
  -- clear the target directory from possible earlier test runs
  when exists $ removeDirectoryRecursive dst
  createDirectory dst
  content <- getDirectoryContents src
  let xs = filter (`notElem` [".", ".."]) content
  forM_ xs $ \name -> do
    let srcPath = src </> name
    let dstPath = dst </> name
    isDirectory <- doesDirectoryExist srcPath
    if isDirectory
      then copyDir srcPath dstPath
      else copyFile srcPath dstPath

commonPrefix :: (Eq e) => [e] -> [e] -> [e]
commonPrefix _ [] = []
commonPrefix [] _ = []
commonPrefix (x:xs) (y:ys)
  | x == y    = x : commonPrefix xs ys
  | otherwise = []

longestCommonPrefix :: (Eq a) => [[a]] -> [a]
longestCommonPrefix = foldl1 commonPrefix