Skip to content

Commit

Permalink
Grafting golden tests into the 'Snapshot/Codec' branch of the TestTre…
Browse files Browse the repository at this point in the history
…e for serialization backwards compatibility testing.
  • Loading branch information
recursion-ninja committed Dec 22, 2024
1 parent 4ce3c9d commit 8b0d38e
Show file tree
Hide file tree
Showing 92 changed files with 270 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.golden -text
2 changes: 2 additions & 0 deletions lsm-tree.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ test-suite lsm-tree-test
Test.Database.LSMTree.Internal.Serialise
Test.Database.LSMTree.Internal.Serialise.Class
Test.Database.LSMTree.Internal.Snapshot.Codec
Test.Database.LSMTree.Internal.Snapshot.Codec.Golden
Test.Database.LSMTree.Internal.Snapshot.FS
Test.Database.LSMTree.Internal.Vector
Test.Database.LSMTree.Internal.Vector.Growing
Expand Down Expand Up @@ -434,6 +435,7 @@ test-suite lsm-tree-test
, split
, stm
, tasty
, tasty-golden
, tasty-hunit
, tasty-quickcheck
, temporary
Expand Down
14 changes: 9 additions & 5 deletions src/Database/LSMTree/Internal/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ data MergePolicy =
{- TODO: disabled for now
| MergePolicyLevelling
-}
deriving stock (Show, Eq)
-- @Bounded@ and @Enum@ instances are required for test-case enumeration.
deriving stock (Bounded, Enum, Eq, Show)

instance NFData MergePolicy where
rnf MergePolicyLazyLevelling = ()
Expand All @@ -166,7 +167,8 @@ instance NFData MergePolicy where
-------------------------------------------------------------------------------}

data SizeRatio = Four
deriving stock (Show, Eq)
-- @Bounded@ and @Enum@ instances are required for test-case enumeration.
deriving stock (Bounded, Enum, Eq, Show)

instance NFData SizeRatio where
rnf Four = ()
Expand Down Expand Up @@ -303,7 +305,8 @@ data FencePointerIndex =
-- | Use an ordinary fence pointer index, without any constraints on
-- serialised keys.
| OrdinaryIndex
deriving stock (Show, Eq)
-- @Bounded@ and @Enum@ instances are required for test-case enumeration.
deriving stock (Bounded, Enum, Eq, Show)

instance NFData FencePointerIndex where
rnf CompactIndex = ()
Expand Down Expand Up @@ -355,7 +358,7 @@ data DiskCachePolicy =
-- Use this policy if expected access pattern for the table has poor
-- spatial or temporal locality, such as uniform random access.
| DiskCacheNone
deriving stock (Eq, Show)
deriving stock (Show, Eq)

instance NFData DiskCachePolicy where
rnf DiskCacheAll = ()
Expand Down Expand Up @@ -399,7 +402,8 @@ data MergeSchedule =
-- merges are fully completed in time for when new merges are started on the
-- same level.
| Incremental
deriving stock (Eq, Show)
-- @Bounded@ and @Enum@ instances are required for test-case enumeration.
deriving stock (Bounded, Enum, Eq, Show)

instance NFData MergeSchedule where
rnf OneShot = ()
Expand Down
3 changes: 2 additions & 1 deletion src/Database/LSMTree/Internal/Merge.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ data MergeState =
| Closed

data Level = MidLevel | LastLevel
deriving stock (Eq, Show)
-- @Bounded@ and @Enum@ instances are required for test-case enumeration.
deriving stock (Bounded, Enum, Eq, Show)

instance NFData Level where
rnf MidLevel = ()
Expand Down
2 changes: 1 addition & 1 deletion src/Database/LSMTree/Internal/MergingRun.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ instance RefCounted m (MergingRun m h) where
getRefCounter = mergeRefCounter

data MergePolicyForLevel = LevelTiering | LevelLevelling
deriving stock (Show, Eq)
deriving stock (Bounded, Enum, Eq, Show)

instance NFData MergePolicyForLevel where
rnf LevelTiering = ()
Expand Down
2 changes: 1 addition & 1 deletion src/Database/LSMTree/Internal/Snapshot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ newtype SnapshotLabel = SnapshotLabel Text

