Skip to content

Commit a868ca2

Browse files
jorisdraldcouttsJoris Dral
committed
Run lockstep tests with simulated HasFS and HasBlockIO
With the nice addition that we now assert that there are no open file handles at the end of each iteration of the tests! We also tweak the state machine tests. We now follow a more normal convention, and allow running individual tests more easily in GHCi via `quickCheck $ theProperty`. Co-authored-by: Duncan Coutts <duncan@well-typed.com> Co-authored-by: Joris Dral <joris@welltyped.com>
1 parent 0098da3 commit a868ca2

File tree

1 file changed

+51
-43
lines changed

1 file changed

+51
-43
lines changed

test/Test/Database/LSMTree/Normal/StateMachine.hs

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ module Test.Database.LSMTree.Normal.StateMachine (
5050
, labelledExamples
5151
) where
5252

53+
import Control.Concurrent.Class.MonadSTM.Strict
54+
import Control.Exception (assert)
5355
import Control.Monad ((<=<))
5456
import Control.Monad.Class.MonadThrow (Handler (..), MonadCatch (..),
5557
MonadThrow (..))
@@ -81,7 +83,10 @@ import System.Directory (removeDirectoryRecursive)
8183
import System.FS.API (HasFS, MountPoint (..), mkFsPath)
8284
import System.FS.BlockIO.API (HasBlockIO, defaultIOCtxParams)
8385
import System.FS.BlockIO.IO (ioHasBlockIO)
86+
import System.FS.BlockIO.Sim (simHasBlockIO)
8487
import System.FS.IO (HandleIO, ioHasFS)
88+
import qualified System.FS.Sim.MockFS as MockFS
89+
import System.FS.Sim.MockFS (MockFS)
8590
import System.IO.Error
8691
import System.IO.Temp (createTempDirectory,
8792
getCanonicalTemporaryDirectory)
@@ -104,18 +109,22 @@ import Test.Util.TypeFamilyWrappers (WrapBlob (..), WrapBlobRef (..),
104109

105110
tests :: TestTree
106111
tests = testGroup "Normal.StateMachine" [
107-
propLockstepIO_ModelIOImpl
108-
, propLockstepIO_RealImpl_RealFS
109-
{- TODO: temporarily disabled until we start on I/O fault testing.
110-
, propLockstepIO_RealImpl_MockFS
111-
-}
112+
testProperty "prop_lockstepIO_ModelIOImpl"
113+
prop_lockstepIO_ModelIOImpl
114+
115+
, testProperty "prop_lockstepIO_RealImpl_RealFS"
116+
prop_lockstepIO_RealImpl_RealFS
117+
118+
, testProperty "prop_lockstepIO_RealImpl_MockFS"
119+
prop_lockstepIO_RealImpl_MockFS
112120
]
113121

114122
labelledExamples :: IO ()
115123
labelledExamples = QC.labelledExamples $ Lockstep.Run.tagActions (Proxy @(ModelState R.TableHandle))
116124

117-
propLockstepIO_ModelIOImpl :: TestTree
118-
propLockstepIO_ModelIOImpl = testProperty "propLockstepIO_ModelIOImpl" $
125+
prop_lockstepIO_ModelIOImpl :: Actions (Lockstep (ModelState M.TableHandle))
126+
-> QC.Property
127+
prop_lockstepIO_ModelIOImpl =
119128
runActionsBracket'
120129
(Proxy @(ModelState M.TableHandle))
121130
acquire
@@ -168,18 +177,19 @@ propLockstepIO_ModelIOImpl = testProperty "propLockstepIO_ModelIOImpl" $
168177
deriving via AllowThunk (M.Session IO)
169178
instance NoThunks (M.Session IO)
170179

171-
propLockstepIO_RealImpl_RealFS :: TestTree
172-
propLockstepIO_RealImpl_RealFS = testProperty "propLockstepIO_RealImpl_RealFS" $
180+
prop_lockstepIO_RealImpl_RealFS :: Actions (Lockstep (ModelState R.TableHandle))
181+
-> QC.Property
182+
prop_lockstepIO_RealImpl_RealFS =
173183
runActionsBracket'
174184
(Proxy @(ModelState R.TableHandle))
175185
acquire
176186
release
177-
(\r (_, session) -> runReaderT r (session, handler))
187+
(\r (_, session) -> runReaderT r (session, realHandler @IO))
178188
tagFinalState'
179189
where
180190
acquire :: IO (FilePath, WrapSession R.TableHandle IO)
181191
acquire = do
182-
(tmpDir, hasFS, hasBlockIO) <- createSystemTempDirectory "propLockstepIO_RealIO"
192+
(tmpDir, hasFS, hasBlockIO) <- createSystemTempDirectory "prop_lockstepIO_RealIO"
183193
session <- R.openSession nullTracer hasFS hasBlockIO (mkFsPath [])
184194
pure (tmpDir, WrapSession session)
185195

@@ -188,42 +198,40 @@ propLockstepIO_RealImpl_RealFS = testProperty "propLockstepIO_RealImpl_RealFS" $
188198
R.closeSession session
189199
removeDirectoryRecursive tmpDir
190200

191-
handler :: Handler IO (Maybe Model.Err)
192-
handler = Handler $ pure . handler'
193-
where
194-
handler' :: LSMTreeError -> Maybe Model.Err
195-
handler' ErrTableClosed = Just Model.ErrTableHandleClosed
196-
handler' ErrCursorClosed = Just Model.ErrCursorClosed
197-
handler' (ErrSnapshotNotExists _snap) = Just Model.ErrSnapshotDoesNotExist
198-
handler' (ErrSnapshotExists _snap) = Just Model.ErrSnapshotExists
199-
handler' (ErrSnapshotWrongType _snap) = Just Model.ErrSnapshotWrongType
200-
handler' (ErrBlobRefInvalid _) = Just Model.ErrBlobRefInvalidated
201-
handler' _ = Nothing
202-
203-
{- TODO: temporarily disabled until we start on I/O fault testing.
204-
propLockstepIO_RealImpl_MockFS :: TestTree
205-
propLockstepIO_RealImpl_MockFS = testProperty "propLockstepIO_RealImpl_MockFS" $
206-
QC.expectFailure $ -- TODO: remove once we have a real implementation
207-
Lockstep.Run.runActionsBracket
201+
prop_lockstepIO_RealImpl_MockFS :: Actions (Lockstep (ModelState R.TableHandle))
202+
-> QC.Property
203+
prop_lockstepIO_RealImpl_MockFS =
204+
runActionsBracket'
208205
(Proxy @(ModelState R.TableHandle))
209206
acquire
210207
release
211-
(\r session -> runReaderT r (session, handler))
208+
(\r (_, session) -> runReaderT r (session, realHandler @IO))
209+
tagFinalState'
212210
where
213-
acquire :: IO (WrapSession R.TableHandle IO)
211+
acquire :: IO (StrictTMVar IO MockFS, WrapSession R.TableHandle IO)
214212
acquire = do
215-
someHasFS <- SomeHasFS <$> simHasFS' MockFS.empty
216-
WrapSession <$> R.openSession someHasFS (mkFsPath [])
217-
218-
release :: WrapSession R.TableHandle IO -> IO ()
219-
release (WrapSession session) = R.closeSession session
220-
221-
handler :: Handler IO (Maybe Model.Err)
222-
handler = Handler $ pure . handler'
223-
where
224-
handler' :: IOError -> Maybe Model.Err
225-
handler' _err = Nothing
226-
-}
213+
fsVar <- newTMVarIO MockFS.empty
214+
(hfs, hbio) <- simHasBlockIO fsVar
215+
session <- R.openSession nullTracer hfs hbio (mkFsPath [])
216+
pure (fsVar, WrapSession session)
217+
218+
release :: (StrictTMVar IO MockFS, WrapSession R.TableHandle IO) -> IO ()
219+
release (fsVar, WrapSession session) = do
220+
R.closeSession session
221+
mockfs <- atomically $ readTMVar fsVar
222+
assert (MockFS.numOpenHandles mockfs == 0) $ pure ()
223+
224+
realHandler :: Monad m => Handler m (Maybe Model.Err)
225+
realHandler = Handler $ pure . handler'
226+
where
227+
handler' :: LSMTreeError -> Maybe Model.Err
228+
handler' ErrTableClosed = Just Model.ErrTableHandleClosed
229+
handler' ErrCursorClosed = Just Model.ErrCursorClosed
230+
handler' (ErrSnapshotNotExists _snap) = Just Model.ErrSnapshotDoesNotExist
231+
handler' (ErrSnapshotExists _snap) = Just Model.ErrSnapshotExists
232+
handler' (ErrSnapshotWrongType _snap) = Just Model.ErrSnapshotWrongType
233+
handler' (ErrBlobRefInvalid _) = Just Model.ErrBlobRefInvalidated
234+
handler' _ = Nothing
227235

228236
createSystemTempDirectory :: [Char] -> IO (FilePath, HasFS IO HandleIO, HasBlockIO IO HandleIO)
229237
createSystemTempDirectory prefix = do
@@ -385,7 +393,7 @@ instance ( Show (Class.TableConfig h)
385393
-- TODO: show instance does not show key-value-blob types. Example:
386394
--
387395
-- Normal.StateMachine
388-
-- propLockstepIO_ModelIOImpl: FAIL
396+
-- prop_lockstepIO_ModelIOImpl: FAIL
389397
-- *** Failed! Exception: 'open: inappropriate type (table type mismatch)' (after 25 tests and 2 shrinks):
390398
-- do action $ New TableConfig
391399
-- action $ Snapshot "snap" (GVar var1 (FromRight . id))

0 commit comments

Comments
 (0)