Display a reason for a failed QuickCheck property and handle exceptions in the tested function

StackOverflow https://stackoverflow.com/questions/13711226

  •  04-12-2021
  •  | 
  •  

Question

I have a QuickCheck property testing a function f. The property maps the function f over some list xs and checks some element-wise property of the result. In the case of failure, I'd like to display the element of xs related to this failure. Consider the following property:

prop x =
    printTestCase ("Failed for value " ++ show failure) $ isNothing failure
  where
    failure = fmap fst $ find (not . snd) $ map (\n -> (n, f x n == n)) [10..20]

This works fine for the implementation

f = (+)

and quickcheck prop outputs

*** Failed! Falsifiable (after 2 tests):                  
1
Failed for value Just 10

However, if f throws an exception, i.e.

f = undefined

then quickcheck prop outputs

*** Failed! Exception: 'Prelude.undefined' (after 1 test): 
()
Failed for value Exception thrown by generator: 'Prelude.undefined'

How can I write a property which catches this second exception and returns "Just 0" as for the previous example? I guess, one could use whenFail or whenFail' for this, but I haven't yet understood the QuickCheck internals.

Was it helpful?

Solution

Thanks to aavogt on the #haskell channel, I found the module Control.Spoon, which provides the functions spoon and teaspoon which can be used to solve this problem. However, I don't know how safe it is to use this package (which uses unsafePerformIO internally).

Anyway, using Control.Spoon, prop can be rewritten the following way:

prop x =
    printTestCase ("Failed for value " ++ show pureFailure) $ isNothing excFailure
  where
    pureFailure = failure (fromMaybe True . teaspoon)
    excFailure = failure id
    failure g = fmap fst $ find (g . not . snd) $ map (\n -> (n, f x n == n)) [10..20]

Wrapping the find predicate with fromMaybe True . teaspoon has the effect that find returns the first element either satisfying the predicate or throwing an exception.

As teaspoon can only be used to see that an exception occured, but not which, I use excFailure here so that QuickCheck still sees the exception. The downside is that the find needs to be evaluated twice. teaspoon only evaluates to WHNF, if you need a deep evaluation to trigger the exception, use spoon instead.

teaspoon catches only a few select exceptions, which is fine for me, because I only really care about undefined and inexhaustive pattern matches.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top