Reference
Quickcheck
JCheck.Quickcheck — Type
QuickcheckContain 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 — Method
Quickcheck(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 predAdd the predicate pred to the set of tests qc with description desc.
Arguments
qc: object of typeQuickcheckdesc: 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
Exprof type->. - The type of each argument appearing on the left-hand side of
predhas to be specified with thex::Typesyntax. - The names of the arguments of
predmatter! Specifically, in a givenQuickcheckobject, the type of every argument must be consistent across predicates (see examples). - Each predicate stored in a given
Quickcheckobject 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 — Type
FailedTestsContainer for failed tests from a @quickcheck run. Wrapper around a Dict{Symbol, Any}.
JCheck.load — Method
load(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:
Is odd
- commutesJCheck.@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:
Is odd
- commutes
julia> pred, valuations = @getcases ft iod
@NamedTuple{predicate::Function, valuations::Vector{Tuple}}((Serialization.__deserialized_types__.var"#11#12"(), Tuple[(0,), (-9223372036854775808,), (-1603514452799603314,), (1420394807175553538,), (4507329505808279390,), (-426481527288535688,), (-5691388592443778052,), (-7859122130299025792,), (-5525456812138927418,), (-7209867710197627164,) … (2031324158527932024,), (7907216074681153692,), (4734352501972781814,), (7649976476383282706,), (-6664068458754296008,), (-5721291110713069694,), (8573438617342549320,), (-5611383820228536680,), (-4303975626508744234,), (-5727584619371173840,)]))
julia> map(x -> pred(x...), valuations)
53-element Vector{Bool}:
0
0
0
0
0
0
0
0
0
0
⋮
0
0
0
0
0
0
0
0
0Random Input Generation
JCheck.generate — Function
generate([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
IntegerexceptBigInt Complex{T <: Real}StringCharArray{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,Sdefaults toSquareMatrix{T}. The exact same thing is true forUpperHessenberg. - Same idea for
<...>diagonal{T, V}withVdefaulting 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
specialcasesandshrinkforTas 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-12JCheck.specialcases — Method
specialcases(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
generatemethod forT. - Be mindful of combinatoric explosion!
@quickcheckgenerate 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
InfShrinkage
JCheck.shrink — Method
shrink(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:
AbstractStringAbstractArray{T, N}for anyTandN
Implementation
- Any implementation of
shrink(x::T)must come with an implementation ofshrinkable(x::T). Failure to do so will prevent@quickcheckfrom callingshrinkon 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 — Method
shrinkable(x)Determines if x is shrinkable.
Note
Shrinkage can easily be disabled for type T using overloading:
shrinkable(::T) = false