packages feed

stm-containers-1.2.2: test/Suites/Map.hs

module Suites.Map (tests) where

import qualified Control.Foldl as Foldl
import Control.Monad.Free
import qualified Data.HashMap.Strict as HashMap
import qualified DeferredFolds.UnfoldlM as UnfoldlM
import qualified Focus
import qualified StmContainers.Map as StmMap
import qualified Suites.Map.Update as Update
import Test.QuickCheck.Instances ()
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Prelude hiding (choose)

interpretStmMapUpdate :: (Hashable k) => Update.Update k v -> STM (StmMap.Map k v)
interpretStmMapUpdate update = do
  m <- StmMap.new
  flip iterM update $ \case
    Update.Insert k v c -> StmMap.insert v k m >> c
    Update.Delete k c -> StmMap.delete k m >> c
    Update.Adjust f k c -> StmMap.focus ((Focus.adjustM . fmap return) f) k m >> c
  return m

interpretHashMapUpdate :: (Hashable k) => Update.Update k v -> HashMap.HashMap k v
interpretHashMapUpdate update =
  flip execState HashMap.empty $ flip iterM update $ \case
    Update.Insert k v c -> modify (HashMap.insert k v) >> c
    Update.Delete k c -> modify (HashMap.delete k) >> c
    Update.Adjust f k c -> modify (adjust f k) >> c
  where
    adjust f k m =
      case HashMap.lookup k m of
        Nothing -> m
        Just a -> HashMap.insert k (f a) m

stmMapToHashMap :: (Hashable k) => StmMap.Map k v -> STM (HashMap.HashMap k v)
stmMapToHashMap = UnfoldlM.foldM (Foldl.generalize Foldl.hashMap) . StmMap.unfoldlM

stmMapFromList :: (Hashable k) => [(k, v)] -> STM (StmMap.Map k v)
stmMapFromList list = do
  m <- StmMap.new
  forM_ list $ \(k, v) -> StmMap.insert v k m
  return m

stmMapToList :: StmMap.Map k v -> STM [(k, v)]
stmMapToList = UnfoldlM.foldM (Foldl.generalize Foldl.list) . StmMap.unfoldlM

-- * Intentional hash collision simulation

-------------------------

newtype TestKey = TestKey Word8
  deriving (Eq, Ord, Show)

instance Arbitrary TestKey where
  arbitrary = TestKey <$> choose (0, 63)

instance Hashable TestKey where
  hashWithSalt salt (TestKey w) =
    if odd w
      then hashWithSalt salt (pred w)
      else hashWithSalt salt w

-- * Tests

-------------------------

tests :: [TestTree]
tests =
  [ testProperty "sizeAndList"
      $ let gen = do
              keys <- nub <$> listOf (choose ('a', 'z'))
              mapM (liftA2 (flip (,)) (choose (0, 99 :: Int)) . pure) keys
            prop list =
              length list == stmMapLength
              where
                stmMapLength =
                  unsafePerformIO $ atomically $ do
                    x <- stmMapFromList list
                    StmMap.size x
         in forAll gen prop,
    testProperty "fromListToListHashMapIsomorphism" $ \(list :: [(Text, Int)]) ->
      let hashMapList = HashMap.toList (HashMap.fromList list)
          stmMapList = unsafePerformIO $ atomically $ stmMapFromList list >>= stmMapToList
       in sort hashMapList === sort stmMapList,
    testProperty "updatesProduceTheSameEffectAsInHashMap" $ \(updates :: [Update.Update TestKey ()]) ->
      let update = sequence_ updates
          hashMap = interpretHashMapUpdate update
          hashMapSize = HashMap.size hashMap
          hashMapList = sort (HashMap.toList hashMap)
          (stmMapList, stmMapSize) = unsafePerformIO $ atomically $ do
            stmMap <- interpretStmMapUpdate update
            size <- StmMap.size stmMap
            stmMapList <- stmMapToList stmMap
            return (sort stmMapList, size)
       in (hashMapSize, hashMapList) === (stmMapSize, stmMapList),
    testCase "focusInsert" $ do
      assertEqual "" (HashMap.fromList [('a', 1), ('b', 2)]) =<< do
        atomically $ do
          m <- StmMap.new
          StmMap.focus (Focus.insert 1) 'a' m
          StmMap.focus (Focus.insert 2) 'b' m
          stmMapToHashMap m,
    testCase "focusInsertAndDelete" $ do
      assertEqual "" (HashMap.fromList [('b', 2)]) =<< do
        atomically $ do
          m <- StmMap.new
          StmMap.focus (Focus.insert 1) 'a' m
          StmMap.focus (Focus.insert 2) 'b' m
          StmMap.focus (Focus.delete) 'a' m
          stmMapToHashMap m,
    testCase "focusInsertAndDeleteWithCollision" $ do
      assertEqual "" (HashMap.fromList [(TestKey 32, 2)]) =<< do
        atomically $ do
          m <- StmMap.new
          StmMap.focus (Focus.insert 2) (TestKey 32) m
          StmMap.focus (Focus.delete) (TestKey 1) m
          stmMapToHashMap m,
    testCase "insert" $ do
      assertEqual "" (HashMap.fromList [('a', 1), ('b', 2), ('c', 3)]) =<< do
        atomically $ do
          m <- StmMap.new
          StmMap.insert 1 'a' m
          StmMap.insert 3 'c' m
          StmMap.insert 2 'b' m
          stmMapToHashMap m,
    testCase "insert2" $ do
      assertEqual "" (HashMap.fromList [(111 :: Int, ()), (207, ())]) =<< do
        atomically $ do
          m <- StmMap.new
          StmMap.insert () 111 m
          StmMap.insert () 207 m
          stmMapToHashMap m,
    testCase "adjust" $ do
      assertEqual "" (HashMap.fromList [('a', 1), ('b', 3)]) =<< do
        atomically $ do
          m <- stmMapFromList [('a', 1), ('b', 2)]
          StmMap.focus (Focus.adjustM (const $ return 3)) 'b' m
          stmMapToHashMap m,
    testCase "focusReachesTheTarget" $ do
      assertEqual "" (Just 2) =<< do
        atomically $ do
          m <- stmMapFromList [('a', 1), ('b', 2)]
          StmMap.focus Focus.lookup 'b' m,
    testCase "notNull" $ do
      assertEqual "" False =<< do
        atomically $ StmMap.null =<< stmMapFromList [('a', ())],
    testCase "nullAfterDeletingTheLastElement" $ do
      assertEqual "" True =<< do
        atomically $ do
          m <- stmMapFromList [('a', ())]
          StmMap.delete 'a' m
          StmMap.null m
  ]