cstat/tests/loc_cli.rs

309 lines
8.9 KiB
Rust
Raw Normal View History

2026-07-21 07:01:47 +00:00
use serde_json::Value;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
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-loc-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/lib.rs"),
r#"
pub fn live() {
helper();
}
/*
block comment only
*/
fn helper() {}
/* single-line block comment only */
fn orphan() {
// comment-only
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn covers_live() {
live();
}
}
"#,
)
.unwrap();
root
}
fn run_cstat_raw(args: &[&str]) -> Output {
let bin = env!("CARGO_BIN_EXE_cstat");
let mut command = Command::new(bin);
command.args(args);
command.output().expect("invoke cstat binary")
}
fn run_cstat(path: &Path, args: &[&str]) -> Output {
let bin = env!("CARGO_BIN_EXE_cstat");
let mut command = Command::new(bin);
command.args(["--no-color", "--path"]);
command.arg(path);
command.args(args);
command.output().expect("invoke cstat binary")
}
fn assert_success(output: &Output) {
assert!(
output.status.success(),
"cstat failed: status={:?}\nstderr={}\nstdout={}",
output.status,
String::from_utf8_lossy(&output.stderr),
String::from_utf8_lossy(&output.stdout),
);
}
fn span_array_contains_function(array: &Value, expected: &str) -> bool {
array
.as_array()
.expect("span array")
.iter()
.any(|item| item.get("function").and_then(Value::as_str) == Some(expected))
}
#[test]
fn loc_help_exposes_project_and_selected_file_modes() {
let output = run_cstat_raw(&["loc", "--help"]);
assert_success(&output);
let stdout = String::from_utf8_lossy(&output.stdout);
for expected in [
"Rust project directory or Rust source file",
"Project mode:",
"Selected-file mode:",
"cstat loc --path .",
"cstat loc --path src/lib.rs",
"cstat loc --explain",
"cstat loc --explain --json",
"code_lines is a static textual classification",
"blank lines are excluded",
"// comment-only lines are excluded",
"block-comment-only regions are excluded",
"Project JSON fields:",
"Selected-file JSON fields:",
"--top only limits the project-mode",
"ignored for selected-file and --explain modes",
] {
assert!(stdout.contains(expected), "missing {expected}: {stdout}");
}
}
#[test]
fn loc_explain_human_is_canonical_reference() {
let output = run_cstat_raw(&[
"--path",
"/definitely/missing/cstat/path",
"loc",
"--explain",
]);
assert_success(&output);
let stdout = String::from_utf8_lossy(&output.stdout);
for expected in [
"loc reference",
"Project mode: cstat loc --path .",
"Machine contract: cstat loc --explain --json",
"code_lines: static textual classification",
"not semantic Rust parsing",
"Project JSON fields:",
"Selected-file JSON fields:",
] {
assert!(stdout.contains(expected), "missing {expected}: {stdout}");
}
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("Error discovering files"),
"unexpected discovery error: {stderr}",
);
}
#[test]
fn loc_explain_json_exposes_machine_contract() {
let output = run_cstat_raw(&["--json", "loc", "--explain"]);
assert_success(&output);
let stdout = String::from_utf8(output.stdout).unwrap();
let value: Value = serde_json::from_str(&stdout).expect("parse loc explain JSON");
assert_eq!(value["probe"], "loc");
assert_eq!(
value["commands"]["explain_json"],
"cstat loc --explain --json",
);
assert_eq!(value["code_lines"]["kind"], "static textual classification",);
assert!(
value["project_json_fields"]["files"]
.as_array()
.expect("project fields")
.iter()
.any(|field| field.as_str() == Some("code_lines")),
"json={stdout}",
);
assert!(
value["selected_file_json_fields"]["reachable_spans"]
.as_array()
.expect("selected fields")
.iter()
.any(|field| field.as_str() == Some("function")),
"json={stdout}",
);
}
#[test]
fn loc_verbose_points_to_loc_explain_not_guide() {
let root = temp_project("verbose-loc-reference");
let output = run_cstat(&root, &["-v", "loc"]);
assert_success(&output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("Full loc reference: cstat loc --explain"),
"stdout={stdout}",
);
assert!(
!stdout.contains("cstat guide size"),
"stdout unexpectedly referenced guide: {stdout}",
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn loc_project_json_exposes_shape_contract() {
let root = temp_project("project-json");
let output = run_cstat(&root, &["--json", "loc"]);
assert_success(&output);
let stdout = String::from_utf8(output.stdout).unwrap();
let value: Value = serde_json::from_str(&stdout).expect("parse loc JSON");
assert_eq!(value["cstat_version"], env!("CARGO_PKG_VERSION"));
let files = value["files"].as_array().expect("files array");
assert!(!files.is_empty(), "json={stdout}");
let lib = files
.iter()
.find(|file| file["path"] == "src/lib.rs")
.expect("src/lib.rs row");
let total_lines = lib["total_lines"].as_u64().expect("total_lines");
let code_lines = lib["code_lines"].as_u64().expect("code_lines");
assert!(
total_lines >= code_lines && code_lines > 0,
"lib row={lib:?}",
);
let aggregate = &value["aggregate"];
for field in [
"total_files",
"total_loc",
"mean",
"std_dev",
"median",
"min",
"max",
] {
assert!(aggregate.get(field).is_some(), "missing {field}: {stdout}");
}
let directories = value["directory_breakdown"]
.as_array()
.expect("directory_breakdown array");
assert!(
directories.iter().any(|directory| {
directory["directory"] == "src"
&& directory["code_lines"]
.as_u64()
.is_some_and(|code_lines| code_lines > 0)
}),
"directory_breakdown={directories:?}",
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn loc_selected_file_human_is_usable() {
let root = temp_project("selected-human");
let file = root.join("src/lib.rs");
let output = run_cstat(&file, &["loc"]);
assert_success(&output);
let stdout = String::from_utf8_lossy(&output.stdout);
for expected in [
"Projected line reachability",
"file src/lib.rs",
"physical lines",
"production code lines",
"statically reachable lines",
"not statically reachable lines",
"reachable function spans:",
"unreachable function spans:",
"live",
"orphan",
] {
assert!(stdout.contains(expected), "missing {expected}: {stdout}");
}
fs::remove_dir_all(root).unwrap();
}
#[test]
fn loc_selected_file_json_exposes_projected_reachability_contract() {
let root = temp_project("selected-json");
let file = root.join("src/lib.rs");
let output = run_cstat(&file, &["--json", "loc"]);
assert_success(&output);
let stdout = String::from_utf8(output.stdout).unwrap();
let value: Value = serde_json::from_str(&stdout).expect("parse selected-file loc JSON");
assert_eq!(value["file"], "src/lib.rs");
let total_lines = value["total_lines"].as_u64().expect("total_lines");
let code_lines = value["code_lines"].as_u64().expect("code_lines");
let projected_reachable_lines = value["projected_reachable_lines"]
.as_u64()
.expect("projected_reachable_lines");
let projected_unreachable_lines = value["projected_unreachable_lines"]
.as_u64()
.expect("projected_unreachable_lines");
assert!(total_lines >= code_lines, "json={stdout}");
assert_eq!(
projected_reachable_lines + projected_unreachable_lines,
code_lines,
"json={stdout}",
);
assert!(
span_array_contains_function(&value["reachable_spans"], "live"),
"json={stdout}",
);
assert!(
span_array_contains_function(&value["reachable_spans"], "helper"),
"json={stdout}",
);
assert!(
span_array_contains_function(&value["unreachable_spans"], "orphan"),
"json={stdout}",
);
fs::remove_dir_all(root).unwrap();
}