smuggler2-0.3.6.2: test/Test.hs
{-# LANGUAGE OverloadedStrings #-}
module Main (
testDir,
optionsList,
mkExt,
testOptions,
goldenTests,
findByExtension',
main,
compile ) where
import Control.Monad ( forM )
import Data.List ( intercalate, sort )
import Data.Maybe ( fromMaybe )
import qualified Data.Set as Set ( fromList, member )
import GHC ( mkModuleName, moduleNameString )
import GHC.Paths ( ghc )
import Smuggler2.Options
( ExportAction(..), ImportAction(..), Options(..) )
import System.Directory
( doesDirectoryExist, getDirectoryContents )
import System.Exit ( ExitCode(ExitFailure, ExitSuccess) )
import System.FilePath
( (-<.>), (</>), takeBaseName, takeExtension )
import System.Process.Typed ( proc, runProcess )
import Test.Tasty ( TestTree, defaultMain, testGroup )
import Test.Tasty.Golden ( goldenVsFileDiff, writeBinaryFile )
-- | Where the tests are, relative to the project level cabal file
testDir :: FilePath
testDir = "test" </> "tests"
-- | Combinations of import and export action options to be tested
optionsList :: [Options]
optionsList =
[ mkOptions PreserveInstanceImports NoExportProcessing [] ["Prelude"],
mkOptions MinimiseImports ReplaceExports [] [],
mkOptions MinimiseImports NoExportProcessing ["Prelude", "Data.Version"] [],
mkOptions NoImportProcessing AddExplicitExports [] [],
mkOptions NoImportProcessing NoExportProcessing [] [],
mkOptions NoImportProcessing ReplaceExports [] []
]
where
mkOptions :: ImportAction -> ExportAction -> [String] -> [String] -> Options
mkOptions ia ea lo mo =
Options
ia
ea
(Just $ mkExt ia ea lo mo)
(mkModuleName <$> lo)
(mkModuleName <$> mo)
-- | Make an extention for an output file
mkExt :: ImportAction -> ExportAction -> [String] -> [String] -> String
mkExt ia ea lo mo =
show ia
++ show ea
++ filter (/= '.') (concat lo ++ concat mo) -- ++ "-" ++ takeFileName ghc
-- | Generate test for a list of 'Options' each of which specify what action to
-- take on imports and exports
testOptions :: [Options] -> IO TestTree
testOptions opts =
testGroup "All" <$> sequenceA (goldenTests <$> opts)
-- | Generate tests for a set of 'Options' (that specifies what to do to
-- imports and exports)
goldenTests :: Options -> IO TestTree
goldenTests opts = do
testFiles <- findByExtension' [".hs"] testDir
return $
testGroup
testName
[ goldenVsFileDiff
(takeBaseName testFile) -- test name
( \ref new -> -- how to display diffs
-- The -G. is needed because cabal sdist changes the golden file
-- permissions and so all the tests fail.
[ "git",
"diff",
"-G.",
"--ignore-cr-at-eol",
"--ws-error-highlight=all",
"--no-index",
"--exit-code",
ref,
new
]
)
(testFile -<.> testName ++ "-golden") -- golden file
outputFilename
( do
-- Write a default output file for those tests where smuggler2
-- (deliberately) does not generate a new one
writeBinaryFile outputFilename "Source file was not touched\r\n"
compile testFile opts
)
| testFile <- sort testFiles,
let outputFilename = testFile -<.> testName
]
where
testName = fromMaybe "NoNewExtension" (newExtension opts)
-- | A version of 'Test.Tasty.Golden.findByExtension' that does not look into
-- subdirectories. This allows tests to be run on a module that imports other
-- modules without risking the race condition where smuggler2 is being run
-- on the imported module directly, and also when the importing module is being
-- tested. ('Tasty' runs test in parallel and the same output files are
-- produced in both direct and imported cases.) Putting the imported module in
-- a subdirectory and not testing it direcly avoids this race condition.
findByExtension' ::
-- | extensions
[FilePath] ->
-- | directory
FilePath ->
-- | paths
IO [FilePath]
findByExtension' extsList = go
where
exts = Set.fromList extsList
go :: FilePath -> IO [FilePath]
go dir = do
allEntries <- getDirectoryContents dir
let entries = filter (not . (`elem` [".", ".."])) allEntries
fmap concat $ forM entries $ \e -> do
let path = dir ++ "/" ++ e
isDir <- doesDirectoryExist path
return $
if isDir
then [] -- don't recurse
else [path | takeExtension path `Set.member` exts]
-- | Just run all the tests
main :: IO ()
main = defaultMain =<< testOptions optionsList
-- | Run a compilation. Assumes that @smuggler2@ has been built with
-- @--write-ghc-environment-files=always@ so that it is picked up from
-- the local database.
compile :: FilePath -> Options -> IO ()
compile testcase opts = do
r <- runProcess (proc ghc ghcArgs)
return $ case r of
ExitSuccess -> ()
ExitFailure c ->
error $ "Failed to compile " ++ testcase ++ ". Exit code " ++ show c
where
ghcArgs :: [String]
ghcArgs =
[ "-v0",
"-dumpdir=" ++ testDir,
"-fno-code",
"-i" ++ testDir,
"-fplugin=Smuggler2.Plugin"
]
++ map
("-fplugin-opt=Smuggler2.Plugin:" ++)
( let ia = importAction opts
ea = exportAction opts
ne = newExtension opts
lo = intercalate "," (moduleNameString <$> leaveOpenImports opts)
mo = intercalate "," (moduleNameString <$> makeOpenImports opts)
in -- The extension should have been set by 'optionsList'
-- The rest of the list are the other arguments for the test
( fromMaybe "missng" ne :
[show ia, show ea]
++ ["LeaveOpenImports:" ++ lo | not (null lo)]
++ ["MakeOpenImports:" ++ mo | not (null mo)]
)
)
++ [testcase]