Skip to content

Commit 21f4bf7

Browse files
authored
Merge pull request #497 from IntersectMBO/jdral/test-codec-generators-shrinkers
Test generators and shrinkers for snapshot metadata
2 parents 70d0db8 + f182875 commit 21f4bf7

File tree

8 files changed

+147
-66
lines changed

8 files changed

+147
-66
lines changed

lsm-tree.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ test-suite lsm-tree-test
384384
Test.Database.LSMTree.StateMachine.Op
385385
Test.Database.LSMTree.UnitTests
386386
Test.System.Posix.Fcntl.NoCache
387+
Test.Util.Arbitrary
387388
Test.Util.FS
388389
Test.Util.Orphans
389390
Test.Util.PrettyProxy

src/Database/LSMTree/Internal/Merge.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module Database.LSMTree.Internal.Merge (
1515
, steps
1616
) where
1717

18+
import Control.DeepSeq (NFData (..))
1819
import Control.Exception (assert)
1920
import Control.Monad (when)
2021
import Control.Monad.Class.MonadST (MonadST)
@@ -74,6 +75,10 @@ data MergeState =
7475
data Level = MidLevel | LastLevel
7576
deriving stock (Eq, Show)
7677

78+
instance NFData Level where
79+
rnf MidLevel = ()
80+
rnf LastLevel = ()
81+
7782
type Mappend = SerialisedValue -> SerialisedValue -> SerialisedValue
7883

