vicinity-0.1.0: test/Spec.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE BangPatterns #-}
import Data.Vicinity (Vicinity)
import Data.Foldable
import Data.Functor.Identity
import Data.Proxy
import Data.Semigroup (Semigroup (..))
import Test.QuickCheck
import Control.Monad
import Data.Monoid
import Numeric.Natural (Natural)
import Control.Exception (Exception,toException)
import Test.QuickCheck.Property (exception)
import Data.Bool (bool)
import Data.Map (Map)
import Test.QuickCheck.Classes as QC
import qualified Data.Vicinity as VC
import qualified Data.Map.Strict as M
main :: IO ()
main = props
props :: IO ()
props = do
lawsCheckMany allPropsApplied
putStrLn "Split-Link Identity"
quickCheck $ \(v :: Vicinity Integer (Sum Integer)) (i :: Integer) -> case VC.splitLookup i v of
(x,m,y) -> case m of
Just c -> VC.uncheckedConcat x (VC.uncheckedConcat (VC.singleton i c) y) == v
Nothing -> VC.uncheckedConcat x y == v
putStrLn "Insert-Fold Identity"
quickCheck $ \(v :: Vicinity Integer (Sum Integer)) ->
VC.foldrWithKey VC.insert mempty v == v
putStrLn "fromList agrees with Data.Map"
quickCheck $ \(xs :: [(Word,Sum Word)]) ->
let expectation = M.toList (M.fromListWith mappend xs)
actual = VC.toList (VC.fromList xs)
in expectation == actual
putStrLn "Element lookup"
quickCheck propLookup
putStrLn "Range Query"
quickCheck propQuery
propLookup :: Property
propLookup = forAllShrink arbitrary shrink $ \(vic :: Vicinity Int (Sum Word)) -> do
case mapM_ (\(k,v) -> bool (Left k) (Right ()) (VC.lookup k vic == v)) (VC.toList vic) of
Left k -> do
let msg = show vic ++ " mangles lookup of key " ++ show k
property $ exception msg (toException PropLookupException)
Right () -> property True
genMapAndInnerBounds :: Gen (Map Word Word, Word, Word)
genMapAndInnerBounds = do
xs <- vector 100
let m = M.fromList xs
case M.lookupMin m of
Nothing -> error "genMapAndInnerBounds: not possible"
Just (theMin,_) -> case M.lookupMax m of
Nothing -> error "genMapAndInnerBounds: not possible"
Just (theMax,_) -> do
a <- choose (theMin,theMax)
b <- choose (theMin,theMax)
let lo = min a b
let hi = max a b
return (m,lo,hi)
propQuery :: Property
propQuery = forAll genMapAndInnerBounds $ \(m,lo,hi) ->
let (_,m1,x) = M.splitLookup lo m
(y,m2,_) = M.splitLookup hi x
extra1 = maybe M.empty (M.singleton lo) m1
extra2 = maybe M.empty (M.singleton hi) m2
submap = M.unionsWith (+) [y,extra1,extra2]
Sum expected = foldMap Sum (M.elems submap)
vic = M.foldrWithKey (\k v xs -> VC.insert k (Sum v) xs) mempty m
Sum actual = VC.query (Just lo) (Just hi) vic
in if expected == actual
then property True
else do
let msg = unlines
[ show m ++ " mangles range query for [" ++ show lo ++ "," ++ show hi ++ "]: expected " ++ show expected ++ ", actual: " ++ show actual
, "Trimmed map: " ++ show submap
]
property $ exception msg (toException PropQueryException)
data PropLookupException = PropLookupException
deriving (Show,Eq)
instance Exception PropLookupException
data PropQueryException = PropQueryException
deriving (Show,Eq)
instance Exception PropQueryException
instance (Ord k, Arbitrary k, Arbitrary v, Monoid v) => Arbitrary (Vicinity k v) where
arbitrary = do
(i :: [(k,v)]) <- arbitrary
pure (VC.fromList i)
shrink s = map VC.fromList (shrink (VC.toList s))
typeclassProps :: (Ord a, Eq a, Monoid a, Show a, Arbitrary a) => Proxy a -> [Laws]
typeclassProps p =
[ QC.eqLaws p
, QC.ordLaws p
, QC.commutativeMonoidLaws p
]
allPropsApplied :: [(String,[Laws])]
allPropsApplied =
[ ("Vicinity",typeclassProps (Proxy :: Proxy (Vicinity Word (Sum Word))))
]