Automatic testing of MoonBit programs
moon add moonbitlang/quickcheck
moon installimport {
"moonbitlang/quickcheck" @qc
"moonbitlang/quickcheck/gen" @gen
}
import {
"moonbitlang/quickcheck"
} for "test" // for test///|
test "reverse" {
debug_inspect(([] : Array[Int]).rev(), content="[]")
debug_inspect([1, 2, 3].rev(), content="[3, 2, 1]")
}///|
fn prop_reverse_identity(arr : Array[Int]) -> Bool {
arr.rev().rev() == arr
}///|
test {
@qc.quick_check_fn(prop_reverse_identity)
// equivalent to
// @qc.quick_check(@qc.Arrow(prop_reverse_identity))
}+++ [100/0/100] Ok, passed!///|
fn remove(arr : Array[Int], x : Int) -> Array[Int] {
match arr.search(x) {
Some(i) => arr.remove(i) |> ignore
None => ()
}
arr
}///|
fn prop_length_is_not_greater(iarr : (Int, Array[Int])) -> Bool {
let (x, arr) = iarr
let len = arr.length()
remove(arr, x).length() <= len
}///|
test {
@qc.quick_check_fn(prop_length_is_not_greater)
}
// +++ [100/0/100] Ok, passed!///|
fn prop_removed_not_present(iarr : (Int, Array[Int])) -> Bool {
let (x, arr) = iarr
!remove(arr, x).contains(x)
}
///|
test {
@qc.quick_check_fn(prop_removed_not_present, expect=Fail)
}*** [8/0/100] Failed! Falsified.
(0, [0, 0])///|
/// path: src/driver.mbt
pub fn[A : @coreqc.Arbitrary + Shrink + Debug, B : Testable] quick_check_fn(
f : (A) -> B,
max_shrinks? : Int,
max_success? : Int,
max_size? : Int,
discard_ratio? : Int,
expect? : Expected = Success,
abort? : Bool = false,
) -> Unit raise Failure {
quick_check(
Arrow(f),
max_shrink?=max_shrinks,
max_success?,
max_size?,
discard_ratio?,
expect~,
abort~,
)
}pub fn quick_check[P : @qc.Testable](prop : P) -> Unit raise Failure
pub(all) struct Arrow[A, P]((A) -> P)
pub impl[P : Testable, A : Arbitrary + Shrink + Debug] Testable for Arrow[A, P]///|
fn prop_remove_not_presence(iarr : (Int, Array[Int])) -> Bool {
let (x, arr) = iarr
!remove(arr, x).contains(x)
}
///|
test {
@qc.quick_check(
@qc.Arrow(prop_remove_not_presence),
max_shrink=1000,
expect=Fail,
)
}///|
pub trait Arbitrary {
arbitrary(Int, @splitmix.RandomState) -> Self
}///|
enum Nat {
Zero
Succ(Nat)
} derive(Arbitrary, Debug)
///|
test {
let nat_gen : @gen.Gen[Nat] = @gen.Gen::spawn()
let nats = nat_gen.samples(size=4)
debug_inspect(
nats,
content=(
#|[
#| Succ(Succ(Succ(Zero))),
#| Succ(Succ(Succ(Succ(Zero)))),
#| Succ(Zero),
#| Succ(Succ(Succ(Zero))),
#|]
),
)
}///|
pub trait Shrink {
shrink(Self) -> Iter[Self]
}///|
struct Gen[T] {
gen : (Int, @splitmix.RandomState) -> T
}///|
let g : @gen.Gen[Int] = {
...
} // Suppose we have a generator for Int
///|
let _x : Int = g.run(100, @splitmix.new()) // Generate a random Int at size 100fn pure[T](val : T) -> Gen[T]
fn fmap[T, U](self : Gen[T], f : (T) -> U) -> Gen[U]
fn ap[T, U](self : Gen[(T) -> U], v : Gen[T]) -> Gen[U]
fn bind[T, U](self : Gen[T], f : (T) -> Gen[U]) -> Gen[U]///|
let g1 : @gen.Gen[Int] = @gen.Gen(
{
...
},
)
///|
let _g2 : @gen.Gen[Int] = g1.fmap(x => x + 1)
///|
let _g3 : @gen.Gen[String] = g1.fmap(x => x.to_string())///|
let dg1 : @gen.Gen[Int] = @gen.Gen(
{
...
},
)
///|
let _dg2 : @gen.Gen[Int] = dg1.bind(x => {
// TODO(upstream) <| does not work here
if x == 0 {
@gen.pure(100)
} else {
@gen.pure(200)
}
})///|
let _gen_bool : @gen.Gen[Bool] = @gen.one_of([@gen.pure(true), @gen.pure(false)])///|
let _gen_freq : @gen.Gen[Bool] = @gen.frequency([
(4, @gen.pure(true)),
(1, @gen.pure(false)),
])pub fn sized[T](f : (Int) -> @gen.Gen[T]) -> @gen.Gen[T]///|
let gen : @gen.Gen[Int] = @gen.sized(@gen.pure)
///|
let arr : Array[Int] = Array::makei(20, i => gen.sample(size=i))
///|
test "sized" {
debug_inspect(
arr,
content="[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]",
)
}///|
let prop_rev : (@list.List[Int]) -> Bool = (x : @list.List[Int]) => {
x.rev().rev() == x
}
///|
test "List reverse" {
@qc.quick_check(@qc.forall(@gen.Gen::spawn(), prop_rev))
}fn Gen::spawn[T : Arbitrary]() -> Gen[T]///|
test {
@qc.quick_check(
@qc.forall(@gen.Gen::spawn(), (a : Array[Int]) => {
@qc.forall(@gen.one_of_array(a), y => !remove(a, y).contains(y)).filter(
a.length() != 0,
)
}),
expect=Fail,
// We expect this test to fail because of the bug in the remove function
)
}*** [4/33/100] Failed! Falsified.
[0, 0]
0///|
test {
fn no_duplicate(x : Array[Int]) -> Bool {
@sorted_set.from_iter(x.iter()).length() == x.length()
}
@qc.quick_check(
@qc.forall(@gen.Gen::spawn(), (iarr : (Int, Array[Int])) => {
let (x, arr) = iarr
@qc.property(!remove(arr.copy(), x).contains(x)).filter(no_duplicate(arr))
}),
max_size=50,
discard_ratio=20,
max_success=100,
)
}///|
test "classes" {
@qc.quick_check_fn((x : @list.List[Int]) => {
@qc.property(prop_rev(x))
.classify(x.length() > 5, "long list")
.classify(x.length() <= 5, "short list")
})
}+++ [100/0/100] Ok, passed!
22% : short list
78% : long list///|
test "label" {
@qc.quick_check_fn((x : @list.List[Int]) => {
@qc.property(prop_rev(x)).label(
if x.is_empty() {
"trivial"
} else {
"non-trivial"
},
)
})
}+++ [100/0/100] Ok, passed!
8% : trivial
92% : non-trivialAutomatic testing of MoonBit programs