packages feed

monoidmap-internal-0.0.0.0: components/monoidmap-test/Data/MonoidMap/Internal/ComparisonSpec.hs

{- HLINT ignore "Redundant bracket" -}
{- HLINT ignore "Use camelCase" -}
{- HLINT ignore "Use null" -}

-- |
-- Copyright: © 2022–2025 Jonathan Knowles
-- License: Apache-2.0
--
module Data.MonoidMap.Internal.ComparisonSpec
    ( spec
    ) where

import Prelude

import Control.Monad
    ( forM_ )
import Data.Function
    ( (&) )
import Data.Maybe
    ( isJust )
import Data.Monoid.Cancellative
    ( Reductive (..) )
import Data.Monoid.GCD
    ( GCDMonoid )
import Data.MonoidMap.Internal
    ( MonoidMap )
import Data.Proxy
    ( Proxy (..) )
import Test.Common
    ( Key
    , Test
    , TestValueType (TestValueType)
    , makeSpec
    , property
    , testValueTypesGCDMonoid
    , testValueTypesAll
    , testValueTypesReductive
    )
import Test.Hspec
    ( Spec, describe, it )
import Test.QuickCheck
    ( Fun (..), Property, applyFun2, cover, expectFailure, (.||.), (===) )

import qualified Data.Monoid.GCD as GCDMonoid
    ( GCDMonoid (..) )
import qualified Data.Monoid.Null as Null
    ( MonoidNull (..) )
import qualified Data.MonoidMap.Internal as MonoidMap
import qualified Data.Set as Set

spec :: Spec
spec = describe "Comparison" $ do

    forM_ testValueTypesGCDMonoid $
        \(TestValueType p) -> specGCDMonoid
            (Proxy @Key) p

    forM_ testValueTypesReductive $
        \(TestValueType p) -> specReductive
            (Proxy @Key) p

    forM_ testValueTypesAll $
        \(TestValueType p) -> specMonoidNull
            (Proxy @Key) p

specGCDMonoid
    :: forall k v. (Test k v, GCDMonoid v) => Proxy k -> Proxy v -> Spec
specGCDMonoid = makeSpec $ do
    it "prop_disjoint_gcd" $
        prop_disjoint_gcd
            @k @v & property
    it "prop_disjoint_intersection" $
        prop_disjoint_intersection
            @k @v & property

specReductive
    :: forall k v. (Test k v, Reductive v) => Proxy k -> Proxy v -> Spec
specReductive = makeSpec $ do
    it "prop_isSubmapOf_minusMaybe" $
        prop_isSubmapOf_minusMaybe
            @k @v & property
    it "prop_isSubmapOf_reduce" $
        prop_isSubmapOf_reduce
            @k @v & property

specMonoidNull
    :: forall k v. Test k v => Proxy k -> Proxy v -> Spec
specMonoidNull = makeSpec $ do
    it "prop_disjointBy_get_total" $
        prop_disjointBy_get_total
            @k @v & property
    it "prop_disjointBy_get_total_failure" $
        prop_disjointBy_get_total_failure
            @k @v & property
    it "prop_isSubmapOfBy_get_total" $
        prop_isSubmapOfBy_get_total
            @k @v & property
    it "prop_isSubmapOfBy_get_total_failure" $
        prop_isSubmapOfBy_get_total_failure
            @k @v & property

prop_disjoint_gcd
    :: (Test k v, GCDMonoid v)
    => MonoidMap k v
    -> MonoidMap k v
    -> k
    -> Property
prop_disjoint_gcd m1 m2 k =
    MonoidMap.disjoint m1 m2 ==>
        (Null.null (GCDMonoid.gcd (MonoidMap.get k m1) (MonoidMap.get k m2)))
    & cover 8
        (MonoidMap.disjoint m1 m2)
        "MonoidMap.disjoint m1 m2"
    & cover 8
        (not (MonoidMap.disjoint m1 m2))
        "not (MonoidMap.disjoint m1 m2)"

prop_disjoint_intersection
    :: (Test k v, GCDMonoid v)
    => MonoidMap k v
    -> MonoidMap k v
    -> Property
prop_disjoint_intersection m1 m2 =
    MonoidMap.disjoint m1 m2 === (MonoidMap.intersection m1 m2 == mempty)
    & cover 8
        (MonoidMap.disjoint m1 m2)
        "MonoidMap.disjoint m1 m2"
    & cover 8
        (not (MonoidMap.disjoint m1 m2))
        "not (MonoidMap.disjoint m1 m2)"

prop_disjointBy_get_total
    :: Test k v
    => Fun (v, v) Bool
    -> MonoidMap k v
    -> MonoidMap k v
    -> k
    -> Property
