diff --git a/crates/aura-runner/src/reproduce.rs b/crates/aura-runner/src/reproduce.rs index 5ba134c..85320a6 100644 --- a/crates/aura-runner/src/reproduce.rs +++ b/crates/aura-runner/src/reproduce.rs @@ -232,3 +232,69 @@ pub fn reproduce_family(id: &str, env: &Env) -> Result<(), RunnerError> { } Ok(()) } + +#[cfg(test)] +mod tests { + use aura_backtest::{RunMetrics, RunReport}; + use aura_core::{Scalar, Timestamp}; + use aura_engine::RunManifest; + use aura_registry::{FamilyKind, Registry}; + + use super::load_family; + + /// A registry over a fresh per-test directory (the #258 tag-keyed pattern: + /// fixed name under the build-tree tmp anchor, pre-create wipe). + fn temp_registry(tag: &str) -> Registry { + let dir = std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/tmp")) + .join(format!("aura-runner-reproduce-{tag}")); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).expect("create temp registry dir"); + Registry::open(dir.join("runs.jsonl")) + } + + /// One minimal persisted member (the aura-registry test fixture shape). + fn minimal_report() -> RunReport { + RunReport { + manifest: RunManifest { + commit: "c".to_string(), + params: vec![("p".to_string(), Scalar::f64(1.0))], + defaults: vec![], + window: (Timestamp(1), Timestamp(2)), + seed: 0, + broker: "b".to_string(), + selection: None, + instrument: None, + topology_hash: None, + project: None, + }, + metrics: RunMetrics { total_pips: 1.0, max_drawdown: 0.5, bias_sign_flips: 0, r: None }, + } + } + + /// One id vocabulary across enumeration and reproduction (C18, #298): the + /// family-identity string a consumer lifts off the registry's own member + /// enumeration (`FamilyRunRecord.family`) resolves through the reproduce + /// lookup when it is unambiguous (a single stored run for the name), and + /// reaches the same family the canonical derived handle + /// (`"{family}-{run}"`, C18) names. Today the lookup exact-matches the + /// derived handle only, so the natural list-then-reproduce library + /// workflow dead-ends on `no such family`. + /// + /// Deliberately NOT pinned here: the ambiguous case (a name with more + /// than one stored run) and name/handle collision precedence — those + /// semantics are the fix's to settle (refuse-don't-guess). + #[test] + fn load_family_accepts_the_enumerated_family_identity() { + let reg = temp_registry("enumerated-id"); + reg.append_family("f", FamilyKind::Sweep, &[minimal_report()]).expect("append family"); + + let members = reg.load_family_members().expect("enumerate members"); + let enumerated = members[0].family.clone(); + let canonical = members[0].family_id(); + + let family = load_family(®, &enumerated).unwrap_or_else(|e| { + panic!("the enumerated family identity '{enumerated}' must resolve: {}", e.message) + }); + assert_eq!(family.id, canonical); + } +}