packages feed

falsify-0.1.0: test/TestSuite/Prop/Generator/Shrinking.hs

module TestSuite.Prop.Generator.Shrinking (tests) where

import Control.Monad
import Data.Default
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 TestSuite.Util.List

tests :: TestTree
tests = testGroup "TestSuite.Prop.Generator.Shrinking" [
      testGroup "prim" [
        testPropertyWith expectFailure  "prim" prop_prim_minimum
      ]
    , 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

{-------------------------------------------------------------------------------
  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