Skip to content

Commit f74e017

Browse files
committed
Add tests for min/max bounds on (un)spent credits
And check max run size (2^40-1, or about 1 trillion)
1 parent 2daf018 commit f74e017

File tree

4 files changed

+77
-4
lines changed

4 files changed

+77
-4
lines changed

lsm-tree.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ test-suite lsm-tree-test
366366
Test.Database.LSMTree.Internal.Index.Ordinary
367367
Test.Database.LSMTree.Internal.Lookup
368368
Test.Database.LSMTree.Internal.Merge
369+
Test.Database.LSMTree.Internal.MergingRun
369370
Test.Database.LSMTree.Internal.MergingTree
370371
Test.Database.LSMTree.Internal.Monkey
371372
Test.Database.LSMTree.Internal.PageAcc

src/Database/LSMTree/Internal/MergingRun.hs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ module Database.LSMTree.Internal.MergingRun (
2323
, Credits (..)
2424
, CreditThreshold (..)
2525
, SuppliedCredits (..)
26+
, SpentCredits (..)
27+
, UnspentCredits (..)
2628

2729
-- * Concurrency
2830
-- $concurrency
@@ -32,16 +34,19 @@ module Database.LSMTree.Internal.MergingRun (
3234
, MergingRunState (..)
3335
, MergeKnownCompleted (..)
3436
, CreditsVar (..)
37+
, pattern CreditsPair
3538
) where
3639

3740
import Control.ActionRegistry
3841
import Control.Concurrent.Class.MonadMVar.Strict
3942
import Control.DeepSeq (NFData (..))
43+
import Control.Exception (ErrorCall (..))
4044
import Control.Monad (when)
4145
import Control.Monad.Class.MonadST (MonadST)
4246
import Control.Monad.Class.MonadSTM (MonadSTM (..))
4347
import Control.Monad.Class.MonadThrow
44-
(MonadCatch (bracketOnError, onException), MonadMask)
48+
(MonadCatch (bracketOnError, onException), MonadMask,
49+
MonadThrow (throwIO))
4550
import Control.Monad.Primitive
4651
import Control.RefCount
4752
import Data.Bits
@@ -183,6 +188,10 @@ unsafeNew ::
183188
-> MergeKnownCompleted
184189
-> MergingRunState m h
185190
-> m (Ref (MergingRun m h))
191+
unsafeNew _ mergeNumEntries _ _
192+
| SpentCredits (numEntriesToTotalDebt mergeNumEntries) > maxBound
193+
= throwIO (ErrorCall "MergingRun.new: run size exceeds maximum of 2^40")
194+
186195
unsafeNew mergeNumRuns mergeNumEntries knownCompleted state = do
187196
mergeCreditsVar <- CreditsVar <$> newPrimVar 0
188197
case state of
@@ -340,6 +349,7 @@ newtype SuppliedCredits = SuppliedCredits Credits
340349
-- spent (by some thread calling 'supplyCredits').
341350
--
342351
newtype SpentCredits = SpentCredits Credits
352+
deriving newtype (Eq, Ord)
343353

344354
-- | 40 bit unsigned number
345355
instance Bounded SpentCredits where
@@ -355,6 +365,7 @@ instance Bounded SpentCredits where
355365
-- current unspent credits being negative for a time.
356366
--
357367
newtype UnspentCredits = UnspentCredits Credits
368+
deriving newtype (Eq, Ord)
358369

359370
-- | 24 bit signed number
360371
instance Bounded UnspentCredits where
@@ -386,11 +397,13 @@ pattern CreditsPair sc uc <- (unpackCreditsPair -> (sc, uc))
386397
#endif
387398
{-# COMPLETE CreditsPair #-}
388399

389-
-- TODO: test pack/unpack round trip with the minBound & maxBounds
390-
391400
{-# INLINE packCreditsPair #-}
392401
packCreditsPair :: SpentCredits -> UnspentCredits -> Int
393-
packCreditsPair (SpentCredits (Credits sc)) (UnspentCredits (Credits uc)) =
402+
packCreditsPair spent@(SpentCredits (Credits sc))
403+
unspent@(UnspentCredits (Credits uc)) =
404+
assert (spent >= minBound && spent <= maxBound) $
405+
assert (unspent >= minBound && unspent <= maxBound) $
406+
394407
sc `unsafeShiftL` 24
395408
.|. (uc .&. 0xffffff)
396409

test/Main.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import qualified Test.Database.LSMTree.Internal.Index.Compact
1717
import qualified Test.Database.LSMTree.Internal.Index.Ordinary
1818
import qualified Test.Database.LSMTree.Internal.Lookup
1919
import qualified Test.Database.LSMTree.Internal.Merge
20+
import qualified Test.Database.LSMTree.Internal.MergingRun
2021
import qualified Test.Database.LSMTree.Internal.MergingTree
2122
import qualified Test.Database.LSMTree.Internal.Monkey
2223
import qualified Test.Database.LSMTree.Internal.PageAcc
@@ -59,6 +60,7 @@ main = do
5960
, Test.Database.LSMTree.Internal.Entry.tests
6061
, Test.Database.LSMTree.Internal.Lookup.tests
6162
, Test.Database.LSMTree.Internal.Merge.tests
63+
, Test.Database.LSMTree.Internal.MergingRun.tests
6264
, Test.Database.LSMTree.Internal.MergingTree.tests
6365
, Test.Database.LSMTree.Internal.Monkey.tests
6466
, Test.Database.LSMTree.Internal.PageAcc.tests
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{-# OPTIONS_GHC -Wno-orphans #-}
2+
3+
module Test.Database.LSMTree.Internal.MergingRun (tests) where
4+
5+
import Database.LSMTree.Internal.MergingRun
6+
import Test.QuickCheck
7+
import Test.Tasty
8+
import Test.Tasty.QuickCheck
9+
10+
tests :: TestTree
11+
tests = testGroup "Test.Database.LSMTree.Internal.MergingRun"
12+
[ testProperty "prop_CreditsPair" prop_CreditsPair
13+
]
14+
15+
-- | Check that the merging tree constructor functions preserve the property
16+
-- that if the inputs are obviously empty, the output is also obviously empty.
17+
--
18+
prop_CreditsPair :: SpentCredits -> UnspentCredits -> Property
19+
prop_CreditsPair spentCredits unspentCredits =
20+
tabulate "bounds" [spentCreditsBound, unspentCreditsBound] $
21+
let cp :: Int
22+
!cp = CreditsPair spentCredits unspentCredits
23+
in case cp of
24+
CreditsPair spentCredits' unspentCredits' ->
25+
(spentCredits, unspentCredits) === (spentCredits', unspentCredits')
26+
where
27+
spentCreditsBound
28+
| spentCredits == minBound = "spentCredits == minBound"
29+
| spentCredits == maxBound = "spentCredits == maxBound"
30+
| otherwise = "spentCredits == other"
31+
32+
unspentCreditsBound
33+
| unspentCredits == minBound = "unspentCredits == minBound"
34+
| unspentCredits == maxBound = "unspentCredits == maxBound"
35+
| otherwise = "unspentCredits == other"
36+
37+
deriving newtype instance Enum SpentCredits
38+
deriving newtype instance Enum UnspentCredits
39+
40+
deriving stock instance Show Credits
41+
deriving stock instance Show SpentCredits
42+
deriving stock instance Show UnspentCredits
43+
44+
instance Arbitrary SpentCredits where
45+
arbitrary =
46+
frequency [ (1, pure minBound)
47+
, (1, pure maxBound)
48+
, (10, arbitraryBoundedEnum)
49+
]
50+
51+
instance Arbitrary UnspentCredits where
52+
arbitrary =
53+
frequency [ (1, pure minBound)
54+
, (1, pure maxBound)
55+
, (10, arbitraryBoundedEnum)
56+
]
57+

0 commit comments

Comments
 (0)