55{-# LANGUAGE NumericUnderscores #-}
66{-# LANGUAGE ScopedTypeVariables #-}
77{-# LANGUAGE TypeApplications #-}
8+ {-# OPTIONS_GHC -Wno-orphans #-}
89{- HLINT ignore "Use camelCase" -}
910
1011module Test.Database.LSMTree.Internal.Monkey (
@@ -15,8 +16,7 @@ module Test.Database.LSMTree.Internal.Monkey (
1516 -- A common interface to bloom filter construction, based on expected false
1617 -- positive rates.
1718 , BloomMaker
18- , mkBloomST
19- , mkBloomEasy
19+ , mkBloomFromAlloc
2020 -- * Verifying FPRs
2121 , measureApproximateFPR
2222 , measureExactFPR
@@ -26,7 +26,6 @@ import Control.Exception (assert)
2626import Control.Monad.ST
2727import Data.BloomFilter (Bloom )
2828import qualified Data.BloomFilter as Bloom
29- import qualified Data.BloomFilter.Easy as Bloom.Easy
3029import Data.BloomFilter.Hash (Hashable )
3130import qualified Data.BloomFilter.Mutable as MBloom
3231import Data.Foldable (Foldable (.. ))
@@ -35,22 +34,26 @@ import Data.Set (Set)
3534import qualified Data.Set as Set
3635import Data.Word (Word64 )
3736import Database.LSMTree.Extras.Random
37+ import qualified Database.LSMTree.Internal.Entry as LSMT
38+ import Database.LSMTree.Internal.RunAcc (RunBloomFilterAlloc (.. ),
39+ falsePositiveRate , newMBloom )
3840import System.Random
3941import Test.QuickCheck
42+ import Test.QuickCheck.Gen
4043import Test.Tasty (TestTree , testGroup )
41- import Test.Tasty.QuickCheck (testProperty )
44+ import Test.Tasty.QuickCheck
45+ import Test.Util.Arbitrary (noTags ,
46+ prop_arbitraryAndShrinkPreserveInvariant )
4247import Text.Printf (printf )
4348
4449tests :: TestTree
4550tests = testGroup " Database.LSMTree.Internal.Monkey" [
46- testGroup " No false negatives" [
47- testProperty " mkBloomEasy" $ prop_noFalseNegatives (Proxy @ Word64 ) mkBloomEasy
48- , testProperty " mkBloomST" $ prop_noFalseNegatives (Proxy @ Word64 ) mkBloomST
49- ]
50- , testGroup " Verify FPR" [
51- testProperty " mkBloomEasy" $ prop_verifyFPR (Proxy @ Word64 ) mkBloomEasy
52- , testProperty " mkBloomST" $ prop_verifyFPR (Proxy @ Word64 ) mkBloomST
53- ]
51+ testProperty " prop_noFalseNegatives" $ prop_noFalseNegatives (Proxy @ Word64 )
52+ , testProperty " prop_verifyFPR" $ prop_verifyFPR (Proxy @ Word64 )
53+ , testGroup " RunBloomFilterAlloc" $
54+ prop_arbitraryAndShrinkPreserveInvariant noTags allocInvariant
55+ , testGroup " NumEntries" $
56+ prop_arbitraryAndShrinkPreserveInvariant noTags numEntriesInvariant
5457 ]
5558
5659{- ------------------------------------------------------------------------------
@@ -59,47 +62,81 @@ tests = testGroup "Database.LSMTree.Internal.Monkey" [
5962
6063prop_noFalseNegatives :: forall a proxy . Hashable a
6164 => proxy a
62- -> (Double -> BloomMaker a )
63- -> FPR -- ^ Requested FPR
65+ -> RunBloomFilterAlloc
6466 -> UniformWithoutReplacement a
6567 -> Property
66- prop_noFalseNegatives _ mkBloom ( FPR requestedFPR) (UniformWithoutReplacement xs) =
67- let xsBloom = mkBloom requestedFPR xs
68+ prop_noFalseNegatives _ alloc (UniformWithoutReplacement xs) =
69+ let xsBloom = mkBloomFromAlloc alloc xs
6870 in property $ all (`Bloom.elem` xsBloom) xs
6971
7072prop_verifyFPR ::
7173 (Ord a , Uniform a , Hashable a )
7274 => proxy a
73- -> (Double -> BloomMaker a )
74- -> FPR -- ^ Requested FPR
75+ -> RunBloomFilterAlloc
7576 -> NumEntries -- ^ @numEntries@
7677 -> Seed -- ^ 'StdGen' seed
7778 -> Property
78- prop_verifyFPR p mkBloom ( FPR requestedFPR) (NumEntries numEntries) (Seed seed) =
79+ prop_verifyFPR p alloc (NumEntries numEntries) (Seed seed) =
7980 let stdgen = mkStdGen seed
80- measuredFPR = measureApproximateFPR p (mkBloom requestedFPR) numEntries stdgen
81- requestedFPR' = requestedFPR + 0.03 -- @requestedFPR@ with an error margin
82- in counterexample (printf " expected %f <= %f" measuredFPR requestedFPR') $
83- FPR measuredFPR <= FPR requestedFPR'
81+ measuredFPR = measureApproximateFPR p (mkBloomFromAlloc alloc) numEntries stdgen
82+ expectedFPR = case alloc of
83+ RunAllocFixed bits ->
84+ falsePositiveRate (fromIntegral numEntries)
85+ (fromIntegral bits * fromIntegral numEntries)
86+ RunAllocRequestFPR requestedFPR -> requestedFPR
87+ -- error margins
88+ lb = expectedFPR - 0.1
89+ ub = expectedFPR + 0.03
90+ in assert (fprInvariant True measuredFPR) $ -- measured FPR is in the range [0,1]
91+ assert (fprInvariant True expectedFPR) $ -- expected FPR is in the range [0,1]
92+ counterexample (printf " expected $f <= %f <= %f" lb measuredFPR ub) $
93+ lb <= measuredFPR .&&. measuredFPR <= ub
8494
8595{- ------------------------------------------------------------------------------
8696 Modifiers
8797-------------------------------------------------------------------------------}
8898
8999--
90- -- FPR
100+ -- Alloc
91101--
92102
93- newtype FPR = FPR { getFPR :: Double }
94- deriving stock (Show , Eq , Ord )
95- deriving newtype (Num , Fractional , Floating )
103+ instance Arbitrary RunBloomFilterAlloc where
104+ arbitrary = oneof [
105+ RunAllocFixed <$> genFixed
106+ , RunAllocRequestFPR <$> genFPR
107+ ]
108+ shrink (RunAllocFixed x) = RunAllocFixed <$> shrinkFixed x
109+ shrink (RunAllocRequestFPR x) = RunAllocRequestFPR <$> shrinkFPR x
110+
111+ allocInvariant :: RunBloomFilterAlloc -> Bool
112+ allocInvariant (RunAllocFixed x) = fixedInvariant x
113+ allocInvariant (RunAllocRequestFPR x) = fprInvariant False x
114+
115+ genFixed :: Gen Word64
116+ genFixed = choose (fixedLB, fixedUB)
117+
118+ shrinkFixed :: Word64 -> [Word64 ]
119+ shrinkFixed x = [ x' | x' <- shrink x, fixedInvariant x']
120+
121+ fixedInvariant :: Word64 -> Bool
122+ fixedInvariant x = fixedLB <= x && x <= fixedUB
96123
97- instance Arbitrary FPR where
98- arbitrary = FPR <$> arbitrary `suchThat` fprInvariant
99- shrink (FPR x) = [FPR x' | x' <- shrink x, fprInvariant x']
124+ fixedLB :: Word64
125+ fixedLB = 0
100126
101- fprInvariant :: Double -> Bool
102- fprInvariant x = x >= 0.01 && x <= 0.99
127+ fixedUB :: Word64
128+ fixedUB = 20
129+
130+ genFPR :: Gen Double
131+ genFPR = genDouble `suchThat` fprInvariant False
132+
133+ shrinkFPR :: Double -> [Double ]
134+ shrinkFPR x = [ x' | x' <- shrink x, fprInvariant False x']
135+
136+ fprInvariant :: Bool -> Double -> Bool
137+ fprInvariant incl x
138+ | incl = 0 <= x && x <= 1
139+ | otherwise = 0 < x && x < 1
103140
104141--
105142-- NumEntries
@@ -110,16 +147,21 @@ newtype NumEntries = NumEntries { getNumEntries :: Int }
110147
111148instance Arbitrary NumEntries where
112149 arbitrary = NumEntries <$> chooseInt (numEntriesLB, numEntriesUB)
113- shrink (NumEntries x) = [NumEntries x' | x' <- shrink x, numEntriesInvariant x']
150+ shrink (NumEntries x) = [
151+ x''
152+ | x' <- shrink x
153+ , let x'' = NumEntries x'
154+ , numEntriesInvariant x''
155+ ]
114156
115157numEntriesLB :: Int
116- numEntriesLB = 10_000
158+ numEntriesLB = 50_000
117159
118160numEntriesUB :: Int
119161numEntriesUB = 100_000
120162
121- numEntriesInvariant :: Int -> Bool
122- numEntriesInvariant x = x >= numEntriesLB && x <= numEntriesUB
163+ numEntriesInvariant :: NumEntries -> Bool
164+ numEntriesInvariant ( NumEntries x) = x >= numEntriesLB && x <= numEntriesUB
123165
124166--
125167-- Seed
@@ -245,18 +287,12 @@ instance Monoid Counts where
245287
246288type BloomMaker a = [a ] -> Bloom a
247289
248- -- | Create a bloom filter through the 'MBloom ' interface. Tunes the bloom
249- -- filter using 'suggestSizing '.
250- mkBloomST :: Hashable a => Double -> BloomMaker a
251- mkBloomST requestedFPR xs = runST $ do
252- b <- MBloom. new numHashFuncs numBits
253- mapM_ (MBloom. insert b ) xs
254- Bloom. freeze b
290+ -- | Create a bloom filter through the 'newMBloom ' interface. Tunes the bloom
291+ -- filter according to 'RunBloomFilterAlloc '.
292+ mkBloomFromAlloc :: Hashable a => RunBloomFilterAlloc -> BloomMaker a
293+ mkBloomFromAlloc alloc xs = runST $ do
294+ mb <- newMBloom n alloc
295+ mapM_ (MBloom. insert mb ) xs
296+ Bloom. unsafeFreeze mb
255297 where
256- numEntries = length xs
257- (numBits, numHashFuncs) = Bloom.Easy. suggestSizing numEntries requestedFPR
258-
259- -- | Create a bloom filter through the "Data.BloomFilter.Easy" interface. Tunes
260- -- the bloom filter using 'suggestSizing'.
261- mkBloomEasy :: Hashable a => Double -> BloomMaker a
262- mkBloomEasy = Bloom.Easy. easyList
298+ n = LSMT. NumEntries $ length xs
0 commit comments