Skip to main content

rustledger_plugin/
test_helpers.rs

1//! Helpers shared between in-crate tests and integration tests.
2//!
3//! These are kept in a public module (rather than `#[cfg(test)]`) so
4//! the `tests/` integration tests can reach them without duplicating
5//! materialization logic.
6
7use crate::types::{DirectiveWrapper, PluginOp, PluginOutput};
8
9/// Materialize a plugin's `ops` against its input directive list,
10/// producing the resulting flat list of wrappers.
11///
12/// Used by tests that want to inspect a plugin's effective output
13/// without going through the loader's `apply_plugin_ops`. The mapping
14/// is:
15/// - `Keep(i)` → `input[i].clone()`
16/// - `Modify(_, w)` and `Insert(w)` → `w.clone()`
17/// - `Delete(_)` → omitted
18///
19/// Unlike the loader's `apply_plugin_ops` — which emits a plugin
20/// error and bails on protocol violations — this helper panics in
21/// debug builds if the ops set isn't a complete partition over
22/// `input` (each input index appears exactly once across Keep /
23/// Modify / Delete). The assert is debug-only so release builds and
24/// fuzz targets don't pay for it; it's there to make plugin-author
25/// mistakes loud in unit tests instead of silently producing
26/// surprising materialization.
27#[must_use]
28pub fn materialize_ops(input: &[DirectiveWrapper], output: &PluginOutput) -> Vec<DirectiveWrapper> {
29    #[cfg(debug_assertions)]
30    {
31        let n = input.len();
32        let mut seen = vec![false; n];
33        for op in &output.ops {
34            let idx = match op {
35                PluginOp::Keep(i) | PluginOp::Modify(i, _) | PluginOp::Delete(i) => Some(*i),
36                PluginOp::Insert(_) => None,
37            };
38            if let Some(i) = idx {
39                assert!(
40                    i < n,
41                    "materialize_ops: out-of-bounds index {i} (input len {n})"
42                );
43                assert!(
44                    !seen[i],
45                    "materialize_ops: index {i} referenced more than once"
46                );
47                seen[i] = true;
48            }
49        }
50        for (i, was_seen) in seen.iter().enumerate() {
51            assert!(
52                *was_seen,
53                "materialize_ops: input index {i} not accounted for (must be Keep/Modify/Delete)"
54            );
55        }
56    }
57
58    let mut out = Vec::with_capacity(output.ops.len());
59    for op in &output.ops {
60        match op {
61            PluginOp::Keep(i) => {
62                if let Some(w) = input.get(*i) {
63                    out.push(w.clone());
64                }
65            }
66            PluginOp::Modify(_, w) | PluginOp::Insert(w) => out.push(w.clone()),
67            PluginOp::Delete(_) => {}
68        }
69    }
70    out
71}