Files
RustAst/src/ast/rtl/streams/tests.rs
T
Brummel 3628802fc8 Refactor streams and hooks logic
This commit reorganizes the stream-related logic by splitting the
`streams.rs` file into smaller, more manageable modules: `nodes.rs`,
`hooks.rs`, and `register.rs`.

The `nodes.rs` module now contains the core stream implementations like
`RootStream`, `PipeStream`, and the associated observer patterns.

The `hooks.rs` module encapsulates the compiler hook logic for `pipe`
and `pipe-series`, including type resolution and argument hinting. It
also includes helper functions for building pipe executors and
extracting stream information.

The `register.rs` module handles the registration of stream-related
built-in functions (`create-random-ohlc`, `create-ticker`, `pipe`,
`pipe-series`) within the environment.

This refactoring improves code organization, maintainability, and
testability by separating concerns into dedicated modules.
2026-03-30 15:06:02 +02:00

150 lines
4.5 KiB
Rust

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<dyn SeriesStorage> =
Rc::new(ScalarSeries::<f64>::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<PipeFn> = 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
);
}