flowrs-engine/crates/flowrs-core/tests/taint.rs
flowRS fd958c0547 flowRS engine, cut from flowRS 3451b065
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 19:04:39 -05:00

716 lines
25 KiB
Rust

//! Taint tracking through the executor (`graph::taint`): every way a value can travel from
//! a neural source to another node is followed, and an agent's run (`withhold`) computes on
//! zeros in the recording's shape, so nothing it reports, logs or prints depends on a
//! sample.
//!
//! The source is a live biosignal stream whose every sample is a distinctive value
//! (4321.333... plus a per-sample offset), and each test tries one way an agent could get a
//! sample out: index one, take its minimum, do arithmetic on it, compare it with a
//! threshold, average a window of one, format it into text or print it, let a widget compute
//! on it, steer a loop by it, pass it through the program scope, a written file, a held wire
//! or an in-place write-back.
use flowrs_core::graph::runtime::Runtime;
use flowrs_core::graph::taint::{self, LOGGED_WITHHELD, Taint};
use flowrs_core::graph::{Graph, GraphExecutor, NodeId, WidgetRegistry};
use flowrs_core::{Value, WireMode};
use std::collections::HashMap;
use std::sync::Arc;
/// A 4-channel synthetic EEG stream, every sample replaced by a value no computation on
/// zeros can produce: 4321.333 + channel + sample / 1024.
fn stream() -> (flowrs_live::LiveStream, Vec<f64>) {
use flowrs_live::biosignal::{Out, Source, stream_for, synthetic};
let mut syn = synthetic::Synthetic::new(synthetic::Config {
channels: 4,
..synthetic::Config::default()
});
let meta = syn.meta().clone();
let mut st = stream_for(&meta, "synthetic EEG");
let mut out = Out::default();
syn.take(64, 0.0, &mut out);
out.apply(&meta, &mut st);
let mut values = Vec::new();
for (c, x) in st.series.iter_mut().enumerate() {
for (i, v) in x.v.iter_mut().enumerate() {
*v = 4321.0 + c as f64 + i as f64 / 1024.0 + 1.0 / 3.0;
values.push(*v);
}
}
(st, values)
}
fn runtime() -> (Arc<Runtime>, Vec<f64>) {
let rt = Runtime::new();
let (st, values) = stream();
rt.put_live("bio", st);
assert!(
rt.is_neural("live", "bio"),
"an EEG stream is marked neural"
);
(rt, values)
}
struct G {
g: Graph,
}
impl G {
fn new() -> Self {
G { g: Graph::new() }
}
fn node(&mut self, ty: &str, props: &[(&str, &str)]) -> NodeId {
let n = self.g.add_node(ty, 0.0, 0.0);
for (k, v) in props {
self.g
.nodes
.get_mut(&n)
.unwrap()
.properties
.insert((*k).into(), (*v).into());
}
n
}
fn wire(&mut self, a: NodeId, ap: &str, b: NodeId, bp: &str) {
self.g.add_wire(a, ap, b, bp, WireMode::Copy).unwrap();
}
/// The biosignal source: channel C3 on `signal`.
fn source(&mut self) -> NodeId {
self.node("live.biosignal", &[("stream", "bio"), ("channel", "C3")])
}
fn int(&mut self, v: i64) -> NodeId {
self.node("IntLiteral", &[("value", &v.to_string())])
}
fn float(&mut self, v: f64) -> NodeId {
self.node("FloatLiteral", &[("value", &v.to_string())])
}
}
/// Run `g` for the person (every value) and for an agent (`withhold`), each on `rt`.
fn both(g: &Graph, rt: &Arc<Runtime>) -> (Run, Run) {
let person = run(g, rt, false);
let agent = run(g, rt, true);
(person, agent)
}
struct Run {
outputs: HashMap<(NodeId, String), Value>,
taint: Taint,
console: String,
log: String,
}
fn run(g: &Graph, rt: &Arc<Runtime>, withhold: bool) -> Run {
let mut ex =
GraphExecutor::with_runtime(WidgetRegistry::with_builtins(), rt.clone()).withhold(withhold);
let before = rt.log().events.len();
let steps = ex.run(g).expect("runs");
let mut outputs = HashMap::new();
for s in steps {
for (p, v) in s.outputs {
outputs.insert((s.node_id, p), v);
}
}
let log = format!("{:?}", &rt.log().events[before..]);
Run {
outputs,
taint: ex.taint().clone(),
console: ex.console().to_string(),
log,
}
}
/// Every rendering of a recorded sample (and of the sample plus or minus one, times two,
/// and of a comparison's threshold side) that must not appear in what an agent run shows.
fn assert_no_sample(text: &str, values: &[f64], what: &str) {
for x in values {
for y in [*x, x + 1.0, x * 2.0] {
let r = format!("{y}");
assert!(!text.contains(&r), "{what}: carries {r}");
}
}
}
/// The port carries neural data in both runs, the person sees the real value, and the
/// agent run's value is the zero-world one (never a sample) and is logged as withheld.
fn assert_withheld(
person: &Run,
agent: &Run,
node: NodeId,
port: &str,
values: &[f64],
what: &str,
) {
assert!(
person.taint.port(node, port),
"{what}: tainted for the person"
);
assert!(
agent.taint.port(node, port),
"{what}: tainted for the agent"
);
let key = (node, port.to_string());
assert!(
person.outputs.contains_key(&key),
"{what}: the person's run computed it"
);
if let Some(v) = agent.outputs.get(&key) {
assert_no_sample(&format!("{v:?}"), values, what);
}
assert_no_sample(&agent.log, values, &format!("{what} (log)"));
assert!(
agent.log.contains(LOGGED_WITHHELD),
"{what}: logged as withheld"
);
}
#[test]
fn indexing_one_sample() {
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let i = g.int(5);
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
g.wire(i, "value", get, "index");
let (p, a) = both(&g.g, &rt);
assert_withheld(&p, &a, get, "item", &values, "VecGet");
// The person reads the real sample.
assert!(matches!(p.outputs[&(get, "item".into())], Value::Float(x) if x > 4321.0));
// The index itself is not neural.
assert!(!a.taint.port(i, "value"));
assert!(!a.taint.node(i));
}
#[test]
fn minimum_and_maximum() {
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let lo = g.node("Min", &[]);
let hi = g.node("Max", &[]);
g.wire(src, "signal", lo, "input");
g.wire(src, "signal", hi, "input");
let (p, a) = both(&g.g, &rt);
assert_withheld(&p, &a, lo, "result", &values, "Min");
assert_withheld(&p, &a, hi, "result", &values, "Max");
}
#[test]
fn arithmetic_on_one_sample() {
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let one = g.float(1.0);
let add = g.node("Add", &[]);
g.wire(get, "item", add, "a");
g.wire(one, "value", add, "b");
let (p, a) = both(&g.g, &rt);
assert_withheld(&p, &a, add, "result", &values, "Add");
}
#[test]
fn a_comparison_used_as_an_oracle() {
// `sample > t`, bisected over t, reads a sample one bit per run. The bit is tainted,
// and so is everything it selects.
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let t = g.float(4321.5);
let gt = g.node("GreaterThan", &[]);
g.wire(get, "item", gt, "a");
g.wire(t, "value", gt, "b");
let yes = g.int(1);
let no = g.int(0);
let pick = g.node("IfThenElse", &[]);
g.wire(gt, "result", pick, "condition");
g.wire(yes, "value", pick, "then_value");
g.wire(no, "value", pick, "else_value");
let (p, a) = both(&g.g, &rt);
assert_withheld(&p, &a, gt, "result", &values, "GreaterThan");
assert_withheld(&p, &a, pick, "result", &values, "IfThenElse");
// For the agent the answer does not depend on the threshold or the data: the source
// is zeros, so `0 > t` is false whatever t is.
assert!(matches!(
a.outputs[&(gt, "result".into())],
Value::Bool(false)
));
}
#[test]
fn statistics_on_a_window_of_one() {
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let v = g.node("VecNew", &[]);
let push = g.node("VecPush", &[]);
g.wire(v, "vec", push, "vec");
g.wire(get, "item", push, "item");
let mean = g.node("Mean", &[]);
g.wire(push, "vec", mean, "input");
let (p, a) = both(&g.g, &rt);
assert_withheld(&p, &a, mean, "result", &values, "Mean of one");
// The person's mean of one sample is that sample.
assert!(matches!(p.outputs[&(mean, "result".into())], Value::Float(x) if x > 4321.0));
}
#[test]
fn string_formatting_and_the_console() {
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let print = g.node("PrintLn", &[]);
g.wire(get, "item", print, "input");
let hello = g.node("StringLiteral", &[("value", "hello")]);
let print2 = g.node("PrintLn", &[]);
g.wire(hello, "value", print2, "input");
let (p, a) = both(&g.g, &rt);
// The person's console has the sample; the agent's console shows the untainted line
// and one fixed marker, never how much was printed or what.
assert!(p.console.contains("4323."), "{}", p.console);
let shown = a.taint.console_for_agent();
assert!(shown.contains("hello"), "{shown}");
assert!(shown.contains("withheld"), "{shown}");
assert_no_sample(&shown, &values, "agent console");
assert!(a.taint.node(print));
assert!(!a.taint.node(print2));
assert_no_sample(&a.log, &values, "log");
}
#[test]
fn a_widgets_output() {
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let two = g.float(2.0);
let mul = g.node("math.mul", &[]);
g.wire(get, "item", mul, "a");
g.wire(two, "value", mul, "b");
let (p, a) = both(&g.g, &rt);
assert_withheld(&p, &a, mul, "product", &values, "math.mul");
}
#[test]
fn a_loop_steered_by_neural_data_taints_its_body() {
// `for x in channel { v.push(1) }`: what the body made depends on how many passes the
// samples drove, and a body node's `break` could have depended on one, so the whole
// body is tainted.
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let lp = g.node("ForLoop", &[]);
g.wire(src, "signal", lp, "iterator");
let v = g.node("VecNew", &[]);
let push = g.node("VecPush", &[]);
g.wire(v, "vec", push, "vec");
let one = g.int(1);
g.wire(one, "value", push, "item");
g.wire(push, "vec", lp, "body");
let owners = flowrs_core::graph::loops::body_owners(&g.g);
assert_eq!(owners.get(&push), Some(&lp), "push is the loop's body");
let (p, a) = both(&g.g, &rt);
assert!(
p.taint.node(lp) && a.taint.node(lp),
"the loop ran on neural data"
);
assert!(a.taint.node(push), "its body ran on neural data");
assert!(a.taint.port(push, "vec"));
assert_no_sample(&a.log, &values, "log");
}
#[test]
fn a_body_node_on_neural_data_taints_its_loop() {
// A loop over literals whose body reads a sample: `break` may depend on it, so the
// loop's result and every body node are tainted.
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let items = g.node("Linspace", &[]);
let lp = g.node("LoopNode", &[]);
g.wire(items, "values", lp, "iterator");
let t = g.float(4321.5);
let gt = g.node("GreaterThan", &[]);
g.wire(get, "item", gt, "a");
g.wire(t, "value", gt, "b");
g.wire(gt, "result", lp, "body");
let (_, a) = both(&g.g, &rt);
let owners = flowrs_core::graph::loops::body_owners(&g.g);
assert_eq!(
owners.get(&gt),
Some(&lp),
"the comparison is the loop's body"
);
assert!(a.taint.node(lp), "the loop is tainted by its body");
assert!(a.taint.port(lp, "result"));
// So is every node of the body, those on no neural wire too: what each made, and
// how often, is the passes the samples allowed.
assert_eq!(
owners.get(&t),
Some(&lp),
"the threshold is computed in the body"
);
assert!(
a.taint.node(t) && a.taint.port(t, "value"),
"a body literal is tainted"
);
assert_no_sample(&a.log, &values, "log");
}
#[test]
fn the_program_scope() {
// A code block binds a sample as `input` in the program's scope; a later code block
// wired to nothing neural prints it.
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let write = g.node("CodeBlock", &[("src", "input + 0.0")]);
g.wire(get, "item", write, "inputs");
let read = g.node("CodeBlockNode", &[("src", "println!(\"{}\", input);")]);
let (p, a) = both(&g.g, &rt);
assert!(a.taint.node(write));
assert!(a.taint.node(read), "the scope carried the sample");
assert!(
p.console.contains("4323."),
"the person's program printed it: {}",
p.console
);
let shown = a.taint.console_for_agent();
assert_no_sample(&shown, &values, "agent console");
assert_no_sample(&a.log, &values, "log");
}
#[test]
fn a_file_the_run_wrote() {
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let fmt = g.node("CodeBlock", &[("src", "format!(\"{}\", input)")]);
g.wire(get, "item", fmt, "inputs");
let name = g.node("StringLiteral", &[("value", "leak.txt")]);
let write = g.node("WriteFile", &[]);
g.wire(name, "value", write, "path");
g.wire(fmt, "outputs", write, "contents");
let name2 = g.node("StringLiteral", &[("value", "leak.txt")]);
let read = g.node("ReadFile", &[]);
g.wire(name2, "value", read, "path");
// A widget that loads the file the run wrote: no wire ties it to the recording, and
// it is not one of the primitives that share the run's state, so only the run's note
// of which files it wrote from neural data taints it.
let fmt_csv = g.node("CodeBlock", &[("src", "format!(\"v\\n{}\\n\", input)")]);
g.wire(get, "item", fmt_csv, "inputs");
let csv_name = g.node("StringLiteral", &[("value", "leak.csv")]);
let write_csv = g.node("WriteFile", &[]);
g.wire(csv_name, "value", write_csv, "path");
g.wire(fmt_csv, "outputs", write_csv, "contents");
let load = g.node("data.csv.load", &[("path", "leak.csv")]);
let (p, a) = both(&g.g, &rt);
assert!(p.taint.node(write) && a.taint.node(write));
assert!(
p.outputs.contains_key(&(load, "data".to_string())),
"the loader ran after the write"
);
assert_withheld(&p, &a, load, "data", &values, "data.csv.load");
if p.outputs.contains_key(&(read, "contents".to_string())) {
assert!(
p.taint.node(read),
"reading what was written from neural data"
);
assert_withheld(&p, &a, read, "contents", &values, "ReadFile");
}
assert_no_sample(&a.log, &values, "log");
}
#[test]
fn a_held_wire_carries_taint_to_the_next_step() {
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let add = g.node("Add", &[]);
g.wire(get, "item", add, "a");
let fb =
g.g.add_wire(add, "result", add, "b", WireMode::Copy)
.unwrap();
g.g.wires.get_mut(&fb).unwrap().feedback = true;
// The person steps first, then an agent on the same runtime: what the person's step
// held is real, and the agent's step reads it as zeros.
for withhold in [false, true] {
let mut ex = GraphExecutor::with_runtime(WidgetRegistry::with_builtins(), rt.clone())
.withhold(withhold);
ex.step(&g.g).unwrap();
let key = flowrs_core::graph::held::key(&g.g.wires[&fb]);
assert!(rt.is_neural("held", &key), "the held value is marked");
ex.step(&g.g).unwrap();
assert!(ex.taint().port(add, "result"));
if withhold {
assert_no_sample(&format!("{:?}", rt.held.get(&key)), &values, "held value");
}
}
rt.clear("held");
}
#[test]
fn an_in_place_write_back_reaches_the_bindings_other_readers() {
// `v.push(sample)` then `v.len()` through the wire `v` came on: the length is read from
// the binding the sample went into.
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let v = g.node("VecNew", &[]);
let push = g.node("VecPush", &[]);
g.wire(v, "vec", push, "vec");
g.wire(get, "item", push, "item");
let len = g.node("VecLen", &[]);
g.wire(v, "vec", len, "vec");
let (p, a) = both(&g.g, &rt);
assert!(p.taint.port(len, "length") && a.taint.port(len, "length"));
assert_no_sample(&a.log, &values, "log");
}
#[test]
fn an_agent_run_computes_on_zeros_in_the_recorded_shape() {
let (rt, values) = runtime();
let mut g = G::new();
let src = g.source();
let (p, a) = both(&g.g, &rt);
let real = &p.outputs[&(src, "signal".into())];
let zero = &a.outputs[&(src, "signal".into())];
assert_eq!(format!("{:?}", taint::zeroed(real)), format!("{zero:?}"));
assert_no_sample(&format!("{zero:?}"), &values, "zeroed signal");
// The same length: the shape is the recording's.
let len = |v: &Value| match v {
Value::Vec(x) => x.len(),
_ => 0,
};
assert_eq!(len(real), len(zero));
assert!(len(real) > 0);
// Every kind of value a source can hand on comes out as zeros of its own shape.
let ts = flowrs_schemas::Timeseries {
sample_rate: 250.0,
t0: 0.0,
channels: vec!["Cz".into(), "Pz".into()],
units: vec!["uV".into(), "uV".into()],
data: vec![vec![values[0], values[1]], vec![values[2], values[3]]],
events: vec![],
epochs: vec![],
};
let Value::Timeseries(z) = taint::zeroed(&Value::Timeseries(std::sync::Arc::new(ts))) else {
panic!("a timeseries stays a timeseries");
};
assert_eq!(z.data, vec![vec![0.0, 0.0], vec![0.0, 0.0]]);
assert_eq!((z.sample_rate, z.channels.len()), (250.0, 2));
assert!(matches!(taint::zeroed(&Value::Float(values[0])), Value::Float(x) if x == 0.0));
assert!(matches!(taint::zeroed(&Value::Int(7)), Value::Int(0)));
// The header's ports are not tainted and keep their values.
for port in ["rate", "channels", "units"] {
if let Some(v) = p.outputs.get(&(src, port.into())) {
assert!(!a.taint.port(src, port), "{port} is metadata");
assert_eq!(
Some(format!("{v:?}")),
a.outputs.get(&(src, port.into())).map(|x| format!("{x:?}")),
"{port}"
);
}
}
assert!(a.taint.sources.contains(&src));
assert!(a.taint.read.contains(&("live".into(), "bio".into())));
}
#[test]
fn a_graph_free_of_neural_data_is_untouched() {
let (rt, _) = runtime();
let mut g = G::new();
let x = g.float(2.5);
let y = g.float(4.0);
let add = g.node("Add", &[]);
g.wire(x, "value", add, "a");
g.wire(y, "value", add, "b");
let (p, a) = both(&g.g, &rt);
assert!(!p.taint.any() && !a.taint.any());
assert!(matches!(a.outputs[&(add, "result".into())], Value::Float(x) if x == 6.5));
assert!(!a.log.contains(LOGGED_WITHHELD));
}
#[test]
fn a_file_is_neural_by_its_format_or_its_owners_word() {
let rt = Runtime::new();
let mut edf = b"0 ".to_vec();
edf.resize(256, b' ');
rt.put_file("x.bin", edf);
assert!(rt.is_neural("file", "x.bin"), "EDF by content");
rt.put_file("notes.txt", b"hello".to_vec());
assert!(!rt.is_neural("file", "notes.txt"));
rt.put_file_neural("export.csv", b"t,Cz\n0,1.5\n".to_vec());
assert!(rt.is_neural("file", "export.csv"), "the owner's word");
rt.put_file("r.dat", vec![1, 2, 3]);
assert!(
!rt.is_neural("file", "r.dat"),
"a .dat alone is not a recording"
);
rt.put_file("r.hea", b"r 1 250 3\n".to_vec());
assert!(rt.is_neural("file", "r.dat"), "with its WFDB header it is");
for name in [
"a.edf", "a.bdf", "a.xdf", "a.vhdr", "a.eeg", "a.nwb", "a.snirf", "a.set",
] {
assert!(taint::neural_file(name, b""), "{name}");
}
assert!(taint::neural_file("x", b"XDF:...."));
assert!(taint::neural_file("x", b"\xffBIOSEMI..."));
assert!(taint::neural_file(
"x",
b"Brain Vision Data Exchange Header File Version 1.0"
));
// A replay applies the owner's word too.
let log = rt.log();
let rt2 = Runtime::new();
let g = Graph::new();
let _ = flowrs_core::graph::runtime::replay(&log, &g, WidgetRegistry::with_builtins());
let _ = rt2;
}
#[test]
fn the_cone_and_its_digest() {
let mut g = G::new();
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let other = g.float(1.0);
let cone = taint::cone(&g.g, get);
assert!(cone.contains(&src) && cone.contains(&get) && !cone.contains(&other));
let d1 = taint::cone_digest(&g.g, &cone);
// Editing anything upstream changes it; editing elsewhere does not.
g.g.nodes
.get_mut(&other)
.unwrap()
.properties
.insert("value".into(), "2".into());
assert_eq!(d1, taint::cone_digest(&g.g, &cone));
g.g.nodes
.get_mut(&src)
.unwrap()
.properties
.insert("channel".into(), "C4".into());
assert_ne!(d1, taint::cone_digest(&g.g, &cone));
}
/// A reader of recordings that takes an input, as a future source may (`neuro.open` and
/// `live.biosignal` take none today): its header ports stay open only while it is tainted
/// for reading alone. Handed a value derived from neural data, its header could be
/// anything that value chose, so it is tainted like every other output.
struct HeaderSource;
impl flowrs_core::graph::widget::Widget for HeaderSource {
fn id(&self) -> String {
"test.header_source".into()
}
fn name(&self) -> String {
self.id()
}
fn category(&self) -> String {
"Test".into()
}
fn inputs(&self) -> Vec<flowrs_core::graph::widget::PortSpec> {
vec![flowrs_core::graph::widget::PortSpec {
name: "pick".into(),
semantic_type: "f64".into(),
variadic: false,
}]
}
fn outputs(&self) -> Vec<flowrs_core::graph::widget::PortSpec> {
["signal", "channels"]
.into_iter()
.map(|n| flowrs_core::graph::widget::PortSpec {
name: n.into(),
semantic_type: "f64".into(),
variadic: false,
})
.collect()
}
fn parameters(&self) -> Vec<flowrs_core::graph::widget::ParameterDef> {
vec![]
}
fn visual(&self) -> flowrs_core::graph::widget::WidgetVisual {
flowrs_core::graph::widget::WidgetVisual {
icon: String::new(),
color: "#000".into(),
min_size: (1, 1),
resizable: false,
}
}
fn codegen(
&self,
_: &flowrs_core::graph::widget::CodegenContext,
_: HashMap<String, String>,
) -> flowrs_core::graph::widget::WidgetCodegenResult {
flowrs_core::graph::widget::WidgetCodegenResult {
outputs: HashMap::new(),
imports: vec![],
init: None,
process: String::new(),
cleanup: None,
state: vec![],
}
}
fn reads_neural(&self) -> bool {
true
}
fn metadata_ports(&self) -> &'static [&'static str] {
&["channels"]
}
fn execute(
&self,
_: &flowrs_core::graph::widget::ExecContext,
inputs: HashMap<String, Value>,
) -> HashMap<String, Value> {
let pick = inputs.get("pick").cloned().unwrap_or(Value::Float(0.0));
HashMap::from([
("signal".to_string(), Value::Float(1.0)),
("channels".to_string(), pick),
])
}
}
#[test]
fn a_header_port_is_open_only_for_a_source_tainted_by_reading_alone() {
let (rt, values) = runtime();
let mut registry = WidgetRegistry::with_builtins();
registry.register(Box::new(HeaderSource));
let mut g = G::new();
let alone = g.node("test.header_source", &[]);
let src = g.source();
let get = g.node("VecGet", &[]);
g.wire(src, "signal", get, "vec");
let fed = g.node("test.header_source", &[]);
g.wire(get, "item", fed, "pick");
let mut ex = GraphExecutor::with_runtime(registry, rt.clone()).withhold(true);
ex.run(&g.g).expect("runs");
let t = ex.taint().clone();
assert!(
t.port(alone, "signal") && !t.port(alone, "channels"),
"reading alone"
);
assert!(
t.port(fed, "signal") && t.port(fed, "channels"),
"handed a sample, its header is the sample's"
);
assert_no_sample(&format!("{:?}", t), &values, "taint");
}