antigen-0.3.1.0: test/Main.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Main (main) where
import Control.Monad (replicateM)
import Data.Data (Proxy (..))
import Data.Word (Word32, Word64)
import Test.AntiGen (
AntiGen,
antiChooseBounded,
antiNegative,
antiNonNegative,
antiNonPositive,
antiPositive,
faultyBool,
faultyNum,
faultyTry,
runAntiGen,
zapAntiGen,
(|!),
(||!),
)
import Test.AntiGen.Internal (countDecisionPoints, evalToPartial)
import Test.Hspec (Spec, describe, hspec, shouldBe, shouldSatisfy)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (
Arbitrary (..),
CoArbitrary,
Gen,
NonNegative (..),
NonPositive (..),
Positive (..),
Property,
Testable (..),
counterexample,
forAll,
forAllBlind,
getSize,
label,
scale,
suchThat,
vector,
(.&&.),
(.||.),
(=/=),
(===),
)
import Test.QuickCheck.GenT (MonadGen (..), listOf1, oneof)
import Type.Reflection (Typeable, typeRep)
antiGenPositive :: AntiGen Int
antiGenPositive = (getPositive @Int <$> arbitrary) |! (getNonPositive <$> arbitrary)
antiGenTuple :: AntiGen (Int, Int)
antiGenTuple = do
x <- antiGenPositive
y <- antiGenPositive
pure (x, y)
antiGenSmall :: AntiGen Int
antiGenSmall = choose (0, 5) |! choose (6, 10)
antiGenLengthStringStatic :: AntiGen (Int, String)
antiGenLengthStringStatic = do
l <- antiGenSmall
pure (l, replicate l 'a')
antiGenLengthString :: AntiGen (Int, String)
antiGenLengthString = do
l <- antiGenSmall
s <-
pure (replicate l 'a') |! do
NonNegative l' <- suchThat arbitrary $ \(NonNegative x) -> x /= l
pure $ replicate l' 'b'
pure (l, s)
antiGenEither :: AntiGen (Either Int [Bool])
antiGenEither = do
oneof
[ Left <$> antiGenPositive
, Right <$> do
l <- antiGenSmall
replicateM l $ pure True |! pure False
]
noneOf :: [Bool] -> Property
noneOf [] = property True
noneOf (x : xs) = not x .&&. noneOf xs
exactlyOne :: [(String, Bool)] -> Property
exactlyOne [] = counterexample "None of the conditions hold" $ property False
exactlyOne ((lbl, p) : ps) = label lbl (p .&&. noneOf (snd <$> ps)) .||. (not p .&&. exactlyOne ps)
someGen :: (Arbitrary a, CoArbitrary a) => Proxy a -> Gen (Gen a)
someGen p =
oneof
[ pure <$> arbitrary
, do
x <- scale (`div` 2) $ someGen p
f <- arbitrary
pure $ f <$> x
, do
x <- scale (`div` 4) $ someGen p
y <- scale (`div` 4) $ someGen p
f <- arbitrary
pure $ f <$> x <*> y
]
zapAntiGenSpec :: Spec
zapAntiGenSpec =
describe "zapAntiGen" $ do
prop "zapping `antiGenPositive` once generates negative examples" $ do
x <- zapAntiGen 1 antiGenPositive
pure $ x <= 0
prop "zapping `antiGenPositive` zero times generates a positive example" $ do
x <- zapAntiGen 0 antiGenPositive
pure $ x > 0
prop "zapping `antiGenTuple` once results in a single non-positive Int" $ do
(x, y) <- zapAntiGen 1 antiGenTuple
pure $
label "x is non-positive" (x <= 0) .||. label "y is non-positive" (y <= 0)
prop "zapping `antiGenTuple` twice results in two non-positive Ints" $ do
(x, y) <- zapAntiGen 2 antiGenTuple
pure $
counterexample ("x = " <> show x <> " is positive") (x <= 0)
.&&. counterexample ("y = " <> show y <> " is positive") (y <= 0)
prop
"zapping the length of the string propagates to the string generator"
. forAll (zapAntiGen 1 antiGenLengthStringStatic)
$ \(l, s) -> length s === l
prop
"zapping `antiGenLengthString` either generates invalid Int or a string of invalid length"
. forAll (zapAntiGen 1 antiGenLengthString)
$ \(l, s) ->
exactlyOne
[ ("l > 5", l > 5)
, ("length s /= l", length s /= l)
]
prop
"zapping `antiGenEither` once gives a nice distribution"
. forAll (zapAntiGen 1 antiGenEither)
$ \x ->
exactlyOne
[
( "Left v <= 0"
, case x of
Right _ -> False
Left v -> v <= 0
)
,
( "Right length (filter not v) == 1"
, case x of
Left _ -> False
Right v -> length (filter not v) == 1
)
,
( "Right length > 5"
, case x of
Left _ -> False
Right v -> length v > 5
)
]
chooseBoundedIntegralTest :: forall a. Typeable a => Spec
chooseBoundedIntegralTest =
prop (show (typeRep @a) <> " (0, n)") $ \(Positive (n :: Word64)) -> do
res <- zapAntiGen 1 (antiChooseBounded (0, n))
pure $ res `shouldSatisfy` (> n)
utilsSpec :: Spec
utilsSpec =
describe "utils" $ do
describe "faultyNum" $ do
prop "positive" $ \(n :: Int) -> do
res <- runAntiGen $ faultyNum n
pure $ res === n
prop "negative" $ \(n :: Int) -> do
res <- zapAntiGen 1 $ faultyNum n
pure $ res =/= n
describe "faultyBool" $ do
prop "positive" $ \b -> do
res <- runAntiGen $ faultyBool b
pure $ res === b
prop "negative" $ \b -> do
res <- zapAntiGen 1 $ faultyBool b
pure $ res =/= b
describe "faultyTry" $ do
describe "String" $ do
prop "positive" $ \(s :: String) -> do
res <- runAntiGen $ faultyTry s
pure $ res === s
prop "negative" $ \(s :: String) -> do
res <- zapAntiGen 1 $ faultyTry s
pure $ res =/= s
describe "antiPositive" $ do
prop "positive" . forAll (runAntiGen $ antiPositive @Int) $ (> 0)
prop "negative" . forAll (zapAntiGen 1 $ antiPositive @Int) $ (<= 0)
describe "antiNegative" $ do
prop "positive" . forAll (runAntiGen $ antiNegative @Int) $ (< 0)
prop "negative" . forAll (zapAntiGen 1 $ antiNegative @Int) $ (>= 0)
describe "antiNonPositive" $ do
prop "positive" . forAll (runAntiGen $ antiNonPositive @Int) $ (<= 0)
prop "negative" . forAll (zapAntiGen 1 $ antiNonPositive @Int) $ (> 0)
describe "antiNonNegative" $ do
prop "positive" . forAll (runAntiGen $ antiNonNegative @Int) $ (>= 0)
prop "negative" . forAll (zapAntiGen 1 $ antiNonNegative @Int) $ (< 0)
describe "(||!)" $ do
prop "positive" $ do
res <- runAntiGen $ listOf1 (antiPositive @Int) ||! pure []
pure $
counterexample "is empty" (not $ null res)
.&&. counterexample "non-positive" (null $ filter (<= 0) res)
prop "negative" $ do
res <- zapAntiGen 1 $ listOf1 (antiPositive @Int) ||! pure []
pure $
exactlyOne
[ ("null", null res)
, ("nonpositive", length (filter (<= 0) res) == 1)
]
describe "chooseBoundedIntegral" $ do
chooseBoundedIntegralTest @Word64
chooseBoundedIntegralTest @Word32
chooseBoundedIntegralTest @Int
main :: IO ()
main = hspec $ do
describe "AntiGen" $ do
describe "treeDepth" $ do
prop "pure has depth of zero" $ do
pt <- evalToPartial $ pure ()
pure $ countDecisionPoints pt `shouldBe` 0
prop "single bind has depth of one, right identity holds" $ do
let
m = return =<< antiGenPositive
pt <- evalToPartial m
pt' <- evalToPartial antiGenPositive
pure $ countDecisionPoints pt === countDecisionPoints pt' .&&. countDecisionPoints pt === 1
zapAntiGenSpec
describe "runAntiGen" $ do
prop "runAntiGen . liftGen == id" $
\(seed :: Int) -> forAllBlind (someGen $ Proxy @Int) $ \g -> do
let g' = runAntiGen (liftGen g)
res <- variant seed g
res' <- variant seed g'
pure $ res === res'
describe "MonadGen" $ do
prop "applying `sized` to a negatable generator preserves negation" $ do
size <- getSize
val <- zapAntiGen 1 . sized $ \sz -> pure sz |! pure (-sz)
pure $ val === -size
prop "`resize` has an effect when zapping" $ do
sz <- choose (0, 10)
val :: [Bool] <- zapAntiGen 1 . resize sz . sized $ \s ->
liftGen (vector $ 2 * s) |! liftGen (vector s)
pure $ length val === sz
prop "nested `resize` works correctly" $ do
x <- resize 30 $ do
a <- getSize
b <- scale (+ 1) $ do
c <- getSize
d <- scale (+ 1) getSize
pure [c, d]
pure $ a : b
pure $ x === [30, 31, 32]
utilsSpec