summaryrefslogtreecommitdiff
path: root/tests/examplefiles/SmallCheck.hs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/examplefiles/SmallCheck.hs')
-rw-r--r--tests/examplefiles/SmallCheck.hs378
1 files changed, 378 insertions, 0 deletions
diff --git a/tests/examplefiles/SmallCheck.hs b/tests/examplefiles/SmallCheck.hs
new file mode 100644
index 00000000..36c39efc
--- /dev/null
+++ b/tests/examplefiles/SmallCheck.hs
@@ -0,0 +1,378 @@
+---------------------------------------------------------------------
+-- SmallCheck: another lightweight testing library.
+-- Colin Runciman, August 2006
+-- Version 0.2 (November 2006)
+--
+-- After QuickCheck, by Koen Claessen and John Hughes (2000-2004).
+---------------------------------------------------------------------
+
+module SmallCheck (
+ smallCheck, depthCheck,
+ Property, Testable,
+ forAll, forAllElem,
+ exists, existsDeeperBy, thereExists, thereExistsElem,
+ (==>),
+ Series, Serial(..),
+ (\/), (><), two, three, four,
+ cons0, cons1, cons2, cons3, cons4,
+ alts0, alts1, alts2, alts3, alts4,
+ N(..), Nat, Natural,
+ depth, inc, dec
+ ) where
+
+import Data.List (intersperse)
+import Control.Monad (when)
+import System.IO (stdout, hFlush)
+
+------------------ <Series of depth-bounded values> -----------------
+
+-- Series arguments should be interpreted as a depth bound (>=0)
+-- Series results should have finite length
+
+type Series a = Int -> [a]
+
+-- sum
+infixr 7 \/
+(\/) :: Series a -> Series a -> Series a
+s1 \/ s2 = \d -> s1 d ++ s2 d
+
+-- product
+infixr 8 ><
+(><) :: Series a -> Series b -> Series (a,b)
+s1 >< s2 = \d -> [(x,y) | x <- s1 d, y <- s2 d]
+
+------------------- <methods for type enumeration> ------------------
+
+-- enumerated data values should be finite and fully defined
+-- enumerated functional values should be total and strict
+
+-- bounds:
+-- for data values, the depth of nested constructor applications
+-- for functional values, both the depth of nested case analysis
+-- and the depth of results
+
+class Serial a where
+ series :: Series a
+ coseries :: Serial b => Series (a->b)
+
+instance Serial () where
+ series _ = [()]
+ coseries d = [ \() -> b
+ | b <- series d ]
+
+instance Serial Int where
+ series d = [(-d)..d]
+ coseries d = [ \i -> if i > 0 then f (N (i - 1))
+ else if i < 0 then g (N (abs i - 1))
+ else z
+ | z <- alts0 d, f <- alts1 d, g <- alts1 d ]
+
+instance Serial Integer where
+ series d = [ toInteger (i :: Int)
+ | i <- series d ]
+ coseries d = [ f . (fromInteger :: Integer->Int)
+ | f <- series d ]
+
+newtype N a = N a
+
+instance Show a => Show (N a) where
+ show (N i) = show i
+
+instance (Integral a, Serial a) => Serial (N a) where
+ series d = map N [0..d']
+ where
+ d' = fromInteger (toInteger d)
+ coseries d = [ \(N i) -> if i > 0 then f (N (i - 1))
+ else z
+ | z <- alts0 d, f <- alts1 d ]
+
+type Nat = N Int
+type Natural = N Integer
+
+instance Serial Float where
+ series d = [ encodeFloat sig exp
+ | (sig,exp) <- series d,
+ odd sig || sig==0 && exp==0 ]
+ coseries d = [ f . decodeFloat
+ | f <- series d ]
+
+instance Serial Double where
+ series d = [ frac (x :: Float)
+ | x <- series d ]
+ coseries d = [ f . (frac :: Double->Float)
+ | f <- series d ]
+
+frac :: (Real a, Fractional a, Real b, Fractional b) => a -> b
+frac = fromRational . toRational
+
+instance Serial Char where
+ series d = take (d+1) ['a'..'z']
+ coseries d = [ \c -> f (N (fromEnum c - fromEnum 'a'))
+ | f <- series d ]
+
+instance (Serial a, Serial b) =>
+ Serial (a,b) where
+ series = series >< series
+ coseries = map uncurry . coseries
+
+instance (Serial a, Serial b, Serial c) =>
+ Serial (a,b,c) where
+ series = \d -> [(a,b,c) | (a,(b,c)) <- series d]
+ coseries = map uncurry3 . coseries
+
+instance (Serial a, Serial b, Serial c, Serial d) =>
+ Serial (a,b,c,d) where
+ series = \d -> [(a,b,c,d) | (a,(b,(c,d))) <- series d]
+ coseries = map uncurry4 . coseries
+
+uncurry3 :: (a->b->c->d) -> ((a,b,c)->d)
+uncurry3 f (x,y,z) = f x y z
+
+uncurry4 :: (a->b->c->d->e) -> ((a,b,c,d)->e)
+uncurry4 f (w,x,y,z) = f w x y z
+
+two :: Series a -> Series (a,a)
+two s = s >< s
+
+three :: Series a -> Series (a,a,a)
+three s = \d -> [(x,y,z) | (x,(y,z)) <- (s >< s >< s) d]
+
+four :: Series a -> Series (a,a,a,a)
+four s = \d -> [(w,x,y,z) | (w,(x,(y,z))) <- (s >< s >< s >< s) d]
+
+cons0 ::
+ a -> Series a
+cons0 c _ = [c]
+
+cons1 :: Serial a =>
+ (a->b) -> Series b
+cons1 c d = [c z | d > 0, z <- series (d-1)]
+
+cons2 :: (Serial a, Serial b) =>
+ (a->b->c) -> Series c
+cons2 c d = [c y z | d > 0, (y,z) <- series (d-1)]
+
+cons3 :: (Serial a, Serial b, Serial c) =>
+ (a->b->c->d) -> Series d
+cons3 c d = [c x y z | d > 0, (x,y,z) <- series (d-1)]
+
+cons4 :: (Serial a, Serial b, Serial c, Serial d) =>
+ (a->b->c->d->e) -> Series e
+cons4 c d = [c w x y z | d > 0, (w,x,y,z) <- series (d-1)]
+
+alts0 :: Serial a =>
+ Series a
+alts0 d = series d
+
+alts1 :: (Serial a, Serial b) =>
+ Series (a->b)
+alts1 d = if d > 0 then series (dec d)
+ else [\_ -> x | x <- series d]
+
+alts2 :: (Serial a, Serial b, Serial c) =>
+ Series (a->b->c)
+alts2 d = if d > 0 then series (dec d)
+ else [\_ _ -> x | x <- series d]
+
+alts3 :: (Serial a, Serial b, Serial c, Serial d) =>
+ Series (a->b->c->d)
+alts3 d = if d > 0 then series (dec d)
+ else [\_ _ _ -> x | x <- series d]
+
+alts4 :: (Serial a, Serial b, Serial c, Serial d, Serial e) =>
+ Series (a->b->c->d->e)
+alts4 d = if d > 0 then series (dec d)
+ else [\_ _ _ _ -> x | x <- series d]
+
+instance Serial Bool where
+ series = cons0 True \/ cons0 False
+ coseries d = [ \x -> if x then b1 else b2
+ | (b1,b2) <- series d ]
+
+instance Serial a => Serial (Maybe a) where
+ series = cons0 Nothing \/ cons1 Just
+ coseries d = [ \m -> case m of
+ Nothing -> z
+ Just x -> f x
+ | z <- alts0 d ,
+ f <- alts1 d ]
+
+instance (Serial a, Serial b) => Serial (Either a b) where
+ series = cons1 Left \/ cons1 Right
+ coseries d = [ \e -> case e of
+ Left x -> f x
+ Right y -> g y
+ | f <- alts1 d ,
+ g <- alts1 d ]
+
+instance Serial a => Serial [a] where
+ series = cons0 [] \/ cons2 (:)
+ coseries d = [ \xs -> case xs of
+ [] -> y
+ (x:xs') -> f x xs'
+ | y <- alts0 d ,
+ f <- alts2 d ]
+
+-- Warning: the coseries instance here may generate duplicates.
+instance (Serial a, Serial b) => Serial (a->b) where
+ series = coseries
+ coseries d = [ \f -> g [f x | x <- series d]
+ | g <- series d ]
+
+-- For customising the depth measure. Use with care!
+
+depth :: Int -> Int -> Int
+depth d d' | d >= 0 = d'+1-d
+ | otherwise = error "SmallCheck.depth: argument < 0"
+
+dec :: Int -> Int
+dec d | d > 0 = d-1
+ | otherwise = error "SmallCheck.dec: argument <= 0"
+
+inc :: Int -> Int
+inc d = d+1
+
+-- show the extension of a function (in part, bounded both by
+-- the number and depth of arguments)
+instance (Serial a, Show a, Show b) => Show (a->b) where
+ show f =
+ if maxarheight == 1
+ && sumarwidth + length ars * length "->;" < widthLimit then
+ "{"++(
+ concat $ intersperse ";" $ [a++"->"++r | (a,r) <- ars]
+ )++"}"
+ else
+ concat $ [a++"->\n"++indent r | (a,r) <- ars]
+ where
+ ars = take lengthLimit [ (show x, show (f x))
+ | x <- series depthLimit ]
+ maxarheight = maximum [ max (height a) (height r)
+ | (a,r) <- ars ]
+ sumarwidth = sum [ length a + length r
+ | (a,r) <- ars]
+ indent = unlines . map (" "++) . lines
+ height = length . lines
+ (widthLimit,lengthLimit,depthLimit) = (80,20,3)::(Int,Int,Int)
+
+---------------- <properties and their evaluation> ------------------
+
+-- adapted from QuickCheck originals: here results come in lists,
+-- properties have depth arguments, stamps (for classifying random
+-- tests) are omitted, existentials are introduced
+
+newtype PR = Prop [Result]
+
+data Result = Result {ok :: Maybe Bool, arguments :: [String]}
+
+nothing :: Result
+nothing = Result {ok = Nothing, arguments = []}
+
+result :: Result -> PR
+result res = Prop [res]
+
+newtype Property = Property (Int -> PR)
+
+class Testable a where
+ property :: a -> Int -> PR
+
+instance Testable Bool where
+ property b _ = Prop [Result (Just b) []]
+
+instance Testable PR where
+ property prop _ = prop
+
+instance (Serial a, Show a, Testable b) => Testable (a->b) where
+ property f = f' where Property f' = forAll series f
+
+instance Testable Property where
+ property (Property f) d = f d
+
+evaluate :: Testable a => a -> Series Result
+evaluate x d = rs where Prop rs = property x d
+
+forAll :: (Show a, Testable b) => Series a -> (a->b) -> Property
+forAll xs f = Property $ \d -> Prop $
+ [ r{arguments = show x : arguments r}
+ | x <- xs d, r <- evaluate (f x) d ]
+
+forAllElem :: (Show a, Testable b) => [a] -> (a->b) -> Property
+forAllElem xs = forAll (const xs)
+
+thereExists :: Testable b => Series a -> (a->b) -> Property
+thereExists xs f = Property $ \d -> Prop $
+ [ Result
+ ( Just $ or [ all pass (evaluate (f x) d)
+ | x <- xs d ] )
+ [] ]
+ where
+ pass (Result Nothing _) = True
+ pass (Result (Just b) _) = b
+
+thereExistsElem :: Testable b => [a] -> (a->b) -> Property
+thereExistsElem xs = thereExists (const xs)
+
+exists :: (Serial a, Testable b) =>
+ (a->b) -> Property
+exists = thereExists series
+
+existsDeeperBy :: (Serial a, Testable b) =>
+ (Int->Int) -> (a->b) -> Property
+existsDeeperBy f = thereExists (series . f)
+
+infixr 0 ==>
+
+(==>) :: Testable a => Bool -> a -> Property
+True ==> x = Property (property x)
+False ==> x = Property (const (result nothing))
+
+--------------------- <top-level test drivers> ----------------------
+
+-- similar in spirit to QuickCheck but with iterative deepening
+
+-- test for values of depths 0..d stopping when a property
+-- fails or when it has been checked for all these values
+smallCheck :: Testable a => Int -> a -> IO String
+smallCheck d = iterCheck 0 (Just d)
+
+depthCheck :: Testable a => Int -> a -> IO String
+depthCheck d = iterCheck d (Just d)
+
+iterCheck :: Testable a => Int -> Maybe Int -> a -> IO String
+iterCheck dFrom mdTo t = iter dFrom
+ where
+ iter :: Int -> IO String
+ iter d = do
+ let Prop results = property t d
+ (ok,s) <- check (mdTo==Nothing) 0 0 True results
+ maybe (iter (d+1))
+ (\dTo -> if ok && d < dTo
+ then iter (d+1)
+ else return s)
+ mdTo
+
+check :: Bool -> Int -> Int -> Bool -> [Result] -> IO (Bool, String)
+check i n x ok rs | null rs = do
+ let s = " Completed "++show n++" test(s)"
+ y = if i then "." else " without failure."
+ z | x > 0 = " But "++show x++" did not meet ==> condition."
+ | otherwise = ""
+ return (ok, s ++ y ++ z)
+
+check i n x ok (Result Nothing _ : rs) = do
+ progressReport i n x
+ check i (n+1) (x+1) ok rs
+
+check i n x f (Result (Just True) _ : rs) = do
+ progressReport i n x
+ check i (n+1) x f rs
+
+check i n x f (Result (Just False) args : rs) = do
+ let s = " Failed test no. "++show (n+1)++". Test values follow."
+ s' = s ++ ": " ++ concat (intersperse ", " args)
+ if i then
+ check i (n+1) x False rs
+ else
+ return (False, s')
+
+progressReport :: Bool -> Int -> Int -> IO ()
+progressReport _ _ _ = return ()