Skip to content

Instantly share code, notes, and snippets.

@pao
Last active October 11, 2015 10:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pao/3843448 to your computer and use it in GitHub Desktop.
Save pao/3843448 to your computer and use it in GitHub Desktop.
An in-development Julia implementation of QuickCheck

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment