Two behaviour-preserving tidies from the post-0013 fieldtest/backlog. #29: aura-engine re-exports Firing/Scalar/ScalarKind/Timestamp at the crate root. A Blueprint builder needs ScalarKind for SourceSpec.kind and Scalar/ Firing/Timestamp for sources & Recorder columns, but aura-engine previously re-exported only the wiring types — forcing a second aura-core import for the vocabulary its own public structs (SourceSpec.kind, ...) demand. The fieldtest flagged this paper cut (mc_4). One import surface now. Guarded by a new reexport_tests module. #14: the two near-identical #[cfg(test)] Recorder fixtures in report.rs and harness.rs are deleted in favour of the shipped aura-std::Recorder (already a dev-dependency). Constructor signature, schema, and try_iter() drain are identical, so no call site changed — only the struct/impl deletion plus adding Recorder to each file's existing aura_std import. report.rs additionally drops four imports (Ctx/InputSpec/Node/NodeSchema) that only the deleted fixture used. The separate TapForward fixture in harness.rs is untouched. Verified: cargo build/test/clippy --workspace all green (49 aura-engine lib tests incl. the new one); bit-identical, golden-snapshot, SMA-disambiguation, and mixed-kind recording tests all stay green — behaviour preserved. closes #29 closes #14
This commit is contained in:
@@ -337,7 +337,7 @@ mod tests {
|
||||
// only reads `nd.schema()` fields, never naming the types), so they are not
|
||||
// brought in by `use super::*` — the fixtures construct them, so import here.
|
||||
use aura_core::{FieldSpec, InputSpec, NodeSchema};
|
||||
use aura_std::{Exposure, Sma, SimBroker, Sub};
|
||||
use aura_std::{Exposure, Recorder, Sma, SimBroker, Sub};
|
||||
use std::sync::mpsc;
|
||||
|
||||
/// Build an f64 source stream from (timestamp, value) points.
|
||||
@@ -486,78 +486,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A recording node (test-local fixture; stands in for a downstream author's
|
||||
/// chart/registry sink). It declares typed input slots and holds an
|
||||
/// `mpsc::Sender`; on every fired cycle it reads the newest of each input plus
|
||||
/// `ctx.now()`, sends the timestamped record out of the graph, and returns
|
||||
/// `None` (pure consumer — C8). Read-back is via the channel, never `Rc`/
|
||||
/// `RefCell`, so `aura-engine/src` stays free of the interior-mutability the
|
||||
/// purity invariant (C7) forbids.
|
||||
struct Recorder {
|
||||
kinds: Vec<ScalarKind>,
|
||||
firing: Firing,
|
||||
tx: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
||||
}
|
||||
impl Recorder {
|
||||
fn new(
|
||||
kinds: &[ScalarKind],
|
||||
firing: Firing,
|
||||
tx: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
||||
) -> Self {
|
||||
Self { kinds: kinds.to_vec(), firing, tx }
|
||||
}
|
||||
}
|
||||
impl Node for Recorder {
|
||||
fn schema(&self) -> NodeSchema {
|
||||
NodeSchema {
|
||||
inputs: self
|
||||
.kinds
|
||||
.iter()
|
||||
.map(|&kind| InputSpec { kind, lookback: 1, firing: self.firing })
|
||||
.collect(),
|
||||
output: vec![], // pure sink: no output port
|
||||
}
|
||||
}
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Scalar]> {
|
||||
let mut row = Vec::with_capacity(self.kinds.len());
|
||||
for (i, &kind) in self.kinds.iter().enumerate() {
|
||||
let v = match kind {
|
||||
ScalarKind::I64 => {
|
||||
let w = ctx.i64_in(i);
|
||||
if w.is_empty() {
|
||||
return None; // not yet warmed
|
||||
}
|
||||
Scalar::I64(w[0])
|
||||
}
|
||||
ScalarKind::F64 => {
|
||||
let w = ctx.f64_in(i);
|
||||
if w.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Scalar::F64(w[0])
|
||||
}
|
||||
ScalarKind::Bool => {
|
||||
let w = ctx.bool_in(i);
|
||||
if w.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Scalar::Bool(w[0])
|
||||
}
|
||||
ScalarKind::Timestamp => {
|
||||
let w = ctx.ts_in(i);
|
||||
if w.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Scalar::Ts(w[0])
|
||||
}
|
||||
};
|
||||
row.push(v);
|
||||
}
|
||||
let _ = self.tx.send((ctx.now(), row)); // out-of-graph side effect
|
||||
None // records, forwards nothing
|
||||
}
|
||||
}
|
||||
|
||||
/// A node that records AND forwards: it sends `(now, value)` out of the graph
|
||||
/// (sink side effect) and returns its value as a one-field output the engine
|
||||
/// forwards downstream (producer). Proves the C8 "both" role.
|
||||
|
||||
@@ -38,3 +38,22 @@ mod report;
|
||||
pub use blueprint::{Blueprint, BlueprintNode, CompileError, Composite, OutPort};
|
||||
pub use harness::{BootstrapError, Edge, Harness, SourceSpec, Target};
|
||||
pub use report::{f64_field, summarize, RunManifest, RunMetrics, RunReport};
|
||||
// #29: re-export the core scalar vocabulary a Blueprint builder needs
|
||||
// (SourceSpec.kind is a ScalarKind; sources/Recorder columns are Scalar /
|
||||
// Firing / Timestamp) so a graph builder has one import surface, not two.
|
||||
pub use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
|
||||
|
||||
#[cfg(test)]
|
||||
mod reexport_tests {
|
||||
// #29: the core scalar vocabulary a Blueprint builder needs is reachable
|
||||
// from aura-engine alone (crate::X == external aura_engine::X), so a
|
||||
// downstream author does not add a second aura-core import for ScalarKind.
|
||||
#[test]
|
||||
fn core_scalar_vocabulary_is_reexported_from_crate_root() {
|
||||
use crate::{Firing, Scalar, ScalarKind, Timestamp};
|
||||
let _k: ScalarKind = ScalarKind::F64;
|
||||
let _f: Firing = Firing::Any;
|
||||
let _s: Scalar = Scalar::F64(0.0);
|
||||
let _t: Timestamp = Timestamp(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +199,8 @@ pub fn f64_field(rows: &[(Timestamp, Vec<Scalar>)], field: usize) -> Vec<(Timest
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Edge, Harness, SourceSpec, Target};
|
||||
use aura_core::{Ctx, Firing, InputSpec, Node, NodeSchema, ScalarKind};
|
||||
use aura_std::{Exposure, SimBroker, Sma, Sub};
|
||||
use aura_core::{Firing, ScalarKind};
|
||||
use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub};
|
||||
use std::sync::mpsc;
|
||||
|
||||
/// Build an f64 source stream from (timestamp, value) points (mirrors the
|
||||
@@ -210,50 +210,6 @@ mod tests {
|
||||
points.iter().map(|&(t, v)| (Timestamp(t), Scalar::F64(v))).collect()
|
||||
}
|
||||
|
||||
/// A recording sink that sends `(now, row)` out of the graph each fired
|
||||
/// cycle and returns `None` (pure consumer, C8). Re-declared here because
|
||||
/// the identically-shaped fixture in `harness.rs` is private to that test
|
||||
/// module.
|
||||
struct Recorder {
|
||||
kinds: Vec<ScalarKind>,
|
||||
firing: Firing,
|
||||
tx: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
||||
}
|
||||
impl Recorder {
|
||||
fn new(
|
||||
kinds: &[ScalarKind],
|
||||
firing: Firing,
|
||||
tx: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
|
||||
) -> Self {
|
||||
Self { kinds: kinds.to_vec(), firing, tx }
|
||||
}
|
||||
}
|
||||
impl Node for Recorder {
|
||||
fn schema(&self) -> NodeSchema {
|
||||
NodeSchema {
|
||||
inputs: self
|
||||
.kinds
|
||||
.iter()
|
||||
.map(|&kind| InputSpec { kind, lookback: 1, firing: self.firing })
|
||||
.collect(),
|
||||
output: vec![],
|
||||
}
|
||||
}
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Scalar]> {
|
||||
// this test records only f64 streams (one f64 column per sink)
|
||||
let mut row = Vec::with_capacity(self.kinds.len());
|
||||
for i in 0..self.kinds.len() {
|
||||
let w = ctx.f64_in(i);
|
||||
if w.is_empty() {
|
||||
return None;
|
||||
}
|
||||
row.push(Scalar::F64(w[0]));
|
||||
}
|
||||
let _ = self.tx.send((ctx.now(), row));
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Bootstrap the cycle-0007 signal-quality harness with TWO sinks: one on
|
||||
/// the SimBroker equity output (node 4 -> node 5) and one on the Exposure
|
||||
/// output (node 3 -> node 6). Returns the harness plus the two receivers.
|
||||
|
||||
Reference in New Issue
Block a user