falsify-0.4.0: Property-based testing with internal integrated shrinking
Safe HaskellNone
LanguageHaskell2010

Test.Falsify.Predicate

Description

Predicates

Intended for qualified import.

import Test.Falsify
import qualified Test.Falsify.Predicate as P

Motivation

Testing libraries must have a way to assert and check intended-to-be-true facts. For example, suppose we have

x, y :: Int
x = 5
y = 10

and we want to assert that x and y are equal. The simplest form that this might take is simply a boolean predicate; for example, tasty-hunit offers assertBool, and in QuickCheck we have a Testable instance for Bool. This allows us to write

test_hunit_bool :: HUnit.Assertion
test_hunit_bool = HUnit.assertBool "uhoh" $ x == y

test_qc_bool :: QuickCheck.Property
test_qc_bool = QuickCheck.property $ x == y

However, when such a property fails we don't get very useful output; we are merely told that the property failed. Both tasty-hunit and QuickCheck offer limited support for producing nicer test ouput; in the specific case of equality, we can write

test_hunit_equal :: HUnit.Assertion
test_hunit_equal = HUnit.assertEqual "uhoh" x y

test_qc_equal :: QuickCheck.Property
test_qc_equal = QuickCheck.property $ x QuickCheck.=== y

instead, which would produce output

uhoh
expected: 5
 but got: 10 (expected failure)

and

*** Failed! Falsified (after 1 test):
5 /= 10

respectively, where we are not only told that the property failed, but also how; in this case, what the values of x and y are. Predicates in falsify as a generalization of this concept.

Introduction to predicates

Think of a predicate of type Predicate '[a, b, ..] as a function a -> b -> .. -> Bool, which can additionally produce useful test output when the predicate does not hold. In order be able to produce that output, a predicate is equipped with a function to generate a description of the failure, given a description of the inputs; those inputs are described by expressions (Expr). For example, here is a very simple way in which we might define a predicate to check that its argument is even:

even1 :: Integral a => Predicate '[a]
even1 = P.unary even $ \a -> "not even: " ++ P.prettyExpr a

When a predicate is applied to an argument, it must be told how to name that argument, how to render the argument, and the value of the argument. For example,

test_even1 :: Property ()
test_even1 = assert $ even1 `P.at` ("x", show x, x)

will result in

 even1:   FAIL
   failed after 0 shrinks
   not even: x
   x: 5

Typically we will use show to render the argument, in which case we can use .$:

test_even2 :: Property ()
test_even2 = assert $ even1 .$ ("x", x)

This scales nicely to any number of arguments; for example, to come back the equality example from the previous section:

test_equal :: Property ()
test_equal = assert $
    P.eq .$ ("x", x)
         .$ ("y", y)

will produce

equal:   FAIL
  failed after 0 shrinks
  x /= y
  x: 5
  y: 10

Compositionality

Suppose that we want to verify that x and y have the same polarity (both are even or both are odd). We could use eq again:

test_samePolarity1 :: Property ()
test_samePolarity1 = assert $
    P.eq .$ ("even x", even x)
         .$ ("even y", even y)

but if we run that, we get

 samePolarity1: FAIL
   failed after 0 shrinks
   even x /= even y
   even x: False
   even y: True

In order to debug the problem, we might like to the value of x and y, not just whether they are even or not. We could instead define a custom predicate specifically for this purpose:

samePolarity :: Integral a => Predicate '[a, a]
samePolarity =
    P.binary
      (\a b -> even a == even b)
      (\a b -> P.prettyExpr a ++ " and " ++ P.prettyExpr b ++ " have different polarity")

test_samePolarity2 :: Property ()
test_samePolarity2 = assert $ samePolarity .$ ("x", x) .$ ("y", y)

which would produce

samePolarity2: FAIL
  failed after 0 shrinks
  x and y have different polarity
  x: 5
  y: 10

but now we have the opposite problem: we see the values of x and y, but not their polarity. Fortunately, we can take advantage of the compositionality of predicates, and state very directly that the function even, applied on both arguments, must produce the same result:

