Automatic testing of MoonBit programs
moon add moonbitlang/quickcheck
moon install{
"import": [{ "path": "moonbitlang/quickcheck", "alias": "qc" }]
}///|
test "reverse" {
inspect(([] : Array[Int]).rev(), content="[]")
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 quick_check!(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 + Show, 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 + Show] Testable for Arrow[A, P]///|
fn prop_remove_not_presence(iarr : (Int, Array[Int])) -> Bool {
let (x, arr) = iarr
not(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, Show)
///|
test {
let nat_gen : @qc.Gen[Nat] = @qc.Gen::spawn()
let nats = nat_gen.samples(size=4)
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 : @qc.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 : @qc.Gen[Int] = @qc.Gen::new(
{
...
},
)
///|
let _g2 : @qc.Gen[Int] = g1.fmap(fn(x) { x + 1 })
///|
let _g3 : @qc.Gen[String] = g1.fmap(fn(x) { x.to_string() })///|
let dg1 : @qc.Gen[Int] = @qc.Gen::new(
{
...
},
)
///|
let _dg2 : @qc.Gen[Int] = dg1.bind(fn(x : Int) {
if x == 0 {
@qc.pure(100)
} else {
@qc.pure(200)
}
})///|
let _gen_bool : @qc.Gen[Bool] = @qc.one_of([@qc.pure(true), @qc.pure(false)])///|
let _gen_freq : @qc.Gen[Bool] = @qc.frequency([
(4, @qc.pure(true)),
(1, @qc.pure(false)),
])pub fn sized[T](f : (Int) -> @qc.Gen[T]) -> @qc.Gen[T]///|
let gen : @qc.Gen[Int] = @qc.sized(@qc.pure)
///|
let arr : Array[Int] = Array::makei(20, i => gen.sample(size=i))
///|
test "sized" {
inspect(
arr,
content="[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]",
)
}///|
using @list {type List}
///|
let prop_rev : (List[Int]) -> Bool = fn(x : List[Int]) { x.rev().rev() == x }
///|
test "List reverse" {
@qc.quick_check(@qc.forall(@qc.Gen::spawn(), prop_rev))
}fn Gen::spawn[T : Arbitrary]() -> Gen[T]///|
test {
@qc.quick_check(
@qc.forall(@qc.Gen::spawn(), fn(a : Array[Int]) {
@qc.forall(@qc.one_of_array(a), fn(y : Int) { !remove(a, y).contains(y) })
|> @qc.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(@qc.Gen::spawn(), fn(iarr : (Int, Array[Int])) {
let (x, arr) = iarr
@qc.filter(!remove(arr.copy(), x).contains(x), no_duplicate(arr))
}),
max_size=50,
discard_ratio=20,
max_success=100,
)
}///|
test "classes" {
@qc.quick_check_fn((x : List[Int]) => {
@qc.Arrow(prop_rev)
|> @qc.classify(x.length() > 5, "long list")
|> @qc.classify(x.length() <= 5, "short list")
})
}+++ [100/0/100] Ok, passed!
22% : short list
78% : long list///|
test "label" {
@qc.quick_check_fn(fn(x : List[Int]) {
@qc.Arrow(prop_rev)
|> @qc.label(if x.is_empty() { "trivial" } else { "non-trivial" })
})
}+++ [100/0/100] Ok, passed!
8% : trivial
92% : non-trivialpub(all) type Arrow[A, B] (A) -> Bpub(all) type ArrowAsync[A, B] async (A) -> B#deprecated("Use `struct T(A)` to declare a newtype and use `.0` access the underlying type instead.")
fn[A, B] ArrowAsync::inner(self : ArrowAsync[A, B]) -> (async (A) -> B)pub(all) type ArrowError[A, B] (A) -> B raiseimpl Testable for ArrowError[A, P]#deprecated("Use `struct T(A)` to declare a newtype and use `.0` access the underlying type instead.")
fn[A, B] ArrowError::inner(self : ArrowError[A, B]) -> ((A) -> B raise)type Axiom[T]type Discardpub(all) struct Equivalence[T] {
lhs : T
rhs : T
}impl Show for Equivalence[T]type Gen[T]pub(all) enum Outcome[T] {
Success
GaveUp
Fail(T)
}type Printertype Propertytype Statefn State::find_failure(self : State, res : SingleResult, ts : Iter[Rose[SingleResult]]) -> TestSuccess raise TestErrorfn State::local_min(self : State, res : SingleResult, ts : Iter[Rose[SingleResult]]) -> (Int, Int, Int, SingleResult)fn[A : Enumerable + Show, B : Testable] small_check(f : (A) -> B, max_size? : Int, expect? : Expected, abort? : Bool) -> Unit raise Failurefn[A : Enumerable + Show, B : Testable] small_check_error(f : (A) -> B raise, max_size? : Int, expect? : Expected, abort? : Bool) -> Unit raise Failurefn[A : Enumerable + Show, B : Testable] small_check_error_silence(f : (A) -> B raise, max_size? : Int, expect? : Expected, abort? : Bool) -> Stringfn[A : Enumerable + Show, B : Testable] small_check_silence(f : (A) -> B, max_size? : Int, expect? : Expected, abort? : Bool) -> StringAutomatic testing of MoonBit programs