Skip to content

Commit

Permalink
Merge pull request #11 from serokell/sereja/add-set-prop-tests
Browse files Browse the repository at this point in the history
[DMS-78] Add property tests for PersistentOrderedSet
  • Loading branch information
Sereja313 authored Oct 14, 2024
2 parents a5aea72 + 04e6074 commit 6f4f2d5
Showing 1 changed file with 286 additions and 0 deletions.
286 changes: 286 additions & 0 deletions test/PersistentOrderedSet.prop.test.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
// @testmode wasi

import Set "../src/PersistentOrderedSet";
import Nat "../src/Nat";
import Iter "../src/Iter";
import Debug "../src/Debug";
import Array "../src/Array";

import Suite "mo:matchers/Suite";
import T "mo:matchers/Testable";
import M "mo:matchers/Matchers";

let { run; test; suite } = Suite;

let natSet = Set.SetOps<Nat>(Nat.compare);

class SetMatcher(expected : Set.Set<Nat>) : M.Matcher<Set.Set<Nat>> {
public func describeMismatch(actual : Set.Set<Nat>, _description : M.Description) {
Debug.print(debug_show (Iter.toArray(Set.elements(actual))) # " should be " # debug_show (Iter.toArray(Set.elements(expected))))
};

public func matches(actual : Set.Set<Nat>) : Bool {
natSet.equals(actual, expected)
}
};

object Random {
var number = 4711;
public func next() : Nat {
number := (15485863 * number + 5) % 15485867;
number
};

public func nextNat(range: (Nat, Nat)): Nat {
let n = next();
let v = n % (range.1 - range.0 + 1) + range.0;
v
};

public func nextEntries(range: (Nat, Nat), size: Nat): [Nat] {
Array.tabulate<Nat>(size, func(_ix) {
let key = nextNat(range); key })
}
};

func setGenN(samples_number: Nat, size: Nat, range: (Nat, Nat), chunkSize: Nat): Iter.Iter<[Set.Set<Nat>]> {
object {
var n = 0;
public func next(): ?([Set.Set<Nat>]) {
n += 1;
if (n > samples_number) {
null
} else {
?Array.tabulate<Set.Set<Nat>>(chunkSize, func _i = natSet.fromIter(Random.nextEntries(range, size).vals()))
}
}
}
};

func run_all_props(range: (Nat, Nat), size: Nat, set_samples: Nat, query_samples: Nat) {
func prop(name: Text, f: Set.Set<Nat> -> Bool): Suite.Suite {
var error_msg: Text = "";
test(name, do {
var error = true;
label stop for(sets in setGenN(set_samples, size, range, 1)) {
if (not f(sets[0])) {
error_msg := "Property \"" # name # "\" failed\n";
error_msg #= "\n s: " # debug_show(Iter.toArray(Set.elements(sets[0])));
break stop;
}
};
error_msg
}, M.describedAs(error_msg, M.equals(T.text(""))))
};

func prop2(name: Text, f: (Set.Set<Nat>, Set.Set<Nat>) -> Bool): Suite.Suite {
var error_msg: Text = "";
test(name, do {
var error = true;
label stop for(sets in setGenN(set_samples, size, range, 2)) {
if (not f(sets[0], sets[1])) {
error_msg := "Property \"" # name # "\" failed\n";
error_msg #= "\n s1: " # debug_show(Iter.toArray(Set.elements(sets[0])));
error_msg #= "\n s2: " # debug_show(Iter.toArray(Set.elements(sets[1])));
break stop;
}
};
error_msg
}, M.describedAs(error_msg, M.equals(T.text(""))))
};

func prop3(name: Text, f: (Set.Set<Nat>, Set.Set<Nat>, Set.Set<Nat>) -> Bool): Suite.Suite {
var error_msg: Text = "";
test(name, do {
var error = true;
label stop for(sets in setGenN(set_samples, size, range, 3)) {
if (not f(sets[0], sets[1], sets[2])) {
error_msg := "Property \"" # name # "\" failed\n";
error_msg #= "\n s1: " # debug_show(Iter.toArray(Set.elements(sets[0])));
error_msg #= "\n s2: " # debug_show(Iter.toArray(Set.elements(sets[1])));
error_msg #= "\n s3: " # debug_show(Iter.toArray(Set.elements(sets[2])));
break stop;
}
};
error_msg
}, M.describedAs(error_msg, M.equals(T.text(""))))
};

func prop_with_elem(name: Text, f: (Set.Set<Nat>, Nat) -> Bool): Suite.Suite {
var error_msg: Text = "";
test(name, do {
label stop for(sets in setGenN(set_samples, size, range, 1)) {
for (_query_ix in Iter.range(0, query_samples-1)) {
let key = Random.nextNat(range);
if (not f(sets[0], key)) {
error_msg #= "Property \"" # name # "\" failed";
error_msg #= "\n s: " # debug_show(Iter.toArray(Set.elements(sets[0])));
error_msg #= "\n e: " # debug_show(key);
break stop;
}
}
};
error_msg
}, M.describedAs(error_msg, M.equals(T.text(""))))
};

run(
suite("Property tests",
[
suite("empty", [
test("not contains(empty(), e)", label res : Bool {
for (_query_ix in Iter.range(0, query_samples-1)) {
let elem = Random.nextNat(range);
if(natSet.contains(Set.empty<Nat>(), elem))
break res(false);
};
true;
}, M.equals(T.bool(true)))
]),

suite("contains & put", [
prop_with_elem("contains(put(s, e), e)", func (s, e) {
natSet.contains(natSet.put(s, e), e)
}),
prop_with_elem("put(put(s, e), e) == put(s, e)", func (s, e) {
let s1 = natSet.put(s, e);
let s2 = natSet.put(natSet.put(s, e), e);
SetMatcher(s1).matches(s2)
}),
]),

suite("delete", [
prop_with_elem("not contains(s, e) ==> delete(s, e) == s", func (s, e) {
if (not natSet.contains(s, e)) {
SetMatcher(s).matches(natSet.delete(s, e))
} else { true }
}),
prop_with_elem("delete(put(s, e), e) == s", func (s, e) {
if (not natSet.contains(s, e)) {
SetMatcher(s).matches(natSet.delete(natSet.put(s, e), e))
} else { true }
}),
prop_with_elem("delete(delete(s, e), e)) == delete(s, e)", func (s, e) {
let s1 = natSet.delete(natSet.delete(s, e), e);
let s2 = natSet.delete(s, e);
SetMatcher(s2).matches(s1)
})
]),

suite("size", [
prop_with_elem("size(put(s, e)) == size(s) + int(not contains(s, e))", func (s, e) {
Set.size(natSet.put(s, e)) == Set.size(s) + (if (not natSet.contains(s, e)) {1} else {0})
}),
prop_with_elem("size(delete(s, e)) + int(contains(s, e)) == size(s)", func (s, e) {
Set.size(natSet.delete(s, e)) + (if (natSet.contains(s, e)) {1} else {0}) == Set.size(s)
})
]),

suite("iter", [
prop("fromIter(elements(s)) == s", func (s) {
SetMatcher(s).matches(natSet.fromIter(Set.elements(s)))
})
]),

suite("mapFilter", [
prop_with_elem("not contains(mapFilter(s, (!=e)), e)", func (s, e) {
not natSet.contains(natSet.mapFilter<Nat>(s,
func (ei) { if (ei != e) {?ei} else {null}}), e)
}),
prop_with_elem("contains(mapFilter(put(s, e), (==e)), e)", func (s, e) {
natSet.contains(natSet.mapFilter<Nat>(natSet.put(s, e),
func (ei) { if (ei == e) {?ei} else {null}}), e)
})
]),

suite("map", [
prop("map(s, id) == s", func (s) {
SetMatcher(s).matches(natSet.map<Nat>(s, func (e) {e}))
})
]),

suite("set operations", [
prop("isSubset(s, s)", func (s) {
natSet.isSubset(s, s)
}),
prop("isSubset(empty(), s)", func (s) {
natSet.isSubset(Set.empty(), s)
}),
prop_with_elem("isSubset(delete(s, e), s)", func (s, e) {
natSet.isSubset(natSet.delete(s, e), s)
}),
prop_with_elem("contains(s, e) ==> not isSubset(s, delete(s, e))", func (s, e) {
if (natSet.contains(s, e)) {
not natSet.isSubset(s, natSet.delete(s, e))
} else { true }
}),
prop_with_elem("isSubset(s, put(s, e))", func (s, e) {
natSet.isSubset(s, natSet.put(s, e))
}),
prop_with_elem("not contains(s, e) ==> not isSubset(put(s, e), s)", func (s, e) {
if (not natSet.contains(s, e)) {
not natSet.isSubset(natSet.put(s, e), s)
} else { true }
}),
prop("intersect(empty(), s) == empty()", func (s) {
SetMatcher(Set.empty()).matches(natSet.intersect(Set.empty(), s))
}),
prop("intersect(s, empty()) == empty()", func (s) {
SetMatcher(Set.empty()).matches(natSet.intersect(s, Set.empty()))
}),
prop("union(s, empty()) == s", func (s) {
SetMatcher(s).matches(natSet.union(s, Set.empty()))
}),
prop("union(empty(), s) == s", func (s) {
SetMatcher(s).matches(natSet.union(Set.empty(), s))
}),
prop("diff(empty(), s) == empty()", func (s) {
SetMatcher(Set.empty()).matches(natSet.diff(Set.empty(), s))
}),
prop("diff(s, empty()) == s", func (s) {
SetMatcher(s).matches(natSet.diff(s, Set.empty()))
}),
prop("intersect(s, s) == s", func (s) {
SetMatcher(s).matches(natSet.intersect(s, s))
}),
prop("union(s, s) == s", func (s) {
SetMatcher(s).matches(natSet.union(s, s))
}),
prop("diff(s, s) == empty()", func (s) {
SetMatcher(Set.empty()).matches(natSet.diff(s, s))
}),
prop2("intersect(s1, s2) == intersect(s2, s1)", func (s1, s2) {
SetMatcher(natSet.intersect(s1, s2)).matches(natSet.intersect(s2, s1))
}),
prop2("union(s1, s2) == union(s2, s1)", func (s1, s2) {
SetMatcher(natSet.union(s1, s2)).matches(natSet.union(s2, s1))
}),
prop2("isSubset(diff(s1, s2), s1)", func (s1, s2) {
natSet.isSubset(natSet.diff(s1, s2), s1)
}),
prop2("intersect(diff(s1, s2), s2) == empty()", func (s1, s2) {
SetMatcher(natSet.intersect(natSet.diff(s1, s2), s2)).matches(Set.empty())
}),
prop3("union(union(s1, s2), s3) == union(s1, union(s2, s3))", func (s1, s2, s3) {
SetMatcher(natSet.union(natSet.union(s1, s2), s3)).matches(natSet.union(s1, natSet.union(s2, s3)))
}),
prop3("intersect(intersect(s1, s2), s3) == intersect(s1, intersect(s2, s3))", func (s1, s2, s3) {
SetMatcher(natSet.intersect(natSet.intersect(s1, s2), s3)).matches(natSet.intersect(s1, natSet.intersect(s2, s3)))
}),
prop3("union(s1, intersect(s2, s3)) == intersect(union(s1, s2), union(s1, s3))", func (s1, s2, s3) {
SetMatcher(natSet.union(s1, natSet.intersect(s2, s3))).matches(
natSet.intersect(natSet.union(s1, s2), natSet.union(s1, s3)))
}),
prop3("intersect(s1, union(s2, s3)) == union(intersect(s1, s2), intersect(s1, s3))", func (s1, s2, s3) {
SetMatcher(natSet.intersect(s1, natSet.union(s2, s3))).matches(
natSet.union(natSet.intersect(s1, s2), natSet.intersect(s1, s3)))
}),
]),
]))
};

run_all_props((1, 3), 0, 1, 10);
run_all_props((1, 5), 5, 100, 100);
run_all_props((1, 10), 10, 100, 100);
run_all_props((1, 100), 20, 100, 100);
run_all_props((1, 1000), 100, 100, 100);

0 comments on commit 6f4f2d5

Please sign in to comment.