samePolarity' :: Integral a => Predicate '[a, a]
samePolarity' = P.eq `P.on` P.fn ("even", even)

test_samePolarity3 :: Property ()
test_samePolarity3 = assert $
    samePolarity'
      .$ ("x", x)
      .$ ("y", y)

produces

samePolarity3: FAIL
  failed after 0 shrinks
  (even x) /= (even y)
  x     : 5
  y     : 10
  even x: False
  even y: True

Occassionally it is useful to suppress the results of functions applications; for example, suppose we have

newtype T = WrapT Int
  deriving stock (Show)

unwrapT :: T -> Int
unwrapT (WrapT a) = a

and we want to check whether X 5 and X 10 have the same polarity; in this case, there isn't much point explicitly including the output of unwrapT, so we can suppress it with transparent:

test_samePolarity4 :: Property ()
test_samePolarity4 = assert $
    samePolarity' `P.on` P.transparent unwrapT
      .$ ("x", WrapT 5)
      .$ ("y", WrapT 10)

N-ary predicates

Most predicates are either unary or binary; these can usually easily be defined using satisfies and relatedBy respectively, which will take care of producing a nice error message. In the general case you can construct predicates of arbitrary arity using lam, pass and fail, though in that case you will be responsible for constructing your own error messages.

For example, suppose we have a "real" implementation of some kind of security policy implementation as well as a "model" implementation:

applyReal, applyModel :: Policy -> Operation -> Resource -> Actor -> Bool

Then we could define a predicate that compares these two as follows:

realVsModel :: Predicate '[Policy, Operation, Resource, Actor]
realVsModel = P.lam $ \p -> P.lam $ \o -> P.lam $ \r -> P.lam $ \a ->
    let real  = applyReal  p o r a
        model = applyModel p o r a
    in if real == model then
         P.pass
       else
         P.fail $ "real says " ++ show real ++ ", model says " ++ show model

Such a predicate can then be used like any other, and rendering of the arguments to the predicate is handled automatically. For example:

test_realVsModel :: Property ()
test_realVsModel = assert $
    realVsModel
      .$ ( "policy"    , policy    )
      .$ ( "operation" , operation )
      .$ ( "resource"  , resource  )
      .$ ( "actor"     , actor     )

(Usually of course these inputs would be randomly generated.) This property might result in

realVsModel: FAIL
  failed after 0 shrinks
  real says False, model says True
  policy   : "strict"
  operation: "delete"
  resource : "db"
  actor    : "joe"
Synopsis

Documentation

data Predicate (a :: [Type]) Source #

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.

Instances

Instances details
Monoid (Predicate a) Source # 
Instance details

Defined in Test.Falsify.Predicate

Semigroup (Predicate a) Source # 
Instance details

Defined in Test.Falsify.Predicate

Methods

(<>) :: Predicate a -> Predicate a -> Predicate a #

sconcat :: NonEmpty (Predicate a) -> Predicate a #

stimes :: Integral b => b -> Predicate a -> Predicate a #

Expressions

data Expr Source #

Simple expression language

The internal details of this type are (currently) not exposed.

prettyExpr :: Expr -> String Source #

Pretty-print expression

Functions

data Fn a b Source #

Function (used for composition of a Predicate with a function)

data FnName Source #

Function name

Instances

Instances details
IsString FnName Source # 
Instance details

Defined in Test.Falsify.Predicate

Methods

fromString :: String -> FnName #

fn :: Show b => (FnName, a -> b) -> Fn a b Source #

Default constructor for a function

fnWith :: (FnName, b -> String, a -> b) -> Fn a b Source #

Generalization of fn that does not depend on Show

transparent :: (a -> b) -> Fn a b Source #

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.

Construction

pass :: forall (xs :: [Type]). Predicate xs Source #

Constant True

fail :: forall (xs :: [Type]). Err -> Predicate xs Source #

Constant False

unary Source #

Arguments

:: (a -> Bool)

The predicate proper

-> (Expr -> Err)

Error message, given Expr describing the input

-> Predicate '[a] 

Unary predicate

This is essentially a function a -> Bool; see Predicate for detailed discussion.

binary Source #

Arguments

