{-# LANGUAGE TupleSections, TemplateHaskell #-} import Test.QuickCheck import Test.QuickCheck.Modifiers import Test.QuickCheck.Monadic import Test.QuickCheck.All import Test.QuickCheck.Test import Control.Monad import Data.IORef import System.Exit import Data.Binding.List as B import Prelude as P -- Change these to exercise different variable and data types type V = IORef type A = Char -- *** Functions to generate lists and pointers *** -- | A random list with at least two elements. newtype List = List [A] deriving Show instance Arbitrary List where arbitrary = do a <- arbitrary b <- arbitrary c <- arbitrary return $ List (a:b:c) shrink (List [a,b]) = [List [a',b'] | a' <- shrink a, b' <- shrink a] shrink (List xs) = map List $ shrink xs -- | Maps i to a pointer in @xs@. anywhere :: Int -> [A] -> Int anywhere i xs = let max = P.length xs - 1 in if max == 0 then 0 else i `mod` max -- | Anywhere in the list except the last element. notLast :: Int -> [A] -> Int notLast i = anywhere i . tail -- *** Test pure functions *** prop_remove' :: [A] -> Int -> Bool prop_remove' xs i = let pos = anywhere i xs actual = remove' xs pos in P.length actual == P.length xs - 1 && take pos actual == take pos xs && drop (pos+1) xs == drop pos actual prop_removeLast' :: [A] -> Bool prop_removeLast' xs = let pos = P.length xs - 1 actual = remove' xs pos in P.length actual == pos && actual == take pos xs prop_insert' :: [A] -> Int -> A -> Bool prop_insert' xs i x = let pos = anywhere i xs actual = insert' xs pos x in P.length actual == P.length xs + 1 && take pos actual == take pos xs && actual !! pos == x && drop pos actual == drop (pos+1) xs -- *** Tests in 'IO'. They are converted to 'Property's with 'monadicIO'. *** -- | Create a 'BindingList', and 'seek' to @pos@. list :: [A] -> Int -> IO (BindingList V A) list xs pos = do bl <- toBindingList xs seek bl pos return bl testSource :: (A,A,A) -> IO (A,A) testSource (a,b,c) = do --bind a source source <- newVar a :: IO (Source V A) target <- newVar c :: IO (Source V A) bind source id target writeVar x <- readVar target --change its value writeVar source b y <- readVar target return (x,y) testSeek :: [A] -> Int -> IO (Int, A) testSeek xs pos = do bl <- toBindingList xs :: IO (BindingList V A) liftM2 (,) (seek bl pos) (readVar bl) testPosition :: [A] -> Int -> IO Int testPosition xs init = list xs init >>= position testSeekBy :: [A] -> Int -> Int -> IO (Int, A) testSeekBy xs init offset = do bl <- list xs init liftM2 (,) (seekBy (offset+) bl) (readVar bl) testNext :: [A] -> Int -> IO (Int, A) testNext xs init = do bl <- list xs init liftM2 (,) (B.next bl) (readVar bl) testPrev :: [A] -> Int -> IO (Int, A) testPrev xs init = do bl <- list xs init liftM2 (,) (prev bl) (readVar bl) testRemove :: [A] -> Int -> IO (Int, [A]) testRemove xs pos = do bl <- list xs pos liftM2 (,) (remove bl) (fromBindingList bl) testInsert :: [A] -> Int -> A -> IO (Int, [A]) testInsert xs pos new = do bl <- list xs pos liftM2 (,) (insert bl new) (fromBindingList bl) -- *** QuickCheck 'Property's for Monadic actions. *** prop_Source :: (A,A,A) -> Property prop_Source (a,b,c) = monadicIO $ do (x,y) <- run $ testSource (a,b,c) assert (x==a && y==b) prop_Length :: NonEmptyList A -> Property prop_Length (NonEmpty xs) = monadicIO $ do l <- run $ (toBindingList xs :: IO (BindingList V A)) >>= B.length assert (l == P.length xs) prop_seek :: NonEmptyList A -> Int -> Property prop_seek (NonEmpty xs) i = let pos = anywhere i xs in monadicIO $ do (new, x) <- run $ testSeek xs pos assert (new == pos && x == xs !! pos) prop_position :: NonEmptyList A -> Int -> Property prop_position (NonEmpty xs) i = let pos = anywhere i xs in monadicIO $ do new <- run $ testPosition xs pos assert (new == pos) prop_seekBy :: List -> Int -> Int -> Property prop_seekBy (List xs) a b = let size = P.length xs init = anywhere a xs offset = anywhere b xs - init in monadicIO $ do (new, x) <- run $ testSeekBy xs init offset assert (new == init + offset && x == xs !! new) prop_next :: List -> Int -> Property prop_next (List xs) i = let pos = notLast i xs in monadicIO $ do (new, x) <- run $ testNext xs pos assert (new == pos + 1 && x == xs !! new) prop_prev :: List -> Int -> Property prop_prev (List xs) i = let pos = anywhere i xs + 1 in monadicIO $ do (new, x) <- run $ testPrev xs pos assert (new == pos - 1 && x == xs !! new) prop_remove :: List -> Int -> Property prop_remove (List xs) i = let pos = notLast i xs in monadicIO $ do (pos',ys) <- run $ testRemove xs pos assert (ys == remove' xs pos && pos' == pos) prop_removeLast :: List -> Property prop_removeLast (List xs) = let pos = P.length xs - 1 in monadicIO $ do (pos',ys) <- run $ testRemove xs pos assert (ys == remove' xs pos && pos' == pos -1) prop_insert :: List -> Int -> A -> Property prop_insert (List xs) i x = let pos = anywhere i xs new = pos + 1 in monadicIO $ do (pos',ys) <- run $ testInsert xs pos x assert (ys == insert' xs new x && pos' == new) -- | Test the 'Property's. main = do passed <- $quickCheckAll unless passed exitFailure