falsify-0.1.0: test/TestSuite/Prop/Generator/Prim.hs
module TestSuite.Prop.Generator.Prim (tests) where
import Prelude hiding (pred)
import Control.Monad
import Control.Selective
import Data.Default
import Data.Word
import Test.Tasty
import Test.Tasty.Falsify
import qualified Test.Falsify.Generator as Gen
import qualified Test.Falsify.Predicate as P
import TestSuite.Util.List
tests :: TestTree
tests = testGroup "TestSuite.Prop.Generator.Prim" [
testGroup "prim" [
testProperty "shrinking" prop_prim_shrinking
, testGroup "minimum" [
testProperty (show target) $ prop_prim_minimum target
| target <- [0 .. 4]
]
, testPropertyWith (def { expectFailure = ExpectFailure })
"prim_minimum_wrong" prop_prim_minimum_wrong
]
, testGroup "applicative" [
testGroup "pair" [
testProperty "shrinking" prop_applicative_pair_shrinking
, testProperty "minimum1" prop_applicative_pair_minimum1
, testProperty "minimum2" prop_applicative_pair_minimum2
]
, testGroup "replicateM" [
testProperty "shrinking" prop_applicative_replicateM_shrinking
, testProperty "minimum" prop_applicative_replicateM_minimum
]
]
, testGroup "monad" [
testGroup "maybe" [
testGroup "towardsNothing" [
testProperty "shrinking" prop_monad_maybe_towardsNothing_shrinking
, testProperty "minimum" prop_monad_maybe_towardsNothing_minimum
, testPropertyWith expectFailure
"shrinking_wrong" prop_monad_maybe_towardsNothing_shrinking_wrong
]
, testGroup "towardsJust" [
testProperty "shrinking" prop_monad_maybe_towardsJust_shrinking
, testProperty "minimum" prop_monad_maybe_towardsJust_minimum
, testPropertyWith expectFailure
"minimum_wrong" prop_monad_maybe_towardsJust_minimum_wrong
]
]
, testGroup "either" [
testProperty "shrinking" prop_monad_either_shrinking
]
]
, testGroup "selective" [
testGroup "either" [
testPropertyWith expectFailure
"shrinking" prop_selective_either_shrinking_wrong
]
]
, testGroup "captureLocalTree" [
testProperty "shrinking1" prop_captureLocalTree_shrinking1
, testProperty "shrinking2" prop_captureLocalTree_shrinking2
]
, testGroup "stream" [
testProperty "shrinking1" prop_stream_shrinking1
, testProperty "shrinking2" prop_stream_shrinking2
, testProperty "minimum" prop_stream_minimum
]
]
where
expectFailure :: TestOptions
expectFailure = def {
expectFailure = ExpectFailure
, overrideNumTests = Just 100_000
}
{-------------------------------------------------------------------------------
Prim
-------------------------------------------------------------------------------}
-- Gen.prime is the only generator where we a /strict/ inequality
prop_prim_shrinking :: Property ()
prop_prim_shrinking = testShrinkingOfGen P.gt $ Gen.prim
-- The minimum is always 0, unless 0 is not a counter-example
prop_prim_minimum :: Word64 -> Property ()
prop_prim_minimum target = do
testMinimum (P.expect $ if target == 0 then 1 else 0) $ do
x <- gen $ Gen.prim
unless (x == target) $ testFailed x
-- | Just to verify that we if we specify the /wrong/ minimum, we get a failure
prop_prim_minimum_wrong :: Property ()
prop_prim_minimum_wrong =
testMinimum (P.expect 1) $ do
x <- gen $ Gen.prim
testFailed x
{-------------------------------------------------------------------------------
Applicative: pairs
-------------------------------------------------------------------------------}
prop_applicative_pair_shrinking :: Property ()
prop_applicative_pair_shrinking =
testShrinkingOfGen (P.relatedBy ("validShrink", validShrink)) $
(,) <$> Gen.prim <*> Gen.prim
where
validShrink :: (Word64, Word64) -> (Word64, Word64) -> Bool
validShrink (x, y) (x', y') = x >= x' && y >= y'
prop_applicative_pair_minimum1 :: Property ()
prop_applicative_pair_minimum1 =
testMinimum (P.expect (1, 0)) $ do
(x, y) <- gen $ (,) <$> Gen.prim <*> Gen.prim
unless (x == 0 || x < y) $ testFailed (x, y)
prop_applicative_pair_minimum2 :: Property ()
prop_applicative_pair_minimum2 =
testMinimum (P.expect (1, 1)) $ do
(x, y) <- gen $ (,) <$> Gen.prim <*> Gen.prim
unless (x == 0 || x > y) $ testFailed (x, y)
{-------------------------------------------------------------------------------
Applicative: replicateM
-------------------------------------------------------------------------------}
genList :: Gen [Word64]
genList = do
n <- (`min` 10) <$> Gen.prim
replicateM (fromIntegral n) Gen.prim
prop_applicative_replicateM_shrinking :: Property ()
prop_applicative_replicateM_shrinking =
testShrinkingOfGen (P.relatedBy ("validShrink", validShrink)) genList
where
validShrink :: [Word64] -> [Word64] -> Bool
validShrink [] [] = True
validShrink [] (_:_) = False
validShrink (_:_) [] = True
validShrink (x:xs) (y:ys) = x >= y && validShrink xs ys
prop_applicative_replicateM_minimum :: Property ()
prop_applicative_replicateM_minimum =
testMinimum (P.expect [0,1]) $ do
xs <- gen $ genList
unless (pairwiseAll (==) xs) $ testFailed xs
{-------------------------------------------------------------------------------
Monad: Maybe (towards 'Nothing')
-------------------------------------------------------------------------------}
genSmall :: Gen Word64
genSmall = do
startWithEven <- Gen.prim
if startWithEven >= maxBound `div` 2
then Gen.exhaustive 100
else Gen.exhaustive 99 -- smaller bound, to ensure shrinking
genTowardsNothing :: Gen (Maybe Word64, Word64)
genTowardsNothing = do
genNothing <- (== 0) <$> Gen.prim
if genNothing
then (\ y -> (Nothing, y)) <$> genSmall
else (\x y -> (Just x, y)) <$> genSmall <*> genSmall
prop_monad_maybe_towardsNothing_shrinking :: Property ()
prop_monad_maybe_towardsNothing_shrinking =
testShrinkingOfGen
(P.relatedBy ("validShrink", validShrink))
genTowardsNothing
where
validShrink :: (Maybe Word64, Word64) -> (Maybe Word64, Word64) -> Bool
validShrink (Nothing , y) (Nothing , y') = y >= y'
validShrink (Just _ , _) (Nothing , _ ) = True -- See @.._wrong@ property
validShrink (Nothing , _) (Just _ , _ ) = False
validShrink (Just x , y) (Just x' , y') = x >= x' && y >= y'
prop_monad_maybe_towardsNothing_minimum :: Property ()
prop_monad_maybe_towardsNothing_minimum =
testMinimum (P.expect expected) $ do
(x, y) <- gen $ genTowardsNothing
unless (even y) $ testFailed (x, y)
where
-- We are using different generators, a switch from 'Just' to 'Nothing'
-- might temporarily because @y@ to increase (see @.._wrong@), but we will
-- then continue to shrink that value.
expected :: (Maybe Word64, Word64)
expected = (Nothing, 1)
prop_monad_maybe_towardsNothing_shrinking_wrong :: Property ()
prop_monad_maybe_towardsNothing_shrinking_wrong =
testShrinkingOfGen
(P.relatedBy ("validShrink", validShrink))
genTowardsNothing
where
-- This property is wrong: the two generators on the RHS have a different
-- structure, and therefore shrink independently. When we switch the
-- LHS from Just to Nothing, we run a /different/ generator.
validShrink :: (Maybe Word64, Word64) -> (Maybe Word64, Word64) -> Bool
validShrink (Nothing , y) (Nothing , y') = y >= y'
validShrink (Just _ , y) (Nothing , y') = y >= y'
validShrink (Nothing , _) (Just _ , _) = False
validShrink (Just x , y) (Just x' , y') = x >= x' && y >= y'
{-------------------------------------------------------------------------------
Monad: Maybe (towards 'Just')
Unlike hypothesis, we are always dealing with infinite sample tree; if a
"simpler" test case needs more samples, then they are available.
-------------------------------------------------------------------------------}
genTowardsJust :: Gen (Maybe Word64, Word64)
genTowardsJust = do
genJust <- (== 0) <$> Gen.prim
if genJust
then (\x y -> (Just x, y)) <$> genSmall <*> genSmall
else (\ y -> (Nothing, y)) <$> genSmall
prop_monad_maybe_towardsJust_shrinking :: Property ()
prop_monad_maybe_towardsJust_shrinking =
testShrinkingOfGen
(P.relatedBy ("validShrink", validShrink))
genTowardsJust
where
validShrink :: (Maybe Word64, Word64) -> (Maybe Word64, Word64) -> Bool
validShrink (Nothing , y) (Nothing , y') = y >= y'
validShrink (Just _ , _) (Nothing , _ ) = False
validShrink (Nothing , _) (Just _ , _ ) = True
validShrink (Just x , y) (Just x' , y') = x >= x' && y >= y'
prop_monad_maybe_towardsJust_minimum :: Property ()
prop_monad_maybe_towardsJust_minimum =
testMinimum (P.satisfies ("expected", expected)) $ do
(x, y) <- gen $ genTowardsJust
unless (even y) $ testFailed (x, y)
where
expected :: (Maybe Word64, Word64) -> Bool
expected (Just _ , y) = y == 1
expected (Nothing , _) = True
prop_monad_maybe_towardsJust_minimum_wrong :: Property ()
prop_monad_maybe_towardsJust_minimum_wrong =
testMinimum (P.expect expected) $ do
(x, y) <- gen $ genTowardsJust
unless (even y) $ testFailed (x, y)
where
-- We might not always be able to shrink from 'Nothing' to 'Just', because
-- the /value/ of that 'Just' might not be a counter-example; we would need
-- to take two shrink steps at once (switch from 'Just' to 'Nothing' /and/
-- reduce the value of the 'Just').
--
-- 'Selective' does not help either (it also would need to take two steps);
-- we /could/ try to solve the problem by generating /both/ values always,
-- and using only one, but as we know, that is not an effective strategy:
-- generated-by-not-used values will always be shrunk to their minimal
-- value, independent of the property.
expected :: (Maybe Word64, Word64)
expected = (Just 0, 1)
{-------------------------------------------------------------------------------
Monad: Either
-------------------------------------------------------------------------------}
genMonadEither :: Gen (Either Word64 Word64)
genMonadEither = do
genLeft <- (== 0) <$> Gen.prim -- shrink towards left
if genLeft
then Left <$> Gen.prim
else Right <$> Gen.prim
prop_monad_either_shrinking :: Property ()
prop_monad_either_shrinking =
testShrinkingOfGen
(P.relatedBy ("validShrink", validShrink))
genMonadEither
where
-- The 'Left' and 'Right' case use the /same/ part of the sample tree, so
-- that if we shrink from one to the other, we /must/ get the same value.
validShrink :: Either Word64 Word64 -> Either Word64 Word64 -> Bool
validShrink _ (Left 0) = True -- We can always shrink to 'Minimal'
validShrink (Left x) (Left x') = x >= x'
validShrink (Left _) (Right _) = False
validShrink (Right x) (Left x') = x == x'
validShrink (Right x) (Right x') = x >= x'
{-------------------------------------------------------------------------------
Selective: either
-------------------------------------------------------------------------------}
genSelectiveEither :: Gen (Either Word64 Word64)
genSelectiveEither =
ifS ((== 0) <$> Gen.prim)
(Left <$> Gen.prim)
(Right <$> Gen.prim)
prop_selective_either_shrinking_wrong :: Property ()
prop_selective_either_shrinking_wrong =
testShrinkingOfGen
(P.relatedBy ("validShrink", validShrink))
genSelectiveEither
where
-- Like in 'prop_monad_either_shrinking', here the two generators are
-- independent, and so it's entirely possible we might shrink from @Right x@
-- to @Left y@ for @x /= y@.
validShrink :: Either Word64 Word64 -> Either Word64 Word64 -> Bool
validShrink _ (Left 0) = True -- We can always shrink to 'Minimal'
validShrink (Left x) (Left x') = x >= x'
validShrink (Left _) (Right _) = False
validShrink (Right x) (Left x') = x == x'
validShrink (Right x) (Right x') = x >= x'
{-------------------------------------------------------------------------------
captureLocalTree
-------------------------------------------------------------------------------}
prop_captureLocalTree_shrinking1 :: Property ()
prop_captureLocalTree_shrinking1 =
testShrinkingOfGen P.alwaysFail $
Gen.captureLocalTree
-- Check that we /still/ cannot shrink (i.e., monadic bind is not
-- introducing a bug somewhere)
prop_captureLocalTree_shrinking2 :: Property ()
prop_captureLocalTree_shrinking2 =
testShrinkingOfGen P.alwaysFail $ do
t1 <- Gen.captureLocalTree
t2 <- Gen.captureLocalTree
return (t1, t2)
{-------------------------------------------------------------------------------
Stream
The purpose of this test is to test generation (and shrinking) of infinite
data structures. The function generation tests will verify that also, but they
are much more complicated.
-------------------------------------------------------------------------------}
-- | Infinite stream of values
--
-- Intentionally does not have a 'Show' instance!
data Stream a = Stream a (Stream a)
prefix :: Stream a -> Word64 -> [a]
prefix _ 0 = []
prefix (Stream x xs) n = x : prefix xs (n - 1)
genStream :: Gen (Stream Word64)
genStream = Stream <$> Gen.exhaustive 10 <*> genStream
genStreamPrefix :: Gen [Word64]
genStreamPrefix = prefix <$> genStream <*> Gen.exhaustive 10
-- | Check that we can test shrinking of infinite structures /at all/
prop_stream_shrinking1 :: Property ()
prop_stream_shrinking1 =
testShrinkingOfGen P.alwaysPass $
genStreamPrefix
-- | Check that we shrink in the way we expect
prop_stream_shrinking2 :: Property ()
prop_stream_shrinking2 =
testShrinkingOfGen pred $
genStreamPrefix
where
pred :: P.Predicate '[[Word64], [Word64]]
pred = mconcat [
P.ge `P.on` P.fn ("length", length)
, P.relatedBy ("elemsRelated", elemsRelated)
]
elemsRelated :: [Word64] -> [Word64] -> Bool
elemsRelated orig shrunk = and $ zipWith (>=) orig shrunk
prop_stream_minimum :: Property ()
prop_stream_minimum =
testMinimum (P.expect [0, 0]) $ do
xs <- gen genStreamPrefix
unless (pairwiseAll (<) xs) $ testFailed xs