packages feed

range-0.3.2.0: Test/RangeLaws.hs

module Test.RangeLaws
   ( rangeLawTestCases
   ) where

import Test.Framework (Test, testGroup)
import Test.QuickCheck
import Test.Framework.Providers.QuickCheck2

import Data.Range
import Test.Generators ()

-- ---------------------------------------------------------------------------
-- Helpers
-- ---------------------------------------------------------------------------

-- Sort before comparing so that order differences don't cause false failures.
-- mergeRanges produces a canonical form, so we use it to normalise both sides.
canonical :: Ord a => [Range a] -> [Range a]
canonical = mergeRanges

eq :: (Ord a) => [Range a] -> [Range a] -> Bool
eq a b = canonical a == canonical b

-- ---------------------------------------------------------------------------
-- Idempotency
-- ---------------------------------------------------------------------------

prop_mergeRanges_idempotent :: [Range Integer] -> Bool
prop_mergeRanges_idempotent xs =
   mergeRanges (mergeRanges xs) == mergeRanges xs

prop_union_idempotent :: [Range Integer] -> Bool
prop_union_idempotent xs =
   union xs xs `eq` xs

prop_intersection_idempotent :: [Range Integer] -> Bool
prop_intersection_idempotent xs =
   intersection xs xs `eq` xs

test_idempotency :: Test
test_idempotency = testGroup "idempotency"
   [ testProperty "mergeRanges is idempotent"       prop_mergeRanges_idempotent
   , testProperty "union with self is self"          prop_union_idempotent
   , testProperty "intersection with self is self"   prop_intersection_idempotent
   ]

-- ---------------------------------------------------------------------------
-- Commutativity
-- ---------------------------------------------------------------------------

prop_union_commutative :: ([Range Integer], [Range Integer]) -> Bool
prop_union_commutative (a, b) =
   union a b `eq` union b a

prop_intersection_commutative :: ([Range Integer], [Range Integer]) -> Bool
prop_intersection_commutative (a, b) =
   intersection a b `eq` intersection b a

test_commutativity :: Test
test_commutativity = testGroup "commutativity"
   [ testProperty "union is commutative"         prop_union_commutative
   , testProperty "intersection is commutative"  prop_intersection_commutative
   ]

-- ---------------------------------------------------------------------------
-- Associativity
-- ---------------------------------------------------------------------------

prop_union_associative :: ([Range Integer], [Range Integer], [Range Integer]) -> Bool
prop_union_associative (a, b, c) =
   union (union a b) c `eq` union a (union b c)

prop_intersection_associative :: ([Range Integer], [Range Integer], [Range Integer]) -> Bool
prop_intersection_associative (a, b, c) =
   intersection (intersection a b) c `eq` intersection a (intersection b c)

test_associativity :: Test
test_associativity = testGroup "associativity"
   [ testProperty "union is associative"         prop_union_associative
   , testProperty "intersection is associative"  prop_intersection_associative
   ]

-- ---------------------------------------------------------------------------
-- Distributivity
-- ---------------------------------------------------------------------------

prop_intersection_distributes_over_union
   :: ([Range Integer], [Range Integer], [Range Integer]) -> Bool
prop_intersection_distributes_over_union (a, b, c) =
   intersection a (union b c) `eq` union (intersection a b) (intersection a c)

prop_union_distributes_over_intersection
   :: ([Range Integer], [Range Integer], [Range Integer]) -> Bool
prop_union_distributes_over_intersection (a, b, c) =
   union a (intersection b c) `eq` intersection (union a b) (union a c)

test_distributivity :: Test
test_distributivity = testGroup "distributivity"
   [ testProperty "intersection distributes over union"
         prop_intersection_distributes_over_union
   , testProperty "union distributes over intersection"
         prop_union_distributes_over_intersection
   ]

-- ---------------------------------------------------------------------------
-- Identity laws
-- ---------------------------------------------------------------------------

-- The empty range list acts as the identity for union
prop_union_identity_empty :: [Range Integer] -> Bool
prop_union_identity_empty xs =
   union xs [] `eq` xs

-- InfiniteRange acts as the identity for intersection
prop_intersection_identity_infinite :: [Range Integer] -> Bool
prop_intersection_identity_infinite xs =
   intersection xs [InfiniteRange] `eq` xs

-- Union with InfiniteRange absorbs everything
prop_union_absorb_infinite :: [Range Integer] -> Bool
prop_union_absorb_infinite xs =
   union xs [InfiniteRange] `eq` [InfiniteRange]

-- Intersection with empty absorbs everything
prop_intersection_absorb_empty :: [Range Integer] -> Bool
prop_intersection_absorb_empty xs =
   intersection xs [] `eq` []

test_identity_absorption :: Test
test_identity_absorption = testGroup "identity and absorption"
   [ testProperty "union with [] is identity"                prop_union_identity_empty
   , testProperty "intersection with InfiniteRange is identity" prop_intersection_identity_infinite
   , testProperty "union with InfiniteRange absorbs"         prop_union_absorb_infinite
   , testProperty "intersection with [] absorbs"             prop_intersection_absorb_empty
   ]

-- ---------------------------------------------------------------------------
-- Difference as intersection with complement
-- ---------------------------------------------------------------------------

prop_difference_eq_intersection_invert
   :: ([Range Integer], [Range Integer]) -> Bool
prop_difference_eq_intersection_invert (a, b) =
   difference a b `eq` intersection a (invert b)

test_difference :: Test
test_difference = testGroup "difference"
   [ testProperty "difference a b == intersection a (invert b)"
         prop_difference_eq_intersection_invert
   ]

-- ---------------------------------------------------------------------------
-- Double inversion
-- ---------------------------------------------------------------------------

prop_invert_twice_identity :: [Range Integer] -> Bool
prop_invert_twice_identity xs =
   invert (invert xs) `eq` xs

test_invert :: Test
test_invert = testGroup "invert"
   [ testProperty "inverting twice is identity"  prop_invert_twice_identity
   ]

-- ---------------------------------------------------------------------------
-- Export
-- ---------------------------------------------------------------------------

rangeLawTestCases :: [Test]
rangeLawTestCases =
   [ test_idempotency
   , test_commutativity
   , test_associativity
   , test_distributivity
   , test_identity_absorption
   , test_difference
   , test_invert
   ]