7984
{-# SPECIALISE new ::

src/Database/LSMTree/Internal/MergeSchedule.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ module Database.LSMTree.Internal.MergeSchedule (
4545
) where
4646

4747
import Control.Concurrent.Class.MonadMVar.Strict
48+
import Control.DeepSeq (NFData (..))
4849
import Control.Monad (void, when, (<$!>))
4950
import Control.Monad.Class.MonadST (MonadST)
5051
import Control.Monad.Class.MonadSTM (MonadSTM (..))
@@ -399,6 +400,10 @@ duplicateMergingRunRuns reg (DeRef mr) =
399400
data MergePolicyForLevel = LevelTiering | LevelLevelling
400401
deriving stock (Show, Eq)
401402

403+
instance NFData MergePolicyForLevel where
404+
rnf LevelTiering = ()
405+
rnf LevelLevelling = ()
406+
402407
mergePolicyForLevel :: MergePolicy -> LevelNo -> Levels m h -> MergePolicyForLevel
403408
mergePolicyForLevel MergePolicyLazyLevelling (LevelNo n) nextLevels
404409
| n == 1
@@ -409,6 +414,7 @@ mergePolicyForLevel MergePolicyLazyLevelling (LevelNo n) nextLevels
409414

410415
newtype NumRuns = NumRuns { unNumRuns :: Int }
411416
deriving stock (Show, Eq)
417+
deriving newtype NFData
412418

413419
newtype UnspentCreditsVar s = UnspentCreditsVar { getUnspentCreditsVar :: PrimVar s Int }
414420

@@ -430,6 +436,10 @@ newtype SpentCreditsVar s = SpentCreditsVar { getSpentCreditsVar :: PrimVar s In
430436
data MergeKnownCompleted = MergeKnownCompleted | MergeMaybeCompleted
431437
deriving stock (Show, Eq, Read)
432438

439+
instance NFData MergeKnownCompleted where
440+
rnf MergeKnownCompleted = ()
441+
rnf MergeMaybeCompleted = ()
442+
433443
{-# SPECIALISE duplicateLevels :: TempRegistry IO -> Levels IO h -> IO (Levels IO h) #-}
434444
duplicateLevels ::
435445
(PrimMonad m, MonadMVar m, MonadMask m)

src/Database/LSMTree/Internal/Snapshot.hs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module Database.LSMTree.Internal.Snapshot (
2424

2525
import Control.Concurrent.Class.MonadMVar.Strict
2626
import Control.Concurrent.Class.MonadSTM (MonadSTM)
27+
import Control.DeepSeq (NFData (..))
2728
import Control.Monad (when)
2829
import Control.Monad.Class.MonadST (MonadST)
2930
import Control.Monad.Class.MonadThrow (MonadMask)
@@ -66,11 +67,17 @@ import System.FS.BlockIO.API (HasBlockIO)
6667
-- is opened at the correct key\/value\/blob type.
6768
newtype SnapshotLabel = SnapshotLabel Text
6869
deriving stock (Show, Eq)
70+
deriving newtype NFData
6971

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

76+
instance NFData SnapshotTableType where
77+
rnf SnapNormalTable = ()
78+
rnf SnapMonoidalTable = ()
79+
rnf SnapFullTable = ()
80+
7481
data SnapshotMetaData = SnapshotMetaData {
7582
-- | See 'SnapshotLabel'.
7683
--
@@ -94,40 +101,58 @@ data SnapshotMetaData = SnapshotMetaData {
94101
}
95102
deriving stock (Show, Eq)
96103

104+
instance NFData SnapshotMetaData where
105+
rnf (SnapshotMetaData a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d
106+
97107
{-------------------------------------------------------------------------------
98108
Levels snapshot format
99109
-------------------------------------------------------------------------------}
100110

101111
newtype SnapLevels r = SnapLevels { getSnapLevels :: V.Vector (SnapLevel r) }
102112
deriving stock (Show, Eq, Functor, Foldable, Traversable)
113+
deriving newtype NFData
103114

104115
data SnapLevel r = SnapLevel {
105116
snapIncoming :: !(SnapIncomingRun r)
106117
, snapResidentRuns :: !(V.Vector r)
107118
}
108119
deriving stock (Show, Eq, Functor, Foldable, Traversable)
109120

121+
instance NFData r => NFData (SnapLevel r) where
122+
rnf (SnapLevel a b) = rnf a `seq` rnf b
123+
110124
data SnapIncomingRun r =
111125
SnapMergingRun !MergePolicyForLevel !NumRuns !NumEntries !UnspentCredits !MergeKnownCompleted !(SnapMergingRunState r)
112126
| SnapSingleRun !r
113127
deriving stock (Show, Eq, Functor, Foldable, Traversable)
114128

129+
instance NFData r => NFData (SnapIncomingRun r) where
130+
rnf (SnapMergingRun a b c d e f) =
131+
rnf a `seq` rnf b `seq` rnf c `seq` rnf d `seq` rnf e `seq` rnf f
132+
rnf (SnapSingleRun a) = rnf a
133+
115134
-- | The total number of unspent credits. This total is used in combination with
116135
-- 'SpentCredits' on snapshot load to restore merging work that was lost when
117136
-- the snapshot was created.
118137
newtype UnspentCredits = UnspentCredits { getUnspentCredits :: Int }
119138
deriving stock (Show, Eq, Read)
139+
deriving newtype NFData
120140

121141
data SnapMergingRunState r =
122142
SnapCompletedMerge !r
123143
| SnapOngoingMerge !(V.Vector r) !SpentCredits !Merge.Level
124144
deriving stock (Show, Eq, Functor, Foldable, Traversable)
125145

146+
instance NFData r => NFData (SnapMergingRunState r) where
147+
rnf (SnapCompletedMerge a) = rnf a
148+
rnf (SnapOngoingMerge a b c) = rnf a `seq` rnf b `seq` rnf c
149+
126150
-- | The total number of spent credits. This total is used in combination with
127151
-- 'UnspentCedits' on snapshot load to restore merging work that was lost when
128152
-- the snapshot was created.
129153
newtype SpentCredits = SpentCredits { getSpentCredits :: Int }
130154
deriving stock (Show, Eq, Read)
155+
deriving newtype NFData
131156

132157
{-------------------------------------------------------------------------------
133158
Conversion to levels snapshot format

test/Test/Database/LSMTree/Generators.hs

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,8 @@
33

44
module Test.Database.LSMTree.Generators (
55
tests
6-
, prop_arbitraryAndShrinkPreserveInvariant
7-
, prop_forAllArbitraryAndShrinkPreserveInvariant
8-
, deepseqInvariant
96
) where
107

11-
import Control.DeepSeq (NFData, deepseq)
128
import Data.Bifoldable (bifoldMap)
139
import Data.Coerce (coerce)
1410
import qualified Data.Map.Strict as Map
@@ -26,9 +22,10 @@ import Database.LSMTree.Internal.RawBytes (RawBytes (..))
2622
import Database.LSMTree.Internal.Serialise
2723

2824
import qualified Test.QuickCheck as QC
29-
import Test.QuickCheck (Arbitrary (..), Gen, Property, Testable (..))
25+
import Test.QuickCheck (Property)
3026
import Test.Tasty (TestTree, localOption, testGroup)
3127
import Test.Tasty.QuickCheck (QuickCheckMaxSize (..), testProperty)
28+
import Test.Util.Arbitrary
3229

3330
tests :: TestTree
3431
tests = testGroup "Test.Database.LSMTree.Generators" [
@@ -54,27 +51,6 @@ tests = testGroup "Test.Database.LSMTree.Generators" [
5451
]
5552
]
5653

57-
prop_arbitraryAndShrinkPreserveInvariant ::
58-
forall a. (Arbitrary a, Show a) => (a -> Bool) -> [TestTree]
59-
prop_arbitraryAndShrinkPreserveInvariant =
60-
prop_forAllArbitraryAndShrinkPreserveInvariant arbitrary shrink
61-
62-
prop_forAllArbitraryAndShrinkPreserveInvariant ::
63-
forall a. Show a => Gen a -> (a -> [a]) -> (a -> Bool) -> [TestTree]
64-
prop_forAllArbitraryAndShrinkPreserveInvariant gen shr inv =
65-
[ testProperty "Arbitrary satisfies invariant" $
66-
property $ QC.forAllShrink gen shr inv
67-
, testProperty "Shrinking satisfies invariant" $
68-
property $ QC.forAll gen $ \x ->
69-
case shr x of
70-
[] -> QC.label "no shrinks" $ property True
71-
xs -> QC.forAll (QC.growingElements xs) inv
72-
]
73-
74-
-- | Trivial invariant, but checks that the value is finite
75-
deepseqInvariant :: NFData a => a -> Bool
76-
deepseqInvariant x = x `deepseq` True
77-
7854
prop_packRawBytesPinnedOrUnpinned :: Bool -> [Word8] -> Bool
7955
prop_packRawBytesPinnedOrUnpinned pinned ws =
8056
packRawBytesPinnedOrUnpinned pinned ws == RawBytes (VP.fromList ws)

test/Test/Database/LSMTree/Internal/Lookup.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ import qualified System.FS.API as FS
6666
import System.FS.API (Handle (..), mkFsPath)
6767
import qualified System.FS.BlockIO.API as FS
6868
import System.FS.BlockIO.API
69-
import Test.Database.LSMTree.Generators (deepseqInvariant,
70-
prop_arbitraryAndShrinkPreserveInvariant,
71-
prop_forAllArbitraryAndShrinkPreserveInvariant)
7269
import Test.QuickCheck
7370
import Test.Tasty
7471
import Test.Tasty.QuickCheck
72+
import Test.Util.Arbitrary (deepseqInvariant,
73+
prop_arbitraryAndShrinkPreserveInvariant,
74+
prop_forAllArbitraryAndShrinkPreserveInvariant)
7575
import Test.Util.FS (withTempIOHasBlockIO)
7676

7777
tests :: TestTree

test/Test/Database/LSMTree/Internal/Snapshot/Codec.hs

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import Codec.CBOR.Encoding
77
import Codec.CBOR.FlatTerm
88
import Codec.CBOR.Read
99
import Codec.CBOR.Write
10+
import Control.DeepSeq (NFData)
1011
import qualified Data.ByteString.Lazy as BSL
1112
import Data.Proxy
1213
import Data.Text (Text)
1314
import qualified Data.Text as Text
15+
import Data.Typeable
1416
import qualified Data.Vector as V
1517
import Database.LSMTree.Internal.Config
1618
import Database.LSMTree.Internal.Entry
@@ -21,6 +23,7 @@ import Database.LSMTree.Internal.Snapshot
2123
import Database.LSMTree.Internal.Snapshot.Codec
2224
import Test.Tasty
2325
import Test.Tasty.QuickCheck
26+
import Test.Util.Arbitrary
2427

2528
-- TODO: we should add golden tests for the CBOR encoders. This should prevent
2629
-- accidental breakage in the format.
@@ -34,45 +37,20 @@ tests = testGroup "Test.Database.LSMTree.Internal.Snapshot.Codec" [
3437
testProperty "roundtripCBOR" $ roundtripCBOR (Proxy @(Versioned SnapshotMetaData))
3538
, testProperty "roundtripFlatTerm" $ roundtripFlatTerm (Proxy @(Versioned SnapshotMetaData))
3639
]
37-
, testGroup "roundtripCBOR'" (propAll roundtripCBOR')
38-
, testGroup "roundtripFlatTerm'" (propAll roundtripFlatTerm')
40+
, testGroup "roundtripCBOR'" $
41+
propAll roundtripCBOR'
42+
, testGroup "roundtripFlatTerm'" $
43+
propAll roundtripFlatTerm'
44+
-- Test generators and shrinkers
45+
, testGroup "Generators and shrinkers are finite" $
46+
testAll $ \(p :: Proxy a) ->
47+
testGroup (show $ typeRep p) $
48+
prop_arbitraryAndShrinkPreserveInvariant @a deepseqInvariant
3949
]
4050

41-
-- | Run a property on all types in the snapshot metadata hierarchy.
42-
propAll ::
43-
( forall a. (Encode a, DecodeVersioned a, Eq a, Show a)
44-
=> Proxy a -> a -> Property
45-
)
46-
-> [TestTree]
47-
propAll prop = [
48-
-- SnapshotMetaData
49-
testProperty "SnapshotMetaData" $ prop (Proxy @SnapshotMetaData)
50-
, testProperty "SnapshotLabel" $ prop (Proxy @SnapshotLabel)
51-
, testProperty "SnapshotTableType" $ prop (Proxy @SnapshotTableType)
52-
-- TableConfig
53-
, testProperty "TableConfig" $ prop (Proxy @TableConfig)
54-
, testProperty "MergePolicy" $ prop (Proxy @MergePolicy)
55-
, testProperty "SizeRatio" $ prop (Proxy @SizeRatio)
56-
, testProperty "WriteBufferAlloc" $ prop (Proxy @WriteBufferAlloc)
57-
, testProperty "NumEntries" $ prop (Proxy @NumEntries)
58-
, testProperty "BloomFilterAlloc" $ prop (Proxy @BloomFilterAlloc)
59-
, testProperty "FencePointerIndex" $ prop (Proxy @FencePointerIndex)
60-
, testProperty "DiskCachePolicy" $ prop (Proxy @DiskCachePolicy)
61-
, testProperty "MergeSchedule" $ prop (Proxy @MergeSchedule)
62-
-- SnapLevels
63-
, testProperty "SnapLevels" $ prop (Proxy @(SnapLevels RunNumber))
64-
, testProperty "SnapLevel" $ prop (Proxy @(SnapLevel RunNumber))
65-
, testProperty "Vector RunNumber" $ prop (Proxy @(V.Vector RunNumber))
66-
, testProperty "RunNumber" $ prop (Proxy @RunNumber)
67-
, testProperty "SnapIncomingRun" $ prop (Proxy @(SnapIncomingRun RunNumber))
68-
, testProperty "NumRuns" $ prop (Proxy @NumRuns)
69-
, testProperty "MergePolicyForLevel" $ prop (Proxy @MergePolicyForLevel)
70-
, testProperty "UnspentCredits" $ prop (Proxy @UnspentCredits)
71-
, testProperty "MergeKnownCompleted" $ prop (Proxy @MergeKnownCompleted)
72-
, testProperty "SnapMergingRunState" $ prop (Proxy @(SnapMergingRunState RunNumber))
73-
, testProperty "SpentCredits" $ prop (Proxy @SpentCredits)
74-
, testProperty "Merge.Level" $ prop (Proxy @Merge.Level)
75-
]
51+
{-------------------------------------------------------------------------------
52+
Properties
53+
-------------------------------------------------------------------------------}
7654

7755
-- | @decode . encode = id@
7856
explicitRoundtripCBOR ::
@@ -148,6 +126,58 @@ roundtripFlatTerm' ::
148126
-> Property
149127
roundtripFlatTerm' _ = explicitRoundtripFlatTerm encode (decodeVersioned currentSnapshotVersion)
150128

129+
{-------------------------------------------------------------------------------
130+
Test and property runners
131+
-------------------------------------------------------------------------------}
132+
133+
type Constraints a = (
134+
Eq a, Show a, Typeable a, Arbitrary a
135+
, Encode a, DecodeVersioned a, NFData a
136+
)
137+
138+
-- | Run a property on all types in the snapshot metadata hierarchy.
139+
propAll ::
140+
(forall a. Constraints a => Proxy a -> a -> Property)
141+
-> [TestTree]
142+
propAll prop = testAll mkTest
143+
where
144+
mkTest :: forall a. Constraints a => Proxy a -> TestTree
145+
mkTest pa = testProperty (show $ typeRep pa) (prop pa)
146+
147+
-- | Run a test on all types in the snapshot metadata hierarchy.
148+
testAll ::
149+
(forall a. Constraints a => Proxy a -> TestTree)
150+
-> [TestTree]
151+
testAll test = [
152+
-- SnapshotMetaData
153+
test (Proxy @SnapshotMetaData)
154+
, test (Proxy @SnapshotLabel)
155+
, test (Proxy @SnapshotTableType)
156+
-- TableConfig
157+
, test (Proxy @TableConfig)
158+
, test (Proxy @MergePolicy)
159+
, test (Proxy @SizeRatio)
160+
, test (Proxy @WriteBufferAlloc)
161+
, test (Proxy @NumEntries)
162+
, test (Proxy @BloomFilterAlloc)
163+
, test (Proxy @FencePointerIndex)
164+
, test (Proxy @DiskCachePolicy)
165+
, test (Proxy @MergeSchedule)
166+
-- SnapLevels
167+
, test (Proxy @(SnapLevels RunNumber))
168+
, test (Proxy @(SnapLevel RunNumber))
169+
, test (Proxy @(V.Vector RunNumber))
170+
, test (Proxy @RunNumber)
171+
, test (Proxy @(SnapIncomingRun RunNumber))
172+
, test (Proxy @NumRuns)
173+
, test (Proxy @MergePolicyForLevel)
174+
, test (Proxy @UnspentCredits)
175+
, test (Proxy @MergeKnownCompleted)
176+
, test (Proxy @(SnapMergingRunState RunNumber))
177+
, test (Proxy @SpentCredits)
178+
, test (Proxy @Merge.Level)
179+
]
180+
151181
{-------------------------------------------------------------------------------
152182
Arbitrary: versioning
153183
-------------------------------------------------------------------------------}

test/Test/Util/Arbitrary.hs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{-# LANGUAGE ScopedTypeVariables #-}
2+
{-# LANGUAGE TypeApplications #-}
3+
4+
module Test.Util.Arbitrary (
5+
prop_arbitraryAndShrinkPreserveInvariant
6+
, prop_forAllArbitraryAndShrinkPreserveInvariant
7+
, deepseqInvariant
8+
) where
9+
10+
import Control.DeepSeq (NFData, deepseq)
11+
import Test.QuickCheck
12+
import Test.Tasty (TestTree)
13+
import Test.Tasty.QuickCheck (testProperty)
14+
15+
prop_arbitraryAndShrinkPreserveInvariant ::
16+
forall a. (Arbitrary a, Show a) => (a -> Bool) -> [TestTree]
17+
prop_arbitraryAndShrinkPreserveInvariant =
18+
prop_forAllArbitraryAndShrinkPreserveInvariant arbitrary shrink
19+
20+
prop_forAllArbitraryAndShrinkPreserveInvariant ::
21+
forall a. Show a => Gen a -> (a -> [a]) -> (a -> Bool) -> [TestTree]
22+
prop_forAllArbitraryAndShrinkPreserveInvariant gen shr inv =
23+
[ testProperty "Arbitrary satisfies invariant" $
24+
property $ forAllShrink gen shr inv
25+
, testProperty "Shrinking satisfies invariant" $
26+
property $ forAll gen $ \x ->
27+
case shr x of
28+
[] -> label "no shrinks" $ property True
29+
xs -> forAll (growingElements xs) inv
30+
]
31+
32+
-- | Trivial invariant, but checks that the value is finite
33+
deepseqInvariant :: NFData a => a -> Bool
34+
deepseqInvariant x = x `deepseq` True

0 commit comments

Comments
 (0)