Skip to content

Commit 02e3bf6

Browse files
authored
Merge pull request #499 from IntersectMBO/recursion-ninja/golden-test-suite
Golden test suite for snapshot codec
2 parents 4377165 + 8b91a09 commit 02e3bf6

File tree

108 files changed

+276
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+276
-5
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.golden -text

lsm-tree.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ test-suite lsm-tree-test
381381
Test.Database.LSMTree.Internal.Serialise
382382
Test.Database.LSMTree.Internal.Serialise.Class
383383
Test.Database.LSMTree.Internal.Snapshot.Codec
384+
Test.Database.LSMTree.Internal.Snapshot.Codec.Golden
384385
Test.Database.LSMTree.Internal.Snapshot.FS
385386
Test.Database.LSMTree.Internal.Vector
386387
Test.Database.LSMTree.Internal.Vector.Growing
@@ -444,6 +445,7 @@ test-suite lsm-tree-test
444445
, split
445446
, stm
446447
, tasty
448+
, tasty-golden
447449
, tasty-hunit
448450
, tasty-quickcheck
449451
, temporary

src/Database/LSMTree/Internal/Config.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ data MergePolicy =
160160
{- TODO: disabled for now
161161
| MergePolicyLevelling
162162
-}
163-
deriving stock (Show, Eq)
163+
deriving stock (Eq, Show)
164164

165165
instance NFData MergePolicy where
166166
rnf MergePolicyLazyLevelling = ()
@@ -170,7 +170,7 @@ instance NFData MergePolicy where
170170
-------------------------------------------------------------------------------}
171171

172172
data SizeRatio = Four
173-
deriving stock (Show, Eq)
173+
deriving stock (Eq, Show)
174174

175175
instance NFData SizeRatio where
176176
rnf Four = ()
@@ -306,7 +306,7 @@ data FencePointerIndex =
306306
-- Ordinary indexes do not have any constraints on keys other than that
307307
-- their serialised forms may not be 64 KiB or more in size.
308308
| OrdinaryIndex
309-
deriving stock (Show, Eq)
309+
deriving stock (Eq, Show)
310310

311311
instance NFData FencePointerIndex where
312312
rnf CompactIndex = ()
@@ -362,7 +362,7 @@ data DiskCachePolicy =
362362
-- Use this policy if expected access pattern for the table has poor
363363
-- spatial or temporal locality, such as uniform random access.
364364
| DiskCacheNone
365-
deriving stock (Eq, Show)
365+
deriving stock (Show, Eq)
366366

367367
instance NFData DiskCachePolicy where
368368
rnf DiskCacheAll = ()

src/Database/LSMTree/Internal/Snapshot.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ newtype SnapshotLabel = SnapshotLabel Text
8686

8787
-- TODO: revisit if we need three table types.
8888
data SnapshotTableType = SnapNormalTable | SnapMonoidalTable | SnapFullTable
89-
deriving stock (Show, Eq)
89+
deriving stock (Eq, Show)
9090

9191
instance NFData SnapshotTableType where
9292
rnf SnapNormalTable = ()