:: (a -> b -> Bool)

The predicate proper

-> (Expr -> Expr -> Err)

Error message, given Expr describing inputs

-> Predicate '[a, b] 

Binary predicate

This is essentially a function a -> b -> Bool; see Predicate for detailed discussion.

Auxiliary construction

satisfies :: (FnName, a -> Bool) -> Predicate '[a] Source #

Specialization of unary for unary relations

relatedBy :: (FnName, a -> b -> Bool) -> Predicate '[a, b] Source #

Specialization of binary for relations

Combinators

dot :: forall x (xs :: [Type]) y. Predicate (x ': xs) -> Fn y x -> Predicate (y ': xs) Source #

Function composition (analogue of (.))

split :: forall x' y' (xs :: [Type]) x y. Predicate (x' ': (y' ': xs)) -> (Fn x x', Fn y y') -> Predicate (x ': (y ': xs)) Source #

Analogue of 'Control.Arrow.(***)'

on :: forall x (xs :: [Type]) y. Predicate (x ': (x ': xs)) -> Fn y x -> Predicate (y ': (y ': xs)) Source #

Analogue of on

flip :: forall x y (zs :: [Type]). Predicate (x ': (y ': zs)) -> Predicate (y ': (x ': zs)) Source #

Analogue of flip

matchEither :: forall a (xs :: [Type]) b. Predicate (a ': xs) -> Predicate (b ': xs) -> Predicate (Either a b ': xs) Source #

Match on the argument, and apply whichever predicate is applicable.

matchBool Source #

Arguments

:: forall (xs :: [Type]). Predicate xs

Predicate to evaluate if the condition is true

-> Predicate xs

Predicate to evaluate if the condition is false

-> Predicate (Bool ': xs) 

Conditional

This is a variation on matchEither that provides no evidence for which branch is taken.

lam :: forall x (xs :: [Type]). (x -> Predicate xs) -> Predicate (x ': xs) Source #

Lambda abstraction

See module documentation of Test.Falsify.Predicate for discussion.

Evaluation and partial evaluation

data VarName Source #

Variable

Instances

Instances details
IsString VarName Source # 
Instance details

Defined in Test.Falsify.Predicate

Methods

fromString :: String -> VarName #

type Err = String Source #

Error message (when the predicate fails)

eval :: Predicate ('[] :: [Type]) -> Either Err () Source #

Evaluate fully applied predicate

(.$) :: forall x (xs :: [Type]). Show x => Predicate (x ': xs) -> (VarName, x) -> Predicate xs Source #

Infix version of at

Typical usage example:

assert $
     P.relatedBy ("equiv", equiv)
  .$ ("x", x)
  .$ ("y", y)

at Source #

Arguments

:: forall x (xs :: [Type]). Predicate (x ': xs) 
-> (VarName, String, x)

Rendered name, expression, and input proper

-> Predicate xs 

Generalization of (.$) that does not require a Show instance

Specific predicates

eq :: Eq a => Predicate '[a, a] Source #

Equal

ne :: Eq a => Predicate '[a, a] Source #

Not equal

lt :: Ord a => Predicate '[a, a] Source #

(Strictly) less than

le :: Ord a => Predicate '[a, a] Source #

Less than or equal to

gt :: Ord a => Predicate '[a, a] Source #

(Strictly) greater than

ge :: Ord a => Predicate '[a, a] Source #

Greater than or equal to

towards :: (Show a, Ord a, Num a) => a -> Predicate '[a, a] Source #

Check that values get closed to the specified target

expect :: (Show a, Eq a) => a -> Predicate '[a] Source #

Specialization of eq, useful when expecting a specific value in a test

between :: (Show a, Ord a) => a -> a -> Predicate '[a] Source #

Check that lo <= x <= hi

even :: Integral a => Predicate '[a] Source #

Number is even

odd :: Integral a => Predicate '[a] Source #

Number is odd

elem :: Eq a => Predicate '[[a], a] Source #

Membership check

pairwise :: Show a => Predicate '[a, a] -> Predicate '[[a]] Source #

Apply predicate to every pair of consecutive elements in the list