2026-07-12 06:42:13 +00:00
|
|
|
use std::fs;
|
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
use std::process::Command;
|
|
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
|
|
|
|
|
|
fn temp_project(name: &str) -> PathBuf {
|
|
|
|
|
let unique = SystemTime::now()
|
|
|
|
|
.duration_since(UNIX_EPOCH)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.as_nanos();
|
|
|
|
|
let root = std::env::temp_dir().join(format!("cstat-call-trace-cli-{name}-{unique}"));
|
|
|
|
|
fs::create_dir_all(root.join("src")).unwrap();
|
|
|
|
|
fs::write(
|
|
|
|
|
root.join("Cargo.toml"),
|
|
|
|
|
"[package]\nname = \"fixture\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
fs::write(
|
|
|
|
|
root.join("src/main.rs"),
|
|
|
|
|
"fn main() { parse(); }\nfn parse() { leaf(); }\nfn leaf() {}\n",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
root
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run_cstat(project: &Path, extra_args: &[&str]) -> std::process::Output {
|
|
|
|
|
let bin = env!("CARGO_BIN_EXE_cstat");
|
|
|
|
|
let mut command = Command::new(bin);
|
|
|
|
|
command.args(["--no-color", "--path"]);
|
|
|
|
|
command.arg(project);
|
|
|
|
|
command.args(extra_args);
|
|
|
|
|
command.output().expect("invoke cstat binary")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn assert_success(output: &std::process::Output) {
|
|
|
|
|
assert!(
|
|
|
|
|
output.status.success(),
|
|
|
|
|
"cstat failed: status={:?}\nstderr={}\nstdout={}",
|
|
|
|
|
output.status,
|
|
|
|
|
String::from_utf8_lossy(&output.stderr),
|
|
|
|
|
String::from_utf8_lossy(&output.stdout),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn call_trace_infers_cargo_run_entry() {
|
|
|
|
|
let root = temp_project("default");
|
|
|
|
|
let output = run_cstat(&root, &["call-trace"]);
|
|
|
|
|
assert_success(&output);
|
|
|
|
|
|
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
|
|
|
assert!(stdout.contains("cstat call trace"), "stdout={stdout}");
|
2026-07-23 05:55:53 +00:00
|
|
|
assert!(
|
|
|
|
|
stdout.contains("static possible-call expansion; not a runtime execution trace"),
|
|
|
|
|
"stdout={stdout}"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
stdout.contains("static expansion summary:"),
|
|
|
|
|
"stdout={stdout}"
|
|
|
|
|
);
|
2026-07-12 06:42:13 +00:00
|
|
|
assert!(
|
|
|
|
|
stdout.contains("cargo command: cargo run --"),
|
|
|
|
|
"stdout={stdout}"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
stdout.contains("entry: src/main.rs::main"),
|
|
|
|
|
"stdout={stdout}"
|
|
|
|
|
);
|
|
|
|
|
assert!(stdout.contains("main"), "stdout={stdout}");
|
|
|
|
|
assert!(stdout.contains("parse"), "stdout={stdout}");
|
|
|
|
|
|
|
|
|
|
fs::remove_dir_all(root).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn call_trace_honors_explicit_entry() {
|
|
|
|
|
let root = temp_project("explicit");
|
|
|
|
|
let output = run_cstat(&root, &["call-trace", "--entry", "src/main.rs::parse"]);
|
|
|
|
|
assert_success(&output);
|
|
|
|
|
|
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
|
|
|
assert!(
|
|
|
|
|
stdout.contains("entry: src/main.rs::parse"),
|
|
|
|
|
"stdout={stdout}"
|
|
|
|
|
);
|
|
|
|
|
assert!(stdout.contains("\nparse\n"), "stdout={stdout}");
|
|
|
|
|
assert!(
|
|
|
|
|
!stdout.contains("cargo command: cargo run --"),
|
|
|
|
|
"stdout={stdout}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
fs::remove_dir_all(root).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn call_trace_entry_dir_lists_parseable_files() {
|
|
|
|
|
let root = temp_project("entry-dir");
|
|
|
|
|
fs::write(root.join("src/broken.rs"), "fn broken(\n").unwrap();
|
|
|
|
|
|
|
|
|
|
let output = run_cstat(&root, &["call-trace", "--entry", "src"]);
|
|
|
|
|
assert_success(&output);
|
|
|
|
|
|
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
|
|
|
assert!(
|
|
|
|
|
stdout.contains("parseable files under src:"),
|
|
|
|
|
"stdout={stdout}"
|
|
|
|
|
);
|
|
|
|
|
assert!(stdout.contains("src/main.rs"), "stdout={stdout}");
|
|
|
|
|
assert!(!stdout.contains("src/broken.rs"), "stdout={stdout}");
|
|
|
|
|
assert!(!stdout.contains("summary:"), "stdout={stdout}");
|
|
|
|
|
|
|
|
|
|
fs::remove_dir_all(root).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn call_trace_entry_file_lists_entrypoints() {
|
|
|
|
|
let root = temp_project("entry-file");
|
|
|
|
|
|
|
|
|
|
let output = run_cstat(&root, &["call-trace", "--entry", "src/main.rs"]);
|
|
|
|
|
assert_success(&output);
|
|
|
|
|
|
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
|
|
|
assert!(
|
|
|
|
|
stdout.contains("entrypoints in src/main.rs:"),
|
|
|
|
|
"stdout={stdout}"
|
|
|
|
|
);
|
|
|
|
|
assert!(stdout.contains("src/main.rs::main"), "stdout={stdout}");
|
|
|
|
|
assert!(stdout.contains("src/main.rs::parse"), "stdout={stdout}");
|
|
|
|
|
assert!(stdout.contains("src/main.rs::leaf"), "stdout={stdout}");
|
|
|
|
|
assert!(!stdout.contains("summary:"), "stdout={stdout}");
|
|
|
|
|
|
|
|
|
|
fs::remove_dir_all(root).unwrap();
|
|
|
|
|
}
|