mirror of
https://github.com/polysemy-research/polysemy.git
synced 2024-12-02 11:54:06 +03:00
b00d451d1e
The IO Resource interpreters had a bug in `BracketOnError` that would fail to run the finalizers if the error was a Sem error (as opposed to an IO exception.) This PR also reworks the bracket spec so that it runs each test for every interpreter, hopefully keeping them all in sync in the future. Fixes #262
219 lines
5.5 KiB
Haskell
219 lines
5.5 KiB
Haskell
module BracketSpec where
|
|
|
|
import Control.Monad
|
|
import Polysemy
|
|
import Polysemy.Error
|
|
import Polysemy.Output
|
|
import Polysemy.Resource
|
|
import Polysemy.State
|
|
import Polysemy.Trace
|
|
import Test.Hspec
|
|
import Unsafe.Coerce
|
|
|
|
|
|
|
|
spec :: Spec
|
|
spec = parallel $ do
|
|
testAllThree "persist state and call the finalizer"
|
|
(\(ts, (s, e)) -> do
|
|
s `shouldBe` "finalized"
|
|
e `shouldBe` Left ()
|
|
ts `shouldBe` ["allocated", "starting block"]
|
|
) $ do
|
|
bracket
|
|
(put "allocated" >> pure ())
|
|
(\() -> do
|
|
get >>= trace
|
|
put "finalized"
|
|
)
|
|
(\() -> do
|
|
get >>= trace
|
|
put "starting block"
|
|
_ <- throw ()
|
|
put "don't get here"
|
|
)
|
|
|
|
testAllThree "persist state and call the finalizer with bracketOnError"
|
|
(\(ts, (s, e)) -> do
|
|
ts `shouldContain` ["allocated"]
|
|
ts `shouldContain` ["starting block"]
|
|
s `shouldBe` "finalized"
|
|
e `shouldBe` Left ()
|
|
) $ do
|
|
bracketOnError
|
|
(put "allocated" >> pure ())
|
|
(\() -> do
|
|
get >>= trace
|
|
put "finalized"
|
|
)
|
|
(\() -> do
|
|
get >>= trace
|
|
put "starting block"
|
|
_ <- throw ()
|
|
put "don't get here"
|
|
)
|
|
|
|
testAllThree "should not call the finalizer if there no error"
|
|
(\(ts, (s, e)) -> do
|
|
ts `shouldContain` ["allocated"]
|
|
ts `shouldNotContain` ["starting block"]
|
|
s `shouldBe` "don't get here"
|
|
e `shouldBe` Right ()
|
|
) $ do
|
|
bracketOnError
|
|
(put "allocated" >> pure ())
|
|
(\() -> do
|
|
get >>= trace
|
|
put "finalized"
|
|
)
|
|
(\() -> do
|
|
get >>= trace
|
|
put "starting block"
|
|
put "don't get here"
|
|
)
|
|
|
|
testAllThree "should call the finalizer on Error"
|
|
(\(ts, (s, e)) -> do
|
|
ts `shouldContain` ["beginning transaction"]
|
|
ts `shouldContain` ["rolling back transaction"]
|
|
s `shouldBe` ""
|
|
e `shouldBe` Left ()
|
|
) $ do
|
|
withTransaction $ do
|
|
void $ throw ()
|
|
pure "hello"
|
|
|
|
testTheIOTwo "io dispatched bracket"
|
|
(\(ts, (s, e)) -> do
|
|
ts `shouldContain` ["allocated"]
|
|
ts `shouldContain` ["starting block"]
|
|
s `shouldBe` "finalized"
|
|
e `shouldBe` Left ()
|
|
) $ do
|
|
bracket
|
|
(put "allocated" >> pure ())
|
|
(\() -> do
|
|
get >>= trace
|
|
put "finalized"
|
|
)
|
|
(\() -> do
|
|
get >>= trace
|
|
put "starting block"
|
|
_ <- throw ()
|
|
put "don't get here"
|
|
)
|
|
|
|
testTheIOTwo "should not lock when done recursively"
|
|
(\(ts, (s, e)) -> do
|
|
ts `shouldContain` [ "hello 1"
|
|
, "hello 2"
|
|
, "RUNNING"
|
|
, "goodbye 2"
|
|
]
|
|
s `shouldBe` "finished"
|
|
e `shouldBe` Left ()
|
|
) $ do
|
|
bracket
|
|
(put "hello 1")
|
|
(\() -> do
|
|
get >>= trace
|
|
put "finished"
|
|
)
|
|
(\() -> do
|
|
get >>= trace
|
|
void $
|
|
bracket (put "hello 2")
|
|
(const $ do
|
|
get >>= trace
|
|
put "goodbye 2"
|
|
)
|
|
(const $ do
|
|
get >>= trace
|
|
put "RUNNING"
|
|
throw ()
|
|
)
|
|
-- This doesn't run due to the thrown error above
|
|
get >>= trace
|
|
put "goodbye 1"
|
|
)
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
|
|
runTest
|
|
:: Sem '[Error (), Resource, State [Char], Trace] a
|
|
-> IO ([String], ([Char], Either () a))
|
|
runTest = pure
|
|
. run
|
|
. runTraceList
|
|
. runState ""
|
|
. runResource
|
|
. runError @()
|
|
|
|
runTest2
|
|
:: Sem '[Error (), Resource, State [Char], Trace, Output String, Embed IO] a
|
|
-> IO ([String], ([Char], Either () a))
|
|
runTest2 = runM
|
|
. ignoreOutput
|
|
. runTraceList
|
|
. runState ""
|
|
. resourceToIO
|
|
. runError @()
|
|
|
|
runTest3
|
|
:: Sem '[Error (), Resource, State [Char], Trace, Output String, Embed IO, Final IO] a
|
|
-> IO ([String], ([Char], Either () a))
|
|
runTest3 = runFinal
|
|
. embedToFinal
|
|
. outputToIOMonoid (:[])
|
|
. traceToOutput
|
|
. stateToIO ""
|
|
. resourceToIOFinal
|
|
. runError @()
|
|
|
|
|
|
testAllThree
|
|
:: String
|
|
-> (([String], ([Char], Either () a)) -> Expectation)
|
|
-> (Sem '[Error (), Resource, State [Char], Trace] a)
|
|
-> Spec
|
|
testAllThree name k m = do
|
|
describe name $ do
|
|
it "via runResource" $ do
|
|
z <- runTest m
|
|
k z
|
|
-- NOTE(sandy): These unsafeCoerces are safe, because we're just weakening
|
|
-- the end of the union
|
|
it "via resourceToIO" $ do
|
|
z <- runTest2 $ unsafeCoerce m
|
|
k z
|
|
it "via resourceToIOFinal" $ do
|
|
z <- runTest3 $ unsafeCoerce m
|
|
k z
|
|
|
|
|
|
testTheIOTwo
|
|
:: String
|
|
-> (([String], ([Char], Either () a)) -> Expectation)
|
|
-> (Sem '[Error (), Resource, State [Char], Trace, Output String, Embed IO] a)
|
|
-> Spec
|
|
testTheIOTwo name k m = do
|
|
describe name $ do
|
|
it "via resourceToIO" $ do
|
|
z <- runTest2 m
|
|
k z
|
|
-- NOTE(sandy): This unsafeCoerces are safe, because we're just weakening
|
|
-- the end of the union
|
|
it "via resourceToIOFinal" $ do
|
|
z <- runTest3 $ unsafeCoerce m
|
|
k z
|
|
|
|
|
|
withTransaction :: (Member Resource r, Member Trace r) => Sem r a -> Sem r a
|
|
withTransaction m =
|
|
bracketOnError
|
|
(trace "beginning transaction")
|
|
(const $ trace "rolling back transaction")
|
|
(const $ m <* trace "committing transaction")
|