Skip to content

Commit 43b6e61

Browse files
committed
blockio: refactor and document the Sim module
1 parent a5ce645 commit 43b6e61

File tree

5 files changed

+155
-20
lines changed

5 files changed

+155
-20
lines changed

blockio/src-sim/System/FS/BlockIO/Sim.hs

Lines changed: 149 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
-- | Simulated instances of 'HasBlockIO' and 'HasFS'.
12
module System.FS.BlockIO.Sim (
2-
fromHasFS
3-
-- * Initialisation helpers
3+
-- * Implementation details #impl#
4+
-- $impl
5+
6+
-- * Runners
7+
runSimHasBlockIO
8+
, runSimErrorHasBlockIO
9+
-- * Initialisation
410
, simHasBlockIO
511
, simHasBlockIO'
612
, simErrorHasBlockIO
713
, simErrorHasBlockIO'
14+
-- ** Unsafe
15+
, unsafeFromHasFS
816
) where
917

1018
import Control.Concurrent.Class.MonadMVar
@@ -24,11 +32,55 @@ import System.FS.Sim.Error
2432
import System.FS.Sim.MockFS hiding (hClose, hOpen)
2533
import System.FS.Sim.STM
2634

27-
fromHasFS ::
35+
{- $impl
36+
37+
We include below some documentation about the effects of calling the interface
38+
functions on the simulated instance of the 'HasBlockIO' interface.
39+
40+
[IO context]: For uniform behaviour across implementations, the simulation
41+
creates and stores a mocked IO context that has the open/closed behaviour
42+
that is specified by the interface.
43+
44+
['close']: Close the mocked context
45+
46+
['submitIO']: Submit a batch of I\/O operations using serial I\/O using a
47+
'HasFS'
48+
49+
['hSetNoCache']: No-op
50+
51+
['hAdvise']: No-op
52+
53+
['hAllocate']: No-op
54+
55+
['tryLockFile']: Simulate a lock by putting the lock state into the file
56+
contents
57+
58+
['hSynchronise']: No-op
59+
60+
['synchroniseDirectory']: No-op
61+
62+
['createHardLink']: Copy all file contents from the source path to the target
63+
path. Therefore, this is currently only correctly simulating hard links
64+
for /immutable/ files.
65+
-}
66+
67+
-- | Simulate a 'HasBlockIO' using the given 'HasFS'.
68+
--
69+
-- === Unsafe
70+
--
71+
-- You will probably want to use one of the safe functions like
72+
-- 'runSimHasBlockIO' or 'simErrorHasBlockIO' instead.
73+
--
74+
-- Only a simulated 'HasFS', like the 'simHasFS' and 'simErrorHasFS'
75+
-- simulations, should be passed to 'unsafeFromHasFS'. Technically, one could
76+
-- pass a 'HasFS' for the /real/ file system, but then the resulting
77+
-- 'HasBlockIO' would contain a mix of simulated functions and real functions,
78+
-- which is probably not what you want.
79+
unsafeFromHasFS ::
2880
forall m. (MonadCatch m, MonadMVar m, PrimMonad m)
2981
=> HasFS m HandleMock
3082
-> m (HasBlockIO m HandleMock)
31-
fromHasFS hfs =
83+
unsafeFromHasFS hfs =
3284
serialHasBlockIO
3385
hSetNoCache
3486
hAdvise
@@ -142,43 +194,131 @@ simCreateHardLink hfs sourcePath targetPath =
142194
void $ API.hPutAll hfs targetHandle bs
143195

144196
{-------------------------------------------------------------------------------
145-
Initialisation helpers
197+
Runners
146198
-------------------------------------------------------------------------------}
147199

