packages feed

pomaps-0.0.2.0: tests/Data/POMap/Strictness.hs

{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-type-defaults #-}
module Data.POMap.Strictness where

import           Data.Function                (on)
import           Data.Functor.Identity
import qualified Data.List                    as List
import           Data.POMap.Arbitrary         ()
import           Data.POMap.Divisibility
import qualified Data.POMap.Lazy              as L
import qualified Data.POMap.Strict            as S
import           GHC.Exts                     (toList)
import           Test.ChasingBottoms.IsBottom
import           Test.Tasty.Hspec
import           Test.Tasty.QuickCheck

type DivMap v = L.POMap Divisibility v

instance {-# OVERLAPPING #-} Eq v => Eq (DivMap v) where
  (==) = (==) `on` List.sortOn (unDiv . fst) . toList

shouldBeBottom :: a -> Expectation
shouldBeBottom x = isBottom x `shouldBe` True

shouldNotBeBottom :: a -> Expectation
shouldNotBeBottom x = isBottom x `shouldBe` False

spec :: Spec
spec =
  describe "POMap" $ do
    describe "singleton" $ do
      it "strict" $ shouldBeBottom (S.singleton (Div 1) bottom)
      it "lazy" $ shouldNotBeBottom (L.singleton (Div 1) bottom)

    describe "member" $
      it "strict in the key" $ shouldBeBottom (L.member (Div bottom) L.empty)
    describe "lookup" $
      it "strict in the key" $ shouldBeBottom (L.lookup (Div bottom) L.empty)
    describe "lookupLT" $
      it "strict in the key" $ shouldBeBottom (L.lookupLT (Div bottom) L.empty)
    describe "lookupLE" $
      it "strict in the key" $ shouldBeBottom (L.lookupLE (Div bottom) L.empty)
    describe "lookupGT" $
      it "strict in the key" $ shouldBeBottom (L.lookupGT (Div bottom) L.empty)
    describe "lookupGE" $
      it "strict in the key" $ shouldBeBottom (L.lookupGE (Div bottom) L.empty)

    let insertTemplate l s = do
          it "strict in the key" $ property $ \(m :: DivMap Int) ->
            shouldBeBottom (l (Div bottom) 0 m)
          it "strict" $ property $ \(m :: DivMap Int) ->
            shouldBeBottom (s (Div 1) bottom m)
          it "lazy" $ property $ \(m :: DivMap Int) ->
            shouldNotBeBottom (l (Div 1) bottom m)

    describe "insert" $
      insertTemplate L.insert S.insert
    describe "insertWithKey" $
      insertTemplate (L.insertWithKey (\_ new _ -> new)) (S.insertWithKey (\_ new _ -> new))
    describe "insertLookupWithKey" $ do
      let templ impl k v m = snd (impl (\_ new _ -> new) k v m)
      insertTemplate (templ L.insertLookupWithKey) (templ S.insertLookupWithKey)

    describe "delete" $
      it "strict in the key" $ property $ \(m :: DivMap Int) ->
        shouldBeBottom (L.delete (Div bottom) m)
    describe "deleteLookup" $
      it "strict in the key" $ property $ \(m :: DivMap Int) ->
        shouldBeBottom (L.deleteLookup (Div bottom) m)

    let adjustTemplate l s = do
          it "strict in the key" $ property $ \(m :: DivMap Int) ->
            shouldBeBottom (l (const 0) (Div bottom) m)
          it "strict" $
            shouldBeBottom (s (const bottom) (Div 1) (L.singleton (Div 1) 1))
          it "lazy" $ property $ \(m :: DivMap Int) ->
            shouldNotBeBottom (l (const bottom) (Div 1) m)
    let ignoreKey impl f = impl (const f)

    describe "adjust" $
      adjustTemplate L.adjust S.adjust
    describe "adjustWithKey" $
      adjustTemplate (ignoreKey L.adjustWithKey) (ignoreKey S.adjustWithKey)
    describe "adjustLookupWithKey" $ do
      let templ impl f k m = snd (ignoreKey impl f k m)
      adjustTemplate (templ L.adjustLookupWithKey) (templ S.adjustLookupWithKey)

    let updateTemplate l s = adjustTemplate (\f -> l (Just . f)) (\f -> s (Just . f))

    describe "update" $
      updateTemplate L.update S.update
    describe "updateWithKey" $
      updateTemplate (ignoreKey L.updateWithKey) (ignoreKey S.updateWithKey)
    describe "updateLookupWithKey" $ do
      let templ impl f k m = snd (ignoreKey impl f k m)
      updateTemplate (templ L.updateLookupWithKey) (templ S.updateLookupWithKey)

    describe "alter" $
      updateTemplate L.alter S.alter
    describe "alterWithKey" $
      updateTemplate (ignoreKey L.alterWithKey) (ignoreKey S.alterWithKey)
    describe "alterLookupWithKey" $ do
      let templ impl f k m = snd (ignoreKey impl f k m)
      updateTemplate (templ L.alterLookupWithKey) (templ S.alterLookupWithKey)
    describe "alterF" $ do
      let insertAt impl k v = impl (const (Identity (Just v))) k
      insertTemplate (insertAt L.alterF) (insertAt S.alterF)

    let mapTemplate l s = do
          it "strict" $ property $ \(m :: DivMap Int) ->
            not (null m) ==> shouldBeBottom (s (const bottom) m)
          it "lazy" $ property $ \(m :: DivMap Int) ->
            shouldNotBeBottom (l (const bottom) m)

    describe "map" $
      mapTemplate L.map S.map
    describe "mapWithKey" $
      mapTemplate (ignoreKey L.mapWithKey) (ignoreKey S.mapWithKey)
    describe "mapAccum" $ do
      let templ impl f m = snd (impl (const f) undefined m)
      mapTemplate (templ L.mapAccum) (templ S.mapAccum)
    describe "mapAccumWithKey" $ do
      let templ impl f m = snd (impl (\_ _ -> f) undefined m)
      mapTemplate (templ L.mapAccumWithKey) (templ S.mapAccumWithKey)
    describe "mapKeysWith" $ do
      it "strict" $ property $ \(m :: DivMap Int) ->
        length m > 1 ==> shouldBeBottom (S.mapKeysWith (\_ _ -> bottom) (const (Div 1)) m)
      it "lazy" $ property $ \(m :: DivMap Int) ->
        shouldNotBeBottom (L.mapKeysWith (\_ _ -> bottom) (const (Div 1)) m)
    describe "mapMaybe" $ do
      let templ impl f = impl (Just . f)
      mapTemplate (templ L.mapMaybe) (templ S.mapMaybe)
    describe "mapMaybeWithKey" $ do
      let templ impl f = impl (\_ v -> Just (f v))
      mapTemplate (templ L.mapMaybeWithKey) (templ S.mapMaybeWithKey)
    describe "mapEither" $ do
      let templ impl f = fst . impl (Left . f)
      mapTemplate (templ L.mapEither) (templ S.mapEither)

    describe "traverseWithKey" $ do
      let templ impl f = impl (\ _ v -> Identity (f v))
      mapTemplate (templ L.traverseWithKey) (templ S.traverseWithKey)
    describe "traverseMaybeWithKey" $ do
      let templ impl f = impl (\ _ v -> Identity (Just (f v)))
      mapTemplate (templ L.traverseMaybeWithKey) (templ S.traverseMaybeWithKey)

    let fromListTemplate l s = do
          it "strict" $ property $ \(xs :: [(Divisibility, Int)]) ->
            not (null xs) ==> shouldBeBottom (s (fmap (\ (k, _) -> (k, bottom)) xs))
          it "lazy" $ property $ \(xs :: [(Divisibility, Int)]) ->
            shouldNotBeBottom (l (fmap (\(k, _) -> (k, bottom)) xs))

    describe "fromList" $
      fromListTemplate L.fromList S.fromList
    describe "fromListWith" $
      fromListTemplate (L.fromListWith const) (S.fromListWith const)
    describe "fromListWithKey" $
      fromListTemplate (L.fromListWithKey (\_ _ v -> v)) (S.fromListWithKey (\_ _ v -> v))

    describe "type class instances" $ do
      describe "Functor" $ do
        describe "fmap" $
          it "always lazy" $ property $ \(m :: DivMap Int) ->
            shouldNotBeBottom (const bottom <$> m)
        describe "<$" $
          it "always lazy" $ property $ \(m :: DivMap Int) ->
            shouldNotBeBottom (bottom <$ m)
      describe "Traversable" $
        describe "traverse" $
          it "always lazy" $ property $ \(m :: DivMap Int) ->
            shouldNotBeBottom (traverse (\_ -> Identity bottom) m)