lifetimes-0.1.0.0: tests/Main.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-type-defaults #-}
module Main (main) where
import Control.Exception.Safe (SomeException, throwString, try)
import Data.IORef
import Lifetimes
import qualified Lifetimes.Gc as Gc
import qualified Lifetimes.Rc as Rc
import System.Mem (performGC)
import Test.Hspec
import Zhp
main :: IO ()
main = hspec $ do
describe "withAcquire" $ do
it "Should run the handler on success" $ do
ref <- newIORef 0
withAcquire (mkAcquire (pure ()) (\() -> writeIORef ref 1)) $ \_ -> pure ()
value <- readIORef ref
value `shouldBe` 1
it "Should run the handler on exceptions" $ do
ref <- newIORef 0
result <- try $ withAcquire (mkAcquire (pure ()) (\() -> writeIORef ref 1)) $ \() ->
throwString "Error"
case result of
Right () -> error "Should have thrown an exception"
Left (_ :: SomeException) -> do
value <- readIORef ref
value `shouldBe` 1
it "Should run cleanup handlers in reverse order" $ do
ref <- newIORef []
withAcquire
(traverse_ (append ref) [1,2,3])
pure
value <- readIORef ref
value `shouldBe` [3,2,1]
describe "nested lifetimes" $ do
it "Should order resources underneath their lifetimes." $ do
ref <- newIORef []
withLifetime $ \lt -> do
void $ acquire lt $ append ref 1
lt' <- acquireValue lt newLifetime
void $ acquire lt $ append ref 2
-- even though 3 is allocated after 2, it will be freed when
-- lt' is freed.
void $ acquire lt' $ append ref 3
value <- readIORef ref
value `shouldBe` [2,3,1]
describe "releaseEarly" $ do
it "Should release the resource immediately" $ do
ref <- newIORef []
withLifetime $ \lt -> do
void $ acquire lt $ append ref 1
res2 <- acquire lt $ append ref 2
void $ acquire lt $ append ref 3
releaseEarly res2
value <- readIORef ref
value `shouldBe` [2,3,1]
describe "moveTo" $ do
it "Should live longer when moved to a longer-lived lifetime" $ do
ref <- newIORef []
withLifetime $ \lt -> do
void $ acquire lt $ append ref 1
lt' <- acquireValue lt newLifetime
void $ acquire lt $ append ref 2
res3 <- acquire lt' $ append ref 3
-- If we didn't move this, it would be freed when lt'
-- is freed, but this will make it live until the end of
-- lt instead.
moveTo res3 lt
value <- readIORef ref
value `shouldBe` [3,2,1]
it "Should do nothing if the resource has already been freed" $ do
ref <- newIORef []
withLifetime $ \lt -> do
res1 <- acquire lt $ append ref 1
lt' <- acquireValue lt newLifetime
void $ acquire lt $ append ref 2
void $ acquire lt' $ append ref 3
releaseEarly res1
moveTo res1 lt'
value <- readIORef ref
value `shouldBe` [1,2,3]
describe "Lifetimes.Rc" $ do
it "Should release the resources in order if no extra references are acquired" $ do
ref <- newIORef []
withLifetime $ \lt -> do
void $ acquire lt $ Rc.refCounted $ append ref 1
void $ acquire lt $ Rc.refCounted $ append ref 2
value <- readIORef ref
value `shouldBe` [2,1]
it "Should last until its final reference is dropped" $ do
ref <- newIORef []
withLifetime $ \lt1 -> do
void $ withLifetime $ \lt2 -> do
res <- acquireValue lt2 $ Rc.refCounted $ append ref 1
acquire lt1 $ Rc.addRef res
value <- readIORef ref
value `shouldBe` []
value <- readIORef ref
value `shouldBe` [1]
describe "Lifetimes.Gc" $ do
it "Should not release the resource while an acquired reference is active." $ do
ref <- newIORef []
cell <- Gc.newCell ()
Gc.addFinalizer cell (modifyIORef ref (<>[1]))
withAcquire (Gc.acquireCell cell) $ \() -> do
performGC
value <- readIORef ref
value `shouldBe` []
describe "moveToGC" $ do
it "Should not release a resource when its original lifetime is up." $ do
ref <- newIORef []
cell <- withLifetime $ \lt -> do
res <- acquire lt $ append ref 1
Gc.moveToGc res
value <- readIORef ref
value `shouldBe` []
-- Touch the value to prevent GC:
withAcquire (Gc.acquireCell cell) $ \_ -> pure ()
append :: IORef [Int] -> Int -> Acquire ()
append ref n = mkAcquire
(pure ())
(\() -> modifyIORef ref (<>[n]))