Skip to content

Commit

Permalink
add snapshot corruption to state machine tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wenkokke committed Feb 13, 2025
1 parent 6475391 commit 0b53a70
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 17 deletions.
22 changes: 21 additions & 1 deletion test/Database/LSMTree/Class.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Database.LSMTree.Class (
, module Types
) where

import Control.Monad (void)
import Control.Monad.Class.MonadThrow (MonadThrow (..))
import Data.Kind (Constraint, Type)
import Data.List.NonEmpty (NonEmpty)
Expand All @@ -24,6 +25,10 @@ import Database.LSMTree as Types (LookupResult (..), QueryResult (..),
resolveDeserialised)
import qualified Database.LSMTree as R
import Database.LSMTree.Class.Common as Common
import qualified Database.LSMTree.Internal as RI (SessionEnv (..), Table (..),
Table' (..), withOpenSession)
import qualified Database.LSMTree.Internal.Paths as RIP
import Test.Util.FS (flipRandomBitInRandomFile)
import Test.Util.QC (Choice)

-- | Class abstracting over table operations.
Expand Down Expand Up @@ -231,6 +236,21 @@ withCursor offset tbl = bracket (newCursor offset tbl) (closeCursor (Proxy @h))
Real instance
-------------------------------------------------------------------------------}

