packages feed

group-with-0.2.0.3: GroupWithTest.hs

import Test.Hspec
import Test.QuickCheck
import Control.Exception (evaluate)
import Control.Monad
import Control.GroupWith
import Data.Map (Map)
import Data.Maybe (fromJust)
import Data.List (sort)
import qualified Data.Map as Map

multimapElemEq :: (Eq k, Ord v) => (k,[v]) -> (k,[v]) -> Bool
multimapElemEq (k1,v1) (k2,v2) = (k1 == k2) && (sort v1 == sort v2)

-- Check if two multimap-representing lists are equal,
-- disregarding value list order
multimapListEq :: (Ord a, Ord b, Eq a, Eq b) => [(a,[b])] -> [(a,[b])] -> Bool
multimapListEq xs ys =
    let zipped = zipWith multimapElemEq xs ys
    in and zipped

multimapEq :: (Ord a, Ord b, Eq a, Eq b) => Map a [b] -> Map a [b] -> Bool
multimapEq x y =
    multimapListEq (Map.toList x) (Map.toList y)

multimapTupleEq :: (Ord a, Ord b, Eq a, Eq b) => (Map a [b], Map a [b]) -> Bool
multimapTupleEq (x,y) = multimapEq x y

never_called_fn :: a
never_called_fn =  error "This function should never be called"

main :: IO ()
main = hspec $ do
  describe "groupWith" $ do
    it "should group a simple value correctly" $
        let data_  = ["a","abc","ab","bc"]
            fn     = take 1
            ref    = Map.fromList [("a",["a","abc","ab"]),("b",["bc"])]
            result = groupWith fn data_
        in (result, ref) `shouldSatisfy` multimapTupleEq
    it "should return an empty map when given an empty list" $
        -- We need to specialize here, because it's ⊥
        let fn     = never_called_fn :: Int -> Int
            data_  = [] :: [Int]
            result = groupWith fn data_
        in result `shouldSatisfy` ((==) 0 . Map.size)
    -- Fuzzing, couldn't get this to compile properly yet
    -- it "should not crash for any input string list" $
    --    property $ (\d -> groupWith (take 1) d `seq` True)
  describe "groupWithUsing" $ do
    it "should return an empty map when given an empty list" $
        -- We need to specialize here, because it's ⊥
        let fn     = never_called_fn :: Int -> Int
            data_  = [] :: [Int]
            result = groupWithUsing id (+) fn data_
        in result `shouldSatisfy` ((==) 0 . Map.size)
    it "should be usable for counting" $
        -- Instead of building up lists, we count the number of elements
        let t n   = 1   -- For each x, count 1
            c     = (+) -- Sum up the counts
            fn    = take 1
            data_ = ["a","abc","ab","bc"]
            ref   = Map.fromList [("a",3),("b",1)] :: Map String Int
            result = groupWithUsing t c fn data_
        in result `shouldBe` ref
  describe "groupWithMultiple" $ do
    it "should group correctly given a simple list" $
        let data_ = ["a","abc","ab","bc"]
            fn x  = [take 1 x, take 2 x]
            -- Note the multiple "a"s in the first line:
            -- one from `take 1`, one from `take 2`
            ref = Map.fromList [("a",["a","a","abc","ab"]),
                                ("ab",["ab","abc"]),
                                ("b",["bc"]),
                                ("bc",["bc"])]
            result = groupWithMultiple fn data_
        in (result, ref) `shouldSatisfy` multimapTupleEq
    it "should return an empty map when given an empty list" $
        -- We need to specialize here, because it's ⊥
        let fn     = never_called_fn :: Int -> [Int]
            data_  = [] :: [Int]
            result = groupWithMultiple fn data_
        in result `shouldSatisfy` ((==) 0 . Map.size)
  {-
    NOTE: We will use Maybe as monad / applicative functor for this test
  -}
  describe "groupWithM" $ do
    it "should group a simple value correctly" $
        let data_  = ["a","abc","ab","bc"]
            fn     = Just . take 1
            ref    = Map.fromList [("a",["a","abc","ab"]),("b",["bc"])]
            result = groupWithM fn data_
        in (fromJust result, ref) `shouldSatisfy` multimapTupleEq
    it "should return an empty map when given an empty list" $
        -- We need to specialize here, because it's ⊥
        let fn     = never_called_fn :: Int -> Maybe Int
            data_  = [] :: [Int]
            result = groupWithM fn data_
        in (fromJust result) `shouldSatisfy` ((==) 0 . Map.size)
    -- Fuzzing, couldn't get this to compile properly yet
    -- it "should not crash for any input string list" $
    --    property $ (\d -> groupWith (take 1) d `seq` True)
  describe "groupWithMultipleM" $ do
    it "should group correctly given a simple list" $
        let data_ = ["a","abc","ab","bc"]
            fn x = Just [take 1 x, take 2 x]
            -- Note the multiple "a"s in the first line:
            -- one from `take 1`, one from `take 2`
            ref = Map.fromList [("a",["a","a","abc","ab"]),
                                ("ab",["ab","abc"]),
                                ("b",["bc"]),
                                ("bc",["bc"])]
            result = groupWithMultipleM fn data_
        in (fromJust result, ref) `shouldSatisfy` multimapTupleEq
    it "should return an empty map when given an empty list" $
        -- We need to specialize here, because it's ⊥
        let fn     = never_called_fn :: Int -> Maybe [Int]
            data_  = [] :: [Int]
            result = groupWithMultipleM fn data_
        in (fromJust result) `shouldSatisfy` ((==) 0 . Map.size)
  describe "groupWithUsingM" $ do
    it "should return an empty map when given an empty list" $
        -- We need to specialize here, because it's ⊥
        let fn     = never_called_fn :: Int -> Maybe Int
            data_  = [] :: [Int]
            result = groupWithUsingM return (+) fn data_
        in (fromJust result) `shouldSatisfy` ((==) 0 . Map.size)
    it "should be usable for counting" $
        -- Instead of building up lists, we count the number of elements
        let t n    = return 1 -- For each x, count 1
            c      = (+)      -- Sum up the counts
            fn     = return . take 1
            data_  = ["a","abc","ab","bc"]
            ref    = Map.fromList [("a",3),("b",1)] :: Map String Int
            result = groupWithUsingM t c fn data_
        in fromJust result `shouldBe` ref