-- | This is a library of fuzzers you can use to supply values to your fuzz
-- tests. You can typically pick out which ones you need according to their
-- types.
--
-- A @Fuzzer a@ knows how to create values of type @a@ in two different ways.
-- It can create them randomly, so that your test's expectations are run
-- against many values. Fuzzers will often generate edge cases likely to find
-- bugs. If the fuzzer can make your test fail, it also knows how to "shrink"
-- that failing input into more minimal examples, some of which might also
-- cause the tests to fail. In this way, fuzzers can usually find the smallest
-- or simplest input that reproduces a bug.
module Fuzz
( -- * Common Fuzzers
int,
intRange,
float,
floatRange,
percentage,
text,
bool,
maybe,
result,
list,
array,
shuffle,
-- * Working with Fuzzers
Fuzzer,
oneOf,
constant,
frequency,
-- * Tuple Fuzzers
tuple,
tuple3,
-- * Uncommon Fuzzers
char,
unit,
order,
)
where
import qualified Array
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import NriPrelude
import Test.Internal (Fuzzer (Fuzzer, unFuzzer))
import qualified Prelude
-- | A fuzzer for int values. It will never produce @NaN@, @Infinity@, or
-- @-Infinity@. It's possible for this fuzzer to generate any 64-bit integer,
-- signed or unsigned, but it favors smaller numbers.
int :: Fuzzer Int
int =
Gen.integral Range.exponentialBounded
|> Fuzzer
-- | A fuzzer for int values between a given minimum and maximum value,
-- inclusive.
intRange :: Int -> Int -> Fuzzer Int
intRange min_ max_ =
Gen.integral (Range.linearFrom (clamp min_ max_ 0) min_ max_)
|> Fuzzer
-- | A fuzzer for float values. It will never produce NaN, Infinity, or
-- -Infinity.
--
-- It's possible for this fuzzer to generate other floating-point value, but it
-- favors smaller numbers.
float :: Fuzzer Float
float =
Gen.double (Range.exponentialFloatFrom 0 (-1e100) 1e100)
|> Fuzzer
-- | A fuzzer for float values within between a given minimum and maximum
-- value, inclusive.
floatRange :: Float -> Float -> Fuzzer Float
floatRange min_ max_ =
Gen.double (Range.linearFracFrom (clamp min_ max_ 0) min_ max_)
|> Fuzzer
-- | A fuzzer for percentage values. Generates random floats between 0.0 and
-- 1.0.
percentage :: Fuzzer Float
percentage =
Gen.double (Range.linearFrac 0 1)
|> Fuzzer
-- | Generates random printable ASCII strings of up to 1000 characters.
--
-- Shorter strings are more common, especially the empty string.
text :: Fuzzer Text
text =
Gen.text (Range.exponential 0 1000) Gen.ascii
|> Fuzzer
-- | A fuzzer for boolean values. It's useful when building up fuzzers of
-- complex types that contain a boolean somewhere.
-- We recommend against writing tests fuzzing over booleans. Write a unit test
-- for the true and false cases explicitly.
bool :: Fuzzer Bool
bool =
Gen.bool
|> Fuzzer
-- | Given a fuzzer of a type, create a fuzzer of a maybe for that type.
maybe :: Fuzzer a -> Fuzzer (Maybe a)
maybe (Fuzzer gen) =
Gen.maybe gen
|> Fuzzer
-- | Given fuzzers for an error type and a success type, create a fuzzer for a
-- result.
result :: Fuzzer error -> Fuzzer value -> Fuzzer (Result error value)
result errorFuzzer valueFuzzer =
oneOf
[ map Err errorFuzzer,
map Ok valueFuzzer
]
-- | Given a fuzzer of a type, create a fuzzer of a list of that type.
-- Generates random lists of varying length, favoring shorter lists.
list :: Fuzzer a -> Fuzzer (List a)
list (Fuzzer gen) =
Gen.list (Range.exponential 0 100) gen
|> Fuzzer
-- | Given a fuzzer of a type, create a fuzzer of an array of that type.
-- Generates random arrays of varying length, favoring shorter arrays.
array :: Fuzzer a -> Fuzzer (Array.Array a)
array itemFuzzer =
list itemFuzzer
|> map Array.fromList
-- | Given a list of a type, create a fuzzer of random permutation of that list.
shuffle :: List a -> Fuzzer (List a)
shuffle list =
Gen.shuffle list
|> Fuzzer
-- | Choose one of the given fuzzers at random. Each fuzzer has an equal chance
-- of being chosen; to customize the probabilities, use 'frequency'.
oneOf :: List (Fuzzer a) -> Fuzzer a
oneOf optionFuzzers =
map unFuzzer optionFuzzers
|> Gen.choice
|> Fuzzer
-- | Create a fuzzer that only and always returns the value provided, and
-- performs no shrinking. This is hardly random, and so this function is best
-- used as a helper when creating more complicated fuzzers.
constant :: a -> Fuzzer a
constant x =
Gen.constant x
|> Fuzzer
-- | Create a new Fuzzer by providing a list of probabilistic weights to use
-- with other fuzzers. For example, to create a Fuzzer that has a 1/4 chance of
-- generating an int between -1 and -100, and a 3/4 chance of generating one
-- between 1 and 100, you could do this:
--
-- > Fuzz.frequency
-- > [ ( 1, Fuzz.intRange -100 -1 )
-- > , ( 3, Fuzz.intRange 1 100 )
-- > ]
--
-- There are a few circumstances in which this function will return an invalid
-- fuzzer, which causes it to fail any test that uses it:
--
-- - If you provide an empty list of frequencies
-- - If any of the weights are less than 0
-- - If the weights sum to 0
-- - Be careful recursively using this fuzzer in its arguments. Often using map
-- is a better way to do what you want. If you are fuzzing a tree-like data
-- structure, you should include a depth limit so to avoid infinite recursion,
-- like so:
--
-- > data Tree
-- > = Leaf
-- > | Branch Tree Tree
-- >
-- > tree :: Int -> Fuzzer Tree
-- > tree i =
-- > if i <= 0 then
-- > Fuzz.constant Leaf
-- >
-- > else
-- > Fuzz.frequency
-- > [ ( 1, Fuzz.constant Leaf )
-- > , ( 2, Fuzz.map2 Branch (tree (i - 1)) (tree (i - 1)) )
-- > ]
frequency :: List (Float, Fuzzer a) -> Fuzzer a
frequency optionFuzzers =
optionFuzzers
|> map (\(float', (Fuzzer gen)) -> (Prelude.round (float' * 1000000), gen))
|> Gen.frequency
|> Fuzzer
-- | Turn a tuple of fuzzers into a fuzzer of tuples.
tuple :: (Fuzzer a, Fuzzer b) -> Fuzzer (a, b)
tuple (fuzzerA, fuzzerB) =
map2 (,) fuzzerA fuzzerB
-- | Turn a 3-tuple of fuzzers into a fuzzer of 3-tuples.
tuple3 :: (Fuzzer a, Fuzzer b, Fuzzer c) -> Fuzzer (a, b, c)
tuple3 (fuzzerA, fuzzerB, fuzzerC) =
map3 (,,) fuzzerA fuzzerB fuzzerC
-- | A fuzzer for char values. Generates random ascii chars disregarding the
-- control characters and the extended character set.
char :: Fuzzer Char
char =
Gen.ascii
|> Fuzzer
-- | A fuzzer for the unit value. Unit is a type with only one value, commonly
-- used as a placeholder.
unit :: Fuzzer ()
unit = constant ()
-- | A fuzzer for order values.
order :: Fuzzer Ordering
order = oneOf [constant EQ, constant LT, constant GT]