packages feed

skeletal-set-0.1.0.0: tests/Data/SkeletalSetTest.hs

{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE MonoLocalBinds        #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Data.SkeletalSetTest where

import Control.Monad.Identity hiding (mapM)
import Data.SkeletalSet            hiding (ø, (\\), (∪))
import Prelude                hiding (map, mapM, filter, null)
import Test.SmallCheck.Series
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.SmallCheck

test_monoid_laws :: [TestTree]
test_monoid_laws =
  [ testProperty "associativity" (d3 (\a b c -> a <> (b <> c) == (a <> b) <> c))
  , testProperty "left identity" (d4 (\a -> a <> ø == a))
  , testProperty "right identity" (d4 (\a -> ø <> a == a))
  ]

test_construction :: [TestTree]
test_construction =
  [ testGroup "empty and singleton" $
    [ testCase "empty" (ø @?= ø)
    , testProperty "not empty" (\x -> st x /= ø)
    , testProperty "equal to itself" (\x -> st x == st x)
    , testProperty "different" (\x y -> x /= y ==> st x /= st y)
    , testProperty "similar" (\x y -> x == y ==> st x == st y)
    , testProperty
        "transitive"
        (d500
           (over
              similar3
              (\(x, y, z) -> (x == y) && (y == z) ==> st (x, x) == st (z, z))))
    ]
  , testGroup "general union laws" $
    [ testProperty "identity" (d3 (\a -> (a ∪ ø == a) && (ø ∪ a == a)))
    , testProperty "reflexivity" (d3 (\a -> a ∪ a == a))
    , testProperty "commutativity" (d3 (\a b -> a ∪ b == b ∪ a))
    , testProperty "associativity" (d3 (\a b c -> a ∪ (b ∪ c) == (a ∪ b) ∪ c))
    , testProperty "nonempty successor" (d3 (\a x -> a ∪ st x /= ø))
    ]
  , testGroup "unionWith" $
    [ testProperty
        "unionWith max behaves the same as union"
        (d3 (\a b -> unionWith max a b == a ∪ b))
    ]
  , testGroup "unions" $
    [ testCase "zero" (unions [ø] @?= ø)
    , testProperty "one" (\a -> unions [st a] == st a)
    , testProperty "many" (d3 (\a b -> unions (a ++ [b]) == unions a ∪ b))
    ]
  , testGroup "difference" $
    [ testProperty "identity" (d3 (\a -> (a \\ ø == a) && (ø \\ a == ø)))
    , testProperty
        "by definition"
        (d3
           (\a b ->
              all (\x -> (x `member` a) && not (x `member` b)) (toList (a \\ b))))
    ]
  , testGroup "filter" $
    [ testProperty "empty" (d3 (\a -> (filter (const True) a == (a :: TestSet))))
    , testProperty "empty" (d3 (\a -> (filter (const False) a == ø)))
    , testProperty "by definition" $
        let p = even . fst
        in d4 $ \(a :: TestSet) ->
           all (\x -> p x && (x `member` a)) (toList (filter p a))
    ]
  ]

test_queries :: [TestTree]
test_queries =
  [ testGroup "null" $
    [ testCase "empty" (null ø @?= True)
    , testProperty "singleton" (\a -> null (st a) == False)
    ]
  , testGroup "size" $
    [ testCase "empty" (size ø @?= 0)
    , testProperty "singleton" (\a -> size (st a) == 1)
    , testProperty
        "non emtpy"
        (d4 (\a -> size (a :: TestSet) == length (toList a)))
    ]
  , testGroup "member" $
    [ testProperty "empty" (\x -> member x ø == False)
    , testProperty "single element" (\x -> member x (st x) == True)
    , testProperty "many elements" (d4 (\a x -> member x (a ∪ st x) == True))
    , testProperty "many elements" (d4 (\a x -> member x (a \\ st x) == False))
    ]
  , testGroup "equivalence" $
    [ testCase "empty case" (ø =~= ø @?= True)
    , testProperty
        "empty never equivalent to nonempty"
        (\x -> not (ø =~= (st x)) && not (st x =~= ø))
    , testProperty "singleton case" (\x -> st x =~= st x)
    , testProperty
        "not equivalent if eqRel is not equal"
        (over
           different
           (\(x, y) ->
              (eqRel x :: Int) /= (eqRel y :: Int) ==> not (st x =~= st y)))
    , testProperty
        "equivalent if eqRel is equal"
        (over
           similar
           (\(x, y) -> (eqRel x :: Int) == (eqRel y :: Int) ==> st x =~= st y))
    ]
  ]

test_traversal :: [TestTree]
test_traversal =
  [ testGroup "map" $
    [ testProperty "∃ x ∈ a: ∀ y ∈ (map f a): f x == y" $
      let f (x, y) = (x * y, x + y)
      in (d4 $ forAll $ \(a :: TestSet) ->
            (`all` toList (map f a :: TestSet)) $ \y ->
                   any (\x -> f x == y) (toList a))
    , testProperty "∀ x ∈ a: f x ∈ (map f a))" $
      let f (x, y) = (x * y, x + y)
      in (d4 $ forAll $ \(a :: TestSet) ->
            (`all` toList a) $ \x ->
                   f x `member` (map f a :: TestSet))
    ]
  , testGroup "mapResolve" $
    [ testProperty "Is the same as map when chosing `max` as resolver" $
      let f (x,y) = (x*y, x+y)
      in d3 $ \(a :: TestSet) ->
         mapResolve max f a == (map f a :: TestSet)
    ]
  , testGroup "mapM" $
    [ testProperty "Results are equivalent to pure version. Note the ordering" $
      let f (x,y) = (x*y, x+y)
      in d3 $ \(a :: TestSet) ->
         mapM (return . f) a `mEqual` return (map f a)
    ]
  ]

test_conversion :: [TestTree]
test_conversion =
  [ testGroup "fromList" $
    [ testCase "zero" (fromList [] @?= ø)
    , testProperty "singleton" (\x -> fromList [x] =~= st x)
    , testProperty
        "via union"
        (d3 (\xs ys -> fromList (xs ++ ys) == fromList xs ∪ fromList ys))
    , testProperty
        "transitivity"
        (d3
           (\xs ys zs ->
              (fromList (xs ++ ys) ∪ fromList zs) =~=
              (fromList xs ∪ fromList (ys ++ zs))))
    ]
  , testGroup "fromListWith" $
    [ testProperty
        "fromListWith max == fromList"
        (d4
           (\xs -> fromListWith max xs == (fromList xs :: TestSet)))
    ]
  , testGroup "toList" $
    [testProperty "inverse to fromList" (d4 (toList `isInverseOf` fromList))]
  ]

instance EquivalenceBy k (k, v) where
  eqRel = fst

type TestSet = SkeletalSet Int (Int, Int)

st
  :: EquivalenceBy Int (Int, Int)
  => (Int, Int) -> SkeletalSet Int (Int, Int)
st = singleton

ø :: TestSet
ø = empty

(<>) :: TestSet -> TestSet -> TestSet
(<>) = mappend

(∪) :: TestSet -> TestSet -> TestSet
(∪) = union

(\\) :: TestSet -> TestSet -> TestSet
(\\) = difference

different :: Series m ((Int, Int), (Int, Int))
different =
  generate (\d -> [((x - 1, y), (x + 1, y)) | x <- [0 .. d], y <- [0 .. 10]])

similar :: Series m ((Int, Int), (Int, Int))
similar =
  generate
    (\d -> [((x, y), (x, z)) | x <- [0 .. d], y <- [0 .. 5], z <- [4 .. 9]])

similar3 :: Series m (Int, Int, Int)
similar3 = generate (\d -> [(k, k, k) | k <- [0 .. d]])

isInverseOf
  :: (TestSet -> a)
  -> (a -> TestSet)
  -> TestSet
  -> Bool
isInverseOf f g a = (g . f) a == a

instance (Monad m, Ord k, Ord v, Serial m v, EquivalenceBy k v) =>
         Serial m (SkeletalSet k v) where
  series = fromList <$> series

mEqual :: (Identity (TestSet))
       -> (Identity (TestSet))
       -> Bool
mEqual f g = runIdentity f == runIdentity g

d1
  :: Testable m a
  => a -> Property m
d1 = changeDepth (const 1)

d2
  :: Testable m a
  => a -> Property m
d2 = changeDepth (const 2)

d3
  :: Testable m a
  => a -> Property m
d3 = changeDepth (const 3)

d4
  :: Testable m a
  => a -> Property m
d4 = changeDepth (const 4)

d5
  :: Testable m a
  => a -> Property m
d5 = changeDepth (const 5)

d10
  :: Testable m a
  => a -> Property m
d10 = changeDepth (const 10)

d500
  :: Testable m a
  => a -> Property m
d500 = changeDepth (const 500)