sydtest-0.11.0.1: src/Test/Syd/Def/SetupFunc.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}
-- | The 'SetupFunc' abstraction makes resource provider functions (of type @(a -> IO r) -> IO r@) composable.
module Test.Syd.Def.SetupFunc where
import Control.Exception
import Control.Monad.IO.Class
import Test.Syd.Def.Around
import Test.Syd.Def.AroundAll
import Test.Syd.Def.TestDefM
import Test.Syd.HList
-- * Creating 'SetupFunc's
-- | A function that can provide a 'resource'.
--
-- You can think of this as a potentially-resource-aware version of 'IO resource'.
-- In other words, it's like an 'IO resource' that can clean up after itself.
--
-- This type has a monad instance, which means you can now compose setup functions using regular do-notation.
-- This works together nicely with most supplier functions.
-- Some examples:
--
-- * [Network.Wai.Handler.Warp.testWithApplication](https://hackage.haskell.org/package/warp-3.3.13/docs/Network-Wai-Handler-Warp.html#v:testWithApplication)
-- * [Path.IO.withSystemTempDir](https://hackage.haskell.org/package/path-io-1.6.2/docs/Path-IO.html#v:withSystemTempDir)
--
-- Note that these examples already have functions defined for them in sydtest companion libraries.
newtype SetupFunc resource = SetupFunc
{ unSetupFunc :: forall r. (resource -> IO r) -> IO r
}
instance Functor SetupFunc where
fmap f (SetupFunc provideA) = SetupFunc $ \takeB ->
let takeA = \a -> takeB $ f a
in provideA takeA
instance Applicative SetupFunc where
pure a = SetupFunc $ \aFunc -> aFunc a
(SetupFunc provideF) <*> (SetupFunc provideA) = SetupFunc $ \takeB ->
provideF
( \f ->
provideA
( \a ->
takeB (f a)
)
)
instance Monad SetupFunc where
(SetupFunc provideA) >>= m = SetupFunc $ \takeB ->
provideA
( \a ->
let (SetupFunc provideB) = m a
in provideB (\b -> takeB b)
)
instance MonadIO SetupFunc where
liftIO ioFunc = SetupFunc $ \takeA -> do
ioFunc >>= takeA
-- | Turn the arguments that you would normally give to 'bracket' into a 'SetupFunc'.
bracketSetupFunc :: IO resource -> (resource -> IO r) -> SetupFunc resource
bracketSetupFunc acquire release = SetupFunc $ \func -> bracket acquire release func
-- * Using 'SetupFunc' to define your test suite
-- | Use 'around' with a 'SetupFunc'
setupAround ::
SetupFunc inner ->
TestDefM outers inner result ->
TestDefM outers any result
setupAround setupFunc = setupAroundWith $ \_ -> setupFunc
-- | Use 'aroundWith' with a 'SetupFunc'
setupAroundWith ::
(oldInner -> SetupFunc newInner) ->
TestDefM outers newInner result ->
TestDefM outers oldInner result
setupAroundWith takeOldInner = aroundWith $ \takeNewInner oldInner ->
let SetupFunc provideNewInner = takeOldInner oldInner
in provideNewInner $ \newInner -> takeNewInner newInner
-- | Use 'aroundWith'' with a 'SetupFunc'
setupAroundWith' ::
HContains outers outer =>
(outer -> oldInner -> SetupFunc newInner) ->
TestDefM outers newInner result ->
TestDefM outers oldInner result
setupAroundWith' f = aroundWith' $ \takeBoth outer oldInner ->
let SetupFunc provideNewInner = f outer oldInner
in provideNewInner $ \newInner -> takeBoth outer newInner
-- | Use 'aroundAll' with a 'SetupFunc'
setupAroundAll ::
SetupFunc outer ->
TestDefM (outer : outers) inner result ->
TestDefM outers inner result
setupAroundAll sf = aroundAll $ \func -> unSetupFunc sf func
-- | Use 'aroundAllWith' with a 'SetupFunc'
setupAroundAllWith ::
(oldOuter -> SetupFunc newOuter) ->
TestDefM (newOuter ': oldOuter ': outers) inner result ->
TestDefM (oldOuter ': outers) inner result
setupAroundAllWith sf = aroundAllWith $ \takeNewOuter oldOuter ->
let SetupFunc provideNewOuter = sf oldOuter
in provideNewOuter $ \newOuter -> takeNewOuter newOuter