plan: 0038 dedup-sma-cross-test-fixtures
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
# Dedup the SMA-cross harness test fixtures — Implementation Plan
|
||||
|
||||
> **Parent spec:** `docs/specs/0038-dedup-sma-cross-test-fixtures.md`
|
||||
>
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement` skill to run
|
||||
> this plan. Steps use `- [ ]` checkboxes for tracking.
|
||||
|
||||
**Goal:** Collapse the three verbatim-duplicated SMA-cross harness `#[cfg(test)]`
|
||||
fixtures (`synthetic_prices`, `sma_cross`, `composite_sma_cross_harness`) in
|
||||
`aura-engine`'s `blueprint.rs` and `sweep.rs` test modules into one shared
|
||||
crate-root `#[cfg(test)] mod test_fixtures`.
|
||||
|
||||
**Architecture:** A new test-only module `crates/aura-engine/src/test_fixtures.rs`
|
||||
holds the three fixtures as `pub(crate)` free fns; it is declared in `lib.rs`
|
||||
with `#[cfg(test)] mod test_fixtures;` (sibling to the existing
|
||||
`#[cfg(test)] mod reexport_tests`). The two consuming test modules delete their
|
||||
local copies and import from `crate::test_fixtures`. Behaviour-preserving,
|
||||
`#[cfg(test)]`-only — no production code, no public API, no runtime path changes.
|
||||
|
||||
**Tech Stack:** Rust, `aura-engine` crate; verified against `cargo test` and
|
||||
`cargo clippy -D warnings`.
|
||||
|
||||
---
|
||||
|
||||
## Files this plan creates or modifies
|
||||
|
||||
- Create: `crates/aura-engine/src/test_fixtures.rs` — the shared `#[cfg(test)]`
|
||||
fixture module (three `pub(crate)` fns).
|
||||
- Modify: `crates/aura-engine/src/lib.rs:53-54` — add the module declaration.
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs` — delete three local fixtures
|
||||
(`:1518-1533`, `:1590-1610`, `:1612-1645`), add one import line to the test
|
||||
module's `use` block (`:755-758`). No existing import becomes unused.
|
||||
- Modify: `crates/aura-engine/src/sweep.rs` — delete three local fixtures incl.
|
||||
two stale doc-comments (`:256-269`, `:271-293`, `:295-328`), and rewrite the
|
||||
test module's `use` block (`:171-177`) to import the fixtures and drop the
|
||||
now-unused imports.
|
||||
|
||||
**Out of scope — must NOT be touched:** `blueprint.rs` `sma_cross_under_root`
|
||||
(`:1114`), `hand_wired_sma_cross_harness` (`:1538`); `sweep.rs` `run_point`
|
||||
(`:335`), `sma_cross_grid` (`:355`); `graph_model.rs` `sma_cross` (`:311`) /
|
||||
`sample_root` (`:340`) — an independent minimal golden copy.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Move the three fixtures into a shared `test_fixtures` module
|
||||
|
||||
This is one atomic move: the new module and both consumers must change together
|
||||
for the crate to build and lint clean. Intermediate steps are intentionally NOT
|
||||
gated; the build / test / clippy gates run only at the end (Steps 5-7), after
|
||||
every consumer is threaded — so no "unused fn / unused import" lint can fire on
|
||||
a half-applied move.
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aura-engine/src/test_fixtures.rs`
|
||||
- Modify: `crates/aura-engine/src/lib.rs:53-54`
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs` (`:755-758`, `:1518-1533`, `:1590-1610`, `:1612-1645`)
|
||||
- Modify: `crates/aura-engine/src/sweep.rs` (`:171-177`, `:256-269`, `:271-293`, `:295-328`)
|
||||
|
||||
- [ ] **Step 1: Create the shared fixture module**
|
||||
|
||||
Create `crates/aura-engine/src/test_fixtures.rs` with exactly this content. The
|
||||
import paths use the crate-root re-exports (`crate::{...}`) — verified present at
|
||||
`lib.rs:40-45`; this matches the import style `sweep.rs`'s test module already
|
||||
uses. (`Edge` and `Target` are re-exported from `crate::harness`, not
|
||||
`crate::blueprint` — the crate-root path sidesteps that.)
|
||||
|
||||
```rust
|
||||
//! 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)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Declare the module in `lib.rs`**
|
||||
|
||||
In `crates/aura-engine/src/lib.rs`, the existing test-only module declaration is:
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod reexport_tests {
|
||||
```
|
||||
|
||||
Immediately **before** that `#[cfg(test)]` line (at `:53`), add the new module
|
||||
declaration so it sits as a sibling:
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod test_fixtures;
|
||||
|
||||
```
|
||||
|
||||
(Result: a `#[cfg(test)] mod test_fixtures;` line, a blank line, then the
|
||||
existing `#[cfg(test)] mod reexport_tests {` block — both gated, both crate-root.)
|
||||
|
||||
- [ ] **Step 3: Switch `blueprint.rs` to the shared fixtures**
|
||||
|
||||
In `crates/aura-engine/src/blueprint.rs`, inside `#[cfg(test)] mod tests`:
|
||||
|
||||
(a) **Delete** the three local fixture definitions entirely (including their
|
||||
doc-comments and the `#[allow(clippy::type_complexity)]` on the harness):
|
||||
- `synthetic_prices` — `:1518-1533`
|
||||
- `sma_cross` — `:1590-1610`
|
||||
- `composite_sma_cross_harness` — `:1612-1645`
|
||||
|
||||
(b) **Add** this import line to the test module's `use` block (currently
|
||||
`:755-758`, which is `use super::*;` plus the `aura_core` / `aura_std` / `mpsc`
|
||||
lines). Insert it directly after `use super::*;`:
|
||||
|
||||
```rust
|
||||
use crate::test_fixtures::{composite_sma_cross_harness, sma_cross, synthetic_prices};
|
||||
```
|
||||
|
||||
Do **not** remove any other import: every existing test-mod import
|
||||
(`Exposure`, `Recorder`, `SimBroker`, `Firing`, `NodeSchema`, `FieldSpec`,
|
||||
`Ctx`, `Ema`, `mpsc`) still has surviving uses in other tests
|
||||
(`hand_wired_sma_cross_harness`, the MACD test, the local dummy-node defs).
|
||||
|
||||
- [ ] **Step 4: Switch `sweep.rs` to the shared fixtures and drop dead imports**
|
||||
|
||||
In `crates/aura-engine/src/sweep.rs`, inside `#[cfg(test)] mod tests`:
|
||||
|
||||
(a) **Delete** the three local fixture definitions entirely, including the two
|
||||
stale "duplicated from `blueprint.rs`" doc-comments:
|
||||
- `synthetic_prices` — `:256-269`
|
||||
- `sma_cross` (+ doc-comment `:271-273`) — `:271-293`
|
||||
- `composite_sma_cross_harness` (+ doc-comment / `#[allow]` `:295-297`) — `:295-328`
|
||||
|
||||
(b) **Replace** the test module's entire `use` block (currently `:171-177`):
|
||||
|
||||
```rust
|
||||
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;
|
||||
```
|
||||
|
||||
with exactly this (imports the shared fixtures; drops everything now used only
|
||||
by the deleted fns — `BlueprintNode`, `Composite`, `Edge`, `OutField`, `Role`,
|
||||
`Target`, `Firing`, the whole `aura_std` line, and `std::sync::mpsc`; keeps
|
||||
`f64_field`, `summarize`, `RunManifest`, `ParamSpec`, `Scalar`, `ScalarKind`,
|
||||
`Timestamp`, which `run_point` / `sma_cross_grid` / the grid tests still use):
|
||||
|
||||
```rust
|
||||
use super::*;
|
||||
use crate::test_fixtures::{composite_sma_cross_harness, sma_cross, synthetic_prices};
|
||||
use crate::{f64_field, summarize, RunManifest};
|
||||
use aura_core::{ParamSpec, Scalar, ScalarKind, Timestamp};
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Build + run the full workspace test suite (primary gate)**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
Expected: PASS — builds clean, every test green, zero failures. The result count
|
||||
must match the pre-change baseline (this is a behaviour-preserving move; no test
|
||||
is added or removed). If the build fails on an import-path error in
|
||||
`test_fixtures.rs`, the engine types are reachable from the crate root
|
||||
(`crate::{...}`) per `lib.rs:40-45` — do not invent a path.
|
||||
|
||||
- [ ] **Step 6: Confirm the two load-bearing fixture-consuming tests are green**
|
||||
|
||||
These two named tests exercise the moved fixtures and are the spec's verification
|
||||
witnesses (C1 determinism; C11/C12 sweep-equivalence). Run each as its own
|
||||
single-filter invocation (one positional filter per run):
|
||||
|
||||
Run: `cargo test -p aura-engine composite_sma_cross_runs_bit_identical_to_hand_wired`
|
||||
Expected: PASS — `test result: ok. 1 passed` (the filter matches exactly this
|
||||
real test in `blueprint.rs:1647`; a `0 passed` here means the filter resolved
|
||||
nothing and is a failure of this gate).
|
||||
|
||||
Run: `cargo test -p aura-engine sweep_equals_n_independent_runs`
|
||||
Expected: PASS — `test result: ok. 1 passed` (filter matches the real test in
|
||||
`sweep.rs:369`).
|
||||
|
||||
- [ ] **Step 7: Lint gate — no unused imports left behind**
|
||||
|
||||
Run: `cargo clippy --workspace --all-targets -- -D warnings`
|
||||
Expected: clean (exit 0, no warnings). This is the backstop for Step 4's import
|
||||
removal: an over-removal fails to compile, an under-removal fails the
|
||||
`unused_imports` lint. A clean run confirms the `sweep.rs` import set is exactly
|
||||
right and the new module has no dead/unused symbol.
|
||||
|
||||
---
|
||||
|
||||
## Self-review
|
||||
|
||||
1. **Spec coverage:** the spec's single in-scope section (the three-fixture move
|
||||
+ module creation + consumer rewiring + import cleanup) is fully covered by
|
||||
Task 1's seven steps. ✓
|
||||
2. **Placeholder scan:** no "TBD" / "TODO" / "implement later" / "similar to
|
||||
Task N" / "add appropriate error handling" present. ✓
|
||||
3. **Type consistency:** fixture names (`synthetic_prices`, `sma_cross`,
|
||||
`composite_sma_cross_harness`), module name (`test_fixtures`), and import
|
||||
paths (`crate::test_fixtures::{...}`, `crate::{...}`) are identical across all
|
||||
steps. ✓
|
||||
4. **Step granularity:** each step is one file action or one command, 2-5 min. ✓
|
||||
5. **No commit steps:** none present; the orchestrator commits. ✓
|
||||
6. **Pin/replacement substring contiguity:** the only presence-pin is Step 6's
|
||||
`1 passed` against two named tests; no verbatim replacement body must contain
|
||||
a pinned substring, so no soft-wrap split risk. ✓
|
||||
7. **Compile-gate vs. deferred-caller ordering:** the move makes the crate fail
|
||||
to compile/lint until BOTH consumers are switched; all consumer threading
|
||||
(Steps 3-4) precedes the first build/test/clippy gate (Steps 5-7). No gate
|
||||
fires on a half-applied move. ✓
|
||||
8. **Verification-command filter strings resolve:** Step 5 is the unfiltered
|
||||
workspace suite (the "nothing ran" masquerade is impossible). Step 6's two
|
||||
filters name real tests verified to exist (`blueprint.rs:1647`,
|
||||
`sweep.rs:369`) and assert `1 passed`, so a non-matching filter would surface
|
||||
as `0 passed` and fail the gate. ✓
|
||||
9. **Parse-the-bytes-you-inline gate:** the project declares no spec-validation
|
||||
parsers (CLAUDE.md project facts) → documented no-op. Additionally the only
|
||||
inlined bodies are Rust (the project's source language), which is the
|
||||
`implement` compile gate's target, not this gate's. ✓
|
||||
Reference in New Issue
Block a user