Packages

The Phi Programming Language

Current section

Files

Jump to
phi lib Test QuickCheck.hm
Raw

lib/Test/QuickCheck.hm

-----------------------------------------------------------------------------
-- |
-- Module : QuickCheck
-- Copyright : (c) 2020-2021 EMQ Technologies Co., Ltd.
-- License : BSD-style (see the LICENSE file)
--
-- Maintainer : Feng Lee, feng@emqx.io
-- Yang M, yangm@emqx.io
-- Stability : experimental
-- Portability : portable
--
-- The QuickCheck module.
--
-----------------------------------------------------------------------------
module Test.QuickCheck where
import Data.Atom (atom)
import Data.Bool (otherwise)
import Data.Enum (range)
import Data.Eq ((==))
import Data.Int (Int)
import Data.List (concat, replicate, length) --init,
import Data.Functor (class Functor, map, (<$>))
import Data.Function (($), identity, error, (<<<))
import Data.Maybe (Maybe(..))
import Data.Ord ((<), (<=), (>=))
import Data.Ring ((-),(/),negate)
import Data.Semigroup ((<>))
import Data.Semiring ((+))
import Data.Show (showAny, show)
import Data.Tuple (fst)
import Data.Foldable (foldl)
import Data.Traversable (sequence)
import Data.Unit (Unit,unit)
import Control.Monad (unsafePerformIO, liftM1, liftM2, class Applicative, pure, class Monad, bind, IO, discard, seqio, (>>=))
import System.IO (printf, println)
import System.Random(randomRIO)
import Data.Binary (listToBin)
import System.Error (catchException)
foreign import data Rand :: Type
foreign import split :: Rand -> (Rand, Rand)
foreign import next :: Rand -> Rand
foreign import mkRand :: Integer -> Rand
class Random a where
randomR :: (a, a) -> Rand -> a
instance Random Integer where
randomR = randomRInt
foreign import randomRInt :: (Integer, Integer) -> Rand -> Integer
instance Random Float where
randomR = randomRFloat
foreign import randomRFloat :: (Float, Float) -> Rand -> Float
instance Random Char where
randomR = randomRChar
foreign import randomRChar :: (Char, Char) -> Rand -> Char
data Gen a = Gen (Integer -> Rand -> a)
choose :: forall a. Random a => (a, a) -> Gen a
choose bounds = Gen (\n r -> randomR bounds r)
rands :: Integer -> Rand -> Rand
rands 0 r = r
rands i r0 = rands (i-1) (next r0)
variant :: forall a. Integer -> Gen a -> Gen a
variant v (Gen m) = Gen (\n r ->
m n $ rands (v+1) r )
promote :: forall a b. (a -> Gen b) -> Gen (a -> b)
promote f = Gen (\n r -> \a ->
let Gen m = f a
in m n r )
sized :: forall a. (Int -> Gen a) -> Gen a
sized fgen = Gen ( \n r ->
let Gen m = fgen n in m n r
)
instance Functor Gen where
map f (Gen m) = Gen $ \n r -> f (m n r)
instance Applicative Gen where
apply (Gen f) (Gen a) = Gen $ \n r0 ->
let (r1, r2) = split r0
vf = f n r1
va = a n r2
in vf va
pure a = Gen $ \n r -> a
instance Monad Gen where
bind (Gen m1) k = Gen $ \n r0 ->
let (r1, r2) = split r0
Gen m2 = k (m1 n r1)
in m2 n r2
-----------------------------------------------------------------------------
index :: forall a. Integer -> [a] -> a
index 0 [x | _] = x
index i [x | xs] = index (i-1) xs
index _ _ = error "error input"
elements :: forall a. [a] -> Gen a
elements xs = map (\v -> index v xs) $ choose (0, length xs -1)
vector :: forall a. Arbitrary a => Integer -> Gen [a]
vector 0 = pure []
vector n = sequence [ arbitrary | i <- [1..n]]
oneof :: forall a. [Gen a] -> Gen a
oneof gens = elements gens >>= identity
pick :: forall a. Integer -> [(Integer, a)] -> a
pick n [(k,x)| xs] | n <= k = x
| otherwise = pick (n-k) xs
pick _ _ = error "error input"
frequency :: forall a. [(Integer, Gen a)] -> Gen a
frequency xs = choose (1, foldl (+) 0 (map fst xs)) >>= \x -> pick x xs
-----------------------------------------------------------------------------
class ArbitraryM a where
arbitraryM :: Integer -> Gen a
class Arbitrary a where
arbitrary :: Gen a
instance Arbitrary Boolean where
arbitrary = elements [true, false]
instance Arbitrary Integer where
arbitrary = sized (\n -> choose (-n, n))
instance Arbitrary Float where
arbitrary = choose (-100000000.0, 100000000.0)
instance Arbitrary Char where
arbitrary = choose ('A', 'z')
instance Arbitrary Atom where
arbitrary = atom <$> arbitrary
instance Arbitrary Binary where
arbitrary = listToBin <$> ( map tabs <$> arbitrary)
where tabs :: Integer -> Integer
tabs x | x >= 0 = x
| otherwise = (-x)
instance (Arbitrary a, Arbitrary b) => Arbitrary (a, b) where
arbitrary = liftM2 (\x y -> (x,y)) arbitrary arbitrary
instance Arbitrary a => Arbitrary [a] where
arbitrary = sized (\n -> choose (0,n) >>= vector)
instance (Coarbitrary a, Arbitrary b) => Arbitrary (a -> b) where
arbitrary = promote (\x -> x `coarbitrary` arbitrary)
instance Arbitrary v => Arbitrary (Maybe v) where
arbitrary = liftM1 Just arbitrary
-----------------------------------------------------------------------------
class Coarbitrary a where
coarbitrary :: forall b. a -> Gen b -> Gen b
instance Coarbitrary Boolean where
coarbitrary b = variant (if b then 0 else 1)
instance Coarbitrary Integer where
coarbitrary n
| n == 0 = variant 0
| n < 0 = variant 2 <<< coarbitrary (- n)
| otherwise = variant 1 <<< coarbitrary (n / 2)
-----------------------------------------------------------------------------
data Result = Result
{ok :: Maybe Boolean, stamp :: [String], arguments :: [String]}
data Property = Prop (Gen Result)
nothing :: forall a. {ok :: Maybe a, stamp :: [String], arguments :: [String]}
nothing = {ok = Nothing, stamp =[], arguments = []}
result :: Result -> Property
result res = Prop (pure res)
class Testable a where
property :: a -> Property
instance Testable Boolean where
property b = result ( Result nothing{ok= Just b})
instance Testable Property where
property prop = prop
instance (Testable a) => Testable (IO a) where
property b = property $ unsafePerformIO b
instance (Arbitrary a, Testable b) => Testable (a -> b) where
property f = forAll arbitrary f
evaluate :: forall a. Testable a => a -> Gen Result
evaluate a = gen where Prop gen = property a
forAll :: forall a b. Testable b => Gen a -> (a -> b) -> Property
forAll gen body = Prop $
do a <- gen
Result res <- unsafePerformIO $ catchException (pure $ evaluate (body a)) handleExcept
pure $ Result (arg a res)
where
arg a res = res{arguments = [showAny a | res.arguments ]}
handleExcept e = pure $ Gen $ \i r -> Result {arguments = [ showAny e], stamp = [], ok = Nothing }
type SpaceCounter = Integer
basePipe :: String
basePipe = "| "
errorPipe :: String
errorPipe = "✖ "
crashPipe :: String
crashPipe = "● "
check :: forall a. Testable a => String -> Integer -> a -> SpaceCounter -> IO TestResult
check s 0 m n = (printf "%s✔ %s" (concat $ replicate (n/5) basePipe) s :: IO ()) >>= \_ -> pure Successed
check s v m n = do
i <- randomRIO 1332292274972041455 7304856964418773083
let Gen fun = evaluate m
Result r = fun 15 (mkRand i)
case r.ok of
Just true -> check s (v-1) m n
Just false -> (printf "%s✖ %s -----> %s"
(concat $ replicate (n/5) errorPipe) s (showAny r.arguments) :: IO ()) >>= \_ -> pure $ Failed s (showAny r.arguments)
Nothing -> (printf "%s● %s ==> crash happened!!! %s"
(concat $ replicate (n/5) crashPipe) s (showAny r.arguments) :: IO ()) >>= \_ -> pure $ Crash s (showAny r.arguments)
quickCheck :: forall a. Testable a => String -> a -> SpaceCounter -> IO TestResult
quickCheck s m n = check s 100 m n
quickCheck1 :: forall a. Testable a => String -> a -> SpaceCounter -> IO TestResult
quickCheck1 s m n = check s 1 m n
data TestResult = Successed
| Failed String String
| Crash String String
data TestGroup a = TxG String [TestGroup a]
| Exe [a]
tgToList :: TestGroup TestResult -> [TestResult]
tgToList (Exe xs) = xs
tgToList (TxG _ xs) = concat $ map tgToList xs
runTestGroup :: SpaceCounter -> TestGroup (Integer -> IO TestResult) -> IO (TestGroup TestResult)
runTestGroup n (Exe fs) = Exe <$> (seqio $ map (\x -> x n) fs)
runTestGroup n (TxG s xs) =
TxG s <$> do println (concat (replicate (n/5) basePipe) <> "✺ "<> s )
res <- seqio $ map (runTestGroup (n+5)) xs
let res1 = concat $ map tgToList res
(succ, fail, crash, total) = tcount (0,0,0,0) res1
renderCrash 0 = ""
renderCrash i = "(crashed " <> show i <> ")"
printf "%s----> ◯ %d, ✔ %d, ✖ %d%s." (concat $ replicate (n/5) basePipe) total succ fail (renderCrash crash)
pure res
runTest :: TestGroup (Integer -> IO TestResult) -> IO ()
runTest g = runTestGroup 0 g >>= \_ -> pure ()
tcount :: (Integer, Integer, Integer, Integer) -> [TestResult] -> (Integer, Integer, Integer, Integer)
tcount (a,b,c,d) [] = (a,b,c,d)
tcount (a,b,c,d) [x|xs] = case x of
Successed -> tcount (a+1 ,b ,c , d+1) xs
Failed _ _ -> tcount (a ,b+1 ,c , d+1) xs
Crash _ _ -> tcount (a ,b+1 ,c+1 , d+1) xs