MyNixOS website logo
Description

A simple checker for stress testing monadic code.

FuzzCheck

FuzzCheck is a library much like QuickCheck, except that instead of test the properties of pure functions, it tests the behavior of applicative or monadic code.

For example, with QuickCheck you would check a property of a function as follows:

prop_reverse xs = xs == reverse (reverse xs)

>>> quickCheck prop_reverse

This would generate a list of random length and contents, and ensure that the stated property is maintained for each instance.

FuzzCheck is for testing monadic (or applicative) code, which may only be testable in the context of other operations. For example, let's test some simple FFI code:

prop_bs_ffi = do
    mem <- "allocate buffer" ?> pure malloc
    n <- "pick a number"     ?> return <$> gen (choose (40::Int,100))
    "poke"                   ?> poke <$> arg mem <*> arg n
    x <- "peek at memory"    ?> peek <$> arg mem
    "make sure it matches"   ?> (@?=) <$> arg x <*> arg n
    "free the buffer"        ?> free <$> arg mem

FuzzCheck interface

There are just three special details introduced by FuzzCheck, the ?> operator, and the arg, rand and gen combinators.

"label" ?> action

This runs a Fuzz action. If an exception occurs, the label is printed along with the exception.

let x = "Hello"
"label" ?> f <$> arg x

This executes a monadic function f, passing it the argument x. This is equivalent to using f x in the surrounding monad, except that if an exception is generated, the error report looks like this:

f "Hello": <text of actual exception here>

You may also use rand, which is just a shorter synonym for QuickCheck's arbitrary, for generating a type-appropriate random value automatically:

"label" ?> f <$> rand

Another option is to use gen, which takes for its argument any combinator from QuickCheck that generates an appropriately typed Gen value. For example:

"label" ?> f <$> gen (choose (1,10))

This tests f by passing it a randomly chosen integer from the given range. If an exception occurs, the actual integer that caused the problem is shown:

f 9: <text of actual exception here>

That's it. To run the test, call fuzzCheck on the property:

>>> fuzzCheck prop_bs_ffi2
+++ OK, passed 100 tests.

You can use fuzzCheck' if you want to change the number of tests executed, or if you want to associate cleanup code with the test after it runs, whether or not it succeeds.

Simplifying tests

The role of ?> is to assign a label to each operation (to assist with error reporting in case of failure), and to execute the Fuzz action in its enclosing Monad. A fuzz test may occur within any monad supporting MonadIO and MonadBaseControl IO (for the purpose of catching exceptions), which means that if we're testing code in IO, we can limit the use of ?> to only those cases we expect might fail:

prop_bs_ffi2 = do
    mem <- malloc
    n <- "pick a number"  ?> return <$> gen (choose (40::Int,100))
    "poke"                ?> poke <$> arg mem <*> arg n
    x <- "peek at memory" ?> peek <$> arg mem
    x @?= n
    free mem

NOTE: Using gen does not mean that that specific function is invoked 100 times at that point in the monadic block. Instead, the entire block passed to fuzzCheck is executed 100 times, with each occurence of gen producing a new value at each run.

Integration with Hspec and HUnit

This all integrates quite nicely with Hspec and Hunit. For example, this is from the smoke tests for this library:

hspec $ it "works with an FFI example" $ fuzzCheck $ do
    mem <- malloc
    n <- "pick a number"  ?> return <$> gen (choose (40::Int,100))
    "poke"                ?> poke <$> arg mem <*> arg n
    x <- "peek at memory" ?> peek <$> arg mem
    x @?= n
    free mem

See also

Although this library was written before I had found this paper and Test.QuickCheck.Monadic, the results are somewhat similar (the paper uses name instead of ?>, while arg is the same).

Metadata

Version

0.1.1

Platforms (75)

    Darwin
    FreeBSD
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    WASI
    Windows
Show all
  • aarch64-darwin
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64_be-none
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-darwin
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-darwin
  • i686-freebsd
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • vc4-none
  • wasm32-wasi
  • wasm64-wasi
  • x86_64-cygwin
  • x86_64-darwin
  • x86_64-freebsd
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-windows