prop_disjointBy_get_total (applyFun2 -> f0) m1 m2 k =
    MonoidMap.disjointBy f m1 m2
        ==>
        f (MonoidMap.get k m1) (MonoidMap.get k m2)
    & cover 8
        (m1 /= mempty && m2 /= mempty && MonoidMap.disjointBy f m1 m2)
        "m1 /= mempty && m2 /= mempty && MonoidMap.disjointBy f m1 m2"
    & cover 2
        (keyWithinIntersection)
        "keyWithinIntersection"
    & cover 2
        (not keyWithinIntersection)
        "not keyWithinIntersection"
  where
    keyWithinIntersection =
        k `Set.member` Set.intersection
            (MonoidMap.nonNullKeys m1)
            (MonoidMap.nonNullKeys m2)
    f v1 v2
        | Null.null v1 = True
        | Null.null v2 = True
        | otherwise = f0 v1 v2

prop_disjointBy_get_total_failure
    :: Test k v
    => Fun (v, v) Bool
    -> MonoidMap k v
    -> MonoidMap k v
    -> k
    -> Property
prop_disjointBy_get_total_failure (applyFun2 -> f) m1 m2 k =
    expectFailure $
    MonoidMap.disjointBy f m1 m2
        ==>
        f (MonoidMap.get k m1) (MonoidMap.get k m2)

prop_isSubmapOf_minusMaybe
    :: (Test k v, Reductive v)
    => MonoidMap k v
    -> MonoidMap k v
    -> Property
prop_isSubmapOf_minusMaybe m1 m2 =
    MonoidMap.isSubmapOf m1 m2
        ==> isJust (m2 `MonoidMap.minusMaybe` m1)
    & cover 0.01
        (nonTrivialSubmap)
        "nonTrivialSubmap"
  where
    nonTrivialSubmap =
        MonoidMap.isSubmapOf m1 m2
        && m1 /= mempty
        && m2 /= mempty
        && m1 /= m2

prop_isSubmapOf_reduce
    :: (Test k v, Reductive v)
    => MonoidMap k v
    -> MonoidMap k v
    -> k
    -> Property
prop_isSubmapOf_reduce m1 m2 k =
    MonoidMap.isSubmapOf m1 m2
        ==> isJust (MonoidMap.get k m2 </> MonoidMap.get k m1)
    & cover 0.001
        (nonTrivialSubmap && nonNullKeyL && nonNullKeyR)
        "nonTrivialSubmap && nonNullKeyL && nonNullKeyR"
    & cover 0.001
        (nonTrivialSubmap && nullKeyL && nonNullKeyR)
        "nonTrivialSubmap && nullKeyL && nonNullKeyR"
    & cover 0.001
        (nonTrivialSubmap && nullKeyL && nullKeyR)
        "nonTrivialSubmap && nullKeyL && nullKeyR"
  where
    nonTrivialSubmap =
        MonoidMap.isSubmapOf m1 m2
        && m1 /= mempty
        && m2 /= mempty
        && m1 /= m2
    nonNullKeyL = MonoidMap.nonNullKey k m1
    nonNullKeyR = MonoidMap.nonNullKey k m2
    nullKeyL = MonoidMap.nullKey k m1
    nullKeyR = MonoidMap.nullKey k m2

prop_isSubmapOfBy_get_total
    :: Test k v
    => Fun (v, v) Bool
    -> MonoidMap k v
    -> MonoidMap k v
    -> k
    -> Property
prop_isSubmapOfBy_get_total (applyFun2 -> f0) m1 m2 k =
    MonoidMap.isSubmapOfBy f m1 m2
        ==>
        f (MonoidMap.get k m1) (MonoidMap.get k m2)
    & cover 0.01
        (nonTrivialSubmap && nonNullKeyL && nonNullKeyR)
        "nonTrivialSubmap && nonNullKeyL && nonNullKeyR"
    & cover 0.1
        (nonTrivialSubmap && nullKeyL && nonNullKeyR)
        "nonTrivialSubmap && nullKeyL && nonNullKeyR"
    & cover 0.1
        (nonTrivialSubmap && nonNullKeyL && nullKeyR)
        "nonTrivialSubmap && nonNullKeyL && nullKeyR"
    & cover 0.1
        (nonTrivialSubmap && nullKeyL && nullKeyR)
        "nonTrivialSubmap && nullKeyL && nullKeyR"
  where
    f v1 v2
        | Null.null v1 = True
        | otherwise = f0 v1 v2
    nonTrivialSubmap =
        MonoidMap.isSubmapOfBy f m1 m2
        && m1 /= mempty
        && m2 /= mempty
        && m1 /= m2
    nonNullKeyL = MonoidMap.nonNullKey k m1
    nonNullKeyR = MonoidMap.nonNullKey k m2
    nullKeyL = MonoidMap.nullKey k m1
    nullKeyR = MonoidMap.nullKey k m2

prop_isSubmapOfBy_get_total_failure
    :: Test k v
    => Fun (v, v) Bool
    -> MonoidMap k v
    -> MonoidMap k v
    -> k
    -> Property
prop_isSubmapOfBy_get_total_failure (applyFun2 -> f) m1 m2 k =
    expectFailure $
    MonoidMap.isSubmapOfBy f m1 m2
        ==>
        f (MonoidMap.get k m1) (MonoidMap.get k m2)

--------------------------------------------------------------------------------
-- Utilities
--------------------------------------------------------------------------------

infixr 3 ==>
(==>) :: Bool -> Bool -> Property
a ==> b = not a .||. b