falsify-0.3.0: test/TestSuite/Prop/Generator/Shrinking.hs
module TestSuite.Prop.Generator.Shrinking (tests) where
import Control.Monad
import Data.Bits
import Data.Default
import Data.Int
import Data.Proxy
import Data.Word
import Test.Tasty
import Test.Tasty.Falsify
import qualified Test.QuickCheck as QuickCheck
import qualified Test.Falsify.Generator as Gen
import qualified Test.Falsify.Predicate as P
import qualified Test.Falsify.Range as Range
import TestSuite.Util.List
tests :: TestTree
tests = testGroup "TestSuite.Prop.Generator.Shrinking" [
testGroup "prim" [
testPropertyWith expectFailure "prim" prop_prim_minimum
]
, testGroup "uniform" [
testProperty "Word8" $ prop_uniform (Proxy @Word8)
, testProperty "Word16" $ prop_uniform (Proxy @Word16)
, testProperty "Word32" $ prop_uniform (Proxy @Word32)
, testProperty "Word64" $ prop_uniform (Proxy @Word64)
, testProperty "Word" $ prop_uniform (Proxy @Word)
, testProperty "Int8" $ prop_uniform (Proxy @Int8)
, testProperty "Int16" $ prop_uniform (Proxy @Int16)
, testProperty "Int32" $ prop_uniform (Proxy @Int32)
, testProperty "Int64" $ prop_uniform (Proxy @Int64)
, testProperty "Int" $ prop_uniform (Proxy @Int)
]
, testGroup "shrinkTo" [
testProperty "shrinking" prop_shrinkTo_shrinking
, testProperty "minimum" prop_shrinkTo_minimum
]
, testGroup "firstThen" [
testProperty "shrinking" prop_firstThen_shrinking
, testProperty "minimum" prop_firstThen_minimum
]
, testGroup "shrinkWith" [
testGroup "minimum" [
testProperty "minimum" prop_shrinkWith_minimum_word
, testGroup "list" [
testProperty (show i) $ prop_shrinkWith_minimum_list i
| i <- [20, 40, 60, 80, 100, 120, 140, 160, 180]
]
]
]
]
where
expectFailure :: TestOptions
expectFailure = def {
expectFailure = ExpectFailure
, overrideNumTests = Just 10_000
}
{-------------------------------------------------------------------------------
prim
-------------------------------------------------------------------------------}
-- Binary search is not guaranteed to always find the minimum value. For
-- example, if we are looking for counter-examples to the property that "all
-- numbers are even", and we start with 3, then binary search will only try 0
-- and 2, both of which are even, and hence conclude that 3 is the minimum
-- counter-example. This is true in QuickCheck, also.
prop_prim_minimum :: Property ()
prop_prim_minimum =
testMinimum (P.expect 1) $ do
x <- gen Gen.prim
unless (even x) $ testFailed x
{-------------------------------------------------------------------------------
uniform
-------------------------------------------------------------------------------}
prop_uniform :: forall a.
(Show a, FiniteBits a, Integral a, Bounded a)
=> Proxy a -> Property ()
prop_uniform _ =
testShrinkingOfGen (P.relatedBy ("validShrink", validShrink)) $
Gen.inRange Range.uniform
where
-- We must be getting closer to 0
validShrink :: a -> a -> Bool
validShrink orig shrunk
| orig >= 0 = shrunk < orig
| otherwise = shrunk > orig
{-------------------------------------------------------------------------------
shrinkTo
-------------------------------------------------------------------------------}
prop_shrinkTo_shrinking :: Property ()
prop_shrinkTo_shrinking =
testShrinkingOfGen (P.relatedBy ("validShrink", validShrink)) $
Gen.shrinkToOneOf 3 [0 :: Word .. 2]
where
-- 'shrinkToOneOf' only shrinks /once/, so the original (pre-shrink) value
-- /must/ be 3.
validShrink :: Word -> Word -> Bool
validShrink 3 0 = True
validShrink 3 1 = True
validShrink 3 2 = True
validShrink _ _ = False
prop_shrinkTo_minimum :: Property ()
prop_shrinkTo_minimum =
testMinimum (P.expect 1) $ do
x <- gen $ Gen.shrinkToOneOf 3 [0 :: Word .. 2]
unless (even x) $ testFailed x
{-------------------------------------------------------------------------------
firstThen
-------------------------------------------------------------------------------}
prop_firstThen_shrinking :: Property ()
prop_firstThen_shrinking =
testShrinkingOfGen (P.relatedBy ("validShrink", validShrink)) $
Gen.firstThen True False
where
validShrink :: Bool -> Bool -> Bool
validShrink True False = True
validShrink _ _ = False
prop_firstThen_minimum :: Property ()
prop_firstThen_minimum =
testMinimum (P.expect False) $ do
x <- gen $ Gen.firstThen True False
testFailed x
{-------------------------------------------------------------------------------
shrinkWith
-------------------------------------------------------------------------------}
-- This is obviously not a valid general-purpose shrinking function for
-- 'Word64', but that is not important here.
shrinkWord :: Word64 -> [Word64]
shrinkWord n = takeWhile (< n) [0 .. 100]
prop_shrinkWith_minimum_word :: Property ()
prop_shrinkWith_minimum_word =
testMinimum (P.expect 1) $ do
x <- gen $ Gen.shrinkWith shrinkWord Gen.prim
unless (even x) $ testFailed x
-- | Test performance of 'shrinkWith'
--
-- We test this for lists of increasing size, to verify that this is not growing
-- exponentially with the size of the list (and thereby verifying that we are
-- not exploring the full shrink tree of those lists, because they certainly
-- /are/ exponential in size).
prop_shrinkWith_minimum_list :: Int -> Property ()
prop_shrinkWith_minimum_list listLength =
testMinimum (P.expect [1,0]) $ do
xs <- gen $ Gen.shrinkWith (QuickCheck.shrinkList shrinkWord) $
replicateM listLength Gen.prim
unless (pairwiseAll (<=) xs) $ testFailed xs