{-# LANGUAGE BangPatterns #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeApplications #-} -- | -- Module : Test.Data.Vector.NonEmpty.MutableU -- Copyright : (c) 2019-2026 Emily Pillmore, Koz Ross -- License : BSD-style -- -- Maintainer : Emily Pillmore -- Stability : experimental -- Portability : non-portable -- -- Properties for non-empty mutable vectors, observed by freezing them -- into lists. -- module Test.Data.Vector.NonEmpty.MutableU ( tests ) where import Control.Monad.ST (ST, runST) import qualified Data.List as List import qualified Data.List.NonEmpty as NonEmpty import Data.STRef (modifySTRef', newSTRef, readSTRef) import qualified Data.Vector.Unboxed as V import Data.Vector.Unboxed.Mutable (MVector) import qualified Data.Vector.Unboxed.Mutable as MV import Data.Vector.NonEmpty.Unboxed.Internal ( NonEmptyMUVector(..) , NonEmptyUVector(..) , NonEmptyIOUVector , NonEmptySTUVector ) import qualified Data.Vector.NonEmpty.Unboxed.Mutable as NEMV import Test.Data.Vector.NonEmpty.Util import Test.QuickCheck import Test.Tasty import Test.Tasty.QuickCheck tests :: TestTree tests = testGroup "Mutable (unboxed)" [ testGroup "Length information" [ testProperty "prop_length" prop_length ] , testGroup "Extracting subvectors" [ testProperty "prop_slice" prop_slice , testProperty "prop_slice_shares" prop_slice_shares , testProperty "prop_init" prop_init , testProperty "prop_tail" prop_tail , testProperty "prop_take" prop_take , testProperty "prop_drop" prop_drop , testProperty "prop_splitAt" prop_splitAt , testProperty "prop_unsafeSlice" prop_unsafeSlice , testProperty "prop_unsafeTake" prop_unsafeTake , testProperty "prop_unsafeDrop" prop_unsafeDrop ] , testGroup "Overlapping" [ testProperty "prop_overlaps_self" prop_overlaps_self , testProperty "prop_overlaps_distinct" prop_overlaps_distinct , testProperty "prop_overlaps_slices" prop_overlaps_slices ] , testGroup "Conversions" [ testProperty "prop_fromMVector" prop_fromMVector , testProperty "prop_toMVector" prop_toMVector , testProperty "prop_unsafeFromMVector" prop_unsafeFromMVector ] , testGroup "Initialisation" [ testProperty "prop_new" prop_new , testProperty "prop_new1" prop_new1 , testProperty "prop_unsafeNew" prop_unsafeNew , testProperty "prop_replicate" prop_replicate , testProperty "prop_replicate1" prop_replicate1 , testProperty "prop_replicateM" prop_replicateM , testProperty "prop_replicate1M" prop_replicate1M , testProperty "prop_clone" prop_clone ] , testGroup "Growing" [ testProperty "prop_grow" prop_grow , testProperty "prop_grow_negative" prop_grow_negative , testProperty "prop_unsafeGrow" prop_unsafeGrow ] , testGroup "Accessing individual elements" [ testProperty "prop_read" prop_read , testProperty "prop_read_out_of_bounds" prop_read_out_of_bounds , testProperty "prop_write" prop_write , testProperty "prop_write_out_of_bounds" prop_write_out_of_bounds , testProperty "prop_modify" prop_modify , testProperty "prop_swap" prop_swap , testProperty "prop_unsafeRead" prop_unsafeRead , testProperty "prop_unsafeWrite" prop_unsafeWrite , testProperty "prop_unsafeModify" prop_unsafeModify , testProperty "prop_unsafeSwap" prop_unsafeSwap ] , testGroup "Modifying vectors" [ testProperty "prop_nextPermutation" prop_nextPermutation , testProperty "prop_nextPermutation_last" prop_nextPermutation_last ] , testGroup "Filling and copying" [ testProperty "prop_set" prop_set , testProperty "prop_copy" prop_copy , testProperty "prop_copy_length_mismatch" prop_copy_length_mismatch , testProperty "prop_unsafeCopy" prop_unsafeCopy , testProperty "prop_move" prop_move , testProperty "prop_unsafeMove" prop_unsafeMove ] ] type V = NonEmptyUVector Int -- ---------------------------------------------------------------------- -- -- Running mutable vectors thaw :: V.Unbox a => NonEmptyUVector a -> ST s (NonEmptySTUVector s a) thaw = fmap NonEmptyMUVector . V.thaw . _neUvec thawIO :: V.Unbox a => NonEmptyUVector a -> IO (NonEmptyIOUVector a) thawIO = fmap NonEmptyMUVector . V.thaw . _neUvec -- | Run a computation against a fresh mutable copy of the vector. -- withThawed :: V.Unbox a => NonEmptyUVector a -> (forall s. NonEmptySTUVector s a -> ST s b) -> b withThawed v k = runST (thaw v >>= k) freezeList :: V.Unbox a => MVector s a -> ST s [a] freezeList = fmap V.toList . V.freeze freeze :: V.Unbox a => NonEmptySTUVector s a -> ST s [a] freeze = freezeList . _nemUvec -- | A mutable operation's effect on the vector's contents. -- mutated :: V -> (forall s. NonEmptySTUVector s Int -> ST s ()) -> [Int] mutated v k = withThawed v (\mv -> k mv >> freeze mv) -- ---------------------------------------------------------------------- -- -- Length information prop_length :: V -> Property prop_length v = withThawed v (pure . NEMV.length) === List.length (elemsU v) -- ---------------------------------------------------------------------- -- -- Extracting subvectors prop_slice :: V -> Property prop_slice v = forAll (genSliceU v) $ \(i, m) -> withThawed v (freezeList . NEMV.slice i m) === List.take m (List.drop i (elemsU v)) -- Subvectors are views: writes through them reach the parent vector. prop_slice_shares :: V -> Int -> Property prop_slice_shares v a = forAll (genIndexU v) $ \i -> mutated v (\mv -> MV.write (NEMV.drop i mv) 0 a) === adjustAt i (const a) (elemsU v) prop_init :: V -> Property prop_init v = withThawed v (freezeList . NEMV.init) === List.take (List.length (elemsU v) - 1) (elemsU v) prop_tail :: V -> Property prop_tail v = withThawed v (freezeList . NEMV.tail) === List.drop 1 (elemsU v) prop_take :: Int -> V -> Property prop_take n v = withThawed v (freezeList . NEMV.take n) === List.take n (elemsU v) prop_drop :: Int -> V -> Property prop_drop n v = withThawed v (freezeList . NEMV.drop n) === List.drop n (elemsU v) prop_splitAt :: Int -> V -> Property prop_splitAt n v = withThawed v (\mv -> let (as, bs) = NEMV.splitAt n mv in (,) <$> freezeList as <*> freezeList bs) === List.splitAt n (elemsU v) prop_unsafeSlice :: V -> Property prop_unsafeSlice v = forAll (genSliceU v) $ \(i, m) -> withThawed v (freezeList . NEMV.unsafeSlice i m) === List.take m (List.drop i (elemsU v)) prop_unsafeTake :: V -> Property prop_unsafeTake v = forAll (choose (0, List.length (elemsU v))) $ \n -> withThawed v (freezeList . NEMV.unsafeTake n) === List.take n (elemsU v) prop_unsafeDrop :: V -> Property prop_unsafeDrop v = forAll (choose (0, List.length (elemsU v))) $ \n -> withThawed v (freezeList . NEMV.unsafeDrop n) === List.drop n (elemsU v) -- ---------------------------------------------------------------------- -- -- Overlapping prop_overlaps_self :: V -> Property prop_overlaps_self v = withThawed v (\mv -> pure (NEMV.overlaps mv mv)) === True prop_overlaps_distinct :: V -> V -> Property prop_overlaps_distinct v w = runST (NEMV.overlaps <$> thaw v <*> thaw w) === False -- Adjacent slices are disjoint; extending one by an element overlaps them. prop_overlaps_slices :: V -> Property prop_overlaps_slices v = n >= 2 ==> forAll (choose (1, n - 1)) $ \k -> withThawed v (\(NonEmptyMUVector mv) -> let front m = NonEmptyMUVector (MV.take m mv) back = NonEmptyMUVector (MV.drop k mv) in pure (NEMV.overlaps (front k) back, NEMV.overlaps (front (k + 1)) back)) === (False, True) where n = List.length (elemsU v) -- ---------------------------------------------------------------------- -- -- Conversions prop_fromMVector :: [Int] -> Property prop_fromMVector as = runST (V.thaw (V.fromList as) >>= traverse freeze . NEMV.fromMVector) === whenNonEmpty as prop_toMVector :: V -> Property prop_toMVector v = withThawed v (freezeList . NEMV.toMVector) === elemsU v prop_unsafeFromMVector :: V -> Property prop_unsafeFromMVector v = runST (V.thaw (_neUvec v) >>= freeze . NEMV.unsafeFromMVector) === elemsU v -- ---------------------------------------------------------------------- -- -- Initialisation prop_new :: NonNegative Int -> Property prop_new (NonNegative n) = runST (fmap NEMV.length <$> NEMV.new @_ @Int n) === if n > 0 then Just n else Nothing prop_new1 :: Int -> Property prop_new1 n = runST (NEMV.length <$> NEMV.new1 @_ @Int n) === max n 1 prop_unsafeNew :: NonNegative Int -> Property prop_unsafeNew (NonNegative n) = runST (fmap NEMV.length <$> NEMV.unsafeNew @_ @Int n) === if n > 0 then Just n else Nothing prop_replicate :: Int -> Int -> Property prop_replicate n a = runST (NEMV.replicate n a >>= traverse freeze) === whenNonEmpty (List.replicate n a) prop_replicate1 :: Int -> Int -> Property prop_replicate1 n a = runST (NEMV.replicate1 n a >>= freeze) === List.replicate (max n 1) a -- The action runs once per element, in order. prop_replicateM :: Int -> Property prop_replicateM n = runST (counter >>= NEMV.replicateM n >>= traverse freeze) === whenNonEmpty [1 .. n] prop_replicate1M :: Int -> Property prop_replicate1M n = runST (counter >>= NEMV.replicate1M n >>= traverse freeze) === Just [1 .. max n 1] -- | An action yielding 1, 2, 3, ... on successive runs. -- counter :: ST s (ST s Int) counter = do ref <- newSTRef 0 pure (modifySTRef' ref (+ 1) >> readSTRef ref) -- A clone has the same contents but no longer aliases the original. prop_clone :: V -> Int -> Property prop_clone v a = withThawed v (\mv -> do c <- NEMV.clone mv MV.set (_nemUvec c) a (,) <$> freeze mv <*> freeze c) === (elemsU v, List.map (const a) (elemsU v)) -- ---------------------------------------------------------------------- -- -- Growing prop_grow :: V -> NonNegative Int -> Property prop_grow v (NonNegative n) = withThawed v (\mv -> do g <- NEMV.grow mv n as <- freezeList (MV.take (NEMV.length mv) (_nemUvec g)) pure (NEMV.length g, as)) === (List.length (elemsU v) + n, elemsU v) prop_grow_negative :: V -> Positive Int -> Property prop_grow_negative v (Positive n) = throwsIO (thawIO v >>= \mv -> NEMV.grow mv (negate n)) prop_unsafeGrow :: V -> NonNegative Int -> Property prop_unsafeGrow v (NonNegative n) = withThawed v (\mv -> do g <- NEMV.unsafeGrow mv n as <- freezeList (MV.take (NEMV.length mv) (_nemUvec g)) pure (NEMV.length g, as)) === (List.length (elemsU v) + n, elemsU v) -- ---------------------------------------------------------------------- -- -- Accessing individual elements prop_read :: V -> Property prop_read v = forAll (genIndexU v) $ \i -> Just (withThawed v (`NEMV.read` i)) === lookupList i (elemsU v) prop_read_out_of_bounds :: V -> Property prop_read_out_of_bounds v = forAll (genOutOfBoundsU v) $ \i -> throwsIO (thawIO v >>= (`NEMV.read` i)) prop_write :: V -> Int -> Property prop_write v a = forAll (genIndexU v) $ \i -> mutated v (\mv -> NEMV.write mv i a) === adjustAt i (const a) (elemsU v) prop_write_out_of_bounds :: V -> Int -> Property prop_write_out_of_bounds v a = forAll (genOutOfBoundsU v) $ \i -> throwsIO (thawIO v >>= \mv -> NEMV.write mv i a) prop_modify :: Fun Int Int -> V -> Property prop_modify (Fn f) v = forAll (genIndexU v) $ \i -> mutated v (\mv -> NEMV.modify mv f i) === adjustAt i f (elemsU v) prop_swap :: V -> Property prop_swap v = forAll ((,) <$> genIndexU v <*> genIndexU v) $ \(i, j) -> mutated v (\mv -> NEMV.swap mv i j) === swapAt i j (elemsU v) prop_unsafeRead :: V -> Property prop_unsafeRead v = forAll (genIndexU v) $ \i -> Just (withThawed v (`NEMV.unsafeRead` i)) === lookupList i (elemsU v) prop_unsafeWrite :: V -> Int -> Property prop_unsafeWrite v a = forAll (genIndexU v) $ \i -> mutated v (\mv -> NEMV.unsafeWrite mv i a) === adjustAt i (const a) (elemsU v) prop_unsafeModify :: Fun Int Int -> V -> Property prop_unsafeModify (Fn f) v = forAll (genIndexU v) $ \i -> mutated v (\mv -> NEMV.unsafeModify mv f i) === adjustAt i f (elemsU v) prop_unsafeSwap :: V -> Property prop_unsafeSwap v = forAll ((,) <$> genIndexU v <*> genIndexU v) $ \(i, j) -> mutated v (\mv -> NEMV.unsafeSwap mv i j) === swapAt i j (elemsU v) -- ---------------------------------------------------------------------- -- -- Modifying vectors -- Starting from sorted order, successive permutations enumerate every -- distinct permutation in lexicographic order. prop_nextPermutation :: Property prop_nextPermutation = forAll (choose (1, 5)) $ \n -> forAll (vectorOf n (choose (0, 3))) $ \as -> let sorted = List.sort (as :: [Int]) in withThawed (vecU sorted) enumerate === List.map NonEmpty.head (NonEmpty.group (List.sort (List.permutations sorted))) where enumerate :: NonEmptySTUVector s Int -> ST s [[Int]] enumerate mv = do as <- freeze mv more <- NEMV.nextPermutation mv if more then (as :) <$> enumerate mv else pure [as] -- The last permutation has no successor and is left unchanged. prop_nextPermutation_last :: V -> Property prop_nextPermutation_last v = withThawed (vecU descending) (\mv -> (,) <$> NEMV.nextPermutation mv <*> freeze mv) === (False, descending) where descending = List.sortBy (flip compare) (elemsU v) -- ---------------------------------------------------------------------- -- -- Filling and copying prop_set :: V -> Int -> Property prop_set v a = mutated v (`NEMV.set` a) === List.map (const a) (elemsU v) -- | Copy @v@ over a vector of the same length, returning the target and -- source contents afterwards. -- copied :: (forall s. NonEmptySTUVector s Int -> NonEmptySTUVector s Int -> ST s ()) -> V -> [Int] -> ([Int], [Int]) copied cp v bs = runST $ do dst <- thaw (vecU bs) src <- thaw v cp dst src (,) <$> freeze dst <*> freeze src prop_copy :: V -> Property prop_copy v = forAll (vectorOf (List.length (elemsU v)) arbitrary) $ \bs -> copied NEMV.copy v bs === (elemsU v, elemsU v) prop_copy_length_mismatch :: V -> Property prop_copy_length_mismatch v = throwsIO $ do dst <- thawIO (vecU (0 : elemsU v)) src <- thawIO v NEMV.copy dst src prop_unsafeCopy :: V -> Property prop_unsafeCopy v = forAll (vectorOf (List.length (elemsU v)) arbitrary) $ \bs -> copied NEMV.unsafeCopy v bs === (elemsU v, elemsU v) -- | Move between the overlapping slices @[0, n - 1)@ and @[1, n)@ of one -- vector, in both directions. -- moved :: (forall s. NonEmptySTUVector s Int -> NonEmptySTUVector s Int -> ST s ()) -> V -> ([Int], [Int]) moved mv v = (shift True, shift False) where n = List.length (elemsU v) shift left = withThawed v $ \(NonEmptyMUVector raw) -> do let lo = NonEmptyMUVector (MV.slice 0 (n - 1) raw) hi = NonEmptyMUVector (MV.slice 1 (n - 1) raw) if left then mv lo hi else mv hi lo freezeList raw prop_move :: V -> Property prop_move v = List.length as >= 2 ==> moved NEMV.move v === (List.drop 1 as ++ List.drop (n - 1) as, List.take 1 as ++ List.take (n - 1) as) where as = elemsU v n = List.length as prop_unsafeMove :: V -> Property prop_unsafeMove v = List.length as >= 2 ==> moved NEMV.unsafeMove v === (List.drop 1 as ++ List.drop (n - 1) as, List.take 1 as ++ List.take (n - 1) as) where as = elemsU v n = List.length as