f68258b044
Public-interface-only field test of the walking-skeleton closing seam (#8): the `aura run` CLI and the reusable aura-std::Recorder sink. A standalone consumer crate (path-deps on the three engine crates, as a C16 project would) drives the real binary as a subprocess and wires Recorder via the raw Harness::bootstrap API. Examples (all built from HEAD and ran green): - c0010_1: drive `aura run` as a downstream CLI/jq consumer — single-line JSON report, byte-identical across two runs (C1), bad-args → exit 2 + exact usage on stderr with empty stdout. - c0010_2: wire Recorder onto Sma(3) via raw bootstrap, drain the mpsc::Receiver — rows match the rustdoc warm-up model exactly. - c0010_3: Recorder over [I64, Bool, Timestamp] — the four-kind claim (never exercised by the CLI's f64-only path) holds, verified from rustdoc alone. Findings: 0 bugs, 4 working, 1 spec_gap, 1 friction. - [spec_gap] `aura run <trailing-arg>` is accepted (exit 0), not rejected — the arg parse keys only on the first token being `run` and ignores the rest. The cycle_scope's "any other or missing subcommand -> exit 2" does not unambiguously cover `run <junk>`; the binary chose the lenient reading. Tracked for a ratify-or-tighten decision. - [friction] verifying the documented {manifest, metrics} JSON nesting needs a hand-rolled string walker — the zero-dep consumer has only to_json() and no typed read-path. Low-priority tidy. Both non-bug findings filed to the forward queue; neither blocks the cycle.
113 lines
5.4 KiB
Rust
113 lines
5.4 KiB
Rust
//! Fieldtest c0010 #1 — `aura run` as a downstream CLI consumer (axis a).
|
|
//!
|
|
//! Question under test: invoking the REAL `aura` binary the way the deferred
|
|
//! C18 registry or a shell/jq pipeline would — capture stdout, treat it as the
|
|
//! single-line canonical JSON report (manifest + metrics) — does the documented
|
|
//! C14 machine-readable face hold? Specifically:
|
|
//! - `aura run` -> exit 0, one line of JSON on stdout, nothing on stderr;
|
|
//! - the JSON parses as an object with {manifest, metrics} nesting (rustdoc:
|
|
//! "prints the run's metrics + manifest as canonical JSON to stdout");
|
|
//! - two runs are byte-identical (C1 determinism, pinned on the surface);
|
|
//! - missing / unknown subcommand -> exit 2 + `aura: usage: aura run` on
|
|
//! stderr, NOTHING on stdout (cycle_scope bad-args contract).
|
|
//!
|
|
//! This consumer does NOT read crates/*/src. It only runs the binary and
|
|
//! inspects observable behaviour. The binary path comes from $AURA_BIN (a CI
|
|
//! consumer would point this at the built artefact); we default to the
|
|
//! sibling workspace target/debug/aura so `cargo run` works out of the box.
|
|
|
|
use std::process::Command;
|
|
|
|
fn aura_bin() -> String {
|
|
if let Ok(p) = std::env::var("AURA_BIN") {
|
|
return p;
|
|
}
|
|
// Default: the workspace debug artefact, two levels up from this crate.
|
|
let here = env!("CARGO_MANIFEST_DIR");
|
|
format!("{here}/../../target/debug/aura")
|
|
}
|
|
|
|
/// A bare-bones JSON value walker — enough for a consumer to confirm the
|
|
/// documented {manifest, metrics} shape WITHOUT a serde dependency (the
|
|
/// workspace is deliberately zero-external-dependency; a downstream consumer
|
|
/// would reach for serde_json or jq, but here we only assert structural facts
|
|
/// the rustdoc promises). Returns the substring value following `"key":`.
|
|
fn raw_value_after<'a>(json: &'a str, key: &str) -> Option<&'a str> {
|
|
let needle = format!("\"{key}\":");
|
|
let idx = json.find(&needle)? + needle.len();
|
|
Some(&json[idx..])
|
|
}
|
|
|
|
fn run_capture(args: &[&str]) -> (String, String, i32) {
|
|
let out = Command::new(aura_bin())
|
|
.args(args)
|
|
.output()
|
|
.unwrap_or_else(|e| panic!("failed to spawn {}: {e}", aura_bin()));
|
|
(
|
|
String::from_utf8_lossy(&out.stdout).to_string(),
|
|
String::from_utf8_lossy(&out.stderr).to_string(),
|
|
out.status.code().unwrap_or(-1),
|
|
)
|
|
}
|
|
|
|
fn main() {
|
|
println!("using binary: {}", aura_bin());
|
|
|
|
// --- `aura run`: exit 0, one JSON line on stdout, clean stderr ---
|
|
let (stdout, stderr, code) = run_capture(&["run"]);
|
|
println!("`aura run` exit={code}");
|
|
println!("`aura run` stdout = {stdout:?}");
|
|
assert_eq!(code, 0, "aura run must exit 0");
|
|
assert!(stderr.is_empty(), "aura run must not write stderr, got {stderr:?}");
|
|
|
|
let line = stdout.trim_end_matches('\n');
|
|
assert!(!line.contains('\n'), "report must be a single line of JSON");
|
|
assert!(line.starts_with('{') && line.ends_with('}'), "report is a JSON object");
|
|
|
|
// Documented nesting: a `manifest` object and a `metrics` object.
|
|
assert!(raw_value_after(line, "manifest").is_some(), "manifest key present");
|
|
assert!(raw_value_after(line, "metrics").is_some(), "metrics key present");
|
|
// Documented manifest fields (RunManifest rustdoc: commit/params/window/seed/broker).
|
|
for k in ["commit", "params", "window", "seed", "broker"] {
|
|
assert!(raw_value_after(line, k).is_some(), "manifest.{k} key present");
|
|
}
|
|
// Documented metrics fields (RunMetrics rustdoc).
|
|
for k in ["total_pips", "max_drawdown", "exposure_sign_flips"] {
|
|
assert!(raw_value_after(line, k).is_some(), "metrics.{k} key present");
|
|
}
|
|
|
|
// --- determinism: two runs byte-identical (C1) ---
|
|
let (stdout2, _, _) = run_capture(&["run"]);
|
|
assert_eq!(stdout, stdout2, "two runs must be byte-identical (C1)");
|
|
println!("determinism: two runs byte-identical OK");
|
|
|
|
// --- bad-args contract: missing subcommand ---
|
|
let (out, err, code) = run_capture(&[]);
|
|
println!("`aura` (no args) exit={code} stderr={err:?}");
|
|
assert_eq!(code, 2, "no subcommand must exit 2");
|
|
assert!(out.is_empty(), "no subcommand must print nothing on stdout, got {out:?}");
|
|
assert_eq!(err.trim_end(), "aura: usage: aura run", "exact usage line on stderr");
|
|
|
|
// --- bad-args contract: unknown subcommand ---
|
|
let (out, err, code) = run_capture(&["play"]);
|
|
println!("`aura play` exit={code} stderr={err:?}");
|
|
assert_eq!(code, 2, "unknown subcommand must exit 2");
|
|
assert!(out.is_empty(), "unknown subcommand must print nothing on stdout");
|
|
assert_eq!(err.trim_end(), "aura: usage: aura run", "exact usage line on stderr");
|
|
|
|
// --- probe: trailing junk after `run`. The cycle_scope says "Any other or
|
|
// missing subcommand prints usage and exits 2"; `run extra` is neither a
|
|
// clean `run` nor a different subcommand. What does the binary do? ---
|
|
let (out, err, code) = run_capture(&["run", "extra-garbage"]);
|
|
println!("`aura run extra-garbage` exit={code} stdout_len={} stderr={err:?}", out.len());
|
|
// We RECORD the behaviour; the reading we assert is the lenient one the
|
|
// binary actually exhibits, and flag the spec ambiguity in the report.
|
|
if code == 0 {
|
|
println!("NOTE: `aura run <extra>` was ACCEPTED (exit 0) — trailing args ignored.");
|
|
} else {
|
|
println!("NOTE: `aura run <extra>` was REJECTED (exit {code}).");
|
|
}
|
|
|
|
println!("c0010_1 OK: aura run -> JSON report; determinism + bad-args contract observed");
|
|
}
|