test/Main.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import qualified Test.Database.LSMTree.Internal.RunReaders
3232
import qualified Test.Database.LSMTree.Internal.Serialise
3333
import qualified Test.Database.LSMTree.Internal.Serialise.Class
3434
import qualified Test.Database.LSMTree.Internal.Snapshot.Codec
35+
import qualified Test.Database.LSMTree.Internal.Snapshot.Codec.Golden
3536
import qualified Test.Database.LSMTree.Internal.Snapshot.FS
3637
import qualified Test.Database.LSMTree.Internal.Vector
3738
import qualified Test.Database.LSMTree.Internal.Vector.Growing
@@ -77,6 +78,7 @@ main = do
7778
, Test.Database.LSMTree.Internal.Serialise.tests
7879
, Test.Database.LSMTree.Internal.Serialise.Class.tests
7980
, Test.Database.LSMTree.Internal.Snapshot.Codec.tests
81+
, Test.Database.LSMTree.Internal.Snapshot.Codec.Golden.tests
8082
, Test.Database.LSMTree.Internal.Snapshot.FS.tests
8183
, Test.Database.LSMTree.Internal.Vector.tests
8284
, Test.Database.LSMTree.Internal.Vector.Growing.tests
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
module Test.Database.LSMTree.Internal.Snapshot.Codec.Golden
3+
(tests) where
4+
5+
import Codec.CBOR.Write (toLazyByteString)
6+
import Control.Monad (when)
7+
import qualified Data.ByteString.Lazy as BSL (writeFile)
8+
import Data.Foldable (fold)
9+
import qualified Data.List as List
10+
import Data.Vector (Vector)
11+
import qualified Data.Vector as V
12+
import Database.LSMTree.Common (BloomFilterAlloc (..),
13+
DiskCachePolicy (..), NumEntries (..), TableConfig (..),
14+
WriteBufferAlloc (..), defaultTableConfig)
15+
import Database.LSMTree.Internal.Config (FencePointerIndex (..),
16+
MergePolicy (..), MergeSchedule (..), SizeRatio (..))
17+
import Database.LSMTree.Internal.Merge (MergeType (..))
18+
import Database.LSMTree.Internal.MergeSchedule
19+
(MergePolicyForLevel (..))
20+
import Database.LSMTree.Internal.MergingRun (NumRuns (..))
21+
import Database.LSMTree.Internal.RunNumber (RunNumber (..))
22+
import Database.LSMTree.Internal.Snapshot
23+
import Database.LSMTree.Internal.Snapshot.Codec
24+
import qualified System.FS.API as FS
25+
import System.FS.API.Types (FsPath, MountPoint (..), fsToFilePath,
26+
mkFsPath, (<.>))
27+
import System.FS.IO (HandleIO, ioHasFS)
28+
import qualified Test.Tasty as Tasty
29+
import Test.Tasty (TestName, TestTree, testGroup)
30+
import qualified Test.Tasty.Golden as Au
31+
32+
-- | Compare the serialization of snapshot metadata with a known reference file.
33+
tests :: TestTree
34+
tests = handleOutputFiles . testGroup
35+
"Test.Database.LSMTree.Internal.Snapshot.Codec.Golden" $
36+
[ testCodecSnapshotLabel
37+
, testCodecSnapshotTableType
38+
, testCodecTableConfig
39+
, testCodecSnapLevels
40+
]
41+
42+
-- | The mount point is defined as the location of the golden file data directory
43+
-- relative to the project root.
44+
goldenDataMountPoint :: MountPoint
45+
goldenDataMountPoint = MountPoint "test/golden-file-data/snapshot-codec"
46+
47+
-- | Delete output files on test-case success.
48+
-- Change the option here if this is undesireable.
49+
handleOutputFiles :: TestTree -> TestTree
50+
handleOutputFiles = Tasty.localOption Au.OnPass
51+
52+
-- | Internally, the function will infer the correct filepath names.
53+
snapshotCodecTest
54+
:: String -- ^ Name of the test
55+
-> SnapshotMetaData -- ^ Data to be serialized
56+
-> TestTree
57+
snapshotCodecTest name datum =
58+
let -- Various paths
59+
--
60+
-- There are three paths for both the checksum and the snapshot files:
61+
-- 1. The filepath of type @FsPath@ to which data is written.
62+
-- 2. The filepath of type @FilePath@ from which data is read.
63+
-- 3. The filepath of type @FilePath@ against which the data is compared.
64+
--
65+
-- These file types' bindings have the following infix annotations, respectively:
66+
-- 1. (Fs) for FsPath
67+
-- 2. (Hs) for "Haskell" path
68+
-- 3. (Au) for "Golden file" path
69+
snapshotFsPath = mkFsPath [name] <.> "snapshot"
70+
snapshotHsPath = fsToFilePath goldenDataMountPoint snapshotFsPath
71+
snapshotAuPath = snapshotHsPath <> ".golden"
72+
73+
-- IO actions
74+
runnerIO :: FS.HasFS IO HandleIO
75+
runnerIO = ioHasFS goldenDataMountPoint
76+
removeIfExists :: FsPath -> IO ()
77+
removeIfExists fp =
78+
FS.doesFileExist runnerIO fp >>= (`when` (FS.removeFile runnerIO fp))
79+
outputAction :: IO ()
80+
outputAction = do
81+
-- Ensure that if the output file already exists, we remove it and
82+
-- re-write out the serialized data. This ensures that there are no
83+
-- false-positives, false-negatives, or irrelavent I/O exceptions.
84+
removeIfExists snapshotFsPath
85+
BSL.writeFile snapshotHsPath . toLazyByteString $ encode datum
86+
87+
in Au.goldenVsFile name snapshotAuPath snapshotHsPath outputAction
88+
89+
testCodecSnapshotLabel :: TestTree
90+
testCodecSnapshotLabel =
91+
let assembler (tagA, valA) =
92+
let (tagB, valB) = basicSnapshotTableType
93+
(tagC, valC) = basicTableConfig
94+
valD = basicRunNumber
95+
(tagE, valE) = basicSnapLevels
96+
in (fuseAnnotations [tagA, tagB, tagC, tagE ], SnapshotMetaData valA valB valC valD valE)
97+
in testCodecBuilder "SnapshotLabels" $ assembler <$> enumerateSnapshotLabel
98+
99+
testCodecSnapshotTableType :: TestTree
100+
testCodecSnapshotTableType =
101+
let assembler (tagB, valB) =
102+
let (tagA, valA) = basicSnapshotLabel
103+
(tagC, valC) = basicTableConfig
104+
valD = basicRunNumber
105+
(tagE, valE) = basicSnapLevels
106+
in (fuseAnnotations [tagA, tagB, tagC, tagE ], SnapshotMetaData valA valB valC valD valE)
107+
in testCodecBuilder "SnapshotTables" $ assembler <$> enumerateSnapshotTableType
108+
109+
testCodecTableConfig :: TestTree
110+
testCodecTableConfig =
111+
let assembler (tagC, valC) =
112+
let (tagA, valA) = basicSnapshotLabel
113+
(tagB, valB) = basicSnapshotTableType
114+
valD = basicRunNumber
115+
(tagE, valE) = basicSnapLevels
116+
in (fuseAnnotations [tagA, tagB, tagC, tagE ], SnapshotMetaData valA valB valC valD valE)
117+
in testCodecBuilder "SnapshotConfig" $ assembler <$> enumerateTableConfig
118+
119+
testCodecSnapLevels :: TestTree
120+
testCodecSnapLevels =
121+
let assembler (tagE, valE) =
122+
let (tagA, valA) = basicSnapshotLabel
123+
(tagB, valB) = basicSnapshotTableType
124+
(tagC, valC) = basicTableConfig
125+
valD = basicRunNumber
126+
in (fuseAnnotations [tagA, tagB, tagC, tagE ], SnapshotMetaData valA valB valC valD valE)
127+
in testCodecBuilder "SnapshotLevels" $ assembler <$> enumerateSnapLevels
128+
129+
testCodecBuilder :: TestName -> [(ComponentAnnotation, SnapshotMetaData)] -> TestTree
130+
testCodecBuilder tName metadata =
131+
testGroup tName $ uncurry snapshotCodecTest <$> metadata
132+
133+
type ComponentAnnotation = String
134+
135+
fuseAnnotations :: [ComponentAnnotation] -> ComponentAnnotation
136+
fuseAnnotations = List.intercalate "-"
137+
138+
blank :: ComponentAnnotation
139+
blank = "__"
140+
141+
{----------------
142+
Defaults used when the SnapshotMetaData sub-component is not under test
143+
----------------}
144+
145+
basicSnapshotLabel :: (ComponentAnnotation, SnapshotLabel)
146+
basicSnapshotLabel = head enumerateSnapshotLabel
147+
148+
basicSnapshotTableType :: (ComponentAnnotation, SnapshotTableType)
149+
basicSnapshotTableType = head enumerateSnapshotTableType
150+
151+
basicTableConfig :: (ComponentAnnotation, TableConfig)
152+
basicTableConfig = ( fuseAnnotations $ "T0" : replicate 4 blank, defaultTableConfig)
153+
154+
basicRunNumber :: RunNumber
155+
basicRunNumber = enumerateRunNumbers
156+
157+
basicSnapLevels :: (ComponentAnnotation, SnapLevels RunNumber)
158+
basicSnapLevels = head enumerateSnapLevels
159+
160+
{----------------
161+
Enumeration of SnapshotMetaData sub-components
162+
----------------}
163+
164+
enumerateSnapshotLabel :: [(ComponentAnnotation, SnapshotLabel)]
165+
enumerateSnapshotLabel =
166+
[ ("B0", SnapshotLabel "UserProvidedLabel")
167+
, ("B1", SnapshotLabel "")
168+
]
169+
170+
enumerateSnapshotTableType :: [(ComponentAnnotation, SnapshotTableType)]
171+
enumerateSnapshotTableType =
172+
[ ("N0", SnapNormalTable)
173+
, ("N1", SnapMonoidalTable)
174+
, ("N2", SnapFullTable)
175+
]
176+
177+
enumerateTableConfig :: [(ComponentAnnotation, TableConfig)]
178+
enumerateTableConfig =
179+
[ ( fuseAnnotations [ "T1", d, e, f, g ]
180+
, TableConfig
181+
policy
182+
ratio
183+
allocs
184+
bloom
185+
fence
186+
cache
187+
merge
188+
)
189+
| (_, policy) <- [(blank, MergePolicyLazyLevelling)]
190+
, (_, ratio ) <- [(blank, Four)]
191+
, (_, allocs) <- fmap (AllocNumEntries . NumEntries) <$> [(blank, magicNumber1)]
192+
, (d, bloom ) <- enumerateBloomFilterAlloc
193+
, (e, fence ) <- [("I0", CompactIndex), ("I1", OrdinaryIndex)]
194+
, (f, cache ) <- enumerateDiskCachePolicy
195+
, (g, merge ) <- [("G0", OneShot), ("G1", Incremental)]
196+
]
197+
198+
enumerateSnapLevels :: [(ComponentAnnotation, SnapLevels RunNumber)]
199+
enumerateSnapLevels = fmap (SnapLevels . V.singleton) <$> enumerateSnapLevel
200+
201+
{----------------
202+
Enumeration of SnapLevel sub-components
203+
----------------}
204+
205+
enumerateSnapLevel :: [(ComponentAnnotation, SnapLevel RunNumber)]
206+
enumerateSnapLevel = do
207+
(a, run) <- enumerateSnapIncomingRun
208+
(b, vec) <- enumerateVectorRunNumber
209+
[( fuseAnnotations [ a, b ], SnapLevel run vec)]
210+
211+
enumerateSnapIncomingRun :: [(ComponentAnnotation, SnapIncomingRun RunNumber)]
212+
enumerateSnapIncomingRun =
213+
let
214+
inSnaps =
215+
[ (fuseAnnotations ["R1", a, b], SnapMergingRun policy numRuns entries credits sState)
216+
| (a, policy ) <- [("P0", LevelTiering), ("P1", LevelLevelling)]
217+
, numRuns <- NumRuns <$> [ magicNumber1 ]
218+
, entries <- NumEntries <$> [ magicNumber2 ]
219+
, credits <- SuppliedCredits <$> [ magicNumber1 ]
220+
, (b, sState ) <- enumerateSnapMergingRunState
221+
]
222+
in fold
223+
[ [(fuseAnnotations $ "R0" : replicate 4 blank, SnapSingleRun enumerateRunNumbers)]
224+
, inSnaps
225+
]
226+
227+
enumerateSnapMergingRunState :: [(ComponentAnnotation, SnapMergingRunState RunNumber)]
228+
enumerateSnapMergingRunState = (fuseAnnotations ["C0", blank, blank], SnapCompletedMerge enumerateRunNumbers) :
229+
[ (fuseAnnotations ["C1", a, b], SnapOngoingMerge runVec mType)
230+
| (a, runVec ) <- enumerateVectorRunNumber
231+
, (b, mType ) <- [("M1", MergeMidLevel), ("M2", MergeLastLevel), ("M3", MergeUnion)]
232+
]
233+
234+
enumerateVectorRunNumber :: [(ComponentAnnotation, Vector RunNumber)]
235+
enumerateVectorRunNumber =
236+
[ ("V0", mempty)
237+
, ("V1", V.fromList [RunNumber magicNumber1])
238+
, ("V2", V.fromList [RunNumber magicNumber1, RunNumber magicNumber2 ])
239+
]
240+
241+
{----------------
242+
Enumeration of SnapshotMetaData sub-sub-components and so on...
243+
----------------}
244+
245+
enumerateBloomFilterAlloc :: [(ComponentAnnotation, BloomFilterAlloc)]
246+
enumerateBloomFilterAlloc =
247+
[ ("A0",AllocFixed magicNumber3)
248+
, ("A1",AllocRequestFPR pi)
249+
, ("A2",AllocMonkey magicNumber3 . NumEntries $ magicNumber3 `div` 4)
250+
]
251+
252+
enumerateDiskCachePolicy :: [(ComponentAnnotation, DiskCachePolicy)]
253+
enumerateDiskCachePolicy =
254+
[ ("D0", DiskCacheAll)
255+
, ("D1", DiskCacheNone)
256+
, ("D2", DiskCacheLevelsAtOrBelow 1)
257+
]
258+
259+
enumerateRunNumbers :: RunNumber
260+
enumerateRunNumbers = RunNumber magicNumber2
261+
262+
-- Randomly chosen numbers
263+
magicNumber1, magicNumber2, magicNumber3 :: Enum e => e
264+
magicNumber1 = toEnum 42
265+
magicNumber2 = toEnum 88
266+
magicNumber3 = toEnum 1024
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)