200+
-- | @'runSimHasBlockIO' mockFS action@ runs an @action@ using a pair of
201+
-- simulated 'HasFS' and 'HasBlockIO'.
202+
--
203+
-- The pair of interfaces share the same mocked file system. The initial state
204+
-- of the mocked file system is set to @mockFs@. The final state of the mocked
205+
-- file system is returned with the result of @action@.
206+
--
207+
-- If you want to have access to the current state of the mocked file system,
208+
-- use 'simHasBlockIO' instead.
209+
runSimHasBlockIO ::
210+
(MonadSTM m, PrimMonad m, MonadCatch m, MonadMVar m)
211+
=> MockFS
212+
-> (HasFS m HandleMock -> HasBlockIO m HandleMock -> m a)
213+
-> m (a, MockFS)
214+
runSimHasBlockIO mockFS k = do
215+
runSimFS mockFS $ \hfs -> do
216+
hbio <- unsafeFromHasFS hfs
217+
k hfs hbio
218+
219+
-- | @'runSimErrorHasBlockIO' mockFS errors action@ runs an @action@ using a
220+
-- pair of simulated 'HasFS' and 'HasBlockIO' that allow fault injection.
221+
--
222+
-- The pair of interfaces share the same mocked file system. The initial state
223+
-- of the mocked file system is set to @mockFs@. The final state of the mocked
224+
-- file system is returned with the result of @action@.
225+
--
226+
-- The pair of interfaces share the same stream of errors. The initial state of
227+
-- the stream of errors is set to @errors@. The final state of the stream of
228+
-- errors is returned with the result of @action@.
229+
--
230+
-- If you want to have access to the current state of the mocked file system
231+
-- or stream of errors, use 'simErrorHasBlockIO' instead.
232+
runSimErrorHasBlockIO ::
233+
(MonadSTM m, PrimMonad m, MonadCatch m, MonadMVar m)
234+
=> MockFS
235+
-> Errors
236+
-> (HasFS m HandleMock -> HasBlockIO m HandleMock -> m a)
237+
-> m (a, MockFS, Errors)
238+
runSimErrorHasBlockIO mockFS errs k = do
239+
fsVar <- newTMVarIO mockFS
240+
errorsVar <- newTVarIO errs
241+
(hfs, hbio) <- simErrorHasBlockIO fsVar errorsVar
242+
a <- k hfs hbio
243+
fs' <- atomically $ takeTMVar fsVar
244+
errs' <- readTVarIO errorsVar
245+
pure (a, fs', errs')
246+
247+
{-------------------------------------------------------------------------------
248+
Initialisation
249+
-------------------------------------------------------------------------------}
250+
251+
-- | @'simHasBlockIO' mockFsVar@ creates a pair of simulated 'HasFS' and
252+
-- 'HasBlockIO'.
253+
--
254+
-- The pair of interfaces share the same mocked file system, which is stored in
255+
-- @mockFsVar@. The current state of the mocked file system can be accessed by
256+
-- the user by reading @mockFsVar@, but note that the user should not leave
257+
-- @mockFsVar@ empty.
148258
simHasBlockIO ::
149259
(MonadCatch m, MonadMVar m, PrimMonad m, MonadSTM m)
150260
=> StrictTMVar m MockFS
151261
-> m (HasFS m HandleMock, HasBlockIO m HandleMock)
152262
simHasBlockIO var = do
153263
let hfs = simHasFS var
154-
hbio <- fromHasFS hfs
264+
hbio <- unsafeFromHasFS hfs
155265
pure (hfs, hbio)
156266

267+
-- | @'simHasBlockIO' mockFs@ creates a pair of simulated 'HasFS' and
268+
-- 'HasBlockIO' that allow fault injection.
269+
--
270+
-- The pair of interfaces share the same mocked file system. The initial state
271+
-- of the mocked file system is set to @mockFs@.
272+
--
273+
-- If you want to have access to the current state of the mocked file system,
274+
-- use 'simHasBlockIO' instead.
157275
simHasBlockIO' ::
158276
(MonadCatch m, MonadMVar m, PrimMonad m, MonadSTM m)
159277
=> MockFS
160278
-> m (HasFS m HandleMock, HasBlockIO m HandleMock)
161279
simHasBlockIO' mockFS = do
162280
hfs <- simHasFS' mockFS
163-
hbio <- fromHasFS hfs
281+
hbio <- unsafeFromHasFS hfs
164282
pure (hfs, hbio)
165283

284+
-- | @'simErrorHasBlockIO' mockFsVar errorsVar@ creates a pair of simulated
285+
-- 'HasFS' and 'HasBlockIO' that allow fault injection.
286+
--
287+
-- The pair of interfaces share the same mocked file system, which is stored in
288+
-- @mockFsVar@. The current state of the mocked file system can be accessed by
289+
-- the user by reading @mockFsVar@, but note that the user should not leave
290+
-- @mockFsVar@ empty.
291+
--
292+
-- The pair of interfaces share the same stream of errors, which is stored in
293+
-- @errorsVar@. The current state of the stream of errors can be accessed by the
294+
-- user by reading @errorsVar@.
166295
simErrorHasBlockIO ::
167296
forall m. (MonadCatch m, MonadMVar m, PrimMonad m, MonadSTM m)
168297
=> StrictTMVar m MockFS
169298
-> StrictTVar m Errors
170299
-> m (HasFS m HandleMock, HasBlockIO m HandleMock)
171300
simErrorHasBlockIO fsVar errorsVar = do
172301
let hfs = simErrorHasFS fsVar errorsVar
173-
hbio <- fromHasFS hfs
302+
hbio <- unsafeFromHasFS hfs
174303
pure (hfs, hbio)
175304

