croaring-ffi-1.0: test/Test/Bitmap64/Iterator.hs
{-# LANGUAGE OverloadedRecordDot #-}
module Test.Bitmap64.Iterator (tests) where
import Control.Monad (forM)
import Data.Bitmap.Roaring.FFI.Bitmap64.Iterator (Iterator (..), Range (..))
import Data.Bitmap.Roaring.FFI.Bitmap64.Iterator qualified as Iterator
import Data.Set qualified as Set
import Data.Word (Word64)
import Foreign.Marshal.Array (allocaArray, peekArray)
import Foreign.Ptr (nullPtr)
import Foreign.Storable (peek, poke)
import Hedgehog (Property, evalIO, forAll)
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Test.Bitmap.Iterator (modelRanges, readAll)
import Test.Bitmap64 (fromValues64, genValues64)
import Test.Property (Test, returns)
import Test.Property qualified as Property
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testProperty)
tests :: TestTree
tests =
testGroup
"Bitmap64.Iterator"
[ testProperty "walks forward and backward" walks
, testProperty "reads values in chunks" chunks
, testProperty "reads maximal ranges" ranges
, testProperty "moves to the first value at or above a probe" moves
, testProperty "copies and reinitialises" copies
, testProperty "with and withLast scope an iterator" scoped
, testProperty "range poke and peek agree" rangeStorable
]
created :: IO Iterator -> Test Iterator
created = Property.owned (Iterator nullPtr) Iterator.free
walk :: (Iterator -> IO Bool) -> Iterator -> IO [Word64]
walk step iterator = do
present <- Iterator.hasValue iterator
if present then do
value <- Iterator.value iterator
_ <- step iterator
(value :) <$> walk step iterator
else
pure []
walks :: Property
walks =
Property.generated $ do
values <- forAll genValues64
bitmap <- fromValues64 values
let expected = Set.toAscList $ Set.fromList values
forward <- created $ Iterator.create bitmap
walk Iterator.advance forward `returns` expected
backward <- created $ Iterator.createLast bitmap
walk Iterator.previous backward `returns` reverse expected
chunks :: Property
chunks =
Property.generated $ do
values <- forAll genValues64
chunk <- forAll . Gen.int $ Range.linear 1 50
bitmap <- fromValues64 values
let expected = Set.toAscList $ Set.fromList values
forward <- created $ Iterator.create bitmap
readAll Iterator.read chunk forward `returns` expected
backward <- created $ Iterator.createLast bitmap
readAll Iterator.readBackward chunk backward `returns` reverse expected
ranges :: Property
ranges =
Property.generated $ do
values <- forAll genValues64
bitmap <- fromValues64 values
let expected = modelRanges . Set.toAscList $ Set.fromList values
forward <- created $ Iterator.create bitmap
readRangesWith Iterator.readRanges forward `returns` expected
backward <- created $ Iterator.createLast bitmap
readRangesWith Iterator.readPrevRanges backward `returns` reverse expected
where
readRangesWith readNext iterator =
allocaArray 3 $ \buffer -> do
let loop = do
count <- fromIntegral <$> readNext iterator buffer 3
found <- map (\range -> (range.min, range.max)) <$> peekArray count buffer
if count < 3 then pure found else (found <>) <$> loop
loop
moves :: Property
moves =
Property.generated $ do
values <- forAll genValues64
probes <- forAll . Gen.list (Range.linear 0 50) $ Gen.word64 Range.constantBounded
bitmap <- fromValues64 values
iterator <- created $ Iterator.create bitmap
let
model = Set.fromList values
probe value = do
Iterator.reinit bitmap iterator
present <- Iterator.moveEqualOrLarger iterator value
if present then Just <$> Iterator.value iterator else pure Nothing
forM probes probe `returns` map (`Set.lookupGE` model) probes
copies :: Property
copies =
Property.example $ do
bitmap <- fromValues64 [3, 2 ^ (40 :: Int), 9]
original <- created $ Iterator.create bitmap
Iterator.advance original `returns` True
copied <- created $ Iterator.copy original
Iterator.advance original `returns` True
Iterator.value original `returns` 2 ^ (40 :: Int)
walk Iterator.advance copied `returns` [9, 2 ^ (40 :: Int)]
evalIO $ Iterator.reinit bitmap copied
Iterator.value copied `returns` 3
evalIO $ Iterator.reinitLast bitmap copied
walk Iterator.previous copied `returns` [2 ^ (40 :: Int), 9, 3]
rangeStorable :: Property
rangeStorable =
Property.example $ do
let range = Range 5 (2 ^ (40 :: Int))
ptr <- Property.array 1
evalIO $ poke ptr range
peek ptr `returns` range
scoped :: Property
scoped =
Property.generated $ do
values <- forAll genValues64
bitmap <- fromValues64 values
let expected = Set.toAscList $ Set.fromList values
Iterator.with bitmap (walk Iterator.advance) `returns` expected
Iterator.withLast bitmap (walk Iterator.previous) `returns` reverse expected