use proptest::prelude::*;
use crate::Slice;
use crate::selection::EvalOpts;
use crate::selection::Selection;
use crate::selection::dsl;
use crate::shape::Range;
pub fn gen_slice(max_dims: usize, max_len: usize) -> impl Strategy<Value = Slice> {
prop::collection::vec(1..=max_len, 1..=max_dims).prop_map(Slice::new_row_major)
}
pub fn gen_selection(depth: u32, shape: Vec<usize>, dim: usize) -> BoxedStrategy<Selection> {
let leaf = Just(dsl::true_()).boxed();
if depth == 0 || dim >= shape.len() {
return leaf;
}
let recur = {
let shape = shape.clone();
move || gen_selection(depth - 1, shape.clone(), dim + 1)
};
let range_strategy = {
let dim_size = shape[dim];
(0..dim_size)
.prop_flat_map(move |start| {
let max_len = dim_size - start;
(1..=max_len).prop_flat_map(move |len| {
(1..=len).prop_map(move |step| {
let r = Range(start, Some(start + len), step);
dsl::range(r, dsl::true_())
})
})
})
.boxed()
};
let all = recur().prop_map(dsl::all).boxed();
let union = (recur(), recur())
.prop_map(|(a, b)| dsl::union(a, b))
.boxed();
let inter = (recur(), recur())
.prop_map(|(a, b)| dsl::intersection(a, b))
.boxed();
prop_oneof![
2 => leaf,
3 => range_strategy,
3 => all,
2 => union,
2 => inter,
]
.prop_filter("valid selection", move |s| {
let slice = Slice::new_row_major(shape.clone());
let eval_opts = EvalOpts {
disallow_dynamic_selections: true,
..EvalOpts::strict()
};
s.validate(&eval_opts, &slice).is_ok()
})
.boxed()
}
mod tests {
use std::collections::HashMap;
use std::collections::HashSet;
use proptest::strategy::ValueTree;
use proptest::test_runner::Config;
use proptest::test_runner::TestRunner;
use super::*;
use crate::selection::EvalOpts;
use crate::selection::routing::RoutingFrame;
use crate::selection::test_utils::collect_commactor_routing_tree;
use crate::selection::test_utils::collect_routed_paths;
#[test]
fn print_some_slices() {
let mut runner = TestRunner::new(Config::default());
for _ in 0..256 {
let strat = gen_slice(4, 8); let value = strat.new_tree(&mut runner).unwrap().current();
println!("{:?}", value);
}
}
proptest! {
#[test]
fn test_slice_properties(slice in gen_slice(4, 8)) {
let total_size: usize = slice.sizes().iter().product();
prop_assert!(total_size > 0);
}
}
#[test]
fn print_some_selections() {
let mut runner = TestRunner::new(Config::default());
for _ in 0..256 {
let strat = gen_selection(3, vec![2, 4, 8], 0);
let value = strat.new_tree(&mut runner).unwrap().current();
println!("{:?}", value);
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 8, ..ProptestConfig::default()
})]
#[test]
fn trace_route_path_determinism(
slice in gen_slice(4, 8)
) {
let shape = slice.sizes().to_vec();
let mut runner = TestRunner::default();
let s = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
let t = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
let eval_opts = EvalOpts::strict();
let sel_s: HashSet<_> = s.eval(&eval_opts, &slice).unwrap().collect();
let sel_t: HashSet<_> = t.eval(&eval_opts, &slice).unwrap().collect();
let ranks: Vec<_> = sel_s.intersection(&sel_t).cloned().collect();
if ranks.is_empty() {
println!("skipping empty intersection");
} else {
println!("testing {} nodes", ranks.len());
for rank in ranks {
let coords = slice.coordinates(rank).unwrap();
let start_s = RoutingFrame::root(s.clone(), slice.clone());
let start_t = RoutingFrame::root(t.clone(), slice.clone());
let path_s = start_s.trace_route(&coords).unwrap();
let path_t = start_t.trace_route(&coords).unwrap();
prop_assert_eq!(
path_s.clone(),
path_t.clone(),
"path to {:?} differs under S and T\nS path: {:?}\nT path: {:?}",
rank, path_s, path_t
);
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 128, ..ProptestConfig::default()
})]
#[test]
fn collect_routed_path_determinism(
slice in gen_slice(4, 8)
) {
let shape = slice.sizes().to_vec();
let mut runner = TestRunner::default();
let s = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
let t = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
let paths_s = collect_routed_paths(&s, &slice);
let paths_t = collect_routed_paths(&t, &slice);
let ranks: Vec<_> = paths_s.delivered.keys()
.filter(|r| paths_t.delivered.contains_key(*r))
.cloned()
.collect();
if ranks.is_empty() {
println!("skipping empty intersection");
} else {
println!("testing {} nodes", ranks.len());
for rank in ranks {
let path_s = paths_s.delivered.get(&rank).unwrap();
let path_t = paths_t.delivered.get(&rank).unwrap();
prop_assert_eq!(
path_s.clone(),
path_t.clone(),
"path to {:?} differs under S and T\nS path: {:?}\nT path: {:?}",
rank, path_s, path_t
);
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 128, ..ProptestConfig::default()
})]
#[test]
fn collect_commactor_routed_path_determinism(
slice in gen_slice(4, 8)
) {
let extents = slice.sizes().to_vec();
let mut runner = TestRunner::default();
let s = gen_selection(4, extents.clone(), 0).new_tree(&mut runner).unwrap().current();
let t = gen_selection(4, extents.clone(), 0).new_tree(&mut runner).unwrap().current();
let tree_s = collect_commactor_routing_tree(&s, &slice);
let tree_t = collect_commactor_routing_tree(&t, &slice);
let ranks: Vec<_> = tree_s
.delivered
.keys()
.filter(|r| tree_t.delivered.contains_key(*r))
.cloned()
.collect();
if ranks.is_empty() {
println!("skipping empty intersection");
} else {
println!("testing {} nodes", ranks.len());
for rank in ranks {
let path_s = &tree_s.delivered[&rank];
let path_t = &tree_t.delivered[&rank];
prop_assert_eq!(
path_s.clone(),
path_t.clone(),
"path to {:?} differs under S and T\nS path: {:?}\nT path: {:?}",
rank, path_s, path_t
);
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 256, ..ProptestConfig::default()
})]
#[test]
fn collect_routed_paths_unique_predecessor(
slice in gen_slice(4, 8)
) {
let shape = slice.sizes().to_vec();
let mut runner = TestRunner::default();
let s = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
let tree = collect_routed_paths(&s, &slice);
for (node, preds) in tree.predecessors {
let non_self_preds: Vec<_> = preds.clone().into_iter()
.filter(|&p| p != node)
.collect();
prop_assert!(
non_self_preds.len() <= 1,
"Node {} had multiple non-self predecessors: {:?} (selection: {})",
node,
non_self_preds,
s,
);
}
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 256, ..ProptestConfig::default()
})]
#[test]
fn commactor_routed_paths_unique_predecessor(
slice in gen_slice(4, 8)
) {
let shape = slice.sizes().to_vec();
let mut runner = TestRunner::default();
let s = gen_selection(4, shape.clone(), 0).new_tree(&mut runner).unwrap().current();
let tree = collect_commactor_routing_tree(&s, &slice);
let mut preds: HashMap<usize, HashSet<usize>> = HashMap::new();
for (from, frames) in &tree.forwards {
for frame in frames {
let to = slice.location(&frame.here).unwrap();
prop_assert_ne!(
*from, to,
"CommActor forwarded to itself: {} → {} (selection: {})",
from, to, s
);
preds.entry(to).or_default().insert(*from);
}
}
for (node, parents) in preds {
let non_self_preds: Vec<_> = parents.into_iter()
.filter(|&p| p != node)
.collect();
prop_assert!(
non_self_preds.len() <= 1,
"Node {} had multiple non-self predecessors: {:?} (selection: {})",
node,
non_self_preds,
s,
);
}
}
}
}