packages feed

nonempty-vector-0.3: test/Test/Data/Vector/NonEmpty/Modification.hs

-- |
-- Module      : Test.Data.Vector.NonEmpty.Modification
-- Copyright   : (c) 2019-2026 Emily Pillmore
-- License     : BSD-style
--
-- Maintainer  : Emily Pillmore <emilypi@cohomolo.gy>
-- Stability   : experimental
-- Portability : non-portable
--
-- Properties for bulk updates, accumulations, permutations, and safe
-- destructive updates.
--
module Test.Data.Vector.NonEmpty.Modification
( tests
) where


import qualified Data.List as List
import Data.Maybe (mapMaybe)
import qualified Data.Vector as V
import qualified Data.Vector.Mutable as MV
import Data.Vector.NonEmpty (NonEmptyVector)
import qualified Data.Vector.NonEmpty as NEV

import Test.Data.Vector.NonEmpty.Util
import Test.QuickCheck
import Test.Tasty
import Test.Tasty.QuickCheck


tests :: TestTree
tests = testGroup "Modification"
    [ testGroup "Bulk updates"
      [ testProperty "prop_bulk_update" prop_bulk_update
      , testProperty "prop_bulk_update_out_of_bounds" prop_bulk_update_out_of_bounds
      , testProperty "prop_update" prop_update
      , testProperty "prop_update_" prop_update_
      , testProperty "prop_unsafeUpd" prop_unsafeUpd
      , testProperty "prop_unsafeUpdate" prop_unsafeUpdate
      , testProperty "prop_unsafeUpdate_" prop_unsafeUpdate_
      ]
    , testGroup "Accumulations"
      [ testProperty "prop_accum" prop_accum
      , testProperty "prop_accum_out_of_bounds" prop_accum_out_of_bounds
      , testProperty "prop_accumulate" prop_accumulate
      , testProperty "prop_accumulate_" prop_accumulate_
      , testProperty "prop_unsafeAccum" prop_unsafeAccum
      , testProperty "prop_unsafeAccumulate" prop_unsafeAccumulate
      , testProperty "prop_unsafeAccumulate_" prop_unsafeAccumulate_
      ]
    , testGroup "Permutations"
      [ testProperty "prop_reverse" prop_reverse
      , testProperty "prop_reverse_involutive" prop_reverse_involutive
      , testProperty "prop_backpermute" prop_backpermute
      , testProperty "prop_backpermute_out_of_bounds" prop_backpermute_out_of_bounds
      , testProperty "prop_unsafeBackpermute" prop_unsafeBackpermute
      ]
    , testGroup "Safe destructive updates"
      [ testProperty "prop_modify" prop_modify
      , testProperty "prop_modify_preserves_input" prop_modify_preserves_input
      ]
    ]

-- ---------------------------------------------------------------------- --
-- Models

-- | Later updates to the same index win.
--
updateList :: [(Int, a)] -> [a] -> [a]
updateList us as = List.foldl' (\acc (i, a) -> adjustAt i (const a) acc) as us

accumList :: (a -> b -> a) -> [(Int, b)] -> [a] -> [a]
accumList f us as = List.foldl' (\acc (i, b) -> adjustAt i (`f` b) acc) as us

-- ---------------------------------------------------------------------- --
-- Bulk updates

