use std::process::Command;
const AXON_BIN: &str = env!("CARGO_BIN_EXE_axon");
fn workspace_root() -> std::path::PathBuf {
let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest.parent().expect("axon-rs parent dir").to_path_buf()
}
fn valid_example_path() -> std::path::PathBuf {
workspace_root().join("examples").join("contract_analyzer.axon")
}
fn valid_trace_path() -> std::path::PathBuf {
workspace_root().join("examples").join("sample.trace.json")
}
fn run_axon(args: &[&str]) -> (i32, String, String) {
let out = Command::new(AXON_BIN)
.args(args)
.output()
.expect("subprocess-invoke axon binary");
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
let code = out.status.code().unwrap_or(-1);
(code, stdout, stderr)
}
#[test]
fn fase39f_s1_version_emits_canonical_string() {
let (code, stdout, stderr) = run_axon(&["version"]);
assert_eq!(code, 0, "version MUST exit 0. stderr: {stderr}");
assert!(
stdout.starts_with("axon-lang "),
"version stdout MUST start with `axon-lang `. Got: {stdout:?}"
);
let trimmed = stdout.trim();
assert!(
trimmed.contains(axon::runner::AXON_VERSION),
"version stdout MUST contain the canonical version slug. \
Got: {trimmed:?}; expected version: {}",
axon::runner::AXON_VERSION
);
}
#[test]
fn fase39f_s2_check_success_exits_zero() {
let example = valid_example_path();
if !example.exists() {
eprintln!("skipped: example not present at {}", example.display());
return;
}
let example_str = example.to_string_lossy().into_owned();
let (code, _stdout, stderr) = run_axon(&["check", &example_str, "--no-color"]);
assert_eq!(
code, 0,
"check on a valid example MUST exit 0. stderr: {stderr}"
);
}
#[test]
fn fase39f_s3_check_missing_file_exits_two() {
let (code, stdout, stderr) =
run_axon(&["check", "examples/__missing__.axon", "--no-color"]);
assert_eq!(code, 2, "missing file MUST exit 2. stdout: {stdout}");
let combined = format!("{stdout}{stderr}");
assert!(
combined.contains("File not found")
|| combined.contains("__missing__"),
"missing-file diagnostic MUST mention the path. Combined: {combined:?}"
);
}
#[test]
fn fase39f_s4_compile_stdout_emits_valid_json() {
let example = valid_example_path();
if !example.exists() {
eprintln!("skipped: example not present at {}", example.display());
return;
}
let example_str = example.to_string_lossy().into_owned();
let (code, stdout, stderr) = run_axon(&["compile", &example_str, "--stdout"]);
assert_eq!(code, 0, "compile --stdout MUST exit 0. stderr: {stderr}");
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("compile --stdout MUST emit valid JSON");
assert_eq!(
parsed.get("node_type").and_then(|v| v.as_str()),
Some("program"),
"compile --stdout top-level MUST be a `program` IR node"
);
}
#[test]
fn fase39f_s5_compile_missing_file_exits_two() {
let (code, stdout, _stderr) =
run_axon(&["compile", "examples/__missing__.axon"]);
assert_eq!(code, 2, "compile on missing file MUST exit 2");
assert!(stdout.is_empty(), "compile error path MUST NOT write to stdout");
}
#[test]
fn fase39f_s6_trace_success_emits_header() {
let trace = valid_trace_path();
if !trace.exists() {
eprintln!("skipped: sample trace not present at {}", trace.display());
return;
}
let trace_str = trace.to_string_lossy().into_owned();
let (code, stdout, stderr) = run_axon(&["trace", &trace_str, "--no-color"]);
assert_eq!(code, 0, "trace on a valid sample MUST exit 0. stderr: {stderr}");
assert!(
stdout.contains("AXON Execution Trace"),
"trace stdout MUST contain the header. Got: {stdout:?}"
);
}
#[test]
fn fase39f_s7_parse_clean_source_emits_no_diagnostics() {
let example = valid_example_path();
if !example.exists() {
eprintln!("skipped: example not present at {}", example.display());
return;
}
let example_str = example.to_string_lossy().into_owned();
let (code, stdout, _stderr) = run_axon(&["parse", &example_str]);
assert_eq!(
code, 0,
"parse on a clean source MUST exit 0. stdout: {stdout}"
);
assert!(
stdout.contains("no diagnostics") || stdout.is_empty(),
"parse on a clean source MUST emit the 'no diagnostics' \
marker (or empty when machine-fed). Got: {stdout:?}"
);
}
#[test]
fn fase39f_s8_parse_json_format_emits_valid_json() {
let example = valid_example_path();
if !example.exists() {
eprintln!("skipped: example not present at {}", example.display());
return;
}
let example_str = example.to_string_lossy().into_owned();
let (code, stdout, _stderr) =
run_axon(&["parse", &example_str, "--json", "--format", "array"]);
assert_eq!(code, 0, "parse --json MUST exit 0 on clean source");
let parsed: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("parse --json MUST emit valid JSON");
assert!(
parsed.is_array(),
"parse --json --format=array MUST emit a JSON array. Got: {parsed:?}"
);
}
#[test]
fn fase39f_s9_fmt_idempotent_on_well_formed_source() {
let example = valid_example_path();
if !example.exists() {
eprintln!("skipped: example not present at {}", example.display());
return;
}
let example_str = example.to_string_lossy().into_owned();
let (code, stdout_once, _stderr) = run_axon(&["fmt", &example_str]);
assert_eq!(code, 0, "fmt on a well-formed source MUST exit 0");
assert!(
!stdout_once.is_empty(),
"fmt MUST emit the formatted source on stdout"
);
let twice = axon::cli_fmt::format_source(&stdout_once).expect("re-format");
assert_eq!(
stdout_once, twice,
"fmt MUST be idempotent: format(format(x)) == format(x)"
);
}
#[test]
fn fase39f_s10_fmt_check_mode_well_formed_exits_zero() {
let tmpdir = std::env::temp_dir().join(format!(
"axon-fase39f-fmt-{}",
std::process::id()
));
let _ = std::fs::create_dir_all(&tmpdir);
let temp_file = tmpdir.join("hello.axon");
let well_formed = "persona Alice {\n confidence_threshold: 0.85\n}\n";
std::fs::write(&temp_file, well_formed).expect("write temp file");
let path_str = temp_file.to_string_lossy().into_owned();
let (code, _stdout, stderr) = run_axon(&["fmt", &path_str, "--check"]);
assert!(
code == 0 || code == 1,
"fmt --check on a well-formed source MUST exit 0 (clean) \
or 1 (would-reformat); got code {code}. stderr: {stderr}"
);
let _ = std::fs::remove_file(&temp_file);
let _ = std::fs::remove_dir(&tmpdir);
}
#[test]
fn fase39f_s_static_grep_all_subcommands_present() {
let src = std::fs::read_to_string("src/main.rs").expect("read main.rs");
let required_subcommands = [
("Check", "check command MUST be declared"),
("Compile", "compile command MUST be declared"),
("Trace", "trace command MUST be declared"),
("Parse", "parse command MUST be declared (39.f addition)"),
("Serve", "serve command MUST be declared"),
("Store", "store command MUST be declared"),
("Fmt", "fmt command MUST be declared (39.f addition)"),
("Version", "version command MUST be declared"),
];
for (variant, msg) in required_subcommands {
assert!(
src.contains(&format!(" {variant} ")) || src.contains(&format!(" {variant},")),
"§39.f §S1 — main.rs Commands enum MUST declare `{variant}`. {msg}"
);
}
}
#[test]
fn fase39f_s_static_grep_parse_fmt_dispatchers_wired() {
let src = std::fs::read_to_string("src/main.rs").expect("read main.rs");
assert!(
src.contains("Commands::Parse {"),
"§39.f §S2 — main.rs MUST have a `Commands::Parse {{ ... }}` \
match arm wiring the parse dispatcher"
);
assert!(
src.contains("Commands::Fmt {"),
"§39.f §S2 — main.rs MUST have a `Commands::Fmt {{ ... }}` \
match arm wiring the fmt dispatcher"
);
assert!(
src.contains("axon::cli_parse::run_parse"),
"§39.f §S2 — the parse dispatcher MUST delegate to \
axon::cli_parse::run_parse"
);
assert!(
src.contains("axon::cli_fmt::format_source"),
"§39.f §S2 — the fmt dispatcher MUST delegate to \
axon::cli_fmt::format_source"
);
}