lsm-tree-1.0.0.0: test/Test/Database/LSMTree/Internal/Vector.hs
{-# OPTIONS_GHC -Wno-orphans #-}
module Test.Database.LSMTree.Internal.Vector (tests) where
import Control.Monad (forM_)
import Control.Monad.ST
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Unboxed.Mutable as VUM
import Data.Word
import Database.LSMTree.Extras
import Database.LSMTree.Internal.Index.CompactAcc
import Database.LSMTree.Internal.Map.Range
import Test.QuickCheck
import Test.QuickCheck.Instances ()
import Test.QuickCheck.Monadic (PropertyM, monadicST, run)
import Test.Tasty (TestTree, localOption, testGroup)
import Test.Tasty.QuickCheck (QuickCheckTests (QuickCheckTests),
testProperty)
import Test.Util.Orphans ()
import Text.Printf (printf)
tests :: TestTree
tests = testGroup "Test.Database.LSMTree.Internal.Vector" [
localOption (QuickCheckTests 400) $
testProperty "propWriteRange" $ \v lb ub (x :: Word8) -> monadicST $ do
mv <- run $ VU.thaw v
propWriteRange mv lb ub x
, localOption (QuickCheckTests 400) $
testProperty "propUnsafeWriteRange" $ \v lb ub (x :: Word8) -> monadicST $ do
mv <- run $ VU.thaw v
propUnsafeWriteRange mv lb ub x
]
instance Arbitrary (Bound Int) where
arbitrary = oneof [
pure NoBound
, BoundInclusive <$> arbitrary
, BoundExclusive <$> arbitrary
]
shrink = \case
NoBound -> []
BoundInclusive x -> NoBound : (BoundInclusive <$> shrink x)
BoundExclusive x -> NoBound : (BoundInclusive <$> shrink x)
++ (BoundExclusive <$> shrink x)
intToInclusiveLowerBound :: Bound Int -> Int
intToInclusiveLowerBound = \case
NoBound -> 0
BoundInclusive i -> i
BoundExclusive i -> i + 1
intToInclusiveUpperBound :: VUM.Unbox a => VU.Vector a -> Bound Int -> Int
intToInclusiveUpperBound xs = \case
NoBound -> VU.length xs - 1
BoundInclusive i -> i
BoundExclusive i -> i - 1
-- | Safe version of 'unsafeWriteRange', used to test the unsafe version
-- against.
writeRange :: VU.Unbox a => VU.MVector s a -> Bound Int -> Bound Int -> a -> ST s Bool
writeRange !v !lb !ub !x
| 0 <= lb' && lb' < VUM.length v
, 0 <= ub' && ub' < VUM.length v
, lb' <= ub'
= forM_ [lb' .. ub'] (\j -> VUM.write v j x) >> pure True
| otherwise = pure False
where
!lb' = vectorLowerBound lb
!ub' = mvectorUpperBound v ub
propWriteRange :: forall s a. (VUM.Unbox a, Eq a, Show a)
=> VU.MVector s a
-> Bound Int
-> Bound Int
-> a
-> PropertyM (ST s) Property
propWriteRange mv1 lb ub x = run $ do
v1 <- VU.unsafeFreeze mv1
v2 <- VU.freeze mv1
b <- writeRange mv1 lb ub x
let xs1 = zip [0 :: Int ..] $ VU.toList v1
xs2 = zip [0..] $ VU.toList v2
lb' = intToInclusiveLowerBound lb
ub' = intToInclusiveUpperBound v1 ub
pure $ tabulate "range size" [showPowersOf10 (ub' - lb' + 1)] $
tabulate "vector size" [showPowersOf10 (VU.length v1)] $
if not b then
label "no suitable range" $ xs1 === xs2
else
counterexample (printf "lb=%d" lb') $
counterexample (printf "ub=%d" ub') $
conjoin [
counterexample "mismatch in prefix" $
take (lb' - 1) xs1 === take (lb' - 1) xs2
, counterexample "mismatch in suffix" $
drop (ub' + 1) xs1 === drop (ub' + 1) xs2
, counterexample "mimsatch in infix" $
fmap snd (drop lb' (take (ub' + 1) xs1)) ===
replicate (ub' - lb' + 1) x
]
propUnsafeWriteRange ::
forall s a. (VUM.Unbox a, Eq a, Show a)
=> VU.MVector s a
-> Bound Int
-> Bound Int
-> a
-> PropertyM (ST s) Property
propUnsafeWriteRange mv1 lb ub x = run $ do
v1 <- VU.unsafeFreeze mv1
v2 <- VU.freeze mv1
mv2 <- VU.unsafeThaw v2
b <- writeRange mv1 lb ub x
if not b then
pure $ label "no suitable range" True
else do
unsafeWriteRange mv2 lb ub x
pure $ v1 === v2