refactor(aura-engine): share the SMA-cross harness test fixtures

Collapse the three verbatim-duplicated #[cfg(test)] fixtures —
synthetic_prices, sma_cross, composite_sma_cross_harness — that lived
in both blueprint.rs's and sweep.rs's test modules into one shared
crate-root `#[cfg(test)] mod test_fixtures` (pub(crate) free fns). The
two consumers now import from `crate::test_fixtures`; sweep.rs's
test-mod `use` block sheds the imports that were only needed by the
moved fns.

Why: the topology had to move in lockstep across the two copies but no
test pinned them equal, so an edit to one silently diverged the other
(#53). One definition removes the hazard structurally — better than a
pin-them-equal test, which loses its premise once there is a single
definition. Test-only; no production code, public API, or runtime path
changes. C16 untouched (a #[cfg(test)] module ships in no artifact).

The CLI sample is no longer a third peer (cycle 0036 enriched it into a
trend+momentum blend), so this is an engine-internal two-copy dedup, not
the three-way the issue originally named. graph_model.rs keeps its own
intentionally-minimal golden copy, out of scope.

Plan glitch fixed in-flight: the plan's literal import list named
sma_cross, but neither consumer calls it directly (only transitively via
composite_sma_cross_harness), so importing it tripped the plan's own
Step-7 clippy -D warnings gate. Trimmed to {composite_sma_cross_harness,
synthetic_prices} — within the task's "drop now-unused imports" scope.

Verified: cargo test --workspace green (aura-engine 114 passed, count
unchanged — behaviour-preserving); the two load-bearing fixture
consumers composite_sma_cross_runs_bit_identical_to_hand_wired (C1) and
sweep_equals_n_independent_runs (C11/C12) both green; cargo clippy
--workspace --all-targets -- -D warnings clean.

closes #53
This commit is contained in:
2026-06-14 00:18:04 +02:00
parent 1718ba0f98
commit 1f01badac7
4 changed files with 94 additions and 154 deletions
+1 -74
View File
@@ -753,6 +753,7 @@ fn slot_kind(t: Target, flat_signatures: &[NodeSchema]) -> Result<ScalarKind, Co
#[cfg(test)]
mod tests {
use super::*;
use crate::test_fixtures::{composite_sma_cross_harness, synthetic_prices};
use aura_core::{Ctx, FieldSpec, Firing, NodeSchema, Timestamp};
use aura_std::{Ema, Exposure, Recorder, SimBroker, Sma, Sub};
use std::sync::mpsc;
@@ -1515,23 +1516,6 @@ mod tests {
}
}
/// The built-in synthetic price stream (a local copy of the CLI sample's
/// stream): rises through t=4 then reverses, so the trace is non-degenerate.
fn synthetic_prices() -> Vec<(Timestamp, Scalar)> {
[
(1_i64, 1.0000_f64),
(2, 1.0010),
(3, 1.0030),
(4, 1.0060),
(5, 1.0040),
(6, 1.0010),
(7, 0.9990),
]
.iter()
.map(|&(t, p)| (Timestamp(t), Scalar::F64(p)))
.collect()
}
/// Today's flat, hand-wired SMA-cross signal-quality harness (the
/// `sample_harness` wiring from `aura-cli`), with two recording sinks.
#[allow(clippy::type_complexity)]
@@ -1587,63 +1571,6 @@ mod tests {
(h, rx_eq, rx_ex)
}
/// The SMA-cross signal as a reusable composite: one input role (price), one
/// output (the fast-minus-slow spread). Interior wired with raw local indices.
/// Value-empty: the two SMA lengths are injected at compile, not baked here.
fn sma_cross() -> Composite {
Composite::new(
"sma_cross",
vec![
Sma::builder().named("fast").into(),
Sma::builder().named("slow").into(),
Sub::builder().into(),
],
vec![
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
],
vec![Role {
name: "price".into(),
targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }], source: None, }],
vec![OutField { node: 2, field: 0, name: "out".into() }],
)
}
/// The same signal-quality harness authored as a composite blueprint.
#[allow(clippy::type_complexity)]
fn composite_sma_cross_harness() -> (
Composite,
mpsc::Receiver<(Timestamp, Vec<Scalar>)>,
mpsc::Receiver<(Timestamp, Vec<Scalar>)>,
) {
let (tx_eq, rx_eq) = mpsc::channel();
let (tx_ex, rx_ex) = mpsc::channel();
let bp = Composite::new(
"root",
vec![
BlueprintNode::Composite(sma_cross()),
Exposure::builder().into(),
SimBroker::builder(0.0001).into(),
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(),
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(),
],
vec![
Edge { from: 0, to: 1, slot: 0, from_field: 0 }, // composite out -> Exposure
Edge { from: 1, to: 2, slot: 0, from_field: 0 }, // exposure -> broker slot 0
Edge { from: 2, to: 3, slot: 0, from_field: 0 }, // equity -> sink
Edge { from: 1, to: 4, slot: 0, from_field: 0 }, // exposure -> sink
],
vec![
Role { name: "src".into(), targets: vec![
Target { node: 0, slot: 0 }, // price -> sma_cross role 0
Target { node: 2, slot: 1 }, // price -> SimBroker price slot
], source: Some(ScalarKind::F64) },
],
vec![], // output
);
(bp, rx_eq, rx_ex)
}
#[test]
fn composite_sma_cross_runs_bit_identical_to_hand_wired() {
let prices = synthetic_prices();
+3
View File
@@ -50,6 +50,9 @@ pub use sweep::{sweep, GridSpace, SweepError, SweepFamily, SweepPoint};
// Firing / Timestamp) so a graph builder has one import surface, not two.
pub use aura_core::{Firing, NodeSchema, ParamSpec, PortSpec, Scalar, ScalarKind, Timestamp};
#[cfg(test)]
mod test_fixtures;
#[cfg(test)]
mod reexport_tests {
// #29: the core scalar vocabulary a Blueprint builder needs is reachable
+3 -80
View File
@@ -169,12 +169,9 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::{
f64_field, summarize, BlueprintNode, Composite, Edge, OutField, Role, RunManifest, Target,
};
use aura_core::{Firing, ParamSpec, Scalar, ScalarKind, Timestamp};
use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub};
use std::sync::mpsc;
use crate::test_fixtures::{composite_sma_cross_harness, synthetic_prices};
use crate::{f64_field, summarize, RunManifest};
use aura_core::{ParamSpec, Scalar, ScalarKind, Timestamp};
fn i64_space(n: usize) -> Vec<ParamSpec> {
(0..n)
@@ -253,80 +250,6 @@ mod tests {
assert_eq!(err, SweepError::EmptyAxis { slot: 0 });
}
fn synthetic_prices() -> Vec<(Timestamp, Scalar)> {
[
(1_i64, 1.0000_f64),
(2, 1.0010),
(3, 1.0030),
(4, 1.0060),
(5, 1.0040),
(6, 1.0010),
(7, 0.9990),
]
.iter()
.map(|&(t, p)| (Timestamp(t), Scalar::F64(p)))
.collect()
}
/// The SMA-cross signal as a reusable value-empty composite (duplicated from
/// `blueprint.rs`'s test module — test fixtures stay module-local, the same
/// way `report.rs` copies its harness helper).
fn sma_cross() -> Composite {
Composite::new(
"sma_cross",
vec![
Sma::builder().named("fast").into(),
Sma::builder().named("slow").into(),
Sub::builder().into(),
],
vec![
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
],
vec![Role {
name: "price".into(),
targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
source: None,
}],
vec![OutField { node: 2, field: 0, name: "out".into() }],
)
}
/// The signal-quality harness as a value-empty composite blueprint with two
/// recording sinks (duplicated from `blueprint.rs`'s test module).
#[allow(clippy::type_complexity)]
fn composite_sma_cross_harness() -> (
Composite,
mpsc::Receiver<(Timestamp, Vec<Scalar>)>,
mpsc::Receiver<(Timestamp, Vec<Scalar>)>,
) {
let (tx_eq, rx_eq) = mpsc::channel();
let (tx_ex, rx_ex) = mpsc::channel();
let bp = Composite::new(
"root",
vec![
BlueprintNode::Composite(sma_cross()),
Exposure::builder().into(),
SimBroker::builder(0.0001).into(),
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(),
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(),
],
vec![
Edge { from: 0, to: 1, slot: 0, from_field: 0 }, // composite out -> Exposure
Edge { from: 1, to: 2, slot: 0, from_field: 0 }, // exposure -> broker slot 0
Edge { from: 2, to: 3, slot: 0, from_field: 0 }, // equity -> sink
Edge { from: 1, to: 4, slot: 0, from_field: 0 }, // exposure -> sink
],
vec![Role {
name: "src".into(),
targets: vec![Target { node: 0, slot: 0 }, Target { node: 2, slot: 1 }],
source: Some(ScalarKind::F64),
}],
vec![], // output: the root ends in sinks
);
(bp, rx_eq, rx_ex)
}
/// Build + bootstrap + run + drain + summarize one grid point into a
/// `RunReport`. A free `fn` (Copy + Sync) so it serves as the `sweep` closure
/// AND a direct reference for the "sweep == N independent runs" comparison.
+87
View File
@@ -0,0 +1,87 @@
//! Shared `#[cfg(test)]` fixtures for the SMA-cross signal-quality harness,
//! consumed by the `blueprint` and `sweep` unit-test modules. Kept in one place
//! so the harness topology cannot silently diverge between them (see #53).
//! Gated test-only at the declaration site in `lib.rs`
//! (`#[cfg(test)] mod test_fixtures;`), matching the `reexport_tests` precedent.
use crate::{BlueprintNode, Composite, Edge, OutField, Role, Target};
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub};
use std::sync::mpsc;
/// Seven synthetic F64 ticks driving the harness; deterministic input fixture.
pub(crate) fn synthetic_prices() -> Vec<(Timestamp, Scalar)> {
[
(1_i64, 1.0000_f64),
(2, 1.0010),
(3, 1.0030),
(4, 1.0060),
(5, 1.0040),
(6, 1.0010),
(7, 0.9990),
]
.iter()
.map(|&(t, p)| (Timestamp(t), Scalar::F64(p)))
.collect()
}
/// The SMA-cross signal as a reusable value-empty composite: one input role
/// (price), one output (fast-minus-slow spread). Lengths injected at compile.
pub(crate) fn sma_cross() -> Composite {
Composite::new(
"sma_cross",
vec![
Sma::builder().named("fast").into(),
Sma::builder().named("slow").into(),
Sub::builder().into(),
],
vec![
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
],
vec![Role {
name: "price".into(),
targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
source: None,
}],
vec![OutField { node: 2, field: 0, name: "out".into() }],
)
}
/// The signal-quality harness as a value-empty composite blueprint with two
/// recording sinks (equity, exposure).
#[allow(clippy::type_complexity)]
pub(crate) fn composite_sma_cross_harness() -> (
Composite,
mpsc::Receiver<(Timestamp, Vec<Scalar>)>,
mpsc::Receiver<(Timestamp, Vec<Scalar>)>,
) {
let (tx_eq, rx_eq) = mpsc::channel();
let (tx_ex, rx_ex) = mpsc::channel();
let bp = Composite::new(
"root",
vec![
BlueprintNode::Composite(sma_cross()),
Exposure::builder().into(),
SimBroker::builder(0.0001).into(),
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(),
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(),
],
vec![
Edge { from: 0, to: 1, slot: 0, from_field: 0 }, // composite out -> Exposure
Edge { from: 1, to: 2, slot: 0, from_field: 0 }, // exposure -> broker slot 0
Edge { from: 2, to: 3, slot: 0, from_field: 0 }, // equity -> sink
Edge { from: 1, to: 4, slot: 0, from_field: 0 }, // exposure -> sink
],
vec![Role {
name: "src".into(),
targets: vec![
Target { node: 0, slot: 0 }, // price -> sma_cross role 0
Target { node: 2, slot: 1 }, // price -> SimBroker price slot
],
source: Some(ScalarKind::F64),
}],
vec![], // output: the root ends in sinks
);
(bp, rx_eq, rx_ex)
}