Files
Aura/docs/plans/0038-dedup-sma-cross-test-fixtures.md
T

13 KiB

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.)

//! 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:

#[cfg(test)]
mod reexport_tests {

Immediately before that #[cfg(test)] line (at :53), add the new module declaration so it sits as a sibling:

#[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::*;:

    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):

    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):

    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. ✓