packages feed

rangeset-0.0.1.0: test/RangeSet.hs

{-# LANGUAGE TypeApplications, StandaloneDeriving, DeriveGeneric, MonoLocalBinds #-}
module Main where
import Test.Tasty (testGroup, defaultMain, TestTree)
import Test.Tasty.HUnit ( testCase, (@?=) )
import Test.Tasty.QuickCheck
  ( listOf, chooseEnum,
    (===),
    (==>),
    (.&&.),
    property,
    testProperty,
    elements,
    forAll,
    genericShrink,
    Arbitrary(arbitrary, shrink),
    Property )

import Prelude hiding (null)

import Data.RangeSet
import Data.RangeSet.Internal
import Data.RangeSet.Internal.TestingUtils
import Data.List (nub, sort, intersect)
import GHC.Generics (Generic)

import Data.Word (Word8)

main :: IO ()
main = defaultMain tests

deriving instance Generic (RangeSet a)

data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine deriving (Ord, Eq, Enum, Bounded, Show, Generic)

instance (Arbitrary a, Enum a, Ord a) => Arbitrary (RangeSet a) where
  arbitrary = fmap fromList (listOf arbitrary)
  shrink = filter valid . genericShrink

instance Arbitrary Digit where
  arbitrary = chooseEnum (Zero, Nine)
  shrink Zero = []
  shrink n = [Zero .. pred n]

tests :: TestTree
tests = testGroup "RangeSet" [
    testProperty "arbitrary RangeSets should be valid" $ valid @Word,
    emptyTests,
    memberTests,
    insertTests,
    deleteTests,
    fromListTests,
    testProperty "elems and unelems shoudld be disjoint" $ elemUnelemDisjoint @Word8,
    testProperty "complement . complement = id" $ complementInverse @Digit,
    testProperty "unelems == elems . complement" $ complementElemsInverse @Digit,
    testProperty "findMin should find the minimum" $ findMinMinimum @Word,
    testProperty "findMax should find the maximum" $ findMaxMaximum @Int,
    testProperty "allLess should find everything strictly less than a value" $ allLessMin @Word,
    testProperty "allMore should find everything strictly more than a value" $ allMoreMax @Word,
    testProperty "union should union" $ uncurry (unionProperty @Int),
    testProperty "intersection should intersect" $ uncurry (intersectionProperty @Digit),
    testProperty "difference should differentiate" $ uncurry (differenceProperty @Word)
  ]

emptyTests :: TestTree
emptyTests = testGroup "empty should" [
    testCase "be null" $ null empty @?= True,
    testCase "have size 0" $ size @Int empty @?= 0
  ]

-- member, notMember
memberTests :: TestTree
memberTests = testGroup "member should" [
    testCase "work when out of range" $ notMember 5 (fromRanges [(0, 4), (6, 9)]) @?= True,
    testCase "work when in range" $ member 5 (fromRanges [(0, 9)]) @?= True,
    testCase "work for exact" $ member 5 (fromRanges [(5, 5)]) @?= True,
    testProperty "perform like elem on elems" $ uncurry (memberElemProperty @Word)
  ]

-- insert
insertTests :: TestTree
insertTests =
  let t = fromList [6, 2, 7, 1, 5] -- 1-2, 5-7
  in testGroup "insert should" [
    testCase "add something in" $ member 3 (insert 3 t) @?= True,
    testCase "not affect membership for other items" $ member 4 (insert 3 t) @?= False,
    testCase "not remove membership" $ member 5 (insert 4 (insert 3 t)) @?= True
  ]

-- delete
deleteTests :: TestTree
deleteTests =
  let t = fromList [6, 2, 7, 1, 5] -- 1-2, 5-7
  in testGroup "delete should" [
    testCase "remove an element" $ notMember 2 (delete 2 t) @?= True,
    testCase "not affect membership for other items" $ member 1 (delete 2 t) @?= True,
    testCase "produce valid trees" $ all valid (scanr delete t (sort (elems t))) @?= True
  ]

fromListTests :: TestTree
fromListTests = testGroup "fromList" [
    testProperty "should compose with elems to form (sort . nub)" $ nubSortProperty @Int,
    testProperty "specifically, case 1" $ nubSortProperty [2,0,3,4,2,6],
    testProperty "specifically, case 2" $ nubSortProperty [6,7,4,0,6,10,2,12,8]
  ]

findMinMinimum :: (Ord a, Show a, Enum a) => RangeSet a -> Property
findMinMinimum t = findMin t === safeMinimum (elems t)
  where
    safeMinimum [] = Nothing
    safeMinimum xs = Just $ minimum xs

findMaxMaximum :: (Ord a, Show a, Enum a) => RangeSet a -> Property
findMaxMaximum t = findMax t === safeMaximum (elems t)
  where
    safeMaximum [] = Nothing
    safeMaximum xs = Just $ maximum xs

nubSortProperty :: (Enum a, Ord a, Show a) => [a] -> Property
nubSortProperty xs = sort (nub xs) === elems (fromList xs)

memberElemProperty :: (Enum a, Ord a, Show a) => a -> RangeSet a -> Property
memberElemProperty x t = member x t === elem x (elems t)

elemUnelemDisjoint :: (Enum a, Bounded a, Eq a, Show a) => RangeSet a -> Property
elemUnelemDisjoint t = intersect (elems t) (unelems t) === []

complementInverse :: (Enum a, Bounded a, Ord a, Show a) => RangeSet a -> Property
complementInverse t = elems (complement (complement t)) === elems t

complementElemsInverse :: (Enum a, Bounded a, Ord a, Show a) => RangeSet a -> Property
complementElemsInverse t = unelems t === elems (complement t)

unionProperty :: (Ord a, Enum a, Show a) => RangeSet a -> RangeSet a -> Property
unionProperty t1 t2 = not (null t1 && null t2) ==>
  forAll (elements (elems t1 ++ elems t2)) (\x ->
         member x (t1 `union` t2))
  .&&. valid (t1 `union` t2)

intersectionProperty :: (Ord a, Enum a, Show a) => RangeSet a -> RangeSet a -> Property
intersectionProperty t1 t2 = not (null t1 && null t2) ==>
  forAll (elements (elems t1 ++ elems t2)) (\x ->
         (member x t1 && member x t2) === member x (t1 `intersection` t2))
  .&&. valid (t1 `intersection` t2)

differenceProperty :: (Ord a, Enum a, Show a) => RangeSet a -> RangeSet a -> Property
differenceProperty t1 t2 = not (null t1 && null t2) ==>
  forAll (elements (elems t1 ++ elems t2)) (\x ->
         (member x t1 && not (member x t2)) === member x (t1 `difference` t2))
  .&&. valid (t1 `difference` t2)

allLessMin :: (Ord a, Enum a, Show a) => RangeSet a -> a -> Property
allLessMin t x = allLess x t === fromList (filter (< x) (elems t))

allMoreMax :: (Ord a, Enum a, Show a) => RangeSet a -> a -> Property
allMoreMax t x = allMore x t === fromList (filter (> x) (elems t))

{-
    fromRanges, insertRange
-}