Interfaces

PropCheck.jl provides a number of interfaces to hook into with your code. Some of these are more robust than others, while some are likely to change in the future. Nevertheless, they are documented here in order to facilitate experimentation, as well as gathering feedback on how well they work and where they have missing functionality/aren't clear enough, before an eventual 1.0 version release.

The interfaces mentioned on this page are intended for user-extension, in the manner described. Overloading the functions in a different way or assuming more of an interface than is guaranteed is not supported.

For the abstract-type based interfaces AbstractIntegrated and ExtentIntegrated, you can use the API provided by RequiredInterfaces.jl to check for compliance, if you want to provide a custom integrated shrinker.

AbstractIntegrated{T}

PropCheck.AbstractIntegratedType
AbstractIntegrated{T}

Abstract supertype for all integrated shrinkers. The T type parameter describes the kinds of objects generated by this integrated shrinker. This is usually going to be a Tree of objects.

Required methods:

  • generate(rng::AbstractRNG, ::A) where A <: AbstractIntegrated
  • freeze(::A) where A <: AbstractIntegrated

Fallback definitions:

  • Base.IteratorEltype -> Base.HasEltype()
  • Base.IteratorSize -> Base.SizeUnknown()
  • Base.eltype -> T
  • Base.iterate(::AbstractIntegrated, rng=default_rng())
    • Requires generate
source

AbstractIntegrated is the most unassuming integrated shrinker type, requiring little more than defining generate. generate on an AbstractIntegrated is, in the current design, only going to return Trees (and others are unlikely to work/not supported by the rest of the package), but that's not technically necessary. A more sophisticated generation process than the implicit & lazy unfolding of a tree could share subtrees, which would be more like a lazy graph. While technically possible, this is not currently planned.

ExtentIntegrated{T}

PropCheck.ExtentIntegratedType
ExtentIntegrated{T} <: InfiniteIntegrated{T}

An integrated shrinker which has bounds. The bounds can be accessed with the extent function and are assumed to have first and last method defined for them.

Required methods:

  • extent(::ExtentIntegrated)
source

ExtentIntegrated extends the AbstractIntegrated interface by a single method - PropCheck.extent. Its purpose is simple - values produced by an ExtentIntegrated are expected to fall within a given ordered set, with a maximum and a minimum, which is what is returned by extent.

InfiniteIntegrated{T}

PropCheck.InfiniteIntegratedType
InfiniteIntegrated{T} <: AbstractIntegrated{T}

Abstract supertype for all integrated shrinkers that provide infinite generation of elements.

Fallback definitions: * Base.IteratorSize(::Type{<:InfiniteIntegrated}) = Base.IsInfinite()

Overwriting Base.IteratorSize for subtypes of this type is disallowed.

source

InfiniteIntegrated are at the core of PropCheck.jl; they allow for arbitrarily long generation of new values. They are currently always implementes as stateless objects, and underpin the majority of generation that is possible with PropCheck.jl.

FiniteIntegrated{T}

PropCheck.FiniteIntegratedType
FiniteIntegrated{T} <: AbstractIntegrated{T}

An integrated shrinker producing only a finite number of elements.

  • Base.IteratorSize(::FiniteIntegrated) must return a Base.HasLength() or Base.HasShape.
    • length(::T) needs to be implemented for your T <: FiniteIntegrated; there is no fallback.
    • If your T <: FiniteIntegrated has a shape, return that from IteratorSize instead & implement size as well.

Once the integrated generator is exhausted, generate(::FiniteIntegrated) will return nothing.

source

FiniteIntegrated are used for stopping generation of values. They are currently always implemented as stateful objects during generation, i.e. they cannot be restarted. This may change in the future, but is (currently) a consequence of the existing design.

Generation & Shrinking

These two functions are required if you want to customize shrinking & type-based generation. It's certainly not necessary to implement these to work with most features of this package, but they are required if you want to customize what kinds of object itype returns.

PropCheck.shrinkFunction
shrink(val::T) where T

Function to be used during shrinking of val. Must return an iterable of shrunk values, which can be lazy. If the returned iterable is empty, it's taken as a signal that the given value cannot shrink further.

Must never return a previously input value, i.e. no value val used as input should ever be produced by shrink(val) or subsequent applications of shrink on the produced elements. This will lead to infinite looping during shrinking.

source
PropCheck.generateFunction
generate(rng::AbstractRNG, ::Type{T}) where T -> T

Function to generate a single value of type T. Falls back to constructor inspection, which will generate values for ::Any typed arguments.

Types that have rand defined for them should forward to it here, assuming rand returns the full spectrum of possible instances. Assumed to return an object of type T.

Float64

A good example for when not to forward to rand is Float64 - by default, Julia only generates values in the half-open interval [0,1), meaning Inf, NaN and similar special values aren't generated at all. As you might imagine, this is not desirable for a framework that ought to find bugs in code that doesn't handle these kinds of values correctly.

source

itype

PropCheck.itypeFunction
itype(T::Type[, shrink=shrink]) -> AbstractIntegrated

A convenience constructor for creating integrated shrinkers, generating their values from a type.

Trees created by this function will have their elements shrink according to shrink.

source

In order to hook into the generation provided by itype, define generate for your type T.

Generally speaking, generate should always produce the full set of possible values of a type. For example, itype(Float64) can produce every possible Float64 value, with every possible bitpattern - that includes all different kinds of NaN.

Be sure to also define a shrinking function for your type, by adding a method to shrink.