test(cli): migrate dissolved-verb e2e tests into the project fixture (#218 prep)
The four dissolved verbs (sweep/generalize/walkforward/mc --real) are about to gain a project gate that refuses outside a project (#218). 25 cli_run.rs tests today run a well-formed verb to completion in a bare cwd and pin its grade / store effect — i.e. they pin the pre-gate behaviour. Relocate them into the built project fixture (fresh_project() over the demo-project cdylib, serialized, store reset per test) so they still exercise the verb path once the gate lands. Grades are byte-identical in-project (the fixture Aura.toml overrides no instrument or data path — DEFAULT_DATA_PATH is absolute), verified against the exact pins; local run 113 passed / 0 failed / 0 skipped with archive data. Two new tests pin that the in-project run stamps manifest.project provenance (the sweep chokepoint and generalize's run_blueprint_member path), a path no cli_run.rs test previously exercised. The built_project/project_lock helpers are duplicated into this binary to keep the change inside cli_run.rs; a shared tests/common extraction is a separate hygiene item. refs #218
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
//! Integration test: drive the built `aura` binary as a downstream user would,
|
||||
//! asserting the `run` subcommand's stdout/exit contract and the bad-args path.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||
|
||||
/// Path to the freshly-built `aura` binary (Cargo sets this env var for the test
|
||||
/// crate; the binary is named `aura` in `Cargo.toml`).
|
||||
@@ -17,6 +19,58 @@ fn temp_cwd(name: &str) -> std::path::PathBuf {
|
||||
dir
|
||||
}
|
||||
|
||||
/// The demo-project fixture (Aura.toml + a built cdylib present), built once —
|
||||
/// mirrors research_docs.rs's helper (a separate test binary, so it needs its
|
||||
/// own `OnceLock`); `cargo build` is idempotent, so a second binary building
|
||||
/// the same fixture is a no-op.
|
||||
fn built_project() -> &'static PathBuf {
|
||||
static BUILT: OnceLock<PathBuf> = OnceLock::new();
|
||||
BUILT.get_or_init(|| {
|
||||
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/demo-project");
|
||||
let out = std::process::Command::new("cargo")
|
||||
.arg("build")
|
||||
.current_dir(&dir)
|
||||
.output()
|
||||
.expect("spawn cargo build for the fixture project");
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"fixture build failed:\n{}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
dir
|
||||
})
|
||||
}
|
||||
|
||||
/// Serializes every test that runs a verb inside the shared fixture (they reset
|
||||
/// `<fixture>/runs`, so parallel threads would race). Poison-tolerant.
|
||||
fn project_lock() -> MutexGuard<'static, ()> {
|
||||
static LOCK: Mutex<()> = Mutex::new(());
|
||||
LOCK.lock().unwrap_or_else(|e| e.into_inner())
|
||||
}
|
||||
|
||||
/// Removes `<fixture>/runs` on drop (including mid-panic) so a migrated test never
|
||||
/// leaks its content-addressed store into the git-tracked fixture tree.
|
||||
struct RunsCleanup(PathBuf);
|
||||
impl Drop for RunsCleanup {
|
||||
fn drop(&mut self) {
|
||||
std::fs::remove_dir_all(&self.0).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Enter the shared built project fixture with a FRESH `runs/` store, serialized.
|
||||
/// Returns the fixture dir (has an Aura.toml, so `provenance()` is `Some`) and the
|
||||
/// guards; bind the guards for the whole test body: `let (dir, _g) = fresh_project();`.
|
||||
/// The guard tuple orders `RunsCleanup` before the `MutexGuard` deliberately: tuple
|
||||
/// fields drop in index order, so the `runs/` removal (index 0) runs while the lock
|
||||
/// (index 1) is still held — the serialization the lock exists to enforce.
|
||||
fn fresh_project() -> (&'static PathBuf, (RunsCleanup, MutexGuard<'static, ()>)) {
|
||||
let lock = project_lock();
|
||||
let dir = built_project();
|
||||
let runs = dir.join("runs");
|
||||
std::fs::remove_dir_all(&runs).ok();
|
||||
(dir, (RunsCleanup(runs), lock))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_prints_json_and_exits_zero() {
|
||||
// `run` is blueprint-only now (#159 cut 4 retired the bare built-in default);
|
||||
@@ -662,15 +716,13 @@ fn local_data_present() -> bool {
|
||||
#[test]
|
||||
fn sweep_real_no_geometry_symbol_refuses_with_exit_1() {
|
||||
// not gated: the pip refusal happens before any data access.
|
||||
let dir = temp_cwd("sweep_real_refuse");
|
||||
let (dir, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(BIN)
|
||||
.args(["sweep", &fixture, "--axis", "sma_signal.fast.length=2,4", "--real", "NONEXISTENT"])
|
||||
.current_dir(&dir).output().unwrap();
|
||||
.current_dir(dir).output().unwrap();
|
||||
assert_eq!(out.status.code(), Some(1));
|
||||
assert!(String::from_utf8_lossy(&out.stderr).contains("no recorded geometry"));
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
// GER40 Sept-2024 window (inclusive Unix-ms) — the same gated calendar month
|
||||
@@ -693,7 +745,7 @@ fn sweep_real_blueprint_member_manifest_stamps_the_default_stop() {
|
||||
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
||||
return;
|
||||
}
|
||||
let dir = temp_cwd("sweep_real_blueprint_no_stop");
|
||||
let (dir, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(BIN)
|
||||
.args([
|
||||
@@ -705,7 +757,7 @@ fn sweep_real_blueprint_member_manifest_stamps_the_default_stop() {
|
||||
"--axis", "sma_signal.slow.length=8",
|
||||
"--name", "brp_nostop",
|
||||
])
|
||||
.current_dir(&dir)
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||
@@ -726,6 +778,53 @@ fn sweep_real_blueprint_member_manifest_stamps_the_default_stop() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Property (#218 migration follow-up): running a dissolved `sweep` inside a
|
||||
/// project (`fresh_project()`, the fixture this migration introduced) stamps
|
||||
/// `report.manifest.project` on every member with the project's audit-trail
|
||||
/// provenance (C18/C16) — the `demo` namespace and a well-formed 64-hex-char
|
||||
/// dylib sha256 — rather than the `None` a bare-cwd run (no `Aura.toml`)
|
||||
/// produces. Before this migration no cli_run.rs sweep test ran inside a real
|
||||
/// project, so this stamping path was never exercised end-to-end. Gated on the
|
||||
/// local GER40 archive; skips cleanly when absent.
|
||||
#[test]
|
||||
fn sweep_real_blueprint_member_manifest_stamps_project_provenance() {
|
||||
if !local_data_present() {
|
||||
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
||||
return;
|
||||
}
|
||||
let (dir, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(BIN)
|
||||
.args([
|
||||
"sweep", &fixture,
|
||||
"--real", "GER40",
|
||||
"--from", GER40_SEPT2024_FROM_MS,
|
||||
"--to", GER40_SEPT2024_TO_MS,
|
||||
"--axis", "sma_signal.fast.length=2,4",
|
||||
"--axis", "sma_signal.slow.length=8",
|
||||
"--name", "brp_provenance",
|
||||
])
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
|
||||
let lines: Vec<&str> = stdout.lines().collect();
|
||||
assert_eq!(lines.len(), 2, "one member line per grid point: {stdout}");
|
||||
|
||||
for line in &lines {
|
||||
let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON");
|
||||
let project = &v["report"]["manifest"]["project"];
|
||||
assert_eq!(
|
||||
project["namespace"].as_str(), Some("demo"),
|
||||
"member manifest carries the fixture project's namespace: {line}"
|
||||
);
|
||||
let sha = project["dylib_sha256"].as_str().unwrap_or_else(|| panic!("dylib_sha256 present: {line}"));
|
||||
assert_eq!(sha.len(), 64, "dylib_sha256 is a full sha256 hex digest: {line}");
|
||||
assert!(sha.chars().all(|c| c.is_ascii_hexdigit()), "dylib_sha256 is hex: {line}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Property: the real-data blueprint sweep path (`aura sweep <blueprint.json>
|
||||
/// --real SYM --axis …`, dispatched as sugar over the one campaign executor)
|
||||
/// prints one member line per grid point, IN AXIS ORDER (odometer: the first
|
||||
@@ -746,7 +845,7 @@ fn sweep_real_blueprint_member_lines_pin_the_dissolved_contract() {
|
||||
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
||||
return;
|
||||
}
|
||||
let dir = temp_cwd("sweep_real_blueprint");
|
||||
let (dir, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(BIN)
|
||||
.args([
|
||||
@@ -758,7 +857,7 @@ fn sweep_real_blueprint_member_lines_pin_the_dissolved_contract() {
|
||||
"--axis", "sma_signal.slow.length=8,16",
|
||||
"--name", "brp",
|
||||
])
|
||||
.current_dir(&dir)
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||
@@ -806,7 +905,7 @@ fn sweep_real_blueprint_member_lines_pin_the_dissolved_contract() {
|
||||
// exactly one new Sweep family persisted, with the grid's four members.
|
||||
let fams = std::process::Command::new(BIN)
|
||||
.args(["runs", "families"])
|
||||
.current_dir(&dir)
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(fams.status.success(), "families exit: {:?}", fams.status);
|
||||
@@ -909,7 +1008,7 @@ fn sweep_real_blueprint_member_lines_pin_the_dissolved_contract() {
|
||||
"--axis", "sma_signal.slow.length=8,16",
|
||||
"--name", "brp",
|
||||
])
|
||||
.current_dir(&dir)
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out2.status.success(), "second run stderr: {}", String::from_utf8_lossy(&out2.stderr));
|
||||
@@ -939,8 +1038,6 @@ fn sweep_real_blueprint_member_lines_pin_the_dissolved_contract() {
|
||||
}
|
||||
assert_eq!(count("processes"), 1, "second identical run dedupes the process doc");
|
||||
assert_eq!(count("campaigns"), 1, "second identical run dedupes the campaign doc");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Property (#210 c0110 fieldtest, "the wrapped-name refusal mangles the axis
|
||||
@@ -1000,7 +1097,7 @@ fn aura_sweep_real_blueprint_subset_axes_refuses_without_store_litter() {
|
||||
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
||||
return;
|
||||
}
|
||||
let dir = temp_cwd("sweep_real_subset_refusal");
|
||||
let (dir, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = Command::new(BIN)
|
||||
.args([
|
||||
@@ -1011,7 +1108,7 @@ fn aura_sweep_real_blueprint_subset_axes_refuses_without_store_litter() {
|
||||
"--axis", "sma_signal.fast.length=2,4",
|
||||
"--name", "c0110-subset",
|
||||
])
|
||||
.current_dir(&dir)
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.expect("spawn aura sweep (subset axes)");
|
||||
assert_ne!(
|
||||
@@ -1030,7 +1127,6 @@ fn aura_sweep_real_blueprint_subset_axes_refuses_without_store_litter() {
|
||||
!runs_log.exists() || std::fs::read_to_string(&runs_log).unwrap().is_empty(),
|
||||
"no realization recorded for a refused sweep"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// The synthetic seed-resweep `mc` is undefined over real bars (one realization ->
|
||||
@@ -1363,7 +1459,7 @@ fn generalize_strategy_flag_is_removed_from_the_grammar() {
|
||||
fn generalize_grades_a_candidate_across_two_instruments() {
|
||||
const FROM_MS: &str = "1725148800000";
|
||||
const TO_MS: &str = "1727740799999";
|
||||
let cwd = temp_cwd("generalize-two");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -1372,7 +1468,7 @@ fn generalize_grades_a_candidate_across_two_instruments() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -1409,7 +1505,7 @@ fn generalize_grades_a_candidate_across_two_instruments() {
|
||||
fn generalize_real_e2e_pins_the_exact_current_grade() {
|
||||
const FROM_MS: &str = "1725148800000";
|
||||
const TO_MS: &str = "1727740799999";
|
||||
let cwd = temp_cwd("generalize-exact-grade");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -1418,7 +1514,7 @@ fn generalize_real_e2e_pins_the_exact_current_grade() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -1466,7 +1562,7 @@ fn generalize_real_e2e_pins_the_exact_current_grade() {
|
||||
fn generalize_grades_a_non_r_sma_blueprint() {
|
||||
const FROM_MS: &str = "1725148800000";
|
||||
const TO_MS: &str = "1727740799999";
|
||||
let cwd = temp_cwd("generalize-non-r-sma");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -1476,7 +1572,7 @@ fn generalize_grades_a_non_r_sma_blueprint() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -1515,7 +1611,7 @@ fn generalize_grades_a_non_r_sma_blueprint() {
|
||||
fn walkforward_real_e2e_pins_the_exact_current_grade() {
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let cwd = temp_cwd("walkforward-exact-grade");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -1524,7 +1620,7 @@ fn walkforward_real_e2e_pins_the_exact_current_grade() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -1577,7 +1673,7 @@ fn walkforward_real_e2e_pins_the_exact_current_grade() {
|
||||
fn walkforward_dissolves_a_non_r_sma_blueprint() {
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let cwd = temp_cwd("walkforward-non-r-sma");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -1587,7 +1683,7 @@ fn walkforward_dissolves_a_non_r_sma_blueprint() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -1664,7 +1760,7 @@ fn walkforward_dissolved_refuses_select_plateau() {
|
||||
/// cleanly on a data refusal.
|
||||
#[test]
|
||||
fn walkforward_dissolved_defaults_a_single_omitted_knob() {
|
||||
let cwd = temp_cwd("walkforward-single-knob-default");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -1672,7 +1768,7 @@ fn walkforward_dissolved_defaults_a_single_omitted_knob() {
|
||||
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
|
||||
"--stop-length", "3",
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -1807,7 +1903,7 @@ fn walkforward_dissolved_refuses_name_and_trace_together() {
|
||||
fn walkforward_dissolves_through_the_campaign_path() {
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let cwd = temp_cwd("walkforward-dissolves");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -1816,7 +1912,7 @@ fn walkforward_dissolves_through_the_campaign_path() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -1862,7 +1958,7 @@ fn walkforward_dissolves_through_the_campaign_path() {
|
||||
|
||||
let fams = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args(["runs", "families"])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn families");
|
||||
let fams_out = String::from_utf8_lossy(&fams.stdout).into_owned();
|
||||
@@ -1898,7 +1994,7 @@ fn walkforward_dissolves_through_the_campaign_path() {
|
||||
fn walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2() {
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let cwd = temp_cwd("walkforward-stopless-defaults");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
// Stop-less: the two knobs are omitted entirely. This is the headline — today it
|
||||
@@ -1910,7 +2006,7 @@ fn walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2() {
|
||||
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if stopless.status.code() == Some(1) {
|
||||
@@ -1939,7 +2035,7 @@ fn walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2() {
|
||||
"--stop-length", "3", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
assert_eq!(
|
||||
@@ -1984,7 +2080,7 @@ fn walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2() {
|
||||
fn generalize_dissolves_through_the_campaign_path() {
|
||||
const FROM_MS: &str = "1725148800000";
|
||||
const TO_MS: &str = "1727740799999";
|
||||
let cwd = temp_cwd("generalize-dissolves");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -1993,7 +2089,7 @@ fn generalize_dissolves_through_the_campaign_path() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -2044,7 +2140,7 @@ fn generalize_dissolves_through_the_campaign_path() {
|
||||
// per-instrument Sweep family per cell (persisted by the executor).
|
||||
let fams = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args(["runs", "families"])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn families");
|
||||
let fams_out = String::from_utf8_lossy(&fams.stdout).into_owned();
|
||||
@@ -2075,7 +2171,7 @@ fn generalize_dissolves_through_the_campaign_path() {
|
||||
fn generalize_repeated_identical_invocation_does_not_litter_the_store() {
|
||||
const FROM_MS: &str = "1725148800000";
|
||||
const TO_MS: &str = "1727740799999";
|
||||
let cwd = temp_cwd("generalize-repeat-idempotent");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let invoke = || {
|
||||
std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
@@ -2085,7 +2181,7 @@ fn generalize_repeated_identical_invocation_does_not_litter_the_store() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura")
|
||||
};
|
||||
@@ -2130,7 +2226,7 @@ fn generalize_repeated_identical_invocation_does_not_litter_the_store() {
|
||||
fn generalize_distinct_invocations_persist_distinct_campaign_documents() {
|
||||
const FROM_MS: &str = "1725148800000";
|
||||
const TO_MS: &str = "1727740799999";
|
||||
let cwd = temp_cwd("generalize-distinct-content");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let invoke = |fast: &str| {
|
||||
let fast_axis = format!("sma_signal.fast.length={fast}");
|
||||
@@ -2141,7 +2237,7 @@ fn generalize_distinct_invocations_persist_distinct_campaign_documents() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura")
|
||||
};
|
||||
@@ -2179,7 +2275,7 @@ fn generalize_distinct_invocations_persist_distinct_campaign_documents() {
|
||||
/// unexercised. Gated on local GER40/USDJPY data.
|
||||
#[test]
|
||||
fn generalize_without_explicit_window_falls_back_to_the_first_symbols_full_window() {
|
||||
let cwd = temp_cwd("generalize-default-window");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -2187,7 +2283,7 @@ fn generalize_without_explicit_window_falls_back_to_the_first_symbols_full_windo
|
||||
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -2197,7 +2293,6 @@ fn generalize_without_explicit_window_falls_back_to_the_first_symbols_full_windo
|
||||
"exit 1 must be a data refusal, got: {stderr}"
|
||||
);
|
||||
eprintln!("skip: no local GER40/USDJPY data");
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
return;
|
||||
}
|
||||
assert_eq!(out.status.code(), Some(0), "exit: {:?}", out.status);
|
||||
@@ -2205,7 +2300,6 @@ fn generalize_without_explicit_window_falls_back_to_the_first_symbols_full_windo
|
||||
assert!(stdout.contains("\"generalize\":"), "aggregate object: {stdout}");
|
||||
assert!(stdout.contains("\"n_instruments\":2"), "two instruments graded: {stdout}");
|
||||
assert!(stdout.contains("\"family_id\":\"generalize-0\""), "family handle: {stdout}");
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// Property: a single-instrument generalize is refused at parse time (exit 2),
|
||||
@@ -2362,7 +2456,7 @@ fn generalize_refuses_an_unknown_axis_name() {
|
||||
fn generalize_persists_a_discoverable_cross_instrument_family() {
|
||||
const FROM_MS: &str = "1725148800000";
|
||||
const TO_MS: &str = "1727740799999";
|
||||
let cwd = temp_cwd("generalize-family");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -2371,7 +2465,7 @@ fn generalize_persists_a_discoverable_cross_instrument_family() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -2381,7 +2475,6 @@ fn generalize_persists_a_discoverable_cross_instrument_family() {
|
||||
"exit 1 must be a data refusal, got: {stderr}"
|
||||
);
|
||||
eprintln!("skip: no local GER40/USDJPY data");
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
return;
|
||||
}
|
||||
assert_eq!(out.status.code(), Some(0), "exit: {:?}", out.status);
|
||||
@@ -2389,7 +2482,7 @@ fn generalize_persists_a_discoverable_cross_instrument_family() {
|
||||
// the run persisted the per-instrument members as a discoverable CrossInstrument family.
|
||||
let fams = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args(["runs", "families"])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn families");
|
||||
assert!(fams.status.success(), "families exit: {:?}", fams.status);
|
||||
@@ -2407,8 +2500,6 @@ fn generalize_persists_a_discoverable_cross_instrument_family() {
|
||||
assert_eq!(cross.len(), 1, "exactly one CrossInstrument family: {fams_out:?}");
|
||||
assert!(cross[0].contains("\"family_id\":\"generalize-0\""), "families: {fams_out:?}");
|
||||
assert!(cross[0].contains("\"members\":2"), "one member per instrument: {fams_out:?}");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// Property (#210 T3, dissolution): the persisted `CrossInstrument` family's
|
||||
@@ -2427,7 +2518,7 @@ fn generalize_persists_a_discoverable_cross_instrument_family() {
|
||||
fn generalize_family_members_are_attributed_to_the_right_instrument() {
|
||||
const FROM_MS: &str = "1725148800000";
|
||||
const TO_MS: &str = "1727740799999";
|
||||
let cwd = temp_cwd("generalize-member-attribution");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -2436,7 +2527,7 @@ fn generalize_family_members_are_attributed_to_the_right_instrument() {
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -2446,14 +2537,13 @@ fn generalize_family_members_are_attributed_to_the_right_instrument() {
|
||||
"exit 1 must be a data refusal, got: {stderr}"
|
||||
);
|
||||
eprintln!("skip: no local GER40/USDJPY data");
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
return;
|
||||
}
|
||||
assert_eq!(out.status.code(), Some(0), "exit: {:?}", out.status);
|
||||
|
||||
let fam = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args(["runs", "family", "generalize-0"])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn runs family");
|
||||
assert!(fam.status.success(), "runs family exit: {:?}", fam.status);
|
||||
@@ -2481,8 +2571,66 @@ fn generalize_family_members_are_attributed_to_the_right_instrument() {
|
||||
members[1]["metrics"]["r"]["expectancy_r"].as_f64(), Some(0.005795903617609842),
|
||||
"USDJPY's own expectancy_r, matching the exact-grade anchor: {fam_out:?}"
|
||||
);
|
||||
}
|
||||
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
/// Property (#218 migration follow-up): `generalize`'s per-instrument members
|
||||
/// route through the shared campaign path (`campaign_run.rs`'s
|
||||
/// `run_blueprint_member` call, a code path distinct from sweep's in
|
||||
/// `main.rs`), and that path ALSO stamps `manifest.project` with the running
|
||||
/// project's provenance (C18/C16) for every persisted member — not just the
|
||||
/// sweep chokepoint. Before the project-fixture migration this cli_run.rs
|
||||
/// path only ever ran outside a project, so `project` was always `None` here.
|
||||
/// Gated on the shared GER40/USDJPY Sept-2024 archive.
|
||||
#[test]
|
||||
fn generalize_family_members_stamp_project_provenance() {
|
||||
const FROM_MS: &str = "1725148800000";
|
||||
const TO_MS: &str = "1727740799999";
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
"generalize", &fixture, "--real", "GER40,USDJPY",
|
||||
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(
|
||||
stderr.contains("no local data") || stderr.contains("no recorded geometry"),
|
||||
"exit 1 must be a data refusal, got: {stderr}"
|
||||
);
|
||||
eprintln!("skip: no local GER40/USDJPY data");
|
||||
return;
|
||||
}
|
||||
assert_eq!(out.status.code(), Some(0), "exit: {:?}", out.status);
|
||||
|
||||
let fam = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args(["runs", "family", "generalize-0"])
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn runs family");
|
||||
assert!(fam.status.success(), "runs family exit: {:?}", fam.status);
|
||||
let fam_out = String::from_utf8(fam.stdout).expect("utf-8");
|
||||
let members: Vec<serde_json::Value> = fam_out
|
||||
.lines()
|
||||
.filter(|l| l.starts_with('{'))
|
||||
.map(|l| serde_json::from_str(l).expect("member line parses as JSON"))
|
||||
.collect();
|
||||
assert_eq!(members.len(), 2, "one member per instrument: {fam_out:?}");
|
||||
for m in &members {
|
||||
let project = &m["manifest"]["project"];
|
||||
assert_eq!(
|
||||
project["namespace"].as_str(), Some("demo"),
|
||||
"campaign-path member manifest carries the fixture project's namespace: {fam_out:?}"
|
||||
);
|
||||
let sha = project["dylib_sha256"].as_str()
|
||||
.unwrap_or_else(|| panic!("dylib_sha256 present: {fam_out:?}"));
|
||||
assert_eq!(sha.len(), 64, "dylib_sha256 is a full sha256 hex digest: {fam_out:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `aura sweep <blueprint.json> --axis <name>=<csv> …` (#166): sweeps a loaded OPEN
|
||||
@@ -3373,7 +3521,7 @@ fn exit_codes_partition_usage_two_from_runtime_one() {
|
||||
fn mc_r_bootstrap_real_e2e_pins_the_exact_current_grade() {
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let cwd = temp_cwd("mc-r-bootstrap-exact-grade");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -3383,7 +3531,7 @@ fn mc_r_bootstrap_real_e2e_pins_the_exact_current_grade() {
|
||||
"--block-len", "5", "--resamples", "1000", "--seed", "42",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -3430,7 +3578,7 @@ fn mc_r_bootstrap_real_e2e_pins_the_exact_current_grade() {
|
||||
fn mc_dissolves_a_non_r_sma_blueprint() {
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let cwd = temp_cwd("mc-non-r-sma");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -3441,7 +3589,7 @@ fn mc_dissolves_a_non_r_sma_blueprint() {
|
||||
"--block-len", "5", "--resamples", "1000", "--seed", "42",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -3486,7 +3634,7 @@ fn mc_dissolves_a_non_r_sma_blueprint() {
|
||||
fn mc_dissolves_through_the_campaign_path() {
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let cwd = temp_cwd("mc-dissolves");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -3496,7 +3644,7 @@ fn mc_dissolves_through_the_campaign_path() {
|
||||
"--block-len", "5", "--resamples", "1000", "--seed", "42",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -3550,7 +3698,7 @@ fn mc_dissolves_through_the_campaign_path() {
|
||||
|
||||
let fams = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args(["runs", "families"])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn families");
|
||||
let fams_out = String::from_utf8_lossy(&fams.stdout).into_owned();
|
||||
@@ -3644,9 +3792,9 @@ fn mc_dissolved_refuses_an_unknown_axis_name() {
|
||||
fn mc_dissolved_seed_changes_the_bootstrap_draw_not_the_pooled_series() {
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let run = |seed: &str| {
|
||||
let cwd = temp_cwd(&format!("mc-seed-{seed}"));
|
||||
std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
"mc", &fixture, "--real", "GER40",
|
||||
@@ -3655,7 +3803,7 @@ fn mc_dissolved_seed_changes_the_bootstrap_draw_not_the_pooled_series() {
|
||||
"--block-len", "5", "--resamples", "1000", "--seed", seed,
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura")
|
||||
};
|
||||
@@ -3707,7 +3855,7 @@ fn walkforward_campaign_runs_an_arbitrary_blueprint() {
|
||||
}
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let cwd = temp_cwd("walkforward-arbitrary-blueprint");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -3717,7 +3865,7 @@ fn walkforward_campaign_runs_an_arbitrary_blueprint() {
|
||||
"--stop-length", "3", "--stop-k", "2.0",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -3759,7 +3907,7 @@ fn mc_campaign_runs_an_arbitrary_blueprint() {
|
||||
}
|
||||
const FROM_MS: &str = "1735689600000";
|
||||
const TO_MS: &str = "1767225599000";
|
||||
let cwd = temp_cwd("mc-arbitrary-blueprint");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_breakout_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -3770,7 +3918,7 @@ fn mc_campaign_runs_an_arbitrary_blueprint() {
|
||||
"--block-len", "5", "--resamples", "100", "--seed", "7",
|
||||
"--from", FROM_MS, "--to", TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
@@ -3808,7 +3956,7 @@ fn generalize_campaign_runs_an_arbitrary_blueprint() {
|
||||
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
||||
return;
|
||||
}
|
||||
let cwd = temp_cwd("generalize-arbitrary-blueprint");
|
||||
let (cwd, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_meanrev_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
@@ -3819,7 +3967,7 @@ fn generalize_campaign_runs_an_arbitrary_blueprint() {
|
||||
"--stop-length", "3", "--stop-k", "2.0",
|
||||
"--from", GER40_SEPT2024_FROM_MS, "--to", GER40_SEPT2024_TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.current_dir(cwd)
|
||||
.output()
|
||||
.expect("spawn aura");
|
||||
if out.status.code() == Some(1) {
|
||||
|
||||
Reference in New Issue
Block a user