test-framework-sandbox-0.1.1: src/Test/Framework/Providers/Sandbox.hs
{- |
Module : Test.Framework.Providers.Sandbox
Maintainer: Benjamin Surma <benjamin.surma@gmail.com>
test-framework interface for test-sandbox
-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Test.Framework.Providers.Sandbox (
-- * Introduction
-- $introduction
-- * Usage example
-- $usage
-- * Initialization
sandboxTests
-- * Test declaration
, sandboxTest
, sandboxTestGroup
, sandboxTestGroup'
, yieldProgress
, withRetry
) where
import Control.Exception.Lifted
import Control.Monad hiding (fail)
import Control.Monad.Reader (ask)
import System.Console.ANSI
import System.Environment
import System.Exit
import System.IO
import System.IO.Temp
import Test.Framework
import Test.Framework.Providers.API (Test (..))
import Data.Typeable
import Test.Sandbox
import Test.Sandbox.Internals hiding (putOptions)
import Test.Framework.Providers.Sandbox.Internals
-- | Executes tests in the Sandbox monad.
sandboxTests :: String -- ^ Name of the sandbox environment
-> [Sandbox Test] -- ^ Tests to perform
-> Test
sandboxTests name tests = testGroup name [
buildTest $ do
options <- interpretArgs =<< getArgs
if isExcluded options name then return $ Test name (SandboxTest Skipped)
else withSystemTempDirectory (name ++ "_") $ \dir -> do
env <- newSandboxState name dir
result <- runSandbox (putOptions options >> sandboxTestGroup name tests `finally` stopAll) env
case result of
Left error' -> return $ Test name (SandboxTest (Failure error'))
Right x -> return x
]
-- | Groups tests in the Sandbox monad.
sandboxTestGroup :: String -- ^ Test group name
-> [Sandbox Test] -- ^ Tests to perform
-> Sandbox Test
sandboxTestGroup name tests = withTest name $ do
liftIO $ putStrLn ""
liftM (testGroup name) (sequence tests)
-- | Variant of sandboxTestGroup: tests will be skipped if the condition is not verified.
sandboxTestGroup' :: String -- ^ Test group name
-> Sandbox Bool -- ^ Condition for group to be evaluated
-> [Sandbox Test] -- ^ Tests to perform if condition stands
-> Sandbox Test
sandboxTestGroup' name condition tests = do
result <- condition
if result then
sandboxTestGroup name tests
else return $ Test (name ++ " (disabled)") (SandboxTest Skipped)
-- | Creates a test from a Sandbox action.
-- Any exception (or error thrown with throwError) will mark the test as failed.
sandboxTest :: String -- ^ Test name
-> Sandbox () -- ^ Action to perform
-> Sandbox Test
sandboxTest name test = withTest name $ do
res <- do
ref <- ask
liftIO $ flip runSandbox ref $ test `catches` handlers
liftIO $ printTestResult res
case res of
Left error' -> return $ Test name (SandboxTest (Failure error'))
Right _ -> return $ Test name (SandboxTest Passed)
where handlers = [ Handler exitHandler
, Handler interruptHandler
, Handler otherHandler ]
exitHandler :: ExitCode -> Sandbox a
exitHandler = throw
interruptHandler :: AsyncException -> Sandbox a
interruptHandler UserInterrupt = liftIO exitFailure
interruptHandler e = throwError $ show e
otherHandler :: SomeException -> Sandbox a
otherHandler = throwError . show
-- | Displays a progress update during a test.
yieldProgress :: String -- ^ Text to display
-> Sandbox ()
yieldProgress p = do
pl <- getVariable prettyPrintVariable []
unless (null pl) $ liftIO $ putStr " / "
_ <- setVariable prettyPrintVariable (p : pl)
liftIO $ putStrColor Dull Blue p >> hFlush stdout
class SandboxRetry a where
withRetry :: Int -> Sandbox a -> Sandbox a
instance SandboxRetry Test where
withRetry num action =
if num <= 1
then action
else do
res <- action
if isPassed res
then return res
else withRetry (num - 1) action
isPassed :: Test -> Bool
isPassed test =
case test of
(Test _ res') ->
case cast res' of
(Just (SandboxTest Passed)) -> True
_ -> False
(TestGroup _ tests) -> and $ map isPassed tests
_ -> error "withRetry does not support this test-type."
instance SandboxRetry () where
withRetry num action =
if num <= 1
then action
else action `catchError` (\_ -> withRetry (num - 1) action)
----------------------------------------------------------------------
-- Docs
----------------------------------------------------------------------
{- $introduction
This module interfaces the Test.Sandbox monad with the test-framework
popular Haskell package for a unified test experience.
Tests share the same sandboxed environment: processes started in one
test can be addressed in another. Variables can and should be used
to pass information between test cases.
-}
{- $usage
The following example describes how the "sed" example from
the Test.Sandbox would be crammed into the Test.Framework model.
Initialization of the Sandbox is performed by the @sandboxTests@
function. Tests are then individually declared by @sandboxTest@ and
grouped by @sandboxTestGroup@.
> import Test.Framework
> import Test.Framework.Providers.Sandbox
> import Test.Sandbox
> import Test.Sandbox.HUnit
>
> setup :: Sandbox ()
> setup = start =<< register "sed_s/a/b/" "sed" [ "-u", "s/a/b/" ] def { psCapture = CaptureStdout }
>
> main = defaultMain [
> sandboxTests "sed_tests" [
> sandboxTest "setup" setup
> , sandboxTest "sed a->b" $ assertEqual "a->b" "b\n" =<< interactWith "sed_s/a/b/" "a\n" 5
> , sandboxTest "sed aa->ba" $ assertEqual "aa->ba" "ba\n" =<< interactWith "sed_s/a/b/" "aa\n" 5
> ]
> ]
-}