-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Property-based testing with internal integrated shrinking -- -- This library provides property based testing with support for internal -- integrated shrinking: integrated in the sense of Hedgehog, meaning -- that there is no need to write a separate shrinker and generator; and -- internal in the sense of Hypothesis, meaning that this works well even -- across monadic bind. However, the actual techniques that power -- falsify are quite different from both of these two libraries. -- -- Most users will probably want to use the integration with -- tasty, and use Test.Tasty.Falsify as their main -- entrypoint into the library. The Test.Falsify.Interactive -- module can be used to experiment with the library in ghci. @package falsify @version 0.2.0 -- | Predicates -- -- Intended for qualified import. module Test.Falsify.Predicate -- | N-ary predicate -- -- A predicate of type -- --
-- Predicate '[Int, Bool, Char, ..] ---- -- is essentially a function Int -> Bool -> Char -> .. -> -- Bool, along with some metadata about that function that allows us -- to render it in a human readable way. In particular, we construct an -- Expr for the values that the predicate has been applied to. data Predicate :: [Type] -> Type -- | Simple expression language -- -- The internal details of this type are (currently) not exposed. data Expr prettyExpr :: Expr -> String -- | Function (used for composition of a Predicate with a function) data Fn a b -- | Default constructor for a function fn :: Show b => (Var, a -> b) -> Fn a b -- | Generalization of fn that does not depend on Show fnWith :: (Var, b -> String, a -> b) -> Fn a b -- | Function that should not be visible in any rendered failure -- -- Consider these two predicates: -- --
-- p1, p2 :: Predicate '[Char, Char] -- p1 = P.eq `P.on` (P.fn "ord" ord) -- p2 = P.eq `P.on` (P.transparent ord) ---- -- Both of these compare two characters on their codepoints (through -- ord), but they result in different failures. The first would -- give us something like -- --
-- (ord x) /= (ord y) -- x : 'a' -- y : 'b' -- ord x: 97 -- ord y: 98 ---- -- whereas the second might give us something like -- --
-- x /= y -- x: 'a' -- y: 'b' ---- -- which of these is more useful is of course application dependent. transparent :: (a -> b) -> Fn a b -- | Constant True alwaysPass :: Predicate xs -- | Constant False alwaysFail :: Predicate xs -- | Unary predicate -- -- This is essentially a function a -> Bool; see -- Predicate for detailed discussion. unary :: (a -> Bool) -> (Expr -> Err) -> Predicate '[a] -- | Binary predicate -- -- This is essentially a function a -> b -> Bool; see -- Predicate for detailed discussion. binary :: (a -> b -> Bool) -> (Expr -> Expr -> Err) -> Predicate [a, b] -- | Specialization of unary for unary relations satisfies :: (Var, a -> Bool) -> Predicate '[a] -- | Specialization of binary for relations relatedBy :: (Var, a -> b -> Bool) -> Predicate [a, b] -- | Function composition (analogue of (.)) dot :: Predicate (x : xs) -> Fn y x -> Predicate (y : xs) -- | Analogue of 'Control.Arrow.(***)' split :: Predicate (x' : (y' : xs)) -> (Fn x x', Fn y y') -> Predicate (x : (y : xs)) -- | Analogue of on on :: Predicate (x : (x : xs)) -> Fn y x -> Predicate (y : (y : xs)) -- | Analogue of flip flip :: Predicate (x : (y : zs)) -> Predicate (y : (x : zs)) -- | Match on the argument, and apply whichever predicate is applicable. matchEither :: Predicate (a : xs) -> Predicate (b : xs) -> Predicate (Either a b : xs) -- | Conditional -- -- This is a variation on choose that provides no evidence for -- which branch is taken. matchBool :: Predicate xs -> Predicate xs -> Predicate (Bool : xs) -- | Evaluate fully applied predicate eval :: Predicate '[] -> Either Err () -- | Infix version of at -- -- Typical usage example: -- --
-- assert $
-- P.relatedBy ("equiv", equiv)
-- .$ ("x", x)
-- .$ ("y", y)
--
(.$) :: Show x => Predicate (x : xs) -> (Var, x) -> Predicate xs
-- | Generalization of (.$) that does not require a Show
-- instance
at :: Predicate (x : xs) -> (Var, String, x) -> Predicate xs
-- | Equal
eq :: Eq a => Predicate [a, a]
-- | Not equal
ne :: Eq a => Predicate [a, a]
-- | (Strictly) less than
lt :: Ord a => Predicate [a, a]
-- | Less than or equal to
le :: Ord a => Predicate [a, a]
-- | (Strictly) greater than
gt :: Ord a => Predicate [a, a]
-- | Greater than or equal to
ge :: Ord a => Predicate [a, a]
-- | Check that values get closed to the specified target
towards :: forall a. (Show a, Ord a, Num a) => a -> Predicate [a, a]
-- | Specialization of eq, useful when expecting a specific value in
-- a test
expect :: (Show a, Eq a) => a -> Predicate '[a]
-- | Check that lo <= x <= hi
between :: (Show a, Ord a) => a -> a -> Predicate '[a]
-- | Number is even
even :: Integral a => Predicate '[a]
-- | Number is odd
odd :: Integral a => Predicate '[a]
-- | Membership check
elem :: Eq a => Predicate [[a], a]
-- | Apply predicate to every pair of consecutive elements in the list
pairwise :: forall a. Show a => Predicate [a, a] -> Predicate '[[a]]
instance GHC.Base.Monoid (Test.Falsify.Predicate.Predicate a)
instance GHC.Base.Semigroup (Test.Falsify.Predicate.Predicate a)
-- | Numerical ranges
module Test.Falsify.Range
-- | Range of values
data Range a
-- | Uniform selection between the given bounds, shrinking towards first
-- bound
between :: forall a. (Integral a, FiniteBits a) => (a, a) -> Range a
-- | Variation on between for types that are Enum but not
-- Integral
--
-- This is useful for types such as Char. However, since this
-- relies on Enum, it's limited by the precision of Int.
enum :: Enum a => (a, a) -> Range a
-- | Selection within the given bounds, shrinking towards the specified
-- origin
--
-- All else being equal, prefers values in the second half of the
-- range (in the common case of say withOrigin (-100, 100) 0,
-- this means we prefer positive values).
withOrigin :: (Integral a, FiniteBits a) => (a, a) -> a -> Range a
-- | Introduce skew (non-uniform selection)
--
-- A skew of s == 0 means no skew: uniform selection.
--
-- A positive skew (s > 0) introduces a bias towards smaller
-- values (this is the typical use case). As example, for a skew of s
-- == 1:
--
-- -- | time% -- s | 50% | 90% -- -------------- -- 0 | 50 | 90 -- 1 | 13 | 56 -- 2 | 4 | 35 -- 3 | 1 | 23 -- 4 | 0 | 16 -- 5 | 0 | 11 -- 6 | 0 | 8 -- 7 | 0 | 6 -- 8 | 0 | 5 -- 9 | 0 | 4 -- 10 | 0 | 3 ---- -- Will shrink towards x, independent of skew. -- -- NOTE: The implementation currently uses something similar to μ-law -- encoding. As a consequence, the generator gets increased precision -- near the end of the range we skew towards, and less precision near the -- other end. This means that not all values in the range can be -- produced. skewedBy :: forall a. (FiniteBits a, Integral a) => Double -> (a, a) -> Range a -- | Origin of the range (value we shrink towards) origin :: Range a -> a -- | Value x such that 0 <= x < 1 data ProperFraction pattern ProperFraction :: Double -> ProperFraction -- | Precision (in bits) newtype Precision Precision :: Word8 -> Precision -- | Range that is x everywhere constant :: a -> Range a -- | Construct a given a fraction -- -- Precondition: f must be monotonically increasing or -- decreasing; i.e. -- --
-- import Test.Falsify.Generator (Gen) -- import qualified Test.Falsify.Generator qualified as Gen --module Test.Falsify.Generator -- | Generator of a random value -- -- Generators can be combined through their Functor, -- Applicative and Monad interfaces. The primitive -- generator is prim, but most users will probably want to -- construct their generators using the predefined from -- Test.Falsify.Generator as building blocks. -- -- Generators support "internal integrated shrinking". Shrinking is -- integrated in the sense of Hedgehog, meaning that we don't -- write a separate shrinker at all, but the shrink behaviour is implied -- by the generator. For example, if you have a generator -- genList for a list of numbers, then -- --
-- filter even <$> genList ---- -- will only generate even numbers, and that property is automatically -- preserved during shrinking. Shrinking is internal in the sense -- of Hypothesis, meaning that unlike in Hedgehog, shrinking works -- correctly even in the context of monadic bind. For example, if you do -- --
-- do n <- genListLength -- replicateM n someOtherGen ---- -- then we can shrink n and the results from -- someOtherGen in any order (that said, users may prefer to use -- the dedicated list generator for this purpose, which improves -- on this in a few ways). -- -- NOTE: Gen is NOT an instance of Alternative; -- this would not be compatible with the generation of infinite data -- structures. For the same reason, we do not have a monad transformer -- version of Gen either. data Gen a -- | Generate random bool, shrink towards the given value -- -- Chooses with equal probability between True and False. bool :: Bool -> Gen Bool -- | Generate value in the specified range inRange :: Range a -> Gen a -- | Deprecated alias for inRange -- | Deprecated: Use inRange instead integral :: Range a -> Gen a -- | Deprecated alias for inRange -- | Deprecated: Use inRange instead enum :: Range a -> Gen a -- | Type-specialization of inRange int :: Range Int -> Gen Int -- | Generate a value with one of two generators -- -- Shrinks towards the first generator;the two generators can shrink -- independently from each other. -- --
-- do b <- bool -- if b then l else r ---- -- In this case, l and r will be generated from the -- same sample tree, and so cannot shrink independently. -- -- It is also different from -- --
-- do x <- l -- y <- r -- b <- bool -- return $ if b then x else y ---- -- In this case, l and r are run against -- different sample trees, like we do here, but in this -- case if the current value produced by the generator is produced by the -- right generator, then the sample tree used for the left generator will -- always shrink to Minimal (this must be possible -- because we're not currently using it); this means that we would then -- only be able to shrink to a value from the left generator if the -- minimal value produced by that generator happens to work. -- -- To rephrase that last point: generating values that are not actually -- used will lead to poor shrinking, since those values can always be -- shrunk to their minimal value, independently from whatever property is -- being tested: the shrinker does not know that the value is not being -- used. The correct way to conditionally use a value is to use the -- selective interface, as we do here. choose :: Gen a -> Gen a -> Gen a -- | Generate a value with one of many generators -- -- Uniformly selects a generator and shrinks towards the first one. oneof :: NonEmpty (Gen a) -> Gen a -- | Generate list of specified length -- -- Shrinking behaviour: -- --
-- frequency [ -- (1, genA) -- , (2, genB) -- ] ---- -- will use genA 13rd of the time, and genB -- 23rds. -- -- Shrinks towards generators earlier in the list; the generators -- themselves are independent from each other (shrinking of genB -- does not affect shrinking of genA). -- -- Precondition: there should at least one generator with non-zero -- frequency. frequency :: forall a. [(Word, Gen a)] -> Gen a data Tree a Leaf :: Tree a pattern Branch :: a -> Tree a -> Tree a -> Tree a drawTree :: Tree String -> String -- | Generate binary tree tree :: forall a. Range Word -> Gen a -> Gen (Tree a) -- | Construct binary search tree -- -- Shrinks by replacing entire subtrees by the empty tree. bst :: forall a b. Integral a => (a -> Gen b) -> Interval a -> Gen (Tree (a, b)) type ShrinkTree = Tree -- | Does a given shrunk value represent a valid shrink step? data IsValidShrink p n ValidShrink :: p -> IsValidShrink p n InvalidShrink :: n -> IsValidShrink p n -- | Generate semi-random path through the tree -- -- Will only construct paths that satisfy the given predicate (typically, -- a property that is being tested). -- -- Shrinks towards shorter paths, and towards paths that use subtrees -- that appear earlier in the list of subtrees at any node in the tree. -- -- See also pathAny. path :: forall a p n. (a -> IsValidShrink p n) -> ShrinkTree a -> Gen (Either n (NonEmpty p)) -- | Variation on path without a predicate. pathAny :: ShrinkTree a -> Gen (NonEmpty a) data Marked f a Marked :: Mark -> f a -> Marked f a [getMark] :: Marked f a -> Mark [unmark] :: Marked f a -> f a data Mark Keep :: Mark Drop :: Mark -- | Traverse the argument, generating all values marked Keep, and -- replacing all values marked Drop by Nothing selectAllKept :: (Traversable t, Selective f) => t (Marked f a) -> f (t (Maybe a)) -- | Mark an element, shrinking towards Drop -- -- This is similar to shrinkToNothing, except that Marked -- still has a value in the Drop case: marks are merely hints, -- that we may or may not use. mark :: Gen a -> Gen (Marked Gen a) -- | Function a -> b which can be shown, generated, and shrunk data Fun a b -- | Apply function to argument -- -- See also the Fn, Fn2, and Fn3 patter synonyms. applyFun :: Fun a b -> a -> b -- | Pattern synonym useful when generating functions of one argument pattern Fn :: (a -> b) -> Fun a b -- | Pattern synonym useful when generating functions of two arguments pattern Fn2 :: (a -> b -> c) -> Fun (a, b) c -- | Pattern synonym useful when generating functions of three arguments pattern Fn3 :: (a -> b -> c -> d) -> Fun (a, b, c) d -- | Generate function a -> b given a generator for b fun :: Function a => Gen b -> Gen (Fun a b) -- | Generating functions class Function a -- | Build reified function -- -- (:->) is an abstract type; if you need to add additional -- Function instances, you need to use functionMap, or rely -- on the default implementation in terms of generics. function :: Function a => Gen b -> Gen (a :-> b) -- | Build reified function -- -- (:->) is an abstract type; if you need to add additional -- Function instances, you need to use functionMap, or rely -- on the default implementation in terms of generics. function :: (Function a, Generic a, GFunction (Rep a)) => Gen b -> Gen (a :-> b) data (:->) :: Type -> Type -> Type -- | The basic building block for Function instances -- -- Provides a Function instance by mapping to and from a type that -- already has a Function instance. functionMap :: (b -> a) -> (a -> b) -> (a :-> c) -> b :-> c -- | n-bit word data WordN WordN :: Precision -> Word64 -> WordN -- | Uniform selection of n-bit word of given precision, shrinking -- towards 0 wordN :: Precision -> Gen WordN -- | Uniform selection of fraction, shrinking towards 0 -- -- Precondition: precision must be at least 1 bit (a zero-bit number is -- constant 0; it is meaningless to have a fraction in a point range). properFraction :: HasCallStack => Precision -> Gen ProperFraction -- | Disable shrinking in the given generator -- -- Due to the nature of internal shrinking, it is always possible that a -- generator gets reapplied to samples that were shrunk wrt to a -- different generator. In this sense, withoutShrinking -- should be considered to be a hint only. -- -- This function is only occassionally necessary; most users will -- probably not need to use it. withoutShrinking :: Gen a -> Gen a -- | Start with x, then shrink to one of the xs -- -- Once shrunk, will not shrink again. -- -- Minimal value is the first shrunk value, if it exists, and the -- original otherwise. shrinkToOneOf :: forall a. a -> [a] -> Gen a -- | Generator that always produces x as initial value, and -- shrinks to y firstThen :: forall a. a -> a -> Gen a -- | Shrink with provided shrinker -- -- This provides compatibility with QuickCheck-style manual shrinking. -- -- Defined in terms of fromShrinkTree; see discussion there for -- some notes on performance. shrinkWith :: forall a. (a -> [a]) -> Gen a -> Gen a -- | Start with Just x for some x, then shrink to -- Nothing shrinkToNothing :: Gen a -> Gen (Maybe a) -- | Construct generator from shrink tree -- -- This provides compatibility with Hedgehog-style integrated shrinking. -- -- This is O(n^2) in the number of shrink steps: as this shrinks, the -- generator is growing a path of indices which locates a particular -- value in the shrink tree (resulting from unfolding the provided -- shrinking function). At each step during the shrinking process the -- shrink tree is re-evaluated and the next value in the tree is located; -- since this path throws linearly, the overall cost is O(n^2). -- -- The O(n^2) cost is only incurred on locating the next element -- to be tested; the property is not reevaluated at already-shrunk -- values. fromShrinkTree :: forall a. Tree a -> Gen a -- | Expose the full shrink tree of a generator -- -- This generator does not shrink. toShrinkTree :: forall a. Gen a -> Gen (Tree a) -- | Selective bind -- -- Unlike monadic bind, the RHS is generated and shrunk completely -- independently for each different value of a produced by the -- LHS. -- -- This is a generalization of bindS to arbitrary integral values; -- it is also much more efficient than bindS. -- -- NOTE: This is only one way to make a generator independent. See -- perturb for more primitive combinator. bindIntegral :: Integral a => Gen a -> (a -> Gen b) -> Gen b -- | Run generator on different part of the sample tree depending on -- a perturb :: Integral a => a -> Gen b -> Gen b -- | Uniform selection of Word64, shrinking towards 0, using binary -- search -- -- This is a primitive generator; most users will probably not want to -- use this generator directly. prim :: Gen Word64 -- | Generalization of prim that allows to override the shrink -- behaviour -- -- This is only required in rare circumstances. Most users will probably -- never need to use this generator. primWith :: (Sample -> [Word64]) -> Gen Sample -- | Generate arbitrary value x <= n -- -- Unlike prim, exhaustive does not execute binary search. -- Instead, all smaller values are considered. This is potentially -- very expensive; the primary use case for this generator is testing -- shrinking behaviour, where binary search can lead to some -- unpredicatable results. -- -- This does NOT do uniform selection: for small n, the -- generator will with overwhelming probability produce n itself -- as initial value. -- -- This is a primitive generator; most users will probably not want to -- use this generator directly. exhaustive :: Word64 -> Gen Word64 -- | Capture the local sample tree -- -- This generator does not shrink. captureLocalTree :: Gen SampleTree -- | Varation on (>>=) that doesn't apply the shortcut to -- Minimal -- -- This function is primarily useful for debugging falsify -- itself; users will probably never need it. bindWithoutShortcut :: Gen a -> (a -> Gen b) -> Gen b -- | Properties -- -- Intended for unqualified import. -- -- Most users will probably use Test.Tasty.Falsify instead of this -- module. module Test.Falsify.Property -- | Property -- -- A Property is a generator that can fail and keeps a track of -- some information about the test run. -- -- In most cases, you will probably want to use Property instead, -- which fixes e at String. data Property' e a -- | Property that uses strings as errors type Property = Property' String -- | Generate value and add it to the log gen :: (HasCallStack, Show a) => Gen a -> Property' e a -- | Generalization of gen that doesn't depend on a Show -- instance -- -- No log entry is added if Nothing. genWith :: HasCallStack => (a -> Maybe String) -> Gen a -> Property' e a -- | Test failure testFailed :: e -> Property' e a -- | Fail the test if the predicate does not hold assert :: Predicate '[] -> Property' String () -- | Log some additional information about the test -- -- This will be shown in verbose mode. info :: String -> Property' e () -- | Discard this test discard :: Property' e a -- | Variation on collect that does not rely on Show -- -- See collect for detailed discussion. label :: String -> [String] -> Property' e () -- | Label this test -- -- See also label, which does not rely on Show. -- --
-- prop_insert_insert :: Property () -- prop_insert_insert = do -- tree <- gen $ .. -- (k1, v1) <- gen $ .. -- (k2, v2) <- gen $ .. -- assert $ .. (insert k1 v1 $ insert k2 v2 $ tree) .. ---- -- We might want to know in what percentage of tests k1 == k2: -- --
-- collect "sameKey" [k1 == k2] ---- -- When we do, falsify will report in which percentage of tests -- the key are the same, and in which percentage of tests they are not. -- --
-- collect "sameKey" [True] -- .. -- collect "sameKey" [False] ---- -- or, equivalently, -- --
-- collect "sameKey" [True, False] ---- -- then every test would have been reported as labelled with -- True (100%) as well as with False@ (also -- 100%). Of course, if we do (like above) -- --
-- collect "sameKey" [k1 == k2] ---- -- each test will be labelled with either True or -- False, and the percentages will add up to 100%. -- --
-- filter even <$> genList ---- -- will only generate even numbers, and that property is automatically -- preserved during shrinking. Shrinking is internal in the sense -- of Hypothesis, meaning that unlike in Hedgehog, shrinking works -- correctly even in the context of monadic bind. For example, if you do -- --
-- do n <- genListLength -- replicateM n someOtherGen ---- -- then we can shrink n and the results from -- someOtherGen in any order (that said, users may prefer to use -- the dedicated list generator for this purpose, which improves -- on this in a few ways). -- -- NOTE: Gen is NOT an instance of Alternative; -- this would not be compatible with the generation of infinite data -- structures. For the same reason, we do not have a monad transformer -- version of Gen either. data Gen a -- | Pattern synonym useful when generating functions of one argument pattern Fn :: (a -> b) -> Fun a b -- | Pattern synonym useful when generating functions of two arguments pattern Fn2 :: (a -> b -> c) -> Fun (a, b) c -- | Pattern synonym useful when generating functions of three arguments pattern Fn3 :: (a -> b -> c -> d) -> Fun (a, b, c) d