{-# OPTIONS_GHC -cpp #-} module Distribution.ShellHarness ( runTests ) where import Prelude hiding( catch ) import System.Directory ( getCurrentDirectory, setPermissions, Permissions(..), getDirectoryContents, findExecutable, createDirectoryIfMissing ) import System.Environment ( getEnv, getEnvironment ) import System.Exit ( ExitCode (..), exitWith ) import System.FilePath import System.IO import System.Process ( ProcessHandle, runInteractiveCommand, runInteractiveProcess, waitForProcess, getProcessExitCode ) import Data.Maybe import Data.List ( isInfixOf, isPrefixOf, (\\), nubBy ) import Control.Concurrent #if __GLASGOW_HASKELL__ >= 610 import Control.OldException #else import Control.Exception #endif import Control.Monad runTests :: String -> [String] -> IO Bool runTests cwd tests = do fails <- run tests if "bugs" `isInfixOf` cwd then if (length tests /= length fails) then do putStrLn $ "Some bug tests passed:" mapM_ putStrLn (tests \\ fails) return False else do putStrLn "All bug tests OK" return True else if fails /= [] then do putStrLn "Some tests failed:" mapM_ putStrLn fails return False else do putStrLn "All tests OK" return True run :: [String] -> IO [String] run tests= do cwd <- getCurrentDirectory path <- getEnv "PATH" env <- getEnvironment darcs_path <- get_darcs_path let myenv = [("HOME",cwd) ,("DARCS_TESTING_HOME",cwd) ,("PWD",cwd) ,("EMAIL","tester") ,("DARCSEMAIL","tester") ,("PATH",(darcs_path++":"++path)) ,("DARCS_DONT_COLOR","1") ,("DARCS_DONT_ESCAPE_ANYTHING","1")] bash <- find_bash let shell = takeWhile (/= '\n') bash putStrLn $ "Using bash shell in '"++shell++"'" set_prefs run_helper shell tests [] (set_env myenv env) where get_darcs_path = do env <- getEnvironment cwd <- getCurrentDirectory case lookup "DARCS" env of Nothing -> return (cwd ++ "/..") Just d -> return $ takeDirectory d set_prefs = do finally (catch (appendFile ".darcs/defaults" "\nALL --ignore-times\n") (\e -> fail $ "Unable to set preferences: " ++ show e)) (createDirectoryIfMissing False ".darcs") run_helper :: String -> [String] -> [String] -> [(String,String)] -> IO [String] run_helper _ [] fails _ = return fails run_helper shell (test:ts) fails env = do putStr $ "Running " ++ test ++ " ..." ++ (replicate (36 - (length test)) ' ') (output,success) <- backtick shell test env if success then do putStrLn " passed." cleanup_dirs run_helper shell ts fails env else do putStrLn " failed." putStrLn $ "Probable reason :" ++ output cleanup_dirs run_helper shell ts (fails++[test]) env where cleanup_dirs :: IO () cleanup_dirs = do dirfiles <- getDirectoryContents (fromJust $ lookup "PWD" env) let tempfiles = (filter ("temp" `isPrefixOf`) dirfiles) ++ (filter ("tmp" `isPrefixOf`) dirfiles) mapM_ (\x-> setPermissions x (Permissions {readable = True ,writable = True ,executable = False ,searchable = True} ) ) tempfiles backtick :: String -> String -> [(String, String)]-> IO (String,Bool) backtick cmd args env = do (exitcode,res) <- backtick_helper cmd args env case exitcode of ExitSuccess -> return (res, True) ExitFailure code -> return (res, False) backtick_helper :: String -> String -> [(String,String)] -> IO (ExitCode, String) backtick_helper cmd args env = process_wrapper (runInteractiveProcess cmd [args] Nothing (Just env) ) "" find_bash = do sh <- findExecutable "bash" case sh of Just p -> return p Nothing -> error "Could not find bash in PATH" -- | Run a process with a list of arguments and return anything from -- /stderr/ or /stdout/ process_wrapper :: IO (Handle, Handle, Handle, ProcessHandle) -> String -> IO (ExitCode, String) process_wrapper f _ = do (_,o,e,pid) <- f hSetBuffering o LineBuffering hSetBuffering e LineBuffering ch <- newChan -- WARNING: beware of hokeyness ahead! let readWrite i = do x <- hGetLine i writeChan ch $ Just x readWrite i `catch` \_ -> writeChan ch Nothing readEO = do x <- readChan ch case x of Just l -> do y <- readEO return $ l:y Nothing -> readEO' readEO' = do x <- readChan ch case x of Just l -> do y <- readEO' return $ l:y Nothing -> return [] forkIO $ readWrite o forkIO $ readWrite e outerr <- readEO ec <- waitForProcessNonBlocking pid threadDelay 1000 case ec of ExitFailure 127 -> fail $ "timeout running command\n\n" ++unlines outerr _ -> return (ec, unlines outerr) -- -- waitForProcess uses a very hokey heuristic to try to avoid burning too -- much CPU time in a busy wait, while also not adding too much extra -- latency. waitForProcessNonBlocking :: ProcessHandle -> IO ExitCode waitForProcessNonBlocking = if rtsSupportsBoundThreads then waitForProcess else wfp 0 where wfp n pid = do mec <- getProcessExitCode pid case mec of Just ec -> return ec Nothing -> do threadDelay n wfp (min 100000 (n+1+n`div`4)) pid set_env :: [(String,String)] -> [(String,String)] -> [(String, String)] set_env es env = nubBy (\(x,_) (y,_) -> x == y) (es ++ env)