linear-base-0.4.0: test/Test/Data/Mutable/Set.hs
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
-- |
-- Tests for mutable sets.
--
-- = How we designed the tests
--
-- We use hedgehog to test properties that are axioms that funtionally define
-- the behavior of sets. There is at least one axiom per each combination of
-- accessor and constructor/mutator. So for the accessor @f@, and
-- constructor/mutator @g@, we have one axiom of the form @f (g (...)) = ...@.
--
-- If however, this is cumbersome, we can often just test against an existing
-- implementation. This is like a homomorphism test (but it's not quite a
-- homomorphism). For unions, this is like saying if we have two lists A and B,
-- and we take their union as lists, then sort and nub (remove duplicates)
-- this is the same as using @Set.fromList@ to make them into sets, taking
-- the union as sets, converting back with @Set.toList@ and then sorting
-- the output list.
--
-- To have such a test replace an axiom, however, we need to have
-- "homomorphisms" for each accessor or modifier. In general, we want to be
-- able to say for any modifier or accessor @f@,
--
-- > toList (f set) = f' (toList set)
--
-- where @f'@ is some reference implementation we know. The key idea is a kind
-- of "homomorphism" between @f@ and a reference implementation @f'@ that
-- holds across the conversion function @toList@.
--
-- Note: We could also formulate this in terms of @fromList@.
--
-- For example, we'd want to have @member x (fromList l) = elem x l@
-- for any @x@ and @l@.
--
-- With this we can prove
--
-- > member x (intersect set1 set2) = member x set1 && member x set2
--
-- Thusly:
--
-- > member x (intersect set1 set2) =
-- > member x (intersect (fromList l1) (fromList l2)) = -- for some l1, l2
-- > member x (fromList (intersect l1 l2)) =
-- > elem x (intersect l1 l2)) =
-- > elem x l1 && elem x l2 =
-- > member x (fromList l1) && member x (fromList l2) =
-- > member x set1 && member x set2
--
-- See https://softwarefoundations.cis.upenn.edu/vfa-current/ADT.html
-- for more about how ADT axioms work.
--
-- Remark: we are not testing @empty@ since it is trivial.
module Test.Data.Mutable.Set
( mutSetTests,
)
where
import Data.Containers.ListUtils (nubOrd)
import qualified Data.Functor.Linear as Data
import qualified Data.List as List
import Data.Set.Mutable.Linear (Set)
import qualified Data.Set.Mutable.Linear as Set
import Data.Unrestricted.Linear
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import qualified Prelude.Linear as Linear
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testPropertyNamed)
-- # Exported Tests
--------------------------------------------------------------------------------
mutSetTests :: TestTree
mutSetTests = testGroup "Mutable set tests" group
group :: [TestTree]
group =
-- Tests of the form [accessor (mutator)]
[ testPropertyNamed "∀ x. member (insert s x) x = True" "memberInsert1" memberInsert1,
testPropertyNamed "∀ x,y/=x. member (insert s x) y = member s y" "memberInsert2" memberInsert2,
testPropertyNamed "∀ x. member (delete s x) x = False" "memberDelete1" memberDelete1,
testPropertyNamed "∀ x,y/=x. member (delete s x) y = member s y" "memberDelete2" memberDelete2,
testPropertyNamed "∀ s, x \\in s. size (insert s x) = size s" "sizeInsert1" sizeInsert1,
testPropertyNamed "∀ s, x \\notin s. size (insert s x) = size s + 1" "sizeInsert2" sizeInsert2,
testPropertyNamed "∀ s, x \\in s. size (delete s x) = size s - 1" "sizeDelete1" sizeDelete1,
testPropertyNamed "∀ s, x \\notin s. size (delete s x) = size s" "sizeDelete2" sizeDelete2,
-- Homomorphism tests
testPropertyNamed "sort . nub = sort . toList" "toListFromList" toListFromList,
testPropertyNamed "member x s = elem x (toList s)" "memberHomomorphism" memberHomomorphism,
testPropertyNamed "size = length . toList" "sizeHomomorphism" sizeHomomorphism,
testPropertyNamed
"sort . nub ((toList s) ∪ (toList s')) = sort . toList (s ∪ s')"
"unionHomomorphism"
unionHomomorphism,
testPropertyNamed
"sort . nub ((toList s) ∩ (toList s')) = sort . toList (s ∩ s')"
"intersecHomomorphism"
intersectHomomorphism
]
-- # Internal Library
--------------------------------------------------------------------------------
type SetTester = Set.Set Int %1 -> Ur (TestT IO ())
-- | A random list
list :: Gen [Int]
list = do
size <- Gen.int $ Range.linearFrom 0 0 1000
let size' = Range.singleton size
Gen.list size' $ Gen.int $ Range.linearFrom 0 (-100) 100
-- | A random value
value :: Gen Int
value = Gen.int (Range.linear (-100) 100)
testEqual ::
(Show a, Eq a) =>
Ur a %1 ->
Ur a %1 ->
Ur (TestT IO ())
testEqual (Ur x) (Ur y) = Ur (x === y)
-- XXX: This is a terrible name
getFst :: (Consumable b) => (a, b) %1 -> a
getFst (a, b) = lseq b a
-- # Tests
--------------------------------------------------------------------------------
memberInsert1 :: Property
memberInsert1 = property $ do
val <- forAll value
l <- forAll list
let tester = memberInsert1Test val
test $ unur Linear.$ Set.fromList l tester
memberInsert1Test :: Int -> SetTester
memberInsert1Test val set =
testEqual
(Ur True)
(getFst (Set.member val (Set.insert val set)))
memberInsert2 :: Property
memberInsert2 = property $ do
val1 <- forAll value
val2 <- forAll $ Gen.filter (/= val1) value
l <- forAll list
let tester = memberInsert2Test val1 val2
test $ unur Linear.$ Set.fromList l tester
memberInsert2Test :: Int -> Int -> SetTester
memberInsert2Test val1 val2 set = fromRead (Set.member val2 set)
where
fromRead :: (Ur Bool, Set.Set Int) %1 -> Ur (TestT IO ())
fromRead (memberVal2, set) =
testEqual
memberVal2
(getFst (Set.member val2 (Set.insert val1 set)))
memberDelete1 :: Property
memberDelete1 = property $ do
val <- forAll value
l <- forAll list
let tester = memberDelete1Test val
test $ unur Linear.$ Set.fromList l tester
memberDelete1Test :: Int -> SetTester
memberDelete1Test val set =
testEqual
(Ur False)
(getFst (Set.member val (Set.delete val set)))
memberDelete2 :: Property
memberDelete2 = property $ do
val1 <- forAll value
val2 <- forAll $ Gen.filter (/= val1) value
l <- forAll list
let tester = memberDelete2Test val1 val2
test $ unur Linear.$ Set.fromList l tester
memberDelete2Test :: Int -> Int -> SetTester
memberDelete2Test val1 val2 set = fromRead (Set.member val2 set)
where
fromRead :: (Ur Bool, Set.Set Int) %1 -> Ur (TestT IO ())
fromRead (memberVal2, set) =
testEqual
memberVal2
(getFst Linear.$ Set.member val2 (Set.delete val1 set))
sizeInsert1 :: Property
sizeInsert1 = property $ do
l <- forAll list
val <- forAll $ Gen.filter (`elem` l) value
let tester = sizeInsert1Test val
test $ unur Linear.$ Set.fromList l tester
sizeInsert1Test :: Int -> SetTester
sizeInsert1Test val set = fromRead (Set.size set)
where
fromRead :: (Ur Int, Set.Set Int) %1 -> Ur (TestT IO ())
fromRead (sizeOriginal, set) =
testEqual
sizeOriginal
(getFst Linear.$ (Set.size (Set.insert val set)))
sizeInsert2 :: Property
sizeInsert2 = property $ do
l <- forAll list
val <- forAll $ Gen.filter (not . (`elem` l)) value
let tester = sizeInsert2Test val
test $ unur Linear.$ Set.fromList l tester
sizeInsert2Test :: Int -> SetTester
sizeInsert2Test val set = fromRead (Set.size set)
where
fromRead :: (Ur Int, Set.Set Int) %1 -> Ur (TestT IO ())
fromRead (sizeOriginal, set) =
testEqual
((Linear.+ 1) Data.<$> sizeOriginal)
(getFst Linear.$ (Set.size (Set.insert val set)))
sizeDelete1 :: Property
sizeDelete1 = property $ do
l <- forAll list
val <- forAll $ Gen.filter (`elem` l) value
let tester = sizeDelete1Test val
test $ unur Linear.$ Set.fromList l tester
sizeDelete1Test :: Int -> SetTester
sizeDelete1Test val set = fromRead (Set.size set)
where
fromRead :: (Ur Int, Set.Set Int) %1 -> Ur (TestT IO ())
fromRead (sizeOriginal, set) =
testEqual
((Linear.- 1) Data.<$> sizeOriginal)
(getFst Linear.$ (Set.size (Set.delete val set)))
sizeDelete2 :: Property
sizeDelete2 = property $ do
l <- forAll list
val <- forAll $ Gen.filter (not . (`elem` l)) value
let tester = sizeDelete2Test val
test $ unur Linear.$ Set.fromList l tester
sizeDelete2Test :: Int -> SetTester
sizeDelete2Test val set = fromRead (Set.size set)
where
fromRead :: (Ur Int, Set.Set Int) %1 -> Ur (TestT IO ())
fromRead (sizeOriginal, set) =
testEqual
sizeOriginal
(getFst Linear.$ (Set.size (Set.delete val set)))
toListFromList :: Property
toListFromList = property $ do
l <- forAll list
let outsideSet = nubOrd . List.sort $ l
List.sort (unur (Set.fromList l Set.toList)) === outsideSet
unionHomomorphism :: Property
unionHomomorphism = property $ do
l <- forAll list
l' <- forAll list
let listUnion = nubOrd $ List.sort $ List.union l l'
let setUnion = List.sort $ unur (fromLists l l' doUnion)
setUnion === listUnion
where
fromLists :: [Int] -> [Int] -> (Set Int %1 -> Set Int %1 -> Ur b) %1 -> Ur b
fromLists l l' f = Set.fromList l (\s -> Set.fromList l' (\s' -> f s s'))
doUnion :: Set Int %1 -> Set Int %1 -> Ur [Int]
doUnion s s' = Set.toList (Set.union s s')
intersectHomomorphism :: Property
intersectHomomorphism = property $ do
l <- forAll list
l' <- forAll list
let listIntersect = nubOrd $ List.sort $ List.intersect l l'
let setIntersect = List.sort $ unur (fromLists l l' doIntersect)
setIntersect === listIntersect
where
fromLists :: [Int] -> [Int] -> (Set Int %1 -> Set Int %1 -> Ur b) %1 -> Ur b
fromLists l l' f = Set.fromList l (\s -> Set.fromList l' (\s' -> f s s'))
doIntersect :: Set Int %1 -> Set Int %1 -> Ur [Int]
doIntersect s s' = Set.toList (Set.intersection s s')
memberHomomorphism :: Property
memberHomomorphism = property $ do
l <- forAll list
x <- forAll value
elem x l === (unur Linear.$ Set.fromList l (getFst Linear.. Set.member x))
sizeHomomorphism :: Property
sizeHomomorphism = property $ do
l <- forAll list
length (nubOrd l) === (unur (Set.fromList l (getFst Linear.. Set.size)))