Reference
Quickcheck
JCheck.Quickcheck
— TypeQuickcheck
Contain a set of properties to check through the generation of random input.
Fields
description::AbstractString
: description for the instancerng::AbstractRNG
: PRNG used to generate inputspredicates::PredsAssoc
: predicates to checkvariables::ArgsDict
: arguments used by the predicatesn::Int
: number of random inputs to generateserialize_fails::Bool
: if true, serialize failing inputs to a JLSO file
JCheck.Quickcheck
— MethodQuickcheck(desc; rng=GLOBAL_RNG, n=100, serialize_fails=true)
Constructor for type Quickcheck
.
Arguments
desc::AbstractString
: description for the instancerng::AbstractRNG
: PRNG used to generate inputsn::Int
: number of random inputs to generateserialize_fails::Bool
: if true, serialize failing inputs to a JLSO file
Examples
julia> qc = Quickcheck("A Test")
A Test: 0 predicate and 0 free variable.
JCheck.@add_predicate
— Macro@add_predicate qc desc pred
Add the predicate pred
to the set of tests qc
with description desc
.
Arguments
qc
: object of typeQuickcheck
desc
: string describing the predicatepred
: predicate in the form of an anonymous function
Notes
The form of pred
is very strict:
- It has to be an anonymous function. Formally, it should be an
Expr
of type->
. - The type of each argument appearing on the left-hand side of
pred
has to be specified with thex::Type
syntax. - The names of the arguments of
pred
matter! Specifically, in a givenQuickcheck
object, the type of every argument must be consistent across predicates (see examples). - Each predicate stored in a given
Quickcheck
object must be given a distinct description.
Examples
julia> qc = Quickcheck("A Test")
A Test: 0 predicate and 0 free variable.
julia> @add_predicate qc "Identity" (x::Float64 -> x == x)
A Test: 1 predicate and 1 free variable.
x::Float64
julia> @add_predicate qc "Sum commute" ((n::Int, x::Float64) -> n + x == x + n)
A Test: 2 predicates and 2 free variables:
n::Int64
x::Float64
julia> @add_predicate qc "Is odd" isodd(x)
ERROR: Predicate declaration must have the form of an anonymous function (... -> ...)
[...]
julia> @add_predicate qc "Is odd" (x::Int -> is_odd(x))
ERROR: A declaration for variable x already exists with type Float64; please choose another name for x
[...]
JCheck.@quickcheck
— Macro@quickcheck qc [file_id::AbstractString="yyyy-mm-dd_HH-MM-SS"]
Check the properties specified in object qc
of type Quickcheck
.
If qc.serialize_fails
is true
, serialize the failing cases to JCheck_<file_id>.jchk
. Those can latter be analyzed using load
and @getcases
.
Note
If no argument file_id
is passed, defaults to current time.
Failed Cases Analysis
JCheck.FailedTests
— TypeFailedTests
Container for failed tests from a @quickcheck
run. Wrapper around a Dict{Symbol, Any}
used for dispatch.
JCheck.load
— Methodload(io)
Load a collection of failed test cases serialized by a @quickcheck
run. Argument io
can be of type IO
, AbstractString
or AbstractPath
.
Examples
julia> ft = JCheck.load("JCheck_test.jchk")
2 failing predicates:
Product commute
Is odd
JCheck.@getcases
— Macro@getcases ft, desc...
Get the predicate with description desc
and the valuations for which it failed.
Note
The predicate with description closest to the one given (in the sense of the Levenshtein distance) will be returned; there is no need to pass the exact description.
Examples
julia> ft = JCheck.load("JCheck_test.jchk")
2 failing predicates:
Product commute
Is odd
julia> pred, valuations = @getcases ft iod
NamedTuple{(:predicate, :valuations), Tuple{Function, Vector{Tuple}}}((Serialization.__deserialized_types__.var"#3#4"(), Tuple[(0,), (-9223372036854775808,), (6444904272543528628,)]))
julia> map(x -> pred(x...), valuations)
3-element Vector{Bool}:
0
0
0
Random Input Generation
JCheck.generate
— Functiongenerate([rng=GLOBAL_RNG], T, n)
Sample n
random instances of type T
.
Arguments
rng::AbstractRNG
: random number generator to useT::Type
: type of the instancesn::Int
: number of realizations to sample
Default generators
generate
methods for the following types are shipped with this package:
- Subtypes of
AbstractFloat
- Subtypes of
Integer
exceptBigInt
Complex{T <: Real}
String
Char
Array{T, N}
BitArray{N}
SquareMatrix{T}
.- Any special matrix implemented by Julia's LinearAlgebra module
Union{T...}
UnitRange{T}
,StepRange{T, T}
In the previous list, T
represents any type for which a generate
method is implemented.
Special Matrices (LinearAlgebra)
- Generators are implemented for
<...>Triangular{T}
as well as<...>Triangular{T, S}
. In the first case,S
defaults toSquareMatrix{T}
. The exact same thing is true forUpperHessenberg
. - Same idea for
<...>diagonal{T, V}
withV
defaulting toVector{T}
. - Generators are only implemented for
Symmetric{T, S}
andHermitian{T, S}
right now. Most of the time, you will want to specifyS
=SquareMatrix{T}
.
Arrays & Strings
General purpose generators for arrays and strings are a little bit tricky to implement given that a length for each sampled element must be specified. The following choices have been made for the default generators shipped with this package:
String
: The length of each string is approximately exponentially distributed with mean 64.Array{T, N}
: The length of each dimension of a given array is approximately exponentially distributed with mean 24 ÷ N + 1 (in a low effort attempt to keep the number of entries manageable).
If this is not appropriate for your needs, don't hesitate to reimplement generate
.
Implementation
When implementing generate
for your type T
keep the following in mind:
- Your method should return a
Vector{T}
. - It is not necessary to write
generate(T, n)
orgenerate([rng, ]Array{T, N}, n) where N
; this is handled automatically. You only need to implementgenerate(::AbstractRNG, ::Type{T}, ::Int)
. - Consider implementing
specialcases
andshrink
forT
as well.
Examples
using Random: Xoshiro
rng = Xoshiro(42)
generate(rng, Float32, 10)
# output
10-element Vector{Float32}:
-1.5388016f7
-5.3113024f-19
-1.3960648f35
1.5957566f31
-4.381218f26
2.380078f35
3.878954f9
-1.1950524f-7
7.525897f24
-3.1891005f-12
JCheck.specialcases
— Methodspecialcases(T)
Non-random inputs that are always checked by @quickcheck
.
Arguments
T::Type
: type of the inputs
Implementation
- Your method should return a
Vector{T}
. - Useless without a
generate
method forT
. - Be mindful of combinatoric explosion!
@quickcheck
generate an input for each element of the Cartesian product of the special cases of every argument of the predicates it is trying to falsify. Only include special cases that are truly special.
Examples
julia> specialcases(Int)
4-element Vector{Int64}:
0
1
-9223372036854775808
9223372036854775807
julia> specialcases(Float64)
4-element Vector{Float64}:
0.0
1.0
-Inf
Inf
Shrinkage
JCheck.shrink
— Methodshrink(x)
Shrink an input. The returned value is a Vector
with elements similar
to x
. Returning a vector of length 1 is interpreted as meaning that no further shrinkage is possible.
Default Shrinkers
shrink
methods for the following types are shipped with this package:
AbstractString
AbstractArray{T, N}
for anyT
andN
Implementation
- Any implementation of
shrink(x::T)
must come with an implementation ofshrinkable(x::T)
. Failure to do so will prevent@quickcheck
from callingshrink
on an object of typeT
. shrink(x)
must return [x] ifshrinkable(x)
evaluate tofalse
. We suggest that the first line of your method is something like:
shrinkable(x) || return typeof(x)[x]
JCheck.shrinkable
— Methodshrinkable(x)
Determines if x
is shrinkable.
Note
Shrinkage can easily be disabled for type T
using overloading:
shrinkable(::T) = false