-- | Snapshot corruption for the real instance.
-- Implemented here, instead of as part of the public API.
rCorruptSnapshot ::
IOLike m
=> Choice
-> SnapshotName
-> R.Table m k v b
-> m ()
rCorruptSnapshot choice name (RI.Table' t) =
RI.withOpenSession (RI.tableSession t) $ \seshEnv ->
let hfs = RI.sessionHasFS seshEnv
root = RI.sessionRoot seshEnv
namedSnapDir = RIP.getNamedSnapshotDir (RIP.namedSnapshotDir root name)
in void $ flipRandomBitInRandomFile hfs choice namedSnapDir

instance IsTable R.Table where
type Session R.Table = R.Session
type TableConfig R.Table = R.TableConfig
Expand All @@ -253,7 +273,7 @@ instance IsTable R.Table where
readCursor _ = R.readCursor

createSnapshot = R.createSnapshot
corruptSnapshot = error "TODO: not yet implemented"
corruptSnapshot = rCorruptSnapshot
openSnapshot sesh snap = R.openSnapshot sesh R.configNoOverride snap

duplicate = R.duplicate
Expand Down
3 changes: 2 additions & 1 deletion test/Database/LSMTree/Model/Session.hs
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,10 @@ runModelMWithInjectedErrors ::
-> Model -> (Either Err a, Model)
runModelMWithInjectedErrors Nothing onNoErrors _ st =
runModelM onNoErrors st
runModelMWithInjectedErrors (Just _) _ onErrors st =
runModelMWithInjectedErrors (Just _errors) _ onErrors st =
runModelM (onErrors >> throwError (ErrFsError "modelled FsError")) st


--
-- Errors
--
Expand Down
49 changes: 34 additions & 15 deletions test/Test/Database/LSMTree/StateMachine.hs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import Control.Tracer (Tracer, nullTracer)
import Data.Bifunctor (Bifunctor (..))
import Data.Constraint (Dict (..))
import Data.Either (partitionEithers)
import Data.Foldable (for_)
import Data.Kind (Type)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NE
Expand Down Expand Up @@ -135,7 +136,8 @@ import qualified Test.QuickCheck.StateModel.Lockstep.Defaults as Lockstep.Defaul
import qualified Test.QuickCheck.StateModel.Lockstep.Run as Lockstep.Run
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (testProperty)
import Test.Util.FS (approximateEqStream, noRemoveDirectoryRecursiveE,
import Test.Util.FS (SilentCorruption (..), SilentCorruptions (..),
approximateEqStream, noRemoveDirectoryRecursiveE,
propNoOpenHandles, propNumOpenHandles)
import Test.Util.PrettyProxy
import Test.Util.QLS
Expand Down Expand Up @@ -604,7 +606,7 @@ instance ( Show (Class.TableConfig h)
-- Snapshots
CreateSnapshot ::
C k v b
=> Maybe Errors
=> Maybe (Either SilentCorruptions Errors)
-> R.SnapshotLabel -> R.SnapshotName -> Var h (WrapTable h IO k v b)
-> Act h ()
OpenSnapshot ::
Expand Down Expand Up @@ -1125,11 +1127,12 @@ runModel lookUp = \case
RetrieveBlobs blobsVar ->
wrap (MVector . fmap (MBlob . WrapBlob))
. Model.runModelM (Model.retrieveBlobs (getBlobRefs . lookUp $ blobsVar))
CreateSnapshot merrs label name tableVar ->
wrap MUnit
. Model.runModelMWithInjectedErrors merrs
(Model.createSnapshot label name (getTable $ lookUp tableVar))
(pure ())
CreateSnapshot mcorrsOrErrs label name tableVar ->
wrap MUnit .
let mCreateSnapshot = Model.createSnapshot label name (getTable $ lookUp tableVar)
in case sequence mcorrsOrErrs of
Left _corrs -> Model.runModelM (mCreateSnapshot >> Model.corruptSnapshot name)
Right merrs -> Model.runModelMWithInjectedErrors merrs mCreateSnapshot (pure ())
OpenSnapshot _ merrs label name ->
wrap MTable
. Model.runModelMWithInjectedErrors merrs
Expand Down Expand Up @@ -1211,10 +1214,18 @@ runIO action lookUp = ReaderT $ \ !env -> do
Class.mupserts (unwrapTable $ lookUp' tableVar) kmups
RetrieveBlobs blobRefsVar -> catchErr handlers $
fmap WrapBlob <$> Class.retrieveBlobs (Proxy @h) session (unwrapBlobRef <$> lookUp' blobRefsVar)
CreateSnapshot merrs label name tableVar ->
runRealWithInjectedErrors "CreateSnapshot" env merrs
(Class.createSnapshot label name (unwrapTable $ lookUp' tableVar))
(\() -> Class.deleteSnapshot session name)
CreateSnapshot mcorrsOrErrs label name tableVar ->
let rCreateSnapshot = Class.createSnapshot label name (unwrapTable $ lookUp' tableVar) in
case sequence mcorrsOrErrs of
Left (SilentCorruptions corrs) -> do
rCreateSnapshot
for_ corrs $ \corr ->
Class.corruptSnapshot (bitChoice corr) name (unwrapTable $ lookUp' tableVar)
pure (Right ())
Right merrs ->
runRealWithInjectedErrors "CreateSnapshot" env merrs
rCreateSnapshot
(\() -> Class.deleteSnapshot session name)
OpenSnapshot _ merrs label name ->
runRealWithInjectedErrors "OpenSnapshot" env merrs
(WrapTable <$> Class.openSnapshot session label name)
Expand Down Expand Up @@ -1273,10 +1284,18 @@ runIOSim action lookUp = ReaderT $ \ !env -> do
Class.mupserts (unwrapTable $ lookUp' tableVar) kmups
RetrieveBlobs blobRefsVar -> catchErr handlers $
fmap WrapBlob <$> Class.retrieveBlobs (Proxy @h) session (unwrapBlobRef <$> lookUp' blobRefsVar)
CreateSnapshot merrs label name tableVar ->
runRealWithInjectedErrors "CreateSnapshot" env merrs
(Class.createSnapshot label name (unwrapTable $ lookUp' tableVar))
(\() -> Class.deleteSnapshot session name)
CreateSnapshot mcorrsOrErrs label name tableVar ->
let rCreateSnapshot = Class.createSnapshot label name (unwrapTable $ lookUp' tableVar) in
case sequence mcorrsOrErrs of
Left (SilentCorruptions corrs) -> do
rCreateSnapshot
for_ corrs $ \corr ->
Class.corruptSnapshot (bitChoice corr) name (unwrapTable $ lookUp' tableVar)
pure (Right ())
Right merrs ->
runRealWithInjectedErrors "CreateSnapshot" env merrs
rCreateSnapshot
(\() -> Class.deleteSnapshot session name)
OpenSnapshot _ merrs label name ->
runRealWithInjectedErrors "OpenSnapshot" env merrs
(WrapTable <$> Class.openSnapshot session label name)
Expand Down
10 changes: 10 additions & 0 deletions test/Test/Util/FS.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ module Test.Util.FS (
, listDirectoryRecursive
, listDirectoryRecursiveFiles
-- * Corruption
, SilentCorruptions (..)
, SilentCorruption (..)
, flipRandomBitInRandomFile
, flipFileBit
, hFlipBit
Expand Down Expand Up @@ -351,6 +353,14 @@ listDirectoryFiles hfs = go Set.empty
Corruption
-------------------------------------------------------------------------------}

newtype SilentCorruptions = SilentCorruptions {unSilentCorruptions :: NonEmpty SilentCorruption}
deriving stock (Eq, Show)
deriving newtype (Arbitrary)

newtype SilentCorruption = SilentCorruption {bitChoice :: Choice}
deriving stock (Eq, Show)
deriving newtype (Arbitrary)

-- | Flip a random bit in a random file in a given directory.
flipRandomBitInRandomFile ::
(PrimMonad m, MonadThrow m)
Expand Down

0 comments on commit 0b53a70

Please sign in to comment.