smuggler2-0.3.3.2: test/Test.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad (forM)
import Data.List (sort)
import Data.Maybe (fromMaybe)
import qualified Data.Set as Set (fromList, member)
import GHC.Paths (ghc)
import Smuggler2.Options (ExportAction (..), ImportAction (..), Options (..))
import System.Directory (doesDirectoryExist, getDirectoryContents)
import System.Environment (lookupEnv)
import System.FilePath ((-<.>), (</>), takeBaseName, takeExtension)
import System.Process.Typed
( ProcessConfig,
proc,
runProcess_,
setEnvInherit,
setWorkingDirInherit,
)
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,
mkOptions MinimiseImports ReplaceExports,
mkOptions MinimiseImports NoExportProcessing,
mkOptions NoImportProcessing AddExplicitExports,
mkOptions NoImportProcessing NoExportProcessing,
mkOptions NoImportProcessing ReplaceExports
]
where
mkOptions :: ImportAction -> ExportAction -> Options
mkOptions ia ea = Options ia ea (Just $ mkExt ia ea)
-- | Make an extention for an output file
mkExt :: ImportAction -> ExportAction -> String
mkExt ia ea = show ia ++ show ea -- ++ "-" ++ 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 -> ["git", "diff", "--no-index", ref, new]) -- how to display diffs
(testFile -<.> testName ++ "-golden") -- golden file
outputFilename
( do
-- Write a default output file for those tests where smuggler
-- (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 smuggler 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
-- | Use `cabal exec` to run the compilation, so that the smuggler plugin is
-- picked up from the local database. GHC alone would use the global one.
compile :: FilePath -> Options -> IO ()
compile testcase opts = do
cabalPath <- lookupEnv "CABAL" -- find, eg, @/opt/ghc/bin/cabal@ or @cabal -vnormal+nowrap@
let cabalCmd = words $ fromMaybe "cabal" cabalPath -- default to @cabal@ if @CABAL@ is not set
let cabalConfig =
setWorkingDirInherit . setEnvInherit $
proc
(head cabalCmd)
(tail cabalCmd ++ cabalArgs) :: ProcessConfig () () ()
runProcess_ cabalConfig
where
cabalArgs :: [String]
cabalArgs =
-- - not sure why it is necessary to mention the smuggler2 package explicitly,
-- but it appears to be hidden otherwise.
-- - This also puts the .imports files that smuggler generates somewhere they
-- can easily be found
[ "--with-compiler=" ++ ghc,
"exec",
ghc,
"--",
"-package smuggler2",
"-v0",
"-dumpdir=" ++ testDir,
"-fno-code",
"-i" ++ testDir,
"-fplugin=Smuggler2.Plugin"
]
++ map
("-fplugin-opt=Smuggler2.Plugin:" ++)
( let ia = importAction opts
ea = exportAction opts
-- the extension should have been set by 'optionsList'
in (fromMaybe "missng" (newExtension opts) : [show ia, show ea])
)
++ [testcase]