-- TODO: revisit if we need three table types.
data SnapshotTableType = SnapNormalTable | SnapMonoidalTable | SnapFullTable
deriving stock (Show, Eq)
deriving stock (Bounded, Enum, Eq, Show)

instance NFData SnapshotTableType where
rnf SnapNormalTable = ()
Expand Down
3 changes: 3 additions & 0 deletions test/Test/Database/LSMTree/Internal/Snapshot/Codec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import Database.LSMTree.Internal.MergingRun
import Database.LSMTree.Internal.RunNumber
import Database.LSMTree.Internal.Snapshot
import Database.LSMTree.Internal.Snapshot.Codec
import Test.Database.LSMTree.Internal.Snapshot.Codec.Golden
(goldenFileTests)
import Test.Tasty
import Test.Tasty.QuickCheck
import Test.Util.Arbitrary
Expand All @@ -47,6 +49,7 @@ tests = testGroup "Test.Database.LSMTree.Internal.Snapshot.Codec" [
testAll $ \(p :: Proxy a) ->
testGroup (show $ typeRep p) $
prop_arbitraryAndShrinkPreserveInvariant @a deepseqInvariant
, goldenFileTests
]

{-------------------------------------------------------------------------------
Expand Down
251 changes: 251 additions & 0 deletions test/Test/Database/LSMTree/Internal/Snapshot/Codec/Golden.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
{-# LANGUAGE OverloadedStrings #-}
module Test.Database.LSMTree.Internal.Snapshot.Codec.Golden
(goldenFileTests) where

import Codec.CBOR.Write (toLazyByteString)
import Control.Monad (when)
import qualified Data.ByteString.Lazy as BSL (writeFile)
import Data.Foldable (fold)
import Data.Vector (Vector)
import qualified Data.Vector as V
import Database.LSMTree.Common (BloomFilterAlloc (..),
DiskCachePolicy (..), NumEntries (..), TableConfig (..),
WriteBufferAlloc (..), defaultTableConfig)
import Database.LSMTree.Internal.Config (FencePointerIndex (..),
MergePolicy (..), MergeSchedule (..), SizeRatio (..))
import Database.LSMTree.Internal.Merge (Level (..))
import Database.LSMTree.Internal.MergingRun (MergePolicyForLevel (..),
NumRuns (..))
import Database.LSMTree.Internal.RunNumber (RunNumber (..))
import Database.LSMTree.Internal.Snapshot
import Database.LSMTree.Internal.Snapshot.Codec
import qualified System.FS.API as FS
import System.FS.API.Types (FsPath, MountPoint (..), fsToFilePath,
mkFsPath, (<.>))
import System.FS.IO (HandleIO, ioHasFS)
import qualified Test.Tasty as Tasty
import Test.Tasty (TestName, TestTree, testGroup)
import qualified Test.Tasty.Golden as Au

-- |
-- Compare the serialization of snapshot metadata with a known reference file.
goldenFileTests :: TestTree
goldenFileTests = handleOutputFiles . testGroup "Golden File Comparisons" $
[ testCodecSnapshotLabel
, testCodecSnapshotTableType
, testCodecTableConfig
, testCodecSnapLevels
]

-- |
-- The mount point is defined as the location of the golden file data directory
-- relative to the project root.
goldenDataMountPoint :: MountPoint
goldenDataMountPoint = MountPoint "test/golden-file-data/snapshot-codec"

-- |
-- Delete output files on test-case success.
-- Change the option here if this is undesireable.
handleOutputFiles :: TestTree -> TestTree
handleOutputFiles = Tasty.localOption Au.OnPass

-- |
-- Internally, the function will infer the correct filepath names.
snapshotCodecTest
:: String -- ^ Name of the test
-> SnapshotMetaData -- ^ Data to be serialized
-> TestTree
snapshotCodecTest name datum =
let -- Various paths
--
-- There are three paths for both the checksum and the snapshot files:
-- 1. The filepath of type @FsPath@ to which data is written.
-- 2. The filepath of type @FilePath@ from which data is read.
-- 3. The filepath of type @FilePath@ against which the data is compared.
--
-- These file types' bindings have the following infix annotations, respectively:
-- 1. (Fs) for FsPath
-- 2. (Hs) for "Haskell" path
-- 3. (Au) for "Golden file" path
snapshotFsPath = mkFsPath [name] <.> "snapshot"
snapshotHsPath = fsToFilePath goldenDataMountPoint snapshotFsPath
snapshotAuPath = snapshotHsPath <> ".golden"

-- IO actions
runnerIO :: FS.HasFS IO HandleIO
runnerIO = ioHasFS goldenDataMountPoint
removeIfExists :: FsPath -> IO ()
removeIfExists fp =
FS.doesFileExist runnerIO fp >>= (`when` (FS.removeFile runnerIO fp))
outputAction :: IO ()
outputAction = do
-- Ensure that if the output file already exists, we remove it and
-- re-write out the serialized data. This ensures that there are no
-- false-positives, false-negatives, or irrelavent I/O exceptions.
removeIfExists snapshotFsPath
BSL.writeFile snapshotHsPath . toLazyByteString $ encode datum

in Au.goldenVsFile name snapshotAuPath snapshotHsPath outputAction

testCodecSnapshotLabel :: TestTree
testCodecSnapshotLabel =
let assembler (tagA, valA) =
let (tagB, valB) = basicSnapshotTableType
(tagC, valC) = basicTableConfig
(tagD, valD) = basicSnapLevels
in (fold [tagA, tagB, tagC, tagD ], SnapshotMetaData valA valB valC valD)
in testCodecBuilder "SnapshotLabels" $ assembler <$> enumerateSnapshotLabel

testCodecSnapshotTableType :: TestTree
testCodecSnapshotTableType =
let assembler (tagB, valB) =
let (tagA, valA) = basicSnapshotLabel
(tagC, valC) = basicTableConfig
(tagD, valD) = basicSnapLevels
in (fold [tagA, tagB, tagC, tagD ], SnapshotMetaData valA valB valC valD)
in testCodecBuilder "SnapshotTables" $ assembler <$> enumerateSnapshotTableType

testCodecTableConfig :: TestTree
testCodecTableConfig =
let assembler (tagC, valC) =
let (tagA, valA) = basicSnapshotLabel
(tagB, valB) = basicSnapshotTableType
(tagD, valD) = basicSnapLevels
in (fold [tagA, tagB, tagC, tagD ], SnapshotMetaData valA valB valC valD)
in testCodecBuilder "SnapshotConfig" $ assembler <$> enumerateTableConfig

testCodecSnapLevels :: TestTree
testCodecSnapLevels =
let assembler (tagD, valD) =
let (tagA, valA) = basicSnapshotLabel
(tagB, valB) = basicSnapshotTableType
(tagC, valC) = basicTableConfig
in (fold [tagA, tagB, tagC, tagD ], SnapshotMetaData valA valB valC valD)
in testCodecBuilder "SnapshotLevels" $ assembler <$> enumerateSnapLevels

testCodecBuilder :: TestName -> [(ComponentAnnotation, SnapshotMetaData)] -> TestTree
testCodecBuilder tName metadata =
testGroup tName $ uncurry snapshotCodecTest <$> metadata

type ComponentAnnotation = String

{----------------
Defaults used when the SnapshotMetaData sub-component is not under test
----------------}

basicSnapshotLabel :: (ComponentAnnotation, SnapshotLabel)
basicSnapshotLabel = head enumerateSnapshotLabel

basicSnapshotTableType :: (ComponentAnnotation, SnapshotTableType)
basicSnapshotTableType = head enumerateSnapshotTableType

basicTableConfig :: (ComponentAnnotation, TableConfig)
basicTableConfig = ("T", defaultTableConfig)

basicSnapLevels :: (ComponentAnnotation, SnapLevels RunNumber)
basicSnapLevels = head enumerateSnapLevels

{----------------
Enumeration of SnapshotMetaData sub-components
----------------}

enumerateSnapshotLabel :: [(ComponentAnnotation, SnapshotLabel)]
enumerateSnapshotLabel =
[ ("Bs", SnapshotLabel "UserProvidedLabel")
, ("Bn", SnapshotLabel "")
]

enumerateSnapshotTableType :: [(ComponentAnnotation, SnapshotTableType)]
enumerateSnapshotTableType =
[ ("Nn", SnapNormalTable)
, ("Nm", SnapMonoidalTable)
, ("Nf", SnapFullTable)
]

enumerateTableConfig :: [(ComponentAnnotation, TableConfig)]
enumerateTableConfig =
[ ( fold [ "T", a, b, c, d, e, f, g ]
, TableConfig
policy
ratio
allocs
bloom
fence
cache
merge
)
| (a, policy) <- [("", MergePolicyLazyLevelling)]
, (b, ratio ) <- [("", Four)]
, (c, allocs) <- fmap (AllocNumEntries . NumEntries) <$> [("", magicNumber1)]
, (d, bloom ) <- enumerateBloomFilterAlloc
, (e, fence ) <- [("Ic", CompactIndex), ("Io", OrdinaryIndex)]
, (f, cache ) <- enumerateDiskCachePolicy
, (g, merge ) <- [("Go", OneShot), ("Gi", Incremental)]
]

enumerateSnapLevels :: [(ComponentAnnotation, SnapLevels RunNumber)]
enumerateSnapLevels = fmap (SnapLevels . V.singleton) <$> enumerateSnapLevel

{----------------
Enumeration of SnapshotMetaData sub-sub-components and so on...
----------------}

enumerateBloomFilterAlloc :: [(ComponentAnnotation, BloomFilterAlloc)]
enumerateBloomFilterAlloc =
[ ("Af",AllocFixed magicNumber3)
, ("Ar",AllocRequestFPR pi)
, ("Am",AllocMonkey magicNumber3 . NumEntries $ magicNumber3 `div` 4)
]

enumerateDiskCachePolicy :: [(ComponentAnnotation, DiskCachePolicy)]
enumerateDiskCachePolicy =
[ ("Da", DiskCacheAll)
, ("Dn", DiskCacheNone)
, ("Db", DiskCacheLevelsAtOrBelow 1)
]

enumerateSnapLevel :: [(ComponentAnnotation, SnapLevel RunNumber)]
enumerateSnapLevel = do
(a, run) <- enumerateSnapIncomingRun
(b, vec) <- enumerateVectorRunNumber
[(a <> b, SnapLevel run vec)]

enumerateSnapIncomingRun :: [(ComponentAnnotation, SnapIncomingRun RunNumber)]
enumerateSnapIncomingRun =
let
inSnaps =
[ (fold ["Rm", "P", a, b], SnapMergingRun policy numRuns entries credits sState)
| (a, policy ) <- [("t", LevelTiering), ("g", LevelLevelling)]
, numRuns <- NumRuns <$> [ magicNumber1 ]
, entries <- NumEntries <$> [ magicNumber2 ]
, credits <- UnspentCredits <$> [ magicNumber1 ]
, (b, sState ) <- enumerateSnapMergingRunState
]
in fold
[ [("Rs", SnapSingleRun enumerateRunNumbers)]
, inSnaps
]

enumerateSnapMergingRunState :: [(ComponentAnnotation, SnapMergingRunState RunNumber)]
enumerateSnapMergingRunState = ("Mc", SnapCompletedMerge enumerateRunNumbers) :
[ (fold ["Mo",a,b,c], SnapOngoingMerge runVec credits level)
| (a, runVec ) <- enumerateVectorRunNumber
, (b, credits) <- [("" , SpentCredits magicNumber1 )]
, (c, level ) <- [("Lm", MidLevel), ("L0", LastLevel)]
]

enumerateVectorRunNumber :: [(ComponentAnnotation, Vector RunNumber)]
enumerateVectorRunNumber =
[ ("V0", mempty)
, ("V1", V.fromList [RunNumber magicNumber1])
, ("V2", V.fromList [RunNumber magicNumber1, RunNumber magicNumber2 ])
]

enumerateRunNumbers :: RunNumber
enumerateRunNumbers = RunNumber magicNumber2

-- Randomly chosen numbers
magicNumber1, magicNumber2, magicNumber3 :: Enum e => e
magicNumber1 = toEnum 42
magicNumber2 = toEnum 88
magicNumber3 = toEnum 1024
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 8b0d38e

Please sign in to comment.