use super::*; use super::nodes::{PipeStream, RootStream}; use crate::ast::types::{PipeFn, Value}; #[test] fn test_root_to_pipe_flow() { let root = RootStream::new(); let pipe = Rc::new(RefCell::new(PipeStream::new( "test-pipe".to_string(), 1, None, ))); root.add_observer(pipe.clone()); // Cycle 1: Root ticks 10.0 root.tick(Value::Float(10.0)); let sig = pipe.borrow().current_signal().unwrap(); assert_eq!(sig.cycle_id, 1); if let Value::Float(v) = sig.value { assert_eq!(v, 10.0); } else { panic!("Value must be Float(10.0)"); } // Cycle 2: Root ticks 20.0 root.tick(Value::Float(20.0)); let sig2 = pipe.borrow().current_signal().unwrap(); assert_eq!(sig2.cycle_id, 2); if let Value::Float(v) = sig2.value { assert_eq!(v, 20.0); } else { panic!("Value must be Float(20.0)"); } } #[test] fn test_barrier_sync() { // Pipe with 2 inputs let pipe = Rc::new(RefCell::new(PipeStream::new( "barrier-pipe".to_string(), 2, None, ))); // Manual notifications simulate different input streams pipe.borrow_mut().notify(0, 1, Value::Float(10.0)); assert!( pipe.borrow().current_signal().is_none(), "Barrier should NOT be reached after 1st input" ); pipe.borrow_mut().notify(1, 1, Value::Float(20.0)); assert!( pipe.borrow().current_signal().is_some(), "Barrier SHOULD be reached after 2nd input" ); let sig = pipe.borrow().current_signal().unwrap(); assert_eq!(sig.cycle_id, 1); } /// Validates the wrapper-executor pattern for `pipe-series`: /// A closure wraps push + fill gate + user lambda, reusing standard PipeStream. #[test] fn test_buffered_pipe_fill_gate() { use crate::ast::rtl::series::data::ScalarSeries; use crate::ast::types::SeriesStorage; let root = RootStream::new(); // Internal series with lookback 3 (simulates what pipe-series creates) let series: Rc = Rc::new(ScalarSeries::::new("FloatSeries", 3)); let series_clone = Rc::clone(&series); // Wrapper-executor: push → fill gate → user lambda (s[0] + s[1]) let mut fill_gate_open = false; let lookback: usize = 3; let wrapper: Box = Box::new(move |args: &[Value]| { // 1. Push incoming value into internal series series_clone .as_pushable() .unwrap() .push_value(args[0].clone()); // 2. Fill gate: wait until series has enough data if !fill_gate_open { if series_clone.len() >= lookback { fill_gate_open = true; } else { return Value::Void; // Filtered by PipeStream::notify } } // 3. User lambda: s[0] + s[1] (two most recent values) let v0 = series_clone.get_item(0).unwrap(); let v1 = series_clone.get_item(1).unwrap(); if let (Value::Float(a), Value::Float(b)) = (&v0, &v1) { Value::Float(a + b) } else { Value::Void } }); // Wire up: RootStream → PipeStream with wrapper executor (manual wiring) let pipe = Rc::new(RefCell::new(PipeStream::new_typed( "buffered-test".to_string(), 1, Some(wrapper), StaticType::Float, ))); root.add_observer(pipe.clone()); // Tick 1: series has 1 element → fill gate closed → Void → no signal root.tick(Value::Float(10.0)); assert!( pipe.borrow().current_signal().is_none(), "Tick 1: fill gate should block (1 < 3)" ); // Tick 2: series has 2 elements → still blocked root.tick(Value::Float(20.0)); assert!( pipe.borrow().current_signal().is_none(), "Tick 2: fill gate should block (2 < 3)" ); // Tick 3: series has 3 elements → fill gate opens → s[0]+s[1] = 30+20 = 50 root.tick(Value::Float(30.0)); let sig3 = pipe.borrow().current_signal().unwrap(); assert_eq!(sig3.cycle_id, 3); assert!( matches!(sig3.value, Value::Float(v) if (v - 50.0).abs() < f64::EPSILON), "Tick 3: expected 50.0 (30+20), got {:?}", sig3.value ); // Tick 4: fill gate stays open → s[0]+s[1] = 40+30 = 70 root.tick(Value::Float(40.0)); let sig4 = pipe.borrow().current_signal().unwrap(); assert_eq!(sig4.cycle_id, 4); assert!( matches!(sig4.value, Value::Float(v) if (v - 70.0).abs() < f64::EPSILON), "Tick 4: expected 70.0 (40+30), got {:?}", sig4.value ); }