Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Golden test suite for snapshot codec #499

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have missed this suggestion: #499 (review)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, did we end up using these instances for the golden tests? It doesn't look like it -- if that's the case, let's remove these instances

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typical approach we use in this test suite is to include the tests at the top level, so in the Main module

]

{-------------------------------------------------------------------------------
Expand Down
255 changes: 255 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,255 @@
{-# 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Au as in the chemical symbol for gold? 😄


-- |
-- Compare the serialization of snapshot metadata with a known reference file.
Comment on lines +30 to +31
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the newline intentional? Does haddock render it correctly?

goldenFileTests :: TestTree
goldenFileTests = handleOutputFiles . testGroup "Golden File Comparisons" $
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
goldenFileTests = handleOutputFiles . testGroup "Golden File Comparisons" $
goldenFileTests = handleOutputFiles . testGroup "Test.Database.LSMTree.Internal.Snapshot.Codec.Golden" $

[ 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was not immediately clear to me what String is used for. Maybe add some documentation?


{----------------
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 "")
]
Comment on lines +152 to +156
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think I understand now that the ComponentAnnotation is used to name the specific enumerations, and this is reflected in the names of the golden files. Can you explain how you picked these labels? And the labels for the other enumerators below?


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 SnapLevel sub-components
----------------}

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 ])
]

{----------------
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)
]

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.
Loading