Now being developed at https://github.com/pao/QuickCheck.jl and available via Pkg as "QuickCheck".
Package documentation is at https://quickcheckjl.rtfd.org/.
# A Julia implementation of QuickCheck, a randomized specification-based tester
#
# QuickCheck was originally written for Haskell by Koen Claessen and John Hughes
# http://www.cse.chalmers.se/~rjmh/QuickCheck/
module QuickCheck
export property
export condproperty
export quantproperty
import Base.*
# Simple properties
function property(f::Function, ntests)
if !isa(f.code, LambdaStaticData)
error("Property must be expressed as an anonymous function")
end
typs = [eval(var.args[2]) for var in f.code.ast.args[1]]
arggens = [size -> generator(typ, size) for typ in typs]
quantproperty(f, ntests, arggens...)
end
property(f::Function) = property(f, 100)
# Conditional properties
function condproperty(f::Function, ntests, maxtests, argconds...)
if !isa(f.code, LambdaStaticData)
error("Property must be expressed as an anonymous function")
end
vars = f.code.ast.args[1]
typs = [eval(var.args[2]) for var in vars]
arggens = [size -> generator(typ, size) for typ in typs]
check_property(f, arggens, argconds, ntests, maxtests)
end
# Quantified properties (custom generators)
function quantproperty(f::Function, ntests, arggens...)
if !isa(f.code, LambdaStaticData)
error("Property must be expressed as an anonymous function")
end
argconds = [_->true for n in f.code.ast.args[1]]
check_property(f, arggens, argconds, ntests, ntests)
end
function check_property(f::Function, arggens, argconds, ntests, maxtests)
totalTests = 0
for i in 1:ntests
goodargs = false
args = {}
while !goodargs
totalTests += 1
if totalTests > maxtests
println("Arguments exhaused after $i tests.")
return
end
args = [arggen(div(i,2)+3) for arggen in arggens]
goodargs = all([apply(x[1], tuple(x[2])) for x in zip(argconds, args)])
end
if !f(args...)
error("Falsifiable, after $i tests:\n$args")
end
end
println("OK, passed $ntests tests")
end
# Default generators for primitive types
generator{T<:Unsigned}(::Type{T}, size) = convert(T, randi(size))
generator{T<:Signed}(::Type{T}, size) = convert(T, randi((-size, size)))
generator(::Type{Any}, size) = error("Property variables cannot by typed Any.")
# Generators for composite/array types
function generator{T,n}(::Type{Array{T,n}}, size)
reshape([generator(T, size) for x in 1:(size^n)], [size for i in 1:n]...)
end
function generator{C}(::Type{C}, size)
if !isa(C, CompositeKind)
error("Type $C is not a composite type.")
end
C([generator(T, size) for T in C.types]...)
end
end