proctest 0.1.2.0 → 0.1.3.0
raw patch · 3 files changed
+86/−7 lines, 3 files
Files
- Test/Proctest.hs +32/−6
- Test/Proctest/Assertions.hs +53/−0
- proctest.cabal +1/−1
Test/Proctest.hs view
@@ -9,6 +9,10 @@ - Beware that the Haskell GC closes process 'Handle's after their last use. If you don't want to be surprised by this, use 'hClose' where you want them to be closed (convenience: 'closeHandles').+ Really do this for EVERY process you create, the behaviour of a program+ writing to a closed handle is undefined. For example,+ 'getProcessExitCode' run on such a program somtimes seems to+ always return 'ExitSuccess', no matter what the program actually does. - Make sure handle buffering is set appropriately. 'run' sets 'LineBuffering' by default.@@ -64,11 +68,13 @@ , asUtf8Str -- * Running and stopping programs+, ProcessHandles , run , RunException (..) , isRunning , terminateProcesses , closeHandles+, closeProcessHandles -- * Timeouts , Timeout (NoTimeout)@@ -80,6 +86,8 @@ -- * Communicating with programs , TimeoutException+, timeoutToSystemTimeoutArg+, withTimeout , waitOutput , waitOutputNoEx , setBuffering@@ -100,7 +108,7 @@ import System.Exit import System.IO import System.Process-import System.Timeout (timeout)+import qualified System.Timeout (timeout) -- | Treats a 'BS.ByteString' as UTF-8 decoded 'Text'.@@ -112,6 +120,10 @@ asUtf8Str = unpack . asUtf8 +-- | Short cut. ALWAYS use the order stdin, stdout, stderr, process handle.+type ProcessHandles = (Handle, Handle, Handle, ProcessHandle)++ -- | Runs a program with the given arguemtns. -- -- Returns @(stdout, stderr, stdin, process)@. See 'runInteractiveProcess'.@@ -137,7 +149,6 @@ -- | Exception to be thrown when a program could not be started. data RunException = CommandNotFound String- | ExitedTooEarly String deriving (Show, Typeable) instance Exception RunException@@ -154,11 +165,20 @@ closeHandles :: [Handle] -> IO () closeHandles = mapM_ hClose +-- | Closes all file handles to all given handle-process-tuples.+--+-- Use this to make sure that handles are not closed due to garbage+-- collection (see "System.IO") while your processes are still running.+--+-- It is safe to call this on processes which have already exited.+closeProcessHandles :: [ProcessHandles] -> IO ()+closeProcessHandles = mapM_ $ \(i, o, e, _) -> closeHandles [i, o, e] + -- | A microsecond timeout, or 'NoTimeout'.-data Timeout = NoTimeout | Micros Int+data Timeout = Micros Int | NoTimeout deriving (Eq, Ord, Show) --- | An error to be thrown if something is to be converted into timeout+-- | An error to be thrown if something is to be converted into 'Timeout' -- that does not fit into 'Int'. data InvalidTimeoutError = InvalidTimeoutError String deriving (Show, Typeable) @@ -195,7 +215,7 @@ -- Throws an exception on 'Int' overflow. -- -- Example: @(seconds 0.2)@ are roughly @Micros 200000@.-seconds :: Float -> Timeout+seconds :: Double -> Timeout seconds s = mkTimeoutUs (round $ s * 1000000) @@ -220,6 +240,12 @@ Micros n -> n NoTimeout -> -1 ++-- | Overflow-safe version of 'System.Timeout.timeout', using 'Timeout'.+withTimeout :: Timeout -> IO a -> IO (Maybe a)+withTimeout t = System.Timeout.timeout (timeoutToSystemTimeoutArg t)++ -- | Blocking wait for output on the given handle. -- -- Returns 'Nothing' timeout is exceeded.@@ -228,7 +254,7 @@ -> Handle -- ^ The handle to read from. -> IO (Maybe BS.ByteString) -- ^ What was read from the handle. waitOutputNoEx t maxBytes handle =- timeout (timeoutToSystemTimeoutArg t) (BS.hGetSome handle maxBytes)+ withTimeout t (BS.hGetSome handle maxBytes) -- | Blocking wait for output on the given handle.
Test/Proctest/Assertions.hs view
@@ -7,10 +7,14 @@ module Test.Proctest.Assertions ( -- * Starting programs runAssert+ , assertExited+ , _PROCTEST_POLL_TIMEOUT+ , assertExitedTimeout ) where +import Control.Monad import Test.Proctest import Test.HUnit @@ -19,6 +23,7 @@ onJust :: (Monad m) => (a -> m ()) -> Maybe a -> m () onJust = maybe (return ()) + -- | Runs the given program with `run` and asserts that it is still running -- after the given timeout. --@@ -40,3 +45,51 @@ return r where program = cmd ++ concatMap (" " ++) args+++-- | Asserts that the given process has shut down.+--+-- You might need to `sleep` before to give the process time to exit.+-- It is usually better to use `assertExitedTimeout` in those cases.+--+-- If the process is still running, a HUnit `assertFailure` exception is thrown.+assertExited :: ProcessHandle -> IO ()+assertExited p = do+ mE <- getProcessExitCode p+ when (mE == Nothing) $ assertFailure "The process is still running"+++-- | How often to poll in waiting functions with maximum timeout.+_PROCTEST_POLL_TIMEOUT :: Timeout+_PROCTEST_POLL_TIMEOUT = mkTimeoutMs (1 :: Int)+++-- | HUnit's `assertFailure` currently does not allow returning any type+-- like normal throw functions. This is a workaround.+--+-- Usage:+--+-- >fixHunitFailure $ assertFailure "boo!"+--+-- TODO Remove this in case it gets fixed in HUnit.+fixHunitFailure :: IO () -> IO a+fixHunitFailure = fmap . const $ error "Test.Proctest.Assertions: Executing after assertFailure, cannot happen!"+++-- | Asserts that the given process has shut down in *at most* the given timeout.+--+-- Periodically polling with `_PROCTEST_POLL_TIMEOUT`,+-- returns as soon as the application has terminated or the timeout is exceeded.+--+-- Use this to write faster tests than with manual `sleep`ing:+-- For most tests, the application will actually finish way before the timeout.+--+-- If the process is still running, a HUnit `assertFailure` exception is thrown.+assertExitedTimeout :: Timeout -> ProcessHandle -> IO ExitCode+assertExitedTimeout timeout p =+ -- withTimeout timeout loop >>= maybe failure return+ -- TODO Fix HUnit.+ withTimeout timeout loop >>= maybe (fixHunitFailure failure) return+ where+ failure = assertFailure "The process is still running"+ loop = getProcessExitCode p >>= maybe loop return
proctest.cabal view
@@ -1,5 +1,5 @@ name: proctest-version: 0.1.2.0+version: 0.1.3.0 license: MIT license-file: LICENSE author: Niklas Hambüchen