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}