//! End-to-end coverage for the `aura process` verb family (#188/#189): drives //! the built `aura` binary over argv/file, the exact surface a data-level //! author uses. use std::path::{Path, PathBuf}; /// A fresh, unique working directory for a process test that persists /// content-addressed documents under `./runs/` (so `aura process register` /// never dirties the repo). Unique per test + per process; no external /// tempfile dependency (mirrors `cli_run.rs` / `cli_broken_pipe.rs`). fn temp_cwd(name: &str) -> PathBuf { let dir = std::env::temp_dir().join(format!("aura-cli-{}-{}", std::process::id(), name)); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).expect("create temp cwd"); dir } fn run_code_in(dir: &Path, args: &[&str]) -> (String, Option) { let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura")) .args(args) .current_dir(dir) .output() .expect("binary runs"); let text = format!( "{}{}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); (text, out.status.code()) } fn run_code(args: &[&str]) -> (String, Option) { run_code_in(Path::new("."), args) } const PROCESS_DOC: &str = r#"{ "format_version": 1, "kind": "process", "name": "wf-deflated-screen", "pipeline": [ { "block": "std::sweep", "metric": "net_expectancy_r", "select": "plateau:worst", "deflate": true }, { "block": "std::gate", "all": [ { "metric": "net_expectancy_r", "cmp": "gt", "value": 0.0 }, { "metric": "overfit_probability", "cmp": "lt", "value": 0.1 } ] }, { "block": "std::walk_forward", "folds": 4, "in_sample_bars": 4000, "out_of_sample_bars": 1000, "metric": "net_expectancy_r", "select": "argmax" } ] }"#; fn write_doc(dir: &Path, name: &str, text: &str) -> PathBuf { let p = dir.join(name); std::fs::write(&p, text).expect("write doc"); p } #[test] fn process_validate_reports_intrinsic_ok() { let dir = temp_cwd("process-validate-ok"); write_doc(&dir, "p.process.json", PROCESS_DOC); let (out, code) = run_code_in(&dir, &["process", "validate", "p.process.json"]); assert_eq!(code, Some(0), "stdout/stderr: {out}"); assert!(out.contains("process document valid (intrinsic): 3 pipeline blocks, 2 gate predicates")); } #[test] fn process_validate_refuses_unknown_metric_as_prose_exit_1() { let dir = temp_cwd("process-validate-bad-metric"); let bad = PROCESS_DOC.replacen( "net_expectancy_r\", \"select\": \"plateau:worst", "netto_r\", \"select\": \"plateau:worst", 1, ); write_doc(&dir, "bad.process.json", &bad); let (out, code) = run_code_in(&dir, &["process", "validate", "bad.process.json"]); assert_eq!(code, Some(1)); assert!(out.contains("unknown metric \"netto_r\"")); assert!(!out.contains("UnknownMetric"), "Debug leak: {out}"); } #[test] fn process_introspect_vocabulary_block_and_content_id() { let (out, code) = run_code(&["process", "introspect", "--vocabulary"]); assert_eq!(code, Some(0)); for id in ["std::sweep", "std::gate", "std::walk_forward", "std::monte_carlo", "std::generalize"] { assert!(out.contains(id), "vocabulary misses {id}: {out}"); } let (out, code) = run_code(&["process", "introspect", "--block", "std::sweep"]); assert_eq!(code, Some(0)); assert!(out.contains("metric")); assert!(out.contains("required")); let dir = temp_cwd("process-introspect-content-id"); write_doc(&dir, "p.process.json", PROCESS_DOC); let (out, code) = run_code_in(&dir, &["process", "introspect", "--content-id", "p.process.json"]); assert_eq!(code, Some(0), "stdout/stderr: {out}"); let line = out.lines().find(|l| l.starts_with("content:")).expect("id line"); assert_eq!(line.len(), "content:".len() + 64); } #[test] fn process_introspect_unknown_block_is_prose_exit_1() { let (out, code) = run_code(&["process", "introspect", "--block", "std::nope"]); assert_eq!(code, Some(1), "stdout/stderr: {out}"); assert!(out.contains("unknown block \"std::nope\""), "stdout/stderr: {out}"); } #[test] fn process_introspect_unwired_lists_open_slots() { let dir = temp_cwd("process-introspect-unwired"); let partial = r#"{ "format_version": 1, "kind": "process", "pipeline": [ { "block": "std::sweep", "metric": "sqn" } ] }"#; write_doc(&dir, "partial.process.json", partial); let (out, code) = run_code_in(&dir, &["process", "introspect", "--unwired", "partial.process.json"]); assert_eq!(code, Some(0)); assert!(out.contains("open slot: name")); assert!(out.contains("open slot: pipeline[0].select")); } #[test] fn process_introspect_no_flag_is_usage_exit_2() { let (_out, code) = run_code(&["process", "introspect"]); assert_eq!(code, Some(2)); } #[test] fn process_register_stores_content_addressed_under_runs_root() { let dir = temp_cwd("process-register"); write_doc(&dir, "p.process.json", PROCESS_DOC); let (out, code) = run_code_in(&dir, &["process", "register", "p.process.json"]); assert_eq!(code, Some(0), "stdout/stderr: {out}"); let line = out.lines().find(|l| l.starts_with("registered process content:")).expect("line"); let id = line .trim_start_matches("registered process content:") .split(' ') .next() .expect("id"); assert!(dir.join("runs").join("processes").join(format!("{id}.json")).is_file()); }