prop_bulk_update :: NonEmptyVector Int -> Property
prop_bulk_update v = forAll (genUpdates v) $ \us ->
    elems (v NEV.// us) === updateList us (elems v)

prop_bulk_update_out_of_bounds :: NonEmptyVector Int -> Int -> Property
prop_bulk_update_out_of_bounds v a = forAll (genOutOfBounds v) $ \i ->
    throws (v NEV.// [(i, a)])

prop_update :: NonEmptyVector Int -> Property
prop_update v = forAll (genUpdates v) $ \us ->
    elems (NEV.update v (V.fromList us)) === updateList us (elems v)

-- Surplus values beyond the index vector are ignored.
prop_update_ :: NonEmptyVector Int -> [Int] -> Property
prop_update_ v surplus = forAll (genUpdates v) $ \us ->
    let (is, as) = List.unzip us
    in elems (NEV.update_ v (V.fromList is) (V.fromList (as ++ surplus)))
         === updateList us (elems v)

prop_unsafeUpd :: NonEmptyVector Int -> Property
prop_unsafeUpd v = forAll (genUpdates v) $ \us ->
    elems (NEV.unsafeUpd v us) === updateList us (elems v)

prop_unsafeUpdate :: NonEmptyVector Int -> Property
prop_unsafeUpdate v = forAll (genUpdates v) $ \us ->
    elems (NEV.unsafeUpdate v (V.fromList us)) === updateList us (elems v)

prop_unsafeUpdate_ :: NonEmptyVector Int -> Property
prop_unsafeUpdate_ v = forAll (genUpdates v) $ \us ->
    let (is, as) = List.unzip us
    in elems (NEV.unsafeUpdate_ v (V.fromList is) (V.fromList as))
         === updateList us (elems v)

-- ---------------------------------------------------------------------- --
-- Accumulations

prop_accum :: Fun (Int, Int) Int -> NonEmptyVector Int -> Property
prop_accum (Fn2 f) v = forAll (genUpdates v) $ \us ->
    elems (NEV.accum f v us) === accumList f us (elems v)

prop_accum_out_of_bounds :: NonEmptyVector Int -> Int -> Property
prop_accum_out_of_bounds v b = forAll (genOutOfBounds v) $ \i ->
    throws (NEV.accum (+) v [(i, b)])

prop_accumulate :: Fun (Int, Int) Int -> NonEmptyVector Int -> Property
prop_accumulate (Fn2 f) v = forAll (genUpdates v) $ \us ->
    elems (NEV.accumulate f v (V.fromList us)) === accumList f us (elems v)

prop_accumulate_ :: Fun (Int, Int) Int -> NonEmptyVector Int -> Property
prop_accumulate_ (Fn2 f) v = forAll (genUpdates v) $ \us ->
    let (is, bs) = List.unzip us
    in elems (NEV.accumulate_ f v (V.fromList is) (V.fromList bs))
         === accumList f us (elems v)

prop_unsafeAccum :: Fun (Int, Int) Int -> NonEmptyVector Int -> Property
prop_unsafeAccum (Fn2 f) v = forAll (genUpdates v) $ \us ->
    elems (NEV.unsafeAccum f v us) === accumList f us (elems v)

prop_unsafeAccumulate :: Fun (Int, Int) Int -> NonEmptyVector Int -> Property
prop_unsafeAccumulate (Fn2 f) v = forAll (genUpdates v) $ \us ->
    elems (NEV.unsafeAccumulate f v (V.fromList us))
      === accumList f us (elems v)

prop_unsafeAccumulate_ :: Fun (Int, Int) Int -> NonEmptyVector Int -> Property
prop_unsafeAccumulate_ (Fn2 f) v = forAll (genUpdates v) $ \us ->
    let (is, bs) = List.unzip us
    in elems (NEV.unsafeAccumulate_ f v (V.fromList is) (V.fromList bs))
         === accumList f us (elems v)

-- ---------------------------------------------------------------------- --
-- Permutations

prop_reverse :: NonEmptyVector Int -> Property
prop_reverse v = elems (NEV.reverse v) === List.reverse (elems v)

prop_reverse_involutive :: NonEmptyVector Int -> Property
prop_reverse_involutive v = NEV.reverse (NEV.reverse v) === v

prop_backpermute :: NonEmptyVector Int -> Property
prop_backpermute v = forAll (genIndices v) $ \is ->
    elems (NEV.backpermute v is)
      === mapMaybe (`lookupList` elems v) (elems is)

prop_backpermute_out_of_bounds :: NonEmptyVector Int -> Property
prop_backpermute_out_of_bounds v = forAll (genOutOfBounds v) $ \i ->
    throws (NEV.backpermute v (NEV.singleton i))

prop_unsafeBackpermute :: NonEmptyVector Int -> Property
prop_unsafeBackpermute v = forAll (genIndices v) $ \is ->
    elems (NEV.unsafeBackpermute v is)
      === mapMaybe (`lookupList` elems v) (elems is)

-- ---------------------------------------------------------------------- --
-- Safe destructive updates

prop_modify :: NonEmptyVector Int -> Int -> Property
prop_modify v a = forAll ((,) <$> genIndex v <*> genIndex v) $ \(i, j) ->
    elems (NEV.modify (\mv -> MV.write mv i a >> MV.swap mv i j) v)
      === swapAt i j (adjustAt i (const a) (elems v))

-- The input vector is copied, never mutated in place.
prop_modify_preserves_input :: NonEmptyList Int -> Int -> Property
prop_modify_preserves_input (NonEmpty as) a =
    (elems w, elems v) === (List.map (const a) as, as)
  where
    v = vec as
    w = NEV.modify (`MV.set` a) v