willow-0.1.0.0: test/Test/Willow/Property/Parser.hs
{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-|
Description:
Copyright: (c) 2020 Samuel May
License: MPL-2.0
Maintainer: ag.eitilt@gmail.com
Stability: experimental
Portability: portable
-}
module Test.Willow.Property.Parser
( tests
) where
import qualified Control.Applicative as A
import qualified Control.Monad as N
import qualified Control.Monad.Fail as N.F
-- import qualified Control.Monad.Fix as N.FX
-- import qualified Control.Monad.IO.Class as N.IO
import qualified Control.Monad.Trans.Class as N.T
import qualified Data.Semigroup as SG
import qualified Hedgehog as H
import qualified Hedgehog.Gen as H.G
import qualified Hedgehog.Range as H.R
import Control.Applicative ( (<|>) )
import Hedgehog ( (===) )
import Web.Willow.Common.Parser
import Test.Willow.Property.Common
tests :: [H.Group]
tests = map laws
[ ("ByteString.Lazy", H.G.bytes $ H.R.linear 0 32, H.G.word8 H.R.linearBounded)
]
laws ::
( Stream stream token
, Eq stream, Show stream, Monoid stream
, Eq token, Show token
) => (String, H.Gen stream, H.Gen token) -> H.Group
laws (name, genStream, genToken) = packGroup ("Web.Willow.Common.Parser " ++ name) . concat $
map (\f -> f genStream genToken)
[ semigroupLaws
, monoidLaws
, alternativeLaws
, applicativeLaws
, functorLaws
, monadLaws
-- , monIoLaws
, monPlusLaws
, monFailLaws
-- , monFixLaws
, monTransLaws
]
data ParserFunc stream token = ParserFunc
{ parserIndex :: String
, parser :: Parser stream token
}
instance Eq (ParserFunc stream token) where
l == r = parserIndex l == parserIndex r
instance Show (ParserFunc stream token) where
show p = "(parser: " ++ parserIndex p ++ ")"
genParser ::
( Stream stream token
, Eq token
) => H.Gen token -> H.Gen (ParserFunc stream token)
genParser x' = H.G.choice
[ return $ ParserFunc "empty" A.empty
, do
x <- x'
return . ParserFunc "pure" $ pure x
, return $ ParserFunc "next" next
, do
x <- x'
return . ParserFunc "token" $ token x
, do
x <- x'
return . ParserFunc "push *> next" $ push x *> next
]
genParserList ::
( Stream stream token
, Eq stream
, Eq token
) => H.Gen token -> H.Gen (ParserFunc stream [token])
genParserList x' = H.G.choice
[ return $ ParserFunc "empty []" A.empty
, do
xs <- H.G.list (H.R.linear 0 16) x'
let xs' = foldr cons mempty xs
return . ParserFunc "pure []" $ pure xs'
, do
n <- H.G.word $ H.R.linear 0 16
return . ParserFunc "nextChunk" . chunkToList $ nextChunk n
, do
xs <- H.G.list (H.R.linear 0 16) x'
let xs' = foldr cons mempty xs
return . ParserFunc "chunk" . chunkToList $ chunk xs'
, do
xs <- H.G.list (H.R.linear 0 16) x'
n <- H.G.word $ H.R.linear 0 16
let xs' = foldr cons mempty xs
return . ParserFunc "pushChunk *> nextChunk" . chunkToList $ pushChunk xs' *> nextChunk n
]
where chunkToList = fmap chunkToList'
chunkToList' stream = case uncons stream of
Just (tok, stream') -> tok : chunkToList' stream'
Nothing -> []
data TokenFunc token = TokenFunc
{ tokIndex :: String
, tokFunc :: token -> token
}
instance Eq (TokenFunc token) where
l == r = tokIndex l == tokIndex r
instance Show (TokenFunc token) where
show f = "(token function: " ++ tokIndex f ++ ")"
genTokenFunc :: H.Gen token -> H.Gen (TokenFunc token)
genTokenFunc _ = H.G.choice
[ return $ TokenFunc "id" id
]
{-
data TwoTokenFunc token = TwoTokenFunc
{ tokIndex2 :: String
, tokFunc2 :: token -> token -> token
}
instance Eq (TwoTokenFunc token) where
l == r = tokIndex2 l == tokIndex2 r
instance Show (TwoTokenFunc token) where
show f = "(two-token function: " ++ tokIndex2 f ++ ")"
genTwoTokenFunc :: H.Gen token -> H.Gen (TwoTokenFunc token)
genTwoTokenFunc _ = H.G.choice
[ return $ TwoTokenFunc "const" const
]
-}
type Laws stream token =
( Stream stream token
, Eq stream, Show stream, Monoid stream
, Eq token, Show token
) => H.Gen stream -> H.Gen token -> [Test]
type Law stream token =
( Stream stream token
, Eq stream, Show stream, Monoid stream
, Eq token, Show token
) => H.Gen stream -> H.Gen token -> Test
semigroupLaws :: Laws stream token
semigroupLaws genStream genToken = map (\f -> f genStream genToken)
[ semiAssoc
, semiConcat
, semiTimes
]
semiAssoc :: Law stream token
semiAssoc genStream genToken = packTest "Semigroup: associativity" $ do
stream <- H.forAll genStream
a' <- H.forAll $ genParserList genToken
b' <- H.forAll $ genParserList genToken
c' <- H.forAll $ genParserList genToken
let a = parser a'
b = parser b'
c = parser c'
let l = runParser (a <> (b <> c)) stream
r = runParser ((a <> b) <> c) stream
l === r
semiConcat :: Law stream token
semiConcat genStream genToken = packTest "Semigroup: concatenation" $ do
stream <- H.forAll genStream
ps' <- H.forAll . H.G.nonEmpty (H.R.linear 1 16) $ genParserList genToken
let ps = parser <$> ps'
let l = runParser (SG.sconcat ps) stream
r = runParser (foldr1 (<>) ps) stream
l === r
semiTimes :: Law stream token
semiTimes genStream genToken = packTest "Semigroup: times" $ do
stream <- H.forAll genStream
a' <- H.forAll $ genParserList genToken
n <- H.forAll . H.G.int $ H.R.linear 1 16
let a = parser a'
let l = runParser (SG.stimes n a) stream
r = runParser (foldr1 (<>) $ replicate n a) stream
l === r
monoidLaws :: Laws stream token
monoidLaws genStream genToken = map (\f -> f genStream genToken)
[ monoidLeft
, monoidRight
, monoidAssoc
, monoidConcat
]
monoidLeft :: Law stream token
monoidLeft genStream genToken = packTest "Monoid: left identity" $ do
stream <- H.forAll genStream
p' <- H.forAll $ genParserList genToken
let p = parser p'
let l = runParser (mempty <> p) stream
r = runParser p stream
l === r
monoidRight :: Law stream token
monoidRight genStream genToken = packTest "Monoid: right identity" $ do
stream <- H.forAll genStream
p' <- H.forAll $ genParserList genToken
let p = parser p'
let l = runParser (p <> mempty) stream
r = runParser p stream
l === r
monoidAssoc :: Law stream token
monoidAssoc genStream genToken = packTest "Monoid: associativity" $ do
stream <- H.forAll genStream
a' <- H.forAll $ genParserList genToken
b' <- H.forAll $ genParserList genToken
c' <- H.forAll $ genParserList genToken
let a = parser a'
b = parser b'
c = parser c'
let l = runParser (a <> (b <> c)) stream
r = runParser ((a <> b) <> c) stream
l === r
monoidConcat :: Law stream token
monoidConcat genStream genToken = packTest "Monoid: concatenation" $ do
stream <- H.forAll genStream
ps' <- H.forAll . H.G.list (H.R.linear 0 16) $ genParserList genToken
let ps = parser <$> ps'
let l = runParser (mconcat ps) stream
r = runParser (foldr mappend mempty ps) stream
l === r
alternativeLaws :: Laws stream token
alternativeLaws genStream genToken = map (\f -> f genStream genToken)
[ altLeft
, altRight
, altAssoc
]
altLeft :: Law stream token
altLeft genStream genToken = packTest "Alternative: left identity" $ do
stream <- H.forAll genStream
a' <- H.forAll $ genParser genToken
let a = parser a'
let l = runParser (A.empty <|> a) stream
r = runParser a stream
l === r
altRight :: Law stream token
altRight genStream genToken = packTest "Alternative: right identity" $ do
stream <- H.forAll genStream
a' <- H.forAll $ genParser genToken
let a = parser a'
let l = runParser (a <|> A.empty) stream
r = runParser a stream
l === r
altAssoc :: Law stream token
altAssoc genStream genToken = packTest "Alternative: associativity" $ do
stream <- H.forAll genStream
a' <- H.forAll $ genParser genToken
b' <- H.forAll $ genParser genToken
c' <- H.forAll $ genParser genToken
let a = parser a'
b = parser b'
c = parser c'
let l = runParser (a <|> (b <|> c)) stream
r = runParser ((a <|> b) <|> c) stream
l === r
applicativeLaws :: Laws stream token
applicativeLaws genStream genToken = map (\f -> f genStream genToken)
[ appId
, appComp
, appHomo
, appInter
, appLift_1
, appLift_2
]
appId :: Law stream token
appId genStream genToken = packTest "Applicative: identity" $ do
stream <- H.forAll genStream
v' <- H.forAll $ genParser genToken
let v = parser v'
let l = runParser (pure id <*> v) stream
r = runParser v stream
l === r
appComp :: Law stream token
appComp genStream genToken = packTest "Applicative: composition" $ do
stream <- H.forAll genStream
u' <- H.forAll $ genTokenFunc genToken
v' <- H.forAll $ genTokenFunc genToken
w' <- H.forAll $ genParser genToken
let u = pure $ tokFunc u'
v = pure $ tokFunc v'
w = parser w'
let l = runParser (pure (.) <*> u <*> v <*> w) stream
r = runParser (u <*> (v <*> w)) stream
l === r
appHomo :: Law stream token
appHomo genStream genToken = packTest "Applicative: homomorphism" $ do
stream <- H.forAll genStream
f' <- H.forAll $ genTokenFunc genToken
x <- H.forAll genToken
let f = tokFunc f'
let l = runParser (pure f <*> pure x) stream
r = runParser (pure $ f x) stream
l === r
appInter :: Law stream token
appInter genStream genToken = packTest "Applicative: interchange" $ do
stream <- H.forAll genStream
u' <- H.forAll $ genTokenFunc genToken
y <- H.forAll genToken
let u = pure $ tokFunc u'
let l = runParser (u <*> pure y) stream
r = runParser (pure ($ y) <*> u) stream
l === r
appLift_1 :: Law stream token
appLift_1 genStream genToken = packTest "Applicative: liftA2 (1)" $ do
stream <- H.forAll genStream
f' <- H.forAll $ genTokenFunc genToken
x' <- H.forAll $ genParser genToken
let f = pure $ tokFunc f'
x = parser x'
let l = runParser (A.liftA2 id f x) stream
r = runParser (f <*> x) stream
l === r
appLift_2 :: Law stream token
appLift_2 genStream genToken = packTest "Applicative: liftA2 (2)" $ do
stream <- H.forAll genStream
f' <- H.forAll $ genTokenFunc genToken
x' <- H.forAll $ genParser genToken
y' <- H.forAll $ genParser genToken
let f = pure . tokFunc f'
x = parser x'
y = parser y'
let l = runParser (A.liftA2 f x y) stream
r = runParser (f <$> x <*> y) stream
l === r
functorLaws :: Laws stream token
functorLaws genStream genToken = map (\f -> f genStream genToken)
[ funId
, funComp
, funConst
]
funId :: Law stream token
funId genStream genToken = packTest "Functor: identity" $ do
stream <- H.forAll genStream
p' <- H.forAll $ genParser genToken
let p = parser p'
let l = runParser (fmap id p) stream
r = runParser p stream
l === r
funComp :: Law stream token
funComp genStream genToken = packTest "Functor: composition" $ do
stream <- H.forAll genStream
p' <- H.forAll $ genParser genToken
f' <- H.forAll $ genTokenFunc genToken
g' <- H.forAll $ genTokenFunc genToken
let p = parser p'
f = tokFunc f'
g = tokFunc g'
let l = runParser (fmap f $ fmap g p) stream
r = runParser (fmap (f . g) p) stream
l === r
funConst :: Law stream token
funConst genStream genToken = packTest "Functor: composition" $ do
stream <- H.forAll genStream
p' <- H.forAll $ genParser genToken
x <- H.forAll genToken
let p = parser p'
let l = runParser (fmap (const x) p) stream
r = runParser (x <$ p) stream
l === r
monadLaws :: Laws stream token
monadLaws genStream genToken = map (\f -> f genStream genToken)
[ monadLeft
, monadRight
, monadAssoc
, monadRet
, monadAp
]
monadLeft :: Law stream token
monadLeft genStream genToken = packTest "Monad: left identity" $ do
stream <- H.forAll genStream
a <- H.forAll genToken
k' <- H.forAll $ genTokenFunc genToken
let k = pure . tokFunc k'
let l = runParser (return a >>= k) stream
r = runParser (k a) stream
l === r
monadRight :: Law stream token
monadRight genStream genToken = packTest "Monad: right identity" $ do
stream <- H.forAll genStream
m' <- H.forAll $ genParser genToken
let m = parser m'
let l = runParser (m >>= return) stream
r = runParser m stream
l === r
monadAssoc :: Law stream token
monadAssoc genStream genToken = packTest "Monad: associativity" $ do
stream <- H.forAll genStream
m' <- H.forAll $ genParser genToken
k' <- H.forAll $ genTokenFunc genToken
h' <- H.forAll $ genTokenFunc genToken
let m = parser m'
k = pure . tokFunc k'
h = pure . tokFunc h'
let l = runParser (m >>= (\x -> k x >>= h)) stream
r = runParser ((m >>= k) >>= h) stream
l === r
monadRet :: Law stream token
monadRet genStream genToken = packTest "Monad: return" $ do
stream <- H.forAll genStream
a <- H.forAll genToken
let l = runParser (return a) stream
r = runParser (pure a) stream
l === r
monadAp :: Law stream token
monadAp genStream genToken = packTest "Monad: ap" $ do
stream <- H.forAll genStream
f' <- H.forAll $ genTokenFunc genToken
x' <- H.forAll $ genParser genToken
let f = pure $ tokFunc f'
x = parser x'
let l = runParser (N.ap f x) stream
r = runParser (f <*> x) stream
l === r
{- Type errors
monIoLaws :: Laws stream token
monIoLaws genStream genToken = map (\f -> f genStream genToken)
[ monIoRet
, monIoLift
]
monIoRet :: Law stream token
monIoRet genStream genToken = packTest "MonadIO: return" $ do
stream <- H.forAll genStream
x <- H.forAll genToken
let l = runParser (N.IO.liftIO $ return x) stream
r = runParser (return x) stream
l === r
monIoLift :: Law stream token
monIoLift genStream genToken = packTest "MonadIO: lift" $ do
stream <- H.forAll genStream
p' <- H.forAll $ genParser genToken
f' <- H.forAll $ genTokenFunc genToken
let p = parser p'
f = pure . tokFunc f'
let l' = runParser (N.IO.liftIO $ p >>= f) stream
r' = runParser (N.IO.liftIO p >>= (N.IO.liftIO . f)) stream
l <- l'
r <- r'
l === r
-}
monPlusLaws :: Laws stream token
monPlusLaws genStream genToken = map (\f -> f genStream genToken)
[ monPlusLeftId
, monPlusRightId
, monPlusAssoc
, monPlusLeft0
, monPlusRight0
]
monPlusLeftId :: Law stream token
monPlusLeftId genStream genToken = packTest "MonadPlus: left identity" $ do
stream <- H.forAll genStream
p' <- H.forAll $ genParser genToken
let p = parser p'
let l = runParser (N.mplus N.mzero p) stream
r = runParser p stream
l === r
monPlusRightId :: Law stream token
monPlusRightId genStream genToken = packTest "MonadPlus: right identity" $ do
stream <- H.forAll genStream
p' <- H.forAll $ genParser genToken
let p = parser p'
let l = runParser (flip N.mplus N.mzero p) stream
r = runParser p stream
l === r
monPlusAssoc :: Law stream token
monPlusAssoc genStream genToken = packTest "MonadPlus: associativity" $ do
stream <- H.forAll genStream
a' <- H.forAll $ genParser genToken
b' <- H.forAll $ genParser genToken
c' <- H.forAll $ genParser genToken
let a = parser a'
b = parser b'
c = parser c'
let l = runParser (N.mplus a $ N.mplus b c) stream
r = runParser (N.mplus (N.mplus a b) c) stream
l === r
monPlusLeft0 :: Law stream token
monPlusLeft0 genStream genToken = packTest "MonadPlus: left zero" $ do
stream <- H.forAll genStream
f' <- H.forAll $ genTokenFunc genToken
let f = pure . tokFunc f'
let l = runParser (N.mzero >>= f) stream
r = runParser N.mzero stream
l === r
monPlusRight0 :: Law stream token
monPlusRight0 genStream genToken = packTest "MonadPlus: right zero" $ do
stream <- H.forAll genStream
v' <- H.forAll $ genParser genToken
let v = parser v'
let l = runParser (v >> N.mzero) stream
r = runParser (N.mzero :: Parser stream ()) stream
l === r
monFailLaws :: Laws stream token
monFailLaws genStream genToken = map (\f -> f genStream genToken)
[ monFailLeft0
]
monFailLeft0 :: Law stream token
monFailLeft0 genStream genToken = packTest "MonadFail: left zero" $ do
stream <- H.forAll genStream
f' <- H.forAll $ genTokenFunc genToken
s <- H.forAll $ H.G.string (H.R.linear 0 16) H.G.ascii
let f = pure . tokFunc f'
let l = runParser (N.F.fail s >>= f) stream
r = runParser (N.F.fail s) stream
l === r
{- Hangs
monFixLaws :: Laws stream token
monFixLaws genStream genToken = map (\f -> f genStream genToken)
[ monFixPure
, monFixShrink
, monFixSlide
, monFixNest
]
monFixPure :: Law stream token
monFixPure genStream genToken = packTest "MonadFix: purity" $ do
stream <- H.forAll genStream
h' <- H.forAll $ genTokenFunc genToken
let h = tokFunc h'
let l = runParser (N.FX.mfix $ return . h) stream
r = runParser (return $ N.FX.fix h) stream
l === r
monFixShrink :: Law stream token
monFixShrink genStream genToken = packTest "MonadFix: left shrinking" $ do
stream <- H.forAll genStream
a' <- H.forAll $ genParser genToken
f' <- H.forAll $ genTwoTokenFunc genToken
let a = parser a'
f x = pure . (tokFunc2 f') x
let l = runParser (N.FX.mfix $ \x -> a >>= \y -> f x y) stream
r = runParser (a >>= \y -> N.FX.mfix (\x -> f x y)) stream
l === r
monFixSlide :: Law stream token
monFixSlide genStream genToken = packTest "MonadFix: sliding" $ do
stream <- H.forAll genStream
f' <- H.forAll $ genTokenFunc genToken
h' <- H.forAll $ genTokenFunc genToken
let f = pure . tokFunc f'
h = tokFunc h'
let l = runParser (N.FX.mfix $ N.liftM h . f) stream
r = runParser (N.liftM h $ N.FX.mfix (f . h)) stream
l === r
monFixNest :: Law stream token
monFixNest genStream genToken = packTest "MonadFix: nesting" $ do
stream <- H.forAll genStream
f' <- H.forAll $ genTwoTokenFunc genToken
let f x = pure . (tokFunc2 f') x
let l = runParser (N.FX.mfix $ \x -> N.FX.mfix (\y -> f x y)) stream
r = runParser (N.FX.mfix $ \x -> f x x) stream
l === r
-}
monTransLaws :: Laws stream token
monTransLaws genStream genToken = map (\f -> f genStream genToken)
[ monTransRet
, monTransLift
]
monTransRet :: Law stream token
monTransRet genStream genToken = packTest "MonadTrans: return" $ do
stream <- H.forAll genStream
x <- H.forAll genToken
let l = runParser (N.T.lift $ return x) stream
r = runParser (return x) stream
l === r
monTransLift :: Law stream token
monTransLift genStream genToken = packTest "MonadTrans: lift" $ do
stream1 <- H.forAll genStream
stream2 <- H.forAll genStream
p' <- H.forAll $ genParser genToken
f' <- H.forAll $ genTokenFunc genToken
let p = parser p'
f = pure . tokFunc f'
let l' = runParserT (N.T.lift $ p >>= f) stream1
r' = runParserT (N.T.lift p >>= (N.T.lift . f)) stream1
l = runParser l' stream2
r = runParser r' stream2
l === r