fs-sim-0.5.0.0: src/System/FS/Sim/STM.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | 'HasFS' instance using 'MockFS' stored in an STM variable
module System.FS.Sim.STM
( runSimFS
, simHasFS
, simHasFS'
) where
import Control.Concurrent.Class.MonadSTM.Strict
import Control.Monad.Class.MonadThrow
import Control.Monad.Primitive
import System.FS.API
import System.FS.Sim.MockFS (HandleMock, MockFS)
import qualified System.FS.Sim.MockFS as Mock
import System.FS.Sim.Prim
{------------------------------------------------------------------------------
The simulation-related types
------------------------------------------------------------------------------}
--- | Runs a computation provided an initial 'MockFS', producing a
--- result, the final state of the filesystem and a sequence of actions occurred
--- in the filesystem.
runSimFS ::
(MonadSTM m, MonadMask m, PrimMonad m) =>
MockFS ->
(HasFS m HandleMock -> m a) ->
m (a, MockFS)
runSimFS fs act = do
var <- newTMVarIO fs
a <- act (simHasFS var)
fs' <- atomically $ takeTMVar var
return (a, fs')
-- | Alternative to 'simHasFS' that creates 'TVar's internally.
simHasFS' ::
(MonadSTM m, MonadMask m, PrimMonad m) =>
MockFS ->
m (HasFS m HandleMock)
simHasFS' mockFS = simHasFS <$> newTMVarIO mockFS
-- | Bracket-style helper that holds the MockFS lock for the duration of @f@.
--
-- 'withMockFS' is not interruptible while waiting for the `MockFS` TMVar, but
-- the body @f@ runs with the caller's original masking state restored, matching
-- the behaviour of 'Control.Concurrent.MVar.withMVar'.
withMockFS ::
(MonadSTM m, MonadMask m) =>
StrictTMVar m MockFS ->
(MockFS -> m (MockFS, Either FsError a)) ->
m a
withMockFS var f = do
result <- uninterruptibleMask $ \restore -> do
st <- atomically $ takeTMVar var
(st', ea) <- restore (f st) `onException` atomically (putTMVar var st)
atomically $ putTMVar var st'
pure ea
either throwIO pure result
-- | Equip @m@ with a @HasFs@ instance using the mock file system
simHasFS ::
forall m.
(MonadSTM m, MonadMask m, PrimMonad m) =>
StrictTMVar m MockFS ->
HasFS m HandleMock
simHasFS var =
HasFS
{ dumpState = sim Mock.dumpState
, hOpen = sim .: Mock.hOpen
, hClose = sim . Mock.hClose
, hIsOpen = sim . Mock.hIsOpen
, hSeek = sim ..: Mock.hSeek
, hGetSome = sim .: Mock.hGetSome
, hGetSomeAt = sim ..: Mock.hGetSomeAt
, hPutSome = sim .: Mock.hPutSome
, hTruncate = sim .: Mock.hTruncate
, hGetSize = sim . Mock.hGetSize
, createDirectory = sim . Mock.createDirectory
, createDirectoryIfMissing = sim .: Mock.createDirectoryIfMissing
, listDirectory = sim . Mock.listDirectory
, doesDirectoryExist = sim . Mock.doesDirectoryExist
, doesFileExist = sim . Mock.doesFileExist
, removeDirectoryRecursive = sim . Mock.removeDirectoryRecursive
, removeFile = sim . Mock.removeFile
, renameFile = sim .: Mock.renameFile
, mkFsErrorPath = fsToFsErrorPathUnmounted
, unsafeToFilePath = \_ -> error "simHasFS:unsafeToFilePath"
, -- File I\/O with user-supplied buffers
hGetBufSome = sim ...: Mock.hGetBufSome
, hGetBufSomeAt = sim ....: Mock.hGetBufSomeAt
, hPutBufSome = sim ...: Mock.hPutBufSome
, hPutBufSomeAt = sim ....: Mock.hPutBufSomeAt
}
where
sim :: FSSimT m a -> m a
sim m = withMockFS var $ \st ->
runFSSimT m st >>= \case
Left e -> pure (st, Left e)
Right (a, st') -> pure (st', Right a)
(.:) :: (y -> z) -> (x0 -> x1 -> y) -> (x0 -> x1 -> z)
(f .: g) x0 x1 = f (g x0 x1)
(..:) :: (y -> z) -> (x0 -> x1 -> x2 -> y) -> (x0 -> x1 -> x2 -> z)
(f ..: g) x0 x1 x2 = f (g x0 x1 x2)
(...:) :: (y -> z) -> (x0 -> x1 -> x2 -> x3 -> y) -> (x0 -> x1 -> x2 -> x3 -> z)
(f ...: g) x0 x1 x2 x3 = f (g x0 x1 x2 x3)
(....:) :: (y -> z) -> (x0 -> x1 -> x2 -> x3 -> x4 -> y) -> (x0 -> x1 -> x2 -> x3 -> x4 -> z)
(f ....: g) x0 x1 x2 x3 x4 = f (g x0 x1 x2 x3 x4)