Files
haskell/exercises/Set13aTest.hs
T
2025-09-26 09:35:08 +03:00

233 lines
8.7 KiB
Haskell

{-# LANGUAGE TemplateHaskell, ScopedTypeVariables, StandaloneDeriving #-}
module Set13aTest where
import Mooc.Test
import Mooc.Th
import Control.Monad.Trans.State
import Data.Char
import Data.List
import qualified Data.Map as Map
import Test.QuickCheck
import Examples.Bank
import Set13a
main = score tests
tests = [(1,"readNames",[ex1_ok, ex1_fail])
,(2,"winnerMaybe",[ex2_small, ex2_big, ex2_fail])
,(3,"selectSum",[ex3_ok, ex3_fail])
,(4,"countAndLog",[ex4_example, ex4_gen])
,(5,"balance",[ex5_balance_examples, ex5_balance_ok, ex5_balance_nok])
,(6,"rob",[ex6_rob_examples, ex6_rob])
,(7,"State update",[ex7])
,(8,"parensMatch",[ex8_paren, ex8_parensMatch])
,(9,"State count",[ex9])
,(10,"State occurrences",[ex10_1, ex10_2])
]
-- -- -- -- -- -- --
word = do fst <- choose ('A','Z')
rest <- listOf (choose ('a','z'))
return $ fst:rest
bad = do a <- choose ('A','Z')
b <- word
c <- elements "0123456789"
d <- word
return $ a:b++c:d
ex1_ok =
forAllBlind word $ \for ->
forAllBlind word $ \sur ->
let str = for++" "++sur
in $(testing [|readNames str|])
(?==Just (for,sur))
m_ex1_fail s =
$(testing [|readNames s|]) (?== Nothing)
ex1_fail =
forAllBlind word $ \for ->
forAllBlind word $ \sur ->
forAllBlind bad $ \b ->
conjoin [m_ex1_fail (for ++ sur),
m_ex1_fail (map toLower for ++ " " ++ sur),
m_ex1_fail (for ++ " " ++ map toLower sur),
m_ex1_fail (for ++ b ++ " " ++ sur),
m_ex1_fail (for ++ " " ++ sur ++ b)]
ex2_small =
forAllBlind word $ \p1 ->
forAllBlind (word `suchThat` (/=p1)) $ \p2 ->
forAllBlind (choose (1,20)) $ \s1 ->
forAllBlind (choose (0,s1)) $ \s2 ->
forAllBlind (shuffle [(p1,s1),(p2,s2)]) $ \inp ->
conjoin [$(testing [|winner inp p1 p2|]) (?==Just p1)
,s2 < s1 ==> $(testing [|winner inp p2 p1|]) (?==Just p1)]
ex2_big =
forAllBlind word $ \w ->
forAllBlind (choose (10,100)) $ \s ->
forAllBlind (nub <$> listOf1 (word`suchThat`(/=w))) $ \names ->
forAllBlind (vectorOf (length names) (choose (0,s-1))) $ \scores ->
forAllBlind (elements names) $ \other ->
forAllBlind (shuffle ((w,s):zip names scores)) $ \inp ->
conjoin [$(testing [|winner inp other w|]) (?==Just w)
,$(testing [|winner inp w other|]) (?==Just w)]
ex2_fail =
forAllBlind (nub <$> listOf1 word) $ \names ->
forAllBlind (vectorOf (length names) (choose (0,100))) $ \scores ->
forAllBlind (elements names) $ \ok ->
forAllBlind (word `suchThat` (not.flip elem names)) $ \nok1 ->
forAllBlind (word `suchThat` (not.flip elem names)) $ \nok2 ->
let inp = zip names scores
in conjoin [$(testing [|winner inp ok nok1|]) (?==Nothing)
,$(testing [|winner inp nok1 ok|]) (?==Nothing)
,$(testing [|winner inp nok1 nok2|]) (?==Nothing)]
ex3_ok = forAll_ $ \(as::[Int]) ->
forAllBlind (shuffle (zip [0..] as)) $ \pairs ->
forAllShrink_ (sublistOf pairs) $ \selected ->
$(testing [|selectSum as (map fst selected)|]) (?==Just (sum $ map snd selected))
ex3_fail = property $ do
as <- arbitrary :: Gen [Int]
is <- listOf (choose (0,length as - 1))
b <- elements [-1,length as]
inp <- shuffle (b:is)
return $ $(testing [|selectSum as inp|]) (?==Nothing)
ex4_example =
$(testing' [|countAndLog even [0,1,2,3,4,5]|]) (?==Logger ["0","2","4"] 3)
ex4_gen = property $ do
i <- choose (0::Int,10)
is <- listOf (choose (0,10) `suchThat` (/=i))
n <- choose (0,5)
input <- shuffle (replicate n i ++ is)
return $ counterexample ("countAndLog (=="++show i++") "++show input) $
countAndLog (==i) input ?== Logger (replicate n (show i)) n
ex5_balance_examples =
conjoin [$(testing' [|runBankOp (balance "harry") exampleBank|]) (?==(10,exampleBank))
,$(testing' [|runBankOp (balance "sean") exampleBank|]) (?==(0,exampleBank))]
bank = do
names <- nub <$> listOf1 word
bals <- vectorOf (length names) (choose (0,100))
return $ Map.fromList (zip names bals)
ex5_balance_ok = forAllBlind bank $ \b ->
forAllBlind (elements (Map.keys b)) $ \account ->
let bank = Bank b
in $(testing' [|runBankOp (balance account) bank|]) $ \out ->
counterexample (" with bank = " ++ show bank++"\n account = "++show account) $
out?==(b Map.! account, bank)
ex5_balance_nok = forAllBlind bank $ \b ->
forAllBlind (word `suchThat` (not.flip elem (Map.keys b))) $ \account ->
let bank = Bank b
in $(testing' [|runBankOp (balance account) bank|]) $ \out ->
counterexample (" with bank = " ++ show bank++"\n account = "++show account) $
out?==(0,bank)
ex6_rob_examples =
conjoin [$(testing' [|runBankOp (rob "cedric" "ginny") exampleBank|])
(?==((),Bank (Map.fromList [("cedric",0),("ginny",8),("harry",10)])))
,$(testing' [|runBankOp (rob "sean" "ginny") exampleBank|])
(?==((),Bank (Map.fromList [("cedric",7),("ginny",1),("harry",10)])))]
ex6_rob = property $ do
from <- word
to <- word `suchThat` (/=from)
others <- listOf (word `suchThat` (not.flip elem [from,to]))
balf <- choose (0,100)
balt <- choose (0,100)
bals <- vectorOf (length others) (choose (0,100))
let run from to bank exp = $(testing' [|runBankOp (rob from to) bank|]) $ \out ->
counterexample (" with bank = " ++ show bank++"\n from = "++show from++"\n to = "++show to) $
out ?== ((),exp)
return $ conjoin [run from to
(Bank . Map.fromList $ (from,balf):(to,balt):zip others bals)
(Bank . Map.fromList $ (from,0):(to,balf+balt):zip others bals)
,run from to
(Bank . Map.fromList $ (to,balt):zip others bals)
(Bank . Map.fromList $ (to,balt):zip others bals)]
ex7 = forAll_ $ \i ->
counterexample ("runState update "++show i) $
runState update i ?== ((),2*i+1)
ex8_paren =
forAll_ $ \(NonNegative i) ->
conjoin [counterexample ("runState (paren '(') "++show i) $
runState (paren '(') i ?== ((),i+1)
,counterexample ("runState (paren ')') "++show i) $
runState (paren ')') i ?== ((),i-1)
,$(testing' [|runState (paren '(') (-1)|]) (?==((),-1))
,$(testing' [|runState (paren ')') (-1)|]) (?==((),-1))]
genMatch = frequency [(3,return "()"),
(1,(\x -> "("++x++")") <$> genMatch),
(1,(++) <$> genMatch <*> genMatch)]
flop ')' = '('
flop '(' = ')'
breakMatch s = do i <- choose (0,length s - 1)
let (pre,x:suf) = splitAt i s
elements [pre++suf, pre++flop x:suf]
filterMatch s = do x <- elements "()"
n <- choose (1,length s)
return $ s \\ replicate n x
ex8_parensMatch =
forAllBlind genMatch $ \match ->
conjoin [$(testing [|parensMatch match|]) (?==True)
,forAllBlind (breakMatch match) $ \broken -> $(testing [|parensMatch broken|]) (?==False)
,forAllBlind (filterMatch match) $ \broken -> $(testing [|parensMatch broken|]) (?==False)]
ex9 = property $ do
is <- fmap nub $ listOf1 (choose ('a','z') :: Gen Char)
fs <- vectorOf (length is) (choose (1,2048))
let assocs = zip is fs
x <- elements is
y <- choose ('0','z') `suchThat` \y -> not (elem y is)
let Just cx = lookup x assocs
s x = counterexample ("runState (count "++show x++") "++show assocs) . counterexample " Final state"
return $ conjoin [s y $
hasElements ((y,1):assocs) (execState (count y) assocs)
,s x $
hasElements ((x,cx+1):delete (x,cx) assocs) (execState (count x) assocs)]
ex10_1 =
forAllShrink_ (nub <$> listOf (choose ('a','z'))) $ \cs ->
forAllBlind (vectorOf (length cs) (choose (1,3))) $ \ns ->
let expanded = concat (zipWith replicate ns cs)
in forAllBlind (shuffle expanded) $ \input ->
counterexample ("runState (occurrences "++show input++") []") $
let out@(x,s) = runState (occurrences input) []
in counterexample (" Was: " ++ show out) $
conjoin [counterexample "Produced value" $ x ?== length cs
,counterexample "Final state" $ hasElements (zip cs ns) s]
ex10_2 =
forAllShrink_ (nub <$> listOf (choose (10,100))) $ \xs ->
forAllBlind (vectorOf (length xs) (choose (1,3))) $ \ns ->
forAllBlind (choose (0::Int,9)) $ \x ->
forAllBlind (choose (1,20)) $ \n0 ->
forAllBlind (choose (1,20)) $ \n ->
let state = zip xs ns++[(x,n0)]
input = replicate n x
in counterexample ("runState (occurrences "++show input++") "++show state) $
let out@(ret,s) = runState (occurrences input) state
in counterexample (" Was: " ++ show out) $
conjoin [counterexample "Produced value" $ ret ?== length xs + 1
,counterexample "Final state" $ hasElements ((x,n+n0):zip xs ns) s]