Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DMS-78] Add property tests for PersistentOrderedSet #11

Merged
merged 1 commit into from
Oct 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);