{- | Test By Convention: Drivers. - Copyright : (C)opyright 2009-2012 {mwotton, peteg42} at gmail dot com - License : BSD3 -} module Test.TBC.Drivers ( Driver(..) , ghci ) where ------------------------------------------------------------------- -- Dependencies. ------------------------------------------------------------------- import Prelude hiding ( catch ) import Control.Exception ( catch, SomeException ) import Control.Concurrent -- ( forkIO ) import Control.Monad ( liftM ) import Data.List ( isInfixOf ) import Distribution.Simple.Utils ( info, debug ) import Distribution.Verbosity ( Verbosity ) import System.Exit import System.IO -- ( hClose, hFlush, hGetContents, hPutStr ) import System.Process ( runInteractiveProcess, waitForProcess, terminateProcess ) ------------------------------------------------------------------- -- | Interaction with a Haskell system. data Driver = MkDriver { hci_send_cmd :: String -> IO [String] -- ^ Execute the given Haskell code and return the response as a list of lines. , hci_load_file :: String -> IO [String] -- ^ Load a file into the Haskell system. , hci_kill :: IO () -- ^ Terminate with prejudice. , hci_close :: IO ExitCode -- ^ Clean exit. } ---------------------------------------- -- | A driver for @GHCi@ using a slave process. ghci :: Verbosity -> String -- ^ ghci command name -> [String] -- ^ flags -> IO Driver ghci verbosity cmd flags = do let extra_flags = [] -- ["-package", "deepseq"] debug verbosity $ unlines [ "system $ " ++ cmd ++ " " ++ concat [ ' ' : a | a <- flags ++ extra_flags ] , "----------------------------------------" ] (hin, hout, herr, hpid) <- runInteractiveProcess cmd flags Nothing Nothing -- FIXME -- Configure GHCi a bit FIXME -- FIXME this doesn't help if GHCi craps out before we get a prompt -- e.g. if the package flags are wrong. This can happen if the package -- hash changes but not the version number. -- Perhaps we need a dup2 wrapper... hPutStrLn hin ":set prompt \"\"" hPutStrLn hin "GHC.Handle.hDuplicateTo System.IO.stdout System.IO.stderr" -- adding "-package deepseq" to the commandline doesn't seem to work (GHC 7.0.3, OS X) -- but this does. hPutStrLn hin ":s -package deepseq" -- We don't use GHCi's stderr, get rid of it. -- FIXME we maybe have to drain it first. -- GHC 7.4.1 dies if we do this. -- hClose herr let load_file f = do cout <- ghci_sync verbosity hin hout (":l *" ++ f ++ "\n") return $ if not (null cout) && "Ok, modules loaded" `isInfixOf` last cout then [] else cout return $ MkDriver { hci_send_cmd = ghci_sync verbosity hin hout , hci_load_file = load_file , hci_kill = terminateProcess hpid , hci_close = do hPutStr hin ":quit\n" hFlush hin `catch` (const (return ()) :: (SomeException -> IO ())) -- FIXME if GHCi is dead already that's fine by us. waitForProcess hpid } -- | Crudely synchronise with the slave process. ghci_sync :: Verbosity -> Handle -> Handle -> String -> IO [String] ghci_sync verbosity hin hout inp = do info verbosity $ "--Sync----------------------------------\n" ++ inp ++ "----------------------------------------\n" -- FIXME do we really need the separate thread? -- Get output + sync outMVar <- newEmptyMVar _ <- forkIO $ hc_sync hout >>= putMVar outMVar -- Tests + sync hPutStr hin inp hPutStr hin hc_sync_print -- This can fail if GHCi has died. hFlush hin `catch` ghciDiedEH outMVar -- Synchronize hc_output <- lint_output `liftM` takeMVar outMVar info verbosity $ unlines ( ">> Output <<" : hc_output ) return hc_output where lint_output :: [[a]] -> [[a]] lint_output = reverse . dropWhile null . reverse . dropWhile null done :: String done = ">>>>done<<<<" hc_sync_print :: String hc_sync_print = "System.IO.putStrLn \"" ++ done ++ "\"\n" -- FIXME EOF, exceptions, etc. hc_sync :: Handle -> IO [String] hc_sync h = sync where sync = do eof <- hIsEOF h if eof then return [] else do l <- hGetLine h info verbosity $ "hc>> " ++ l -- FIXME should be "debug" if done `isInfixOf` l then return [] else (l:) `liftM` sync ghciDiedEH outMVar e = do hc_output <- takeMVar outMVar putStr $ unlines ( ">> GHCi died. Output <<" : hc_output ) ioError e