lsm-tree-1.1.0.0: test/Test/Database/LSMTree/Snapshots.hs
{-# LANGUAGE OverloadedStrings #-}
module Test.Database.LSMTree.Snapshots (tests) where
import Control.Tracer (nullTracer)
import Data.Monoid (Sum (Sum))
import qualified Data.Vector as V
import Data.Void (Void)
import Data.Word (Word64)
import Database.LSMTree (ResolveValue, Salt, SerialiseKey,
SerialiseValue, Table, TableConfig (confWriteBufferAlloc),
WriteBufferAlloc (AllocNumEntries), defaultTableConfig,
exportSnapshot, getValue, importSnapshot, inserts, lookups,
saveSnapshot, withOpenSession, withTableFromSnapshot,
withTableWith)
import Database.LSMTree.Extras (showRangesOf)
import qualified System.FS.API as FS
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (Arbitrary, Property, Small (Small),
checkCoverage, ioProperty, tabulate, testProperty, (===))
import Test.Util.FS (withTempIOHasBlockIO)
tests :: TestTree
tests = testGroup "Test.Database.LSMTree.Snapshots" [
testProperty "prop_exportImportSnapshot" prop_exportImportSnapshot
]
{-------------------------------------------------------------------------------
Snapshot import and export
-------------------------------------------------------------------------------}
newtype Key = Key Word64
deriving stock (Show, Eq, Ord)
deriving newtype SerialiseKey
deriving Arbitrary via Small Word64
newtype Value = Value Word64
deriving stock (Show, Eq)
deriving newtype (SerialiseValue)
deriving ResolveValue via Sum Word64
deriving Arbitrary via Small Word64
-- | Tables opened from imported snapshots behave the same as the original table
-- that the snapshot was created for.
--
-- We test this by looking at the observable behaviour of the two tables. If
-- lookups on both tables produce the same result, then they are logically
-- equivalent.
--
-- Moreover, if importing or exporting were to corrupt the snapshots, then
-- opening the snapshot would throw an error, which this test would catch.
prop_exportImportSnapshot ::
V.Vector (Key, Value)
-> V.Vector Key
-> Property
prop_exportImportSnapshot ins los =
checkCoverage $
ioProperty $
withTempIOHasBlockIO "prop_exportImportSnapshot" $ \hfs hbio -> do
FS.createDirectoryIfMissing hfs True sessionDir
-- Open a session and create a snapshot for some arbitrary table contents
withOpenSession nullTracer hfs hbio salt sessionDir $ \session ->
withTableWith conf session $ \(table1 :: Table IO Key Value Void) -> do
inserts table1 $ V.map (\(k, v) -> (k, v, Nothing)) ins
saveSnapshot "snap1" "KeyValueBlob" table1
-- Export then re-import the snapshot
exportSnapshot session "snap1" exportDir
importSnapshot session "snap2" exportDir
-- Open a table from the re-imported snapshot. Any corruption of the
-- snapshot would be identified here.
withTableFromSnapshot session "snap2" "KeyValueBlob" $ \(table2 :: Table IO Key Value Void) -> do
-- Check that lookups on both the original and re-imported table
-- match
lrs1 <- V.map getValue <$> lookups table1 los
lrs2 <- V.map getValue <$> lookups table2 los
pure $
tabulate "# of physical table entries" [ showRangesOf 5 $ V.length ins ] $
tabulate "# of lookups" [ showRangesOf 5 $ V.length los ] $
tabulate "# of successful lookups" [ showRangesOf 5 $ V.length $ V.catMaybes lrs1 ] $
tabulate "# of unsuccessful lookups" [ showRangesOf 5 $ V.length $ V.filter (==Nothing) lrs1 ] $
lrs1 === lrs2
where
sessionDir = FS.mkFsPath ["session"]
-- | Directory for storing exported snapshots
exportDir = FS.mkFsPath ["export"]
salt :: Salt
salt = 17
conf :: TableConfig
conf = defaultTableConfig {
confWriteBufferAlloc = AllocNumEntries 3
}