module Test.Hspec.WebDriver.Internal (
session
, runState
, with
, SessionExample(..)
, AbortSessionEx(..)
) where
import Control.Applicative
import Control.Concurrent.MVar
import Control.Monad (replicateM)
import Control.Monad.Trans.State (state, evalState, execState, execStateT, StateT)
import Data.Traversable (traverse)
import Data.Typeable (Typeable, cast)
import Data.IORef
import Test.Hspec
import Test.Hspec.Core hiding (describe, it)
import qualified Control.Exception as E
traverseTree :: Applicative f => (Item -> f Item) -> SpecTree -> f SpecTree
traverseTree f (SpecItem i) = SpecItem <$> f i
traverseTree f (SpecGroup msg ss) = SpecGroup msg <$> traverse (traverseTree f) ss
traverseTree f (SpecWithCleanup c ss) = SpecWithCleanup c <$> traverse (traverseTree f) ss
traverseSpec :: Applicative f => (Item -> f Item) -> [SpecTree] -> f [SpecTree]
traverseSpec f = traverse (traverseTree f)
mapWithCounter :: (Int -> Item -> Item) -> [SpecTree] -> [SpecTree]
mapWithCounter f s = flip evalState 0 $ traverseSpec go s
where
go item = state $ \cnt -> (f cnt item, cnt+1)
countItems :: [SpecTree] -> Int
countItems s = flip execState 0 $ traverseSpec go s
where
go item = state $ \cnt -> (item, cnt+1)
data AbortSessionEx = AbortSessionEx
deriving Typeable
instance E.Exception AbortSessionEx
instance Show AbortSessionEx where
show AbortSessionEx = "Session Aborted"
data SessionTest a = SessionTest (IO () -> IO ()) (a -> IO a)
deriving Typeable
instance Show (SessionTest a) where
show _ = "Test must be contained within a session of matching state type"
instance Typeable a => E.Exception (SessionTest a)
data SessionExample s = SessionExample (s -> IO s)
instance Typeable a => Example (SessionExample a) where
evaluateExample (SessionExample f) _ act _ = E.throwIO $ SessionTest act f
data Session a = Session {
sessionCount :: Int
, sessionMVars :: [MVar (Either E.SomeException a)]
, sessionCreate :: IO a
, sessionClose :: a -> IO ()
}
sessionItem :: Typeable a => Session a -> Int -> Item -> Item
sessionItem sess i item =
item { itemExample = \p a prog -> runTest $ itemExample item p a prog }
where
open | i == 0 = E.try $ sessionCreate sess
| otherwise = takeMVar $ sessionMVars sess !! i
close ma | i == sessionCount sess 1 = either (const $ return ()) (sessionClose sess) ma
| otherwise = putMVar (sessionMVars sess !! (i+1)) ma
runTest ex = do
ma <- open
mres <- E.try ex
case mres of
Right res -> close ma >> return res
Left (E.SomeException err) ->
case (ma, cast err) of
(_, Nothing) -> close ma >> E.throwIO err
(Right a, Just (SessionTest act f)) -> do
aborted <- newIORef False
act $ do
mstate <- E.try $ f a
case mstate of
Right st -> close (Right st)
Left serr@(E.SomeException actErr) ->
case cast actErr of
Just AbortSessionEx -> close (Left serr) >> writeIORef aborted True
Nothing -> close ma >> E.throwIO serr
abrt <- readIORef aborted
return $ if abrt then Pending (Just "Session Aborted") else Success
(Left err', _) -> close ma >> E.throwIO err'
session :: Typeable s => IO s
-> (s -> IO ())
-> Spec
-> Spec
session create close s = do
(sess, trees) <- runIO $ do
trees <- runSpecM s
let cnt = countItems trees
mvars <- replicateM cnt newEmptyMVar
return (Session cnt mvars create close, trees)
fromSpecList (mapWithCounter (sessionItem sess) trees)
runState :: StateT s IO () -> SessionExample s
runState = SessionExample . execStateT
with :: (s -> IO ()) -> SessionExample s
with f = SessionExample $ \a -> f a >> return a