module PackageTests.PackageTester ( PackageSpec(..), Success(..), Result(..), cabal_configure, cabal_build, cabal_test, cabal_bench, cabal_install, unregister, run ) where import qualified Control.Exception.Extensible as E import System.Directory import System.FilePath import System.IO import System.Posix.IO import System.Process import System.Exit import Control.Concurrent.Chan import Control.Concurrent.MVar import Control.Concurrent import Control.Monad import Data.List import Data.Maybe import qualified Data.ByteString.Char8 as C data PackageSpec = PackageSpec { directory :: FilePath, configOpts :: [String] } data Success = Failure | ConfigureSuccess | BuildSuccess | InstallSuccess | TestSuccess | BenchSuccess deriving (Eq, Show) data Result = Result { successful :: Bool, success :: Success, outputText :: String } deriving Show nullResult :: Result nullResult = Result True Failure "" recordRun :: (String, ExitCode, String) -> Success -> Result -> Result recordRun (cmd, exitCode, exeOutput) thisSucc res = res { successful = successful res && exitCode == ExitSuccess, success = if exitCode == ExitSuccess then thisSucc else success res, outputText = (if null $ outputText res then "" else outputText res ++ "\n") ++ cmd ++ "\n" ++ exeOutput } cabal_configure :: PackageSpec -> IO Result cabal_configure spec = do res <- doCabalConfigure spec record spec res return res doCabalConfigure :: PackageSpec -> IO Result doCabalConfigure spec = do cleanResult@(_, _, cleanOutput) <- cabal spec ["clean"] requireSuccess cleanResult res <- cabal spec $ ["configure", "--user"] ++ configOpts spec return $ recordRun res ConfigureSuccess nullResult doCabalBuild :: PackageSpec -> IO Result doCabalBuild spec = do configResult <- doCabalConfigure spec if successful configResult then do res <- cabal spec ["build"] return $ recordRun res BuildSuccess configResult else return configResult cabal_build :: PackageSpec -> IO Result cabal_build spec = do res <- doCabalBuild spec record spec res return res unregister :: String -> IO () unregister libraryName = do res@(_, _, output) <- run Nothing "ghc-pkg" ["unregister", "--user", libraryName] if "cannot find package" `isInfixOf` output then return () else requireSuccess res -- | Install this library in the user area cabal_install :: PackageSpec -> IO Result cabal_install spec = do buildResult <- doCabalBuild spec res <- if successful buildResult then do res <- cabal spec ["install"] return $ recordRun res InstallSuccess buildResult else return buildResult record spec res return res cabal_test :: PackageSpec -> [String] -> IO Result cabal_test spec extraArgs = do res <- cabal spec $ "test" : extraArgs let r = recordRun res TestSuccess nullResult record spec r return r cabal_bench :: PackageSpec -> [String] -> IO Result cabal_bench spec extraArgs = do res <- cabal spec $ "bench" : extraArgs let r = recordRun res BenchSuccess nullResult record spec r return r -- | Returns the command that was issued, the return code, and hte output text cabal :: PackageSpec -> [String] -> IO (String, ExitCode, String) cabal spec cabalArgs = do wd <- getCurrentDirectory r <- run (Just $ directory spec) "ghc" [ "--make" -- HPC causes trouble -- see #1012 -- , "-fhpc" , "-package-conf " ++ wd "../dist/package.conf.inplace" , "Setup.hs" ] requireSuccess r run (Just $ directory spec) (wd directory spec "Setup") cabalArgs -- | Returns the command that was issued, the return code, and hte output text run :: Maybe FilePath -> String -> [String] -> IO (String, ExitCode, String) run cwd cmd args = do -- Posix-specific (outf, outf0) <- createPipe outh <- fdToHandle outf outh0 <- fdToHandle outf0 pid <- runProcess cmd args cwd Nothing Nothing (Just outh0) (Just outh0) -- fork off a thread to start consuming the output output <- suckH [] outh hClose outh -- wait on the process ex <- waitForProcess pid let fullCmd = intercalate " " $ cmd:args return ("\"" ++ fullCmd ++ "\" in " ++ fromMaybe "" cwd, ex, output) where suckH output h = do eof <- hIsEOF h if eof then return (reverse output) else do c <- hGetChar h suckH (c:output) h requireSuccess :: (String, ExitCode, String) -> IO () requireSuccess (cmd, exitCode, output) = do case exitCode of ExitSuccess -> return () ExitFailure r -> do ioError $ userError $ "Command " ++ cmd ++ " failed." record :: PackageSpec -> Result -> IO () record spec res = do C.writeFile (directory spec "test-log.txt") (C.pack $ outputText res)