//! End-to-end coverage for #246 tasks 1-2: `PrimitiveBuilder::bind`/`unbind` //! storing the bound value as data merged back at build time (aura-core), and //! `Composite::reopen` re-opening a bound param at its `param_space()` path //! (aura-engine). The in-crate unit tests (`node.rs`/`blueprint.rs` `mod //! tests`) already pin the mechanism directly against `PrimitiveBuilder`; this //! file instead drives the **full public compile pipeline** //! (`Composite::reopen` -> `compile_with_params` -> `FlatGraph`) through only //! `aura_engine`'s exported surface — the seam the future CLI axis-override //! path (the rest of #246) will call. A regression that broke the merge only //! when routed through `compile_with_params` (as opposed to calling //! `PrimitiveBuilder::build` directly) would pass every existing unit test //! while breaking this. use aura_core::{Scalar, ScalarKind}; use aura_engine::{Composite, Role, Target}; use aura_std::Sma; use aura_strategy::Bias; /// A single bound `Sma`, wired from a root role so it compiles standalone. fn one_bound_sma() -> Composite { Composite::new( "sig", vec![Sma::builder().named("sma").bind("length", Scalar::i64(2)).into()], vec![], vec![Role { name: "series".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64), }], vec![], ) } /// Property: a bound param, once reopened, is genuinely re-bindable through /// the full `compile_with_params` pipeline — the compiled node reflects the /// value supplied AT COMPILE TIME, not the stale bound default. This is the /// build-time merge (`PrimitiveBuilder::build`, aura-core #246 task 1) proven /// under the compile boundary it must survive (aura-engine #246 task 2), not /// just called directly. #[test] fn reopened_param_rebuilds_with_the_freshly_supplied_value_not_the_stale_default() { // Before reopen: fully bound, nothing to supply, and the default (2) is // what gets built. (A fresh `Composite` — `reopen`/`compile_with_params` // both consume `self`, so the "before" and "after" instances are built // separately from the same constructor rather than cloned.) let flat = one_bound_sma().compile_with_params(&[]).expect("fully-bound composite compiles with no params"); assert_eq!(flat.nodes[0].label(), "SMA(2)", "the bound default builds unchanged"); let reopened = one_bound_sma().reopen("sma.length").expect("bound knob reopens"); let space = reopened.param_space(); assert_eq!(space.len(), 1, "reopen returns exactly the one knob to the open surface"); assert_eq!(space[0].name, "sma.length"); let flat2 = reopened .compile_with_params(&[Scalar::i64(9)]) .expect("reopened composite compiles once the freed slot is supplied"); assert_eq!( flat2.nodes[0].label(), "SMA(9)", "the compiled node must be built from the freshly supplied value (9), not the stale bound default (2)" ); } /// Property: reopening one node's bound param does not disturb a SIBLING /// node's own still-bound default. `BoundParam`'s per-node bookkeeping /// (`pos`, restored on `unbind`) must stay node-local through the compile /// pipeline — a bug that let one node's reopen leak into another's merge /// would build the sibling from the wrong cell. #[test] fn reopening_one_node_leaves_a_sibling_bound_default_intact_through_compile() { let signal = Composite::new( "sig", vec![ Sma::builder().named("fast").bind("length", Scalar::i64(2)).into(), Bias::builder().named("b").bind("scale", Scalar::f64(0.5)).into(), ], vec![], vec![ Role { name: "series".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64), }, Role { name: "signal".into(), targets: vec![Target { node: 1, slot: 0 }], source: Some(ScalarKind::F64), }, ], vec![], ); let reopened = signal.reopen("fast.length").expect("fast.length reopens"); let flat = reopened .compile_with_params(&[Scalar::i64(7)]) .expect("compiles once the one freed slot is supplied"); assert_eq!(flat.nodes[0].label(), "SMA(7)", "the reopened node builds from the freshly supplied value"); assert_eq!( flat.nodes[1].label(), "Bias(0.5)", "the sibling's still-bound default must build unchanged — reopen must not perturb it" ); }