packages feed

unimap-0.1.0: test/Main.hs

{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Control.Monad.State.Strict (MonadState (..), State, StateT (..), evalStateT, runState)
import Control.Monad.Trans (lift)
import Data.Bifunctor (bimap, first)
import Data.Char (chr, ord)
import Data.Semigroup (Max)
import Data.Void (Void)
import IntLike.Map (IntLikeMap)
import IntLike.Map qualified as ILM
import IntLike.Set (IntLikeSet)
import IntLike.Set qualified as ILS
import PropUnit (TestTree, testGroup, testMain, testUnit, (===))
import Unimap qualified as UM

runS :: (Monad m) => s -> StateT s m () -> m ()
runS = flip evalStateT

testS :: (Monad m) => (s -> m a) -> StateT s m a
testS p = get >>= lift . p

applyS :: (Monad m) => State s a -> StateT s m a
applyS = state . runState

applyTestS :: (Monad m) => State s a -> (a -> s -> m b) -> StateT s m b
applyTestS act check = do
  a <- applyS act
  s <- get
  lift (check a s)

newtype V = V {unV :: Int}
  deriving newtype (Eq)
  deriving stock (Show)

toV :: Char -> V
toV = V . ord

fromV :: V -> Char
fromV = chr . unV

setV :: String -> IntLikeSet V
setV = ILS.fromList . fmap toV

mapV :: [(Char, a)] -> IntLikeMap V a
mapV = ILM.fromList . fmap (first toV)

mapVV :: [(Char, Char)] -> IntLikeMap V V
mapVV = ILM.fromList . fmap (bimap toV toV)

multiMapVV :: [(Char, String)] -> IntLikeMap V (IntLikeSet V)
multiMapVV = ILM.fromList . fmap (bimap toV setV)

type UMV = UM.UnionMap V (Max Int)

emptyUMV :: UMV
emptyUMV = UM.empty

mergeOneUMV :: UM.MergeOne Void (Max Int) ()
mergeOneUMV = UM.concatMergeOne

testUmSimple :: TestTree
testUmSimple = testUnit "UM simple" $ runS emptyUMV $ do
  -- start with empty map
  testS $ \um -> UM.size um === 0
  -- add 'a'
  applyTestS (UM.addM (toV 'a') 1) $ \res um -> do
    res === UM.AddValAdded
    UM.size um === 1
    UM.trace (toV 'a') um === UM.TraceResFound (toV 'a') 1 []
    UM.values um === mapV [('a', 1)]
  -- lookup 'a'
  applyTestS (UM.lookupM (toV 'a')) $ \res _ ->
    res === UM.LookupValOk (toV 'a') 1 UM.ChangedNo
  -- try to add 'a' again
  applyTestS (UM.addM (toV 'a') 1) $ \res um -> do
    res === UM.AddValDuplicate
    UM.size um === 1
  -- add 'b' and 'c' and check them
  _ <- applyS (UM.addM (toV 'b') 2)
  _ <- applyS (UM.addM (toV 'c') 3)
  testS $ \um -> do
    UM.size um === 3
    UM.trace (toV 'b') um === UM.TraceResFound (toV 'b') 2 []
    UM.trace (toV 'c') um === UM.TraceResFound (toV 'c') 3 []
    UM.values um === mapV [('a', 1), ('b', 2), ('c', 3)]
  applyTestS UM.equivM $ \(UM.Equiv fwd bwd) _ -> do
    fwd === multiMapVV [('a', ""), ('b', ""), ('c', "")]
    bwd === mapVV []
  -- merge 'a' and 'c'
  applyTestS (UM.mergeOneM mergeOneUMV (toV 'a') (toV 'c')) $ \res um -> do
    res === UM.MergeValMerged (toV 'a') 3 ()
    UM.size um === 3
    UM.trace (toV 'a') um === UM.TraceResFound (toV 'a') 3 []
    UM.trace (toV 'b') um === UM.TraceResFound (toV 'b') 2 []
    UM.trace (toV 'c') um === UM.TraceResFound (toV 'a') 3 [toV 'c']
    UM.values um === mapV [('a', 3), ('b', 2)]
  applyTestS UM.equivM $ \(UM.Equiv fwd bwd) _ -> do
    fwd === multiMapVV [('a', "c"), ('b', "")]
    bwd === mapVV [('c', 'a')]
  -- try to merge again
  applyTestS (UM.mergeOneM mergeOneUMV (toV 'a') (toV 'c')) $ \res _ ->
    res === UM.MergeValMerged (toV 'a') 3 ()
  -- and the other way around
  applyTestS (UM.mergeOneM mergeOneUMV (toV 'c') (toV 'a')) $ \res _ ->
    res === UM.MergeValMerged (toV 'a') 3 ()
  -- and a non-existent merge
  applyTestS (UM.mergeOneM mergeOneUMV (toV 'b') (toV 'z')) $ \res _ ->
    res === UM.MergeValMissing (toV 'z')
  -- and creating merge
  applyTestS (UM.mergeOneM mergeOneUMV (toV 'z') (toV 'b')) $ \res _ ->
    res === UM.MergeValMerged (toV 'z') 2 ()
  applyTestS UM.equivM $ \(UM.Equiv fwd bwd) _ -> do
    fwd === multiMapVV [('a', "c"), ('z', "b")]
    bwd === mapVV [('b', 'z'), ('c', 'a')]

testUmRec :: TestTree
testUmRec = testUnit "UM rec" $ runS emptyUMV $ do
  _ <- applyS (UM.addM (toV 'a') 1)
  _ <- applyS (UM.addM (toV 'b') 2)
  _ <- applyS (UM.addM (toV 'c') 3)
  _ <- applyS (UM.addM (toV 'c') 3)
  applyTestS (UM.mergeOneM mergeOneUMV (toV 'b') (toV 'c')) $ \res um -> do
    res === UM.MergeValMerged (toV 'b') 3 ()
    UM.size um === 3
    UM.trace (toV 'a') um === UM.TraceResFound (toV 'a') 1 []
    UM.trace (toV 'b') um === UM.TraceResFound (toV 'b') 3 []
    UM.trace (toV 'c') um === UM.TraceResFound (toV 'b') 3 [toV 'c']
    UM.values um === mapV [('a', 1), ('b', 3)]
  applyTestS UM.equivM $ \(UM.Equiv fwd bwd) _ -> do
    fwd === multiMapVV [('a', ""), ('b', "c")]
    bwd === mapVV [('c', 'b')]
  applyTestS (UM.mergeOneM mergeOneUMV (toV 'a') (toV 'b')) $ \res um -> do
    res === UM.MergeValMerged (toV 'a') 3 ()
    UM.size um === 3
    UM.trace (toV 'a') um === UM.TraceResFound (toV 'a') 3 []
    UM.trace (toV 'b') um === UM.TraceResFound (toV 'a') 3 [toV 'b']
    UM.trace (toV 'c') um === UM.TraceResFound (toV 'a') 3 [toV 'b', toV 'c']
    UM.values um === mapV [('a', 3)]
  applyTestS UM.equivM $ \(UM.Equiv fwd bwd) _ -> do
    fwd === multiMapVV [('a', "bc")]
    bwd === mapVV [('b', 'a'), ('c', 'a')]

testUmTail :: TestTree
testUmTail = testUnit "UM tail" $ runS emptyUMV $ do
  applyS $ do
    _ <- UM.addM (toV 'a') 1
    _ <- UM.addM (toV 'b') 2
    _ <- UM.addM (toV 'c') 3
    _ <- UM.addM (toV 'd') 4
    _ <- UM.mergeOneM mergeOneUMV (toV 'c') (toV 'd')
    _ <- UM.mergeOneM mergeOneUMV (toV 'b') (toV 'c')
    _ <- UM.mergeOneM mergeOneUMV (toV 'a') (toV 'b')
    pure ()
  testS $ \um ->
    UM.trace (toV 'd') um === UM.TraceResFound (toV 'a') 4 [toV 'b', toV 'c', toV 'd']
  applyTestS UM.equivM $ \(UM.Equiv fwd bwd) _ -> do
    fwd === multiMapVV [('a', "bcd")]
    bwd === mapVV [('b', 'a'), ('c', 'a'), ('d', 'a')]
  testS $ \um ->
    UM.trace (toV 'd') um === UM.TraceResFound (toV 'a') 4 [toV 'd']

main :: IO ()
main =
  testMain $ \_ ->
    testGroup
      "Unimap"
      [ testUmSimple
      , testUmRec
      , testUmTail
      ]