module Distribution.Simple.Test.LibV09
( runTest
, simpleTestStub
, stubFilePath, stubMain, stubName, stubWriteLog
, writeSimpleTestStub
) where
import Prelude ()
import Distribution.Compat.Prelude
import Distribution.Types.UnqualComponentName
import Distribution.Compat.CreatePipe
import Distribution.Compat.Environment
import Distribution.Compat.Internal.TempFile
import Distribution.ModuleName
import qualified Distribution.PackageDescription as PD
import Distribution.Simple.Build.PathsModule
import Distribution.Simple.BuildPaths
import Distribution.Simple.Compiler
import Distribution.Simple.Hpc
import Distribution.Simple.InstallDirs
import qualified Distribution.Simple.LocalBuildInfo as LBI
import qualified Distribution.Types.LocalBuildInfo as LBI
import Distribution.Simple.Setup
import Distribution.Simple.Test.Log
import Distribution.Simple.Utils
import Distribution.System
import Distribution.TestSuite
import Distribution.Pretty
import Distribution.Verbosity
import qualified Control.Exception as CE
import System.Directory
( createDirectoryIfMissing, canonicalizePath
, doesDirectoryExist, doesFileExist
, getCurrentDirectory, removeDirectoryRecursive, removeFile
, setCurrentDirectory )
import System.Exit ( exitSuccess, exitWith, ExitCode(..) )
import System.FilePath ( (</>), (<.>) )
import System.IO ( hClose, hGetContents, hPutStr )
import System.Process (StdStream(..), waitForProcess)
runTest :: PD.PackageDescription
-> LBI.LocalBuildInfo
-> LBI.ComponentLocalBuildInfo
-> TestFlags
-> PD.TestSuite
-> IO TestSuiteLog
runTest pkg_descr lbi clbi flags suite = do
let isCoverageEnabled = LBI.testCoverage lbi
way = guessWay lbi
pwd <- getCurrentDirectory
existingEnv <- getEnvironment
let cmd = LBI.buildDir lbi </> stubName suite
</> stubName suite <.> exeExtension (LBI.hostPlatform lbi)
exists <- doesFileExist cmd
unless exists $
die' verbosity $ "Error: Could not find test program \"" ++ cmd
++ "\". Did you build the package first?"
unless (fromFlag $ testKeepTix flags) $ do
let tDir = tixDir distPref way testName'
exists' <- doesDirectoryExist tDir
when exists' $ removeDirectoryRecursive tDir
createDirectoryIfMissing True $ tixDir distPref way testName'
notice verbosity $ summarizeSuiteStart testName'
suiteLog <- CE.bracket openCabalTemp deleteIfExists $ \tempLog -> do
(rOut, wOut) <- createPipe
(Just wIn, _, _, process) <- do
let opts = map (testOption pkg_descr lbi suite) $ testOptions flags
dataDirPath = pwd </> PD.dataDir pkg_descr
tixFile = pwd </> tixFilePath distPref way testName'
pkgPathEnv = (pkgPathEnvVar pkg_descr "datadir", dataDirPath)
: existingEnv
shellEnv = [("HPCTIXFILE", tixFile) | isCoverageEnabled]
++ pkgPathEnv
shellEnv' <-
if LBI.withDynExe lbi
then do
let (Platform _ os) = LBI.hostPlatform lbi
paths <- LBI.depLibraryPaths True False lbi clbi
cpath <- canonicalizePath $ LBI.componentBuildDir lbi clbi
return (addLibraryPath os (cpath : paths) shellEnv)
else return shellEnv
case testWrapper flags of
Flag path -> createProcessWithEnv verbosity path (cmd:opts) Nothing (Just shellEnv')
CreatePipe (UseHandle wOut) (UseHandle wOut)
NoFlag -> createProcessWithEnv verbosity cmd opts Nothing (Just shellEnv')
CreatePipe (UseHandle wOut) (UseHandle wOut)
hPutStr wIn $ show (tempLog, PD.testName suite)
hClose wIn
logText <- hGetContents rOut
length logText `seq` return ()
exitcode <- waitForProcess process
unless (exitcode == ExitSuccess) $ do
debug verbosity $ cmd ++ " returned " ++ show exitcode
let finalLogName l = testLogDir
</> testSuiteLogPath
(fromFlag $ testHumanLog flags) pkg_descr lbi
(unUnqualComponentName $ testSuiteName l) (testLogs l)
suiteLog <- fmap (\s -> (\l -> l { logFile = finalLogName l })
. fromMaybe (error $ "panic! read @TestSuiteLog " ++ show s) $ readMaybe s)
$ readFile tempLog
appendFile (logFile suiteLog) $ summarizeSuiteStart testName'
appendFile (logFile suiteLog) logText
appendFile (logFile suiteLog) $ summarizeSuiteFinish suiteLog
let details = fromFlag $ testShowDetails flags
whenPrinting = when $ (details > Never)
&& (not (suitePassed $ testLogs suiteLog) || details == Always)
&& verbosity >= normal
whenPrinting $ putStr $ unlines $ lines logText
return suiteLog
notice verbosity $ summarizeSuiteFinish suiteLog
when isCoverageEnabled $
markupTest verbosity lbi distPref (prettyShow $ PD.package pkg_descr) suite
return suiteLog
where
testName' = unUnqualComponentName $ PD.testName suite
deleteIfExists file = do
exists <- doesFileExist file
when exists $ removeFile file
testLogDir = distPref </> "test"
openCabalTemp = do
(f, h) <- openTempFile testLogDir $ "cabal-test-" <.> "log"
hClose h >> return f
distPref = fromFlag $ testDistPref flags
verbosity = fromFlag $ testVerbosity flags
testOption :: PD.PackageDescription
-> LBI.LocalBuildInfo
-> PD.TestSuite
-> PathTemplate
-> String
testOption pkg_descr lbi suite template =
fromPathTemplate $ substPathTemplate env template
where
env = initialPathTemplateEnv
(PD.package pkg_descr) (LBI.localUnitId lbi)
(compilerInfo $ LBI.compiler lbi) (LBI.hostPlatform lbi) ++
[(TestSuiteNameVar, toPathTemplate $ unUnqualComponentName $ PD.testName suite)]
stubName :: PD.TestSuite -> FilePath
stubName t = unUnqualComponentName (PD.testName t) ++ "Stub"
stubFilePath :: PD.TestSuite -> FilePath
stubFilePath t = stubName t <.> "hs"
writeSimpleTestStub :: PD.TestSuite
-> FilePath
-> NoCallStackIO ()
writeSimpleTestStub t dir = do
createDirectoryIfMissing True dir
let filename = dir </> stubFilePath t
m = case PD.testInterface t of
PD.TestSuiteLibV09 _ m' -> m'
_ -> error "writeSimpleTestStub: invalid TestSuite passed"
writeFile filename $ simpleTestStub m
simpleTestStub :: ModuleName -> String
simpleTestStub m = unlines
[ "module Main ( main ) where"
, "import Distribution.Simple.Test.LibV09 ( stubMain )"
, "import " ++ show (pretty m) ++ " ( tests )"
, "main :: IO ()"
, "main = stubMain tests"
]
stubMain :: IO [Test] -> IO ()
stubMain tests = do
(f, n) <- fmap (\s -> fromMaybe (error $ "panic! read " ++ show s) $ readMaybe s) getContents
dir <- getCurrentDirectory
results <- (tests >>= stubRunTests) `CE.catch` errHandler
setCurrentDirectory dir
stubWriteLog f n results
where
errHandler :: CE.SomeException -> NoCallStackIO TestLogs
errHandler e = case CE.fromException e of
Just CE.UserInterrupt -> CE.throwIO e
_ -> return $ TestLog { testName = "Cabal test suite exception",
testOptionsReturned = [],
testResult = Error $ show e }
stubRunTests :: [Test] -> IO TestLogs
stubRunTests tests = do
logs <- traverse stubRunTests' tests
return $ GroupLogs "Default" logs
where
stubRunTests' (Test t) = do
l <- run t >>= finish
summarizeTest normal Always l
return l
where
finish (Finished result) =
return TestLog
{ testName = name t
, testOptionsReturned = defaultOptions t
, testResult = result
}
finish (Progress _ next) = next >>= finish
stubRunTests' g@(Group {}) = do
logs <- traverse stubRunTests' $ groupTests g
return $ GroupLogs (groupName g) logs
stubRunTests' (ExtraOptions _ t) = stubRunTests' t
maybeDefaultOption opt =
maybe Nothing (\d -> Just (optionName opt, d)) $ optionDefault opt
defaultOptions testInst = mapMaybe maybeDefaultOption $ options testInst
stubWriteLog :: FilePath -> UnqualComponentName -> TestLogs -> NoCallStackIO ()
stubWriteLog f n logs = do
let testLog = TestSuiteLog { testSuiteName = n, testLogs = logs, logFile = f }
writeFile (logFile testLog) $ show testLog
when (suiteError logs) $ exitWith $ ExitFailure 2
when (suiteFailed logs) $ exitWith $ ExitFailure 1
exitSuccess