test(cli): synthetic per-test M1 archive — no-window generalize tests go hostless
The two generalize E2E tests whose property is no-window span resolution (shared-window fallback, intersection semantics) no longer stream the 6.6 GB host archive: fresh_project_with_data() generates a deterministic two-symbol M1 archive (SYMA 2024-01..08, SYMB 2024-03..06 strictly inside it, so the intersection differs from symbols[0]'s window on both bounds) in the exact zip/48-byte-record format data-server reads, plus geometry sidecars, under the per-test project's paths.data. Both tests drop their local_data_present gates — they now run on any host, data mount or not. ~35s and ~50s become 0.74s and 1.14s; the full cli_run binary lands at ~25s (211s at the branch base). Full workspace suite: 247s -> 45.7s. refs #250
This commit is contained in:
@@ -5,7 +5,7 @@ use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
mod common;
|
||||
use common::fresh_project;
|
||||
use common::{fresh_project, fresh_project_with_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`).
|
||||
@@ -3471,30 +3471,24 @@ fn generalize_distinct_invocations_persist_distinct_campaign_documents() {
|
||||
/// independent full window). A wrong intersection (or a panic on the
|
||||
/// per-symbol probe) in that fallback would only surface when `--from`/`--to`
|
||||
/// are absent — every other generalize e2e pins the explicit-window path,
|
||||
/// leaving this one unexercised. Gated on local GER40/USDJPY data.
|
||||
/// leaving this one unexercised. The property is the no-window RESOLUTION
|
||||
/// mechanics, not any particular archive's span, so it runs against
|
||||
/// `fresh_project_with_data`'s tiny synthetic two-symbol archive (#250) —
|
||||
/// hostless, no data-refusal skip arm needed.
|
||||
#[test]
|
||||
fn generalize_without_explicit_window_resolves_a_shared_window_and_completes() {
|
||||
let (cwd, _g) = fresh_project();
|
||||
let (cwd, _g) = fresh_project_with_data();
|
||||
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",
|
||||
"generalize", &fixture, "--real", "SYMA,SYMB",
|
||||
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
|
||||
"--stop-length", "14", "--stop-k", "2.0",
|
||||
])
|
||||
.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);
|
||||
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");
|
||||
assert!(stdout.contains("\"generalize\":"), "aggregate object: {stdout}");
|
||||
assert!(stdout.contains("\"n_instruments\":2"), "two instruments graded: {stdout}");
|
||||
@@ -3510,24 +3504,21 @@ fn generalize_without_explicit_window_resolves_a_shared_window_and_completes() {
|
||||
/// instrument conflates the instrument axis with the period axis, so the common
|
||||
/// window is the only one that isolates what the verb measures. Today's fallback
|
||||
/// picks `symbols[0]`'s full window alone (the superseded shape test above):
|
||||
/// listing the WIDER symbol first (`AAPL.US`, archive from 2006) keeps AAPL's
|
||||
/// early start, whereas the intersection with the later-starting `GER40` (archive
|
||||
/// from 2014) must start at GER40's first bar and end at whichever archive ends
|
||||
/// first — so on the current tree the resolved window differs from the
|
||||
/// listing the WIDER synthetic symbol first (`SYMA`, spanning 2024-01..08) keeps
|
||||
/// SYMA's early start, whereas the intersection with `SYMB` (spanning 2024-03..06,
|
||||
/// strictly inside SYMA's span) must start at SYMB's first bar and end at SYMB's
|
||||
/// last — so on the current tree the resolved window differs from the
|
||||
/// intersection on both bounds. Each symbol's full window is resolved
|
||||
/// independently here: a single-symbol `sweep` with no window records it into its
|
||||
/// generated campaign document, in the same Unix-ms currency and via the same
|
||||
/// `campaign_window_ms(full_window)` probe generalize itself resolves against —
|
||||
/// so the expected intersection is derived from the live archive, never a
|
||||
/// hard-coded span that shifts as the archive grows. Archive-gated; skips
|
||||
/// cleanly on a data-less host.
|
||||
/// hard-coded span. The archive's SPAN SHAPE is authored (#250,
|
||||
/// `fresh_project_with_data`), not its size, so this runs hostless in
|
||||
/// milliseconds instead of streaming two full host archives.
|
||||
#[test]
|
||||
fn generalize_without_explicit_window_resolves_the_intersection_of_all_symbols() {
|
||||
if !local_data_present() {
|
||||
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
||||
return;
|
||||
}
|
||||
let (cwd, _g) = fresh_project();
|
||||
let (cwd, _g) = fresh_project_with_data();
|
||||
let fixture = format!("{}/examples/r_sma_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
// The resolved shared window persists verbatim in the generated campaign
|
||||
@@ -3571,24 +3562,24 @@ fn generalize_without_explicit_window_resolves_the_intersection_of_all_symbols()
|
||||
window_of(name)
|
||||
};
|
||||
|
||||
let aapl = probe("AAPL.US", "probe-aapl");
|
||||
let ger40 = probe("GER40", "probe-ger40");
|
||||
let syma = probe("SYMA", "probe-syma");
|
||||
let symb = probe("SYMB", "probe-symb");
|
||||
// Precondition: the archives genuinely differ, so "the intersection" is a
|
||||
// distinct claim from "symbols[0]'s full window" (else the test is vacuous).
|
||||
assert_ne!(
|
||||
aapl, ger40,
|
||||
"AAPL.US {aapl:?} and GER40 {ger40:?} must span different windows for the test to bite"
|
||||
syma, symb,
|
||||
"SYMA {syma:?} and SYMB {symb:?} must span different windows for the test to bite"
|
||||
);
|
||||
|
||||
// The contract: the shared window is the intersection — latest start, earliest end.
|
||||
let expected = (aapl.0.max(ger40.0), aapl.1.min(ger40.1));
|
||||
let expected = (syma.0.max(symb.0), syma.1.min(symb.1));
|
||||
|
||||
// AAPL.US listed FIRST: today's `symbols[0]` fallback keeps AAPL's (wider)
|
||||
// SYMA listed FIRST: today's `symbols[0]` fallback keeps SYMA's (wider)
|
||||
// window, which differs from the intersection on both bounds — so a pass here
|
||||
// can only mean the intersection semantics landed.
|
||||
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||
.args([
|
||||
"generalize", &fixture, "--real", "AAPL.US,GER40",
|
||||
"generalize", &fixture, "--real", "SYMA,SYMB",
|
||||
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
|
||||
"--name", "gen-both",
|
||||
])
|
||||
@@ -3604,8 +3595,8 @@ fn generalize_without_explicit_window_resolves_the_intersection_of_all_symbols()
|
||||
|
||||
assert_eq!(
|
||||
resolved, expected,
|
||||
"no-window generalize must resolve the INTERSECTION {expected:?} of AAPL.US {aapl:?} \
|
||||
and GER40 {ger40:?} — not symbols[0]'s ({aapl:?}) full window (resolved {resolved:?})"
|
||||
"no-window generalize must resolve the INTERSECTION {expected:?} of SYMA {syma:?} \
|
||||
and SYMB {symb:?} — not symbols[0]'s ({syma:?}) full window (resolved {resolved:?})"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user