diff --git a/crates/aura-cli/tests/cli_run.rs b/crates/aura-cli/tests/cli_run.rs index 13cb19a..c139755 100644 --- a/crates/aura-cli/tests/cli_run.rs +++ b/crates/aura-cli/tests/cli_run.rs @@ -5,7 +5,7 @@ use std::path::Path; use std::process::Command; mod common; -use common::{fresh_project, fresh_project_with_data}; +use common::{fresh_project, fresh_project_with_data, fresh_project_with_gapped_data}; /// Path to the freshly-built `aura` binary (Cargo sets this env var for the test /// crate; the binary is named `aura` in `Cargo.toml`). @@ -5862,3 +5862,45 @@ fn shipped_r_meanrev_example_runs_end_to_end_via_aura_run() { assert!(stdout.contains("\"n_trades\":0"), "{stdout}"); assert!(stdout.contains("\"total_pips\":0.0"), "{stdout}"); } + +/// Property (#264, coverage cut 1): `aura data coverage ` surfaces the +/// archive's month COVERAGE from the monthly file index — the first and last +/// present month AND every interior missing-month range (a gap) — so the Copper +/// failure mode is visible up front. Copper had files from 2016-08 but was +/// missing 2018-01..2018-10, so a first/last-bounds check passed while a +/// window opening in the hole had no data, aborting a multi-hour campaign +/// mid-run; a per-symbol gap report would have caught it before the run. Over a +/// hermetic synthetic archive whose `GAPSYM` has 2024-01..02 and 2024-05..06 +/// present and 2024-03..04 absent, the verb exits 0, frames the span by its +/// first (2024-01) and last (2024-06) present month, and reports the interior +/// hole as a single collapsed month-range line `missing: 2024-03..2024-04` +/// (one line for the whole contiguous gap, as Copper's ten-month hole would be +/// one line — not one line per missing month). +#[test] +fn data_coverage_reports_month_span_and_interior_gap_range() { + let (cwd, _g) = fresh_project_with_gapped_data(); + let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) + .args(["data", "coverage", "GAPSYM"]) + .current_dir(&cwd) + .output() + .expect("spawn aura data coverage"); + assert_eq!( + out.status.code(), + Some(0), + "exit: {:?}, stderr: {}", + out.status, + String::from_utf8_lossy(&out.stderr) + ); + let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout"); + // The interior gap is the headline — the Copper failure the verb exists to + // surface — reported as one collapsed range for the whole contiguous hole. + assert!( + stdout.contains("missing: 2024-03..2024-04"), + "interior gap month-range reported as one line: {stdout}" + ); + // The span bounds frame the gap: first and last present month. + assert!( + stdout.contains("2024-01") && stdout.contains("2024-06"), + "first (2024-01) and last (2024-06) present month reported: {stdout}" + ); +} diff --git a/crates/aura-cli/tests/common/mod.rs b/crates/aura-cli/tests/common/mod.rs index ad139ff..8b3697b 100644 --- a/crates/aura-cli/tests/common/mod.rs +++ b/crates/aura-cli/tests/common/mod.rs @@ -127,6 +127,26 @@ pub fn fresh_project_with_data() -> (PathBuf, FreshProject) { (root.clone(), FreshProject { root }) } +/// Like [`fresh_project_with_data`], but the single symbol `GAPSYM` carries a +/// DELIBERATE interior month gap: monthly files exist for 2024-01..2024-02 and +/// 2024-05..2024-06, while 2024-03..2024-04 are absent. This is the Copper +/// failure shape (#264 — files present at both ends while an interior window +/// has no data, so a first/last-bounds check passes but a campaign aborts +/// mid-run). For tests whose property is per-symbol month-gap reporting: the +/// two `write_symbol_archive` calls write disjoint contiguous segments into the +/// same `data/` dir (the second re-writes GAPSYM's identical geometry sidecar), +/// leaving March and April as a real hole in the monthly file index. +#[allow(dead_code)] +pub fn fresh_project_with_gapped_data() -> (PathBuf, FreshProject) { + let root = mint_tempdir(); + write_project_files(&root, Some("data")); + let data_dir = root.join("data"); + std::fs::create_dir_all(&data_dir).expect("create synthetic gapped archive dir"); + synthetic_data::write_symbol_archive(&data_dir, "GAPSYM", (2024, 1), (2024, 2)); + synthetic_data::write_symbol_archive(&data_dir, "GAPSYM", (2024, 5), (2024, 6)); + (root.clone(), FreshProject { root }) +} + /// A scratch filesystem entry a test writes under the git-tracked /// demo-project fixture root (only `runs/` there is fixture-gitignored), /// removed on drop — including during a mid-test panic — so a failed