Automatic testing of MoonBit programs
moon add moonbitlang/quickcheck
moon install{
"import": [{ "path": "moonbitlang/quickcheck", "alias": "qc" }]
}test "reverse" {
inspect!(reverse(([] : Array[Int])), content="[]")
inspect!(reverse([1, 2, 3]), content="[3, 2, 1]")
}fn prop_reverse_identity(arr : Array[Int]) -> Bool {
reverse(reverse(arr)) == 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_remove_not_presence(iarr : (Int, Array[Int])) -> Bool {
let (x, arr) = iarr
remove(arr, x).contains(x).not()
}
test {
@qc.quick_check_fn!(prop_remove_not_presence)
}*** [8/0/100] Failed! Falsified.
(0, [0, 0])///| path: src/driver.mbt
pub fn quick_check_fn[A : Arbitrary + Shrink + Show, B : Testable](
f : (A) -> B
) -> Unit!Failure {
quick_check!(Arrow(f))
}pub fn quick_check[P : Testable](prop : P) -> Unit!Failure
type Arrow[A, P] (A) -> P
impl[P : Testable, A : Arbitrary + Shrink + Show] Testable for Arrow[A, P]test {
@qc.quick_check!(
@qc.Arrow(prop_remove_not_presence) |> @qc.with_max_success(1000),
)
}
///| Output: +++ [1000/0/1000] Ok, passed!pub trait Arbitrary {
arbitrary(Int, 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, RandomState) -> T
}let g : Gen[Int] = Gen::new(..) // Suppose we have a generator for Int
let x : Int = g.run(100, RandomState::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 g : Gen[Int] = Gen::new(..)
let g2 : Gen[Int] = g.fmap(fn(x) { x + 1 })
let g3 : Gen[String] = g.fmap(fn(x) { x.to_string() })let g : Gen[Int] = Gen::new(..)
let g2 : Gen[Int] = g.bind(fn(x : Int) {
if x == 0 {
Gen::pure(100)
} else {
Gen::pure(200)
}
})let gen_bool : Gen[Bool] = one_of([pure(true), pure(false)])let gen_freq : Gen[Bool] = frequency([(4, pure(true)), (1, pure(false))])pub fn sized[T](f : (Int) -> Gen[T]) -> Gen[T]let gen : Gen[Int] = sized(fn { size => pure(size) })
let arr = Array::makei(20, fn { i => gen.sample(size=i) })
inspect!(arr, content="[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]")quick_check!(forall(spawn(), fn(x : List[Int]) { x.rev().rev() == x }))fn Gen::spawn[T : Arbitrary]() -> Gen[T]test {
quick_check!(
forall(
spawn(),
fn(a : Array[Int]) {
forall(one_of_array(a),
fn(y : Int) { remove(a, y).contains(y).not() })
|> filter(a.length() != 0)
},
),
)
}*** [4/33/100] Failed! Falsified.
[0, 0]
0test {
fn no_duplicate(x : Array[Int]) -> Bool {
@sorted_set.from_iter(x.iter()).size() == x.length().to_int64()
}
quick_check!(
forall(
spawn(),
fn(iarr : (Int, Array[Int])) {
let (x, arr) = iarr
filter(remove(arr.copy(), x).contains(x).not(), no_duplicate(arr))
},
),
)
}test "classes" {
quick_check_fn!(
fn(x : List[Int]) {
Arrow(prop_rev)
|> classify(x.length() > 5, "long list")
|> classify(x.length() <= 5, "short list")
},
)
}+++ [100/0/100] Ok, passed!
22% : short list
78% : long listtest "label" {
quick_check_fn!(
fn(x : List[Int]) {
Arrow(prop_rev)
|> 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) -> Btype 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)Automatic testing of MoonBit programs