305+
-- | @'simErrorHasBlockIO' mockFs errors@ creates a pair of simulated 'HasFS'
306+
-- and 'HasBlockIO' that allow fault injection.
307+
--
308+
-- The pair of interfaces share the same mocked file system. The initial state
309+
-- of the mocked file system is set to @mockFs@.
310+
--
311+
-- The pair of interfaces share the same stream of errors. The initial state of
312+
-- the stream of errors is set to @errors@.
313+
--
314+
-- If you want to have access to the current state of the mocked file system
315+
-- or stream of errors, use 'simErrorHasBlockIO' instead.
176316
simErrorHasBlockIO' ::
177317
(MonadCatch m, MonadMVar m, PrimMonad m, MonadSTM m)
178318
=> MockFS
179319
-> Errors
180320
-> m (HasFS m HandleMock, HasBlockIO m HandleMock)
181321
simErrorHasBlockIO' mockFS errs = do
182322
hfs <- simErrorHasFS' mockFS errs
183-
hbio <- fromHasFS hfs
323+
hbio <- unsafeFromHasFS hfs
184324
pure (hfs, hbio)

test/Test/Database/LSMTree/Internal/Merge.hs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,7 @@ tests = testGroup "Test.Database.LSMTree.Internal.Merge"
5151
=> (FS.HasFS IO FsSim.HandleMock -> FS.HasBlockIO IO FsSim.HandleMock -> IO p)
5252
-> Property
5353
ioPropertyWithMockFS prop = ioProperty $ do
54-
(res, mockFS) <-
55-
FsSim.runSimErrorFS FsSim.empty FsSim.emptyErrors $ \_ fs -> do
56-
hbio <- FsSim.fromHasFS fs
57-
prop fs hbio
54+
(res, mockFS, _) <- FsSim.runSimErrorHasBlockIO FsSim.empty FsSim.emptyErrors prop
5855
pure $ res
5956
.&&. counterexample "open handles"
6057
(FsSim.numOpenHandles mockFS === 0)

test/Test/Database/LSMTree/Internal/Readers.hs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import qualified System.FS.API as FS
3838
import qualified System.FS.BlockIO.API as FS
3939
import qualified System.FS.BlockIO.Sim as FsSim
4040
import qualified System.FS.Sim.MockFS as MockFS
41-
import qualified System.FS.Sim.STM as FsSim
4241
import qualified Test.QuickCheck as QC
4342
import Test.Tasty (TestTree, testGroup)
4443
import Test.Tasty.QuickCheck
@@ -54,8 +53,7 @@ tests = testGroup "Database.LSMTree.Internal.Readers"
5453
[ testProperty "prop_lockstep" $
5554
Lockstep.runActionsBracket (Proxy @ReadersState)
5655
mempty mempty $ \act () -> do
57-
(prop, mockFS) <- FsSim.runSimFS MockFS.empty $ \hfs -> do
58-
hbio <- FsSim.fromHasFS hfs
56+
(prop, mockFS) <- FsSim.runSimHasBlockIO MockFS.empty $ \hfs hbio -> do
5957
(prop, RealState _ mCtx) <- runRealMonad hfs hbio
6058
(RealState 0 Nothing) act
6159
traverse_ closeReadersCtx mCtx -- close current readers

test/Test/Util/FS.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ import System.FS.API as FS
7575
import qualified System.FS.API.Lazy as FSL
7676
import System.FS.BlockIO.API
7777
import System.FS.BlockIO.IO hiding (unsafeFromHasFS)
78-
import System.FS.BlockIO.Sim (fromHasFS)
78+
import System.FS.BlockIO.Sim (unsafeFromHasFS)
7979
import System.FS.IO
8080
import System.FS.Sim.Error
8181
import System.FS.Sim.MockFS (HandleMock, MockFS, numOpenHandles,
@@ -136,7 +136,7 @@ withSimHasBlockIO ::
136136
-> m Property
137137
withSimHasBlockIO post fs k = do
138138
withSimHasFS post fs $ \hfs fsVar -> do
139-
hbio <- fromHasFS hfs
139+
hbio <- unsafeFromHasFS hfs
140140
k hfs hbio fsVar
141141

142142
{-------------------------------------------------------------------------------
@@ -180,7 +180,7 @@ withSimErrorHasBlockIO ::
180180
-> m Property
181181
withSimErrorHasBlockIO post fs errs k =
182182
withSimErrorHasFS post fs errs $ \hfs fsVar errsVar -> do
183-
hbio <- fromHasFS hfs
183+
hbio <- unsafeFromHasFS hfs
184184
k hfs hbio fsVar errsVar
185185

186186
{-------------------------------------------------------------------------------

test/Test/Util/FS/Error.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ simErrorHasBlockIOLogged ::
114114
-> m (HasFS m HandleMock, HasBlockIO m HandleMock)
115115
simErrorHasBlockIOLogged fsVar errorsVar logVar = do
116116
let hfs = simErrorHasFSLogged fsVar errorsVar logVar
117-
hbio <- fromHasFS hfs
117+
hbio <- unsafeFromHasFS hfs
118118
pure (hfs, hbio)
119119

120120
-- | Produce a simulated file system with injected errors and a logger for those

0 commit comments

Comments
 (0)