I want to thoroughly test an implementation of the intersection of two BTreeSets. I can write:
use self::proptest::prelude::*;
proptest! {
#[test]
fn intersect_this(s1: BTreeSet<i32>, s2: BTreeSet<i32>) {
// ...
}
}
But this has poor code coverage, because the code is specialized in some cases that random sets are unlikely to hit. One of the special cases is sets whose ranges of elements are almost disjoint (one set has values <= x, the other set has values >= x). In Python with hypothesis (in which I'm a little less of a newbie), I'd write:
from hypothesis import given
from hypothesis.strategies import builds, integers, sets
from typing import Set
def touching_ranges(elements: Set[int], split: int):
return {elt for elt in elements if elt < split}.union({split}), \
{elt for elt in elements if elt > split}.union({split})
@given(builds(touching_ranges, sets(integers()), integers()))
def test_touching_ranges(sets):
s1, s2 = sets
assert len(s1.intersection(s2)) == 1
In Rust, I got no further than stuffing everything inside the body:
#[test]
fn touching(mut s1: BTreeSet<i32>, split: i32) {
let mut s2 = s1.split_off(&split);
s1.insert(split);
s2.insert(split);
prop_assert_eq!(s1.intersection(&s2).count(), 1);
}
How can I keep the transformation of arbitrary values out of the test case body? I couldn't understand any of the code samples I found regarding strategies and Stack Overflow has few proptest-related questions.
There is a built-in
BTreeSetStrategyinproptest, so it is relatively straightforward:Some syntax here is not vanilla Rust so it may need further explanation:
proptest!macro, the tests are normal Rust functions except they also have access to thein Strategysyntax in order to generate inputs.prop_compose!macro. Again, this is a normal Rust function except it can have two argument lists. The first argument list is the usual input; the second one can use thein Strategysyntax and arguments from the first. The return type indicates the type of value being generated. In this case, a tuple of twoBTreeSets.Strategyimplementations for tuples, so a tuple of types that implementStrategyis itself aStrategy. That's why the functiontouching_rangescan be used as one.