Skip to main content

weir/test_harness/
csr_factory.rs

1//! Reusable CSR fixtures for Weir dataflow tests.
2
3use std::collections::{HashSet, VecDeque};
4
5use rand::{Rng, SeedableRng};
6
7#[derive(Clone, Debug)]
8pub struct CsrGraphFixture {
9    pub node_count: u32,
10    pub edge_offsets: Vec<u32>,
11    pub edge_targets: Vec<u32>,
12    pub edge_kind_mask: Vec<u32>,
13    pub expected_reachability: Vec<HashSet<u32>>,
14}
15
16pub struct CsrFactory;
17
18impl CsrFactory {
19    pub fn random_valid(node_count: u32, edge_probability: f64, seed: u64) -> CsrGraphFixture {
20        let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
21        let probability = edge_probability.clamp(0.0, 1.0);
22        let mut adjacency = vec![Vec::new(); node_count as usize];
23        for src in 0..node_count {
24            for dst in 0..node_count {
25                if rng.random::<f64>() < probability {
26                    adjacency[src as usize].push(dst);
27                }
28            }
29            adjacency[src as usize].sort_unstable();
30            adjacency[src as usize].dedup();
31        }
32        fixture_from_adjacency(adjacency)
33    }
34
35    pub fn adversarial_empty_rows() -> CsrGraphFixture {
36        fixture_from_adjacency(vec![vec![1], Vec::new(), vec![0]])
37    }
38
39    pub fn adversarial_non_monotonic_offsets() -> CsrGraphFixture {
40        CsrGraphFixture {
41            node_count: 3,
42            edge_offsets: vec![0, 2, 1, 3],
43            edge_targets: vec![1, 2, 0],
44            edge_kind_mask: vec![u32::MAX; 3],
45            expected_reachability: Vec::new(),
46        }
47    }
48
49    pub fn adversarial_oob_targets() -> CsrGraphFixture {
50        CsrGraphFixture {
51            node_count: 3,
52            edge_offsets: vec![0, 1, 2, 3],
53            edge_targets: vec![1, 99, 0],
54            edge_kind_mask: vec![u32::MAX; 3],
55            expected_reachability: Vec::new(),
56        }
57    }
58
59    pub fn adversarial_terminal_mismatch() -> CsrGraphFixture {
60        CsrGraphFixture {
61            node_count: 3,
62            edge_offsets: vec![0, 1, 2, 4],
63            edge_targets: vec![1, 2, 0],
64            edge_kind_mask: vec![u32::MAX; 3],
65            expected_reachability: Vec::new(),
66        }
67    }
68}
69
70fn fixture_from_adjacency(adjacency: Vec<Vec<u32>>) -> CsrGraphFixture {
71    let node_count = adjacency.len() as u32;
72    let mut edge_offsets = Vec::with_capacity(adjacency.len() + 1);
73    let mut edge_targets = Vec::new();
74    edge_offsets.push(0);
75    for targets in &adjacency {
76        edge_targets.extend(targets.iter().copied());
77        edge_offsets.push(edge_targets.len() as u32);
78    }
79    let edge_kind_mask = vec![u32::MAX; edge_targets.len()];
80    let expected_reachability = reachability_sets(&adjacency);
81    CsrGraphFixture {
82        node_count,
83        edge_offsets,
84        edge_targets,
85        edge_kind_mask,
86        expected_reachability,
87    }
88}
89
90fn reachability_sets(adjacency: &[Vec<u32>]) -> Vec<HashSet<u32>> {
91    let mut out = Vec::with_capacity(adjacency.len());
92    for start in 0..adjacency.len() as u32 {
93        let mut seen = HashSet::new();
94        let mut queue = VecDeque::new();
95        seen.insert(start);
96        queue.push_back(start);
97        while let Some(node) = queue.pop_front() {
98            let Some(targets) = adjacency.get(node as usize) else {
99                continue;
100            };
101            for &target in targets {
102                if (target as usize) < adjacency.len() && seen.insert(target) {
103                    queue.push_back(target);
104                }
105            }
106        }
107        out.push(seen);
108    }
109    out
110}