use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::TempDir;
fn cli() -> Command {
Command::cargo_bin("bca").unwrap()
}
fn fixture_path() -> String {
let manifest = env!("CARGO_MANIFEST_DIR");
let workspace = std::path::Path::new(manifest)
.parent()
.expect("manifest dir has parent");
workspace
.join("tests/repositories/DeepSpeech/stats.py")
.to_str()
.expect("path is utf-8")
.to_string()
}
#[test]
fn metrics_writes_per_file_json_to_output_dir() {
let dir = TempDir::new().unwrap();
cli()
.args([
"--paths",
&fixture_path(),
"metrics",
"-O",
"json",
"-o",
dir.path().to_str().unwrap(),
])
.assert()
.success();
let entries: Vec<_> = walkdir_entries(dir.path(), "json").collect();
assert_eq!(entries.len(), 1, "expected one .json file: {entries:?}");
let content = std::fs::read_to_string(&entries[0]).unwrap();
let parsed: serde_json::Value =
serde_json::from_str(&content).expect("metrics output must be valid JSON");
assert!(
parsed.get("metrics").is_some() && parsed.get("name").is_some(),
"expected metrics record with both `metrics` and `name` fields, got: {parsed}"
);
}
#[test]
fn metrics_pretty_emits_indented_json() {
let dir = TempDir::new().unwrap();
cli()
.args([
"--paths",
&fixture_path(),
"metrics",
"-O",
"json",
"--pretty",
"-o",
dir.path().to_str().unwrap(),
])
.assert()
.success();
let entries: Vec<_> = walkdir_entries(dir.path(), "json").collect();
let content = std::fs::read_to_string(&entries[0]).unwrap();
assert!(
content.contains("\n "),
"pretty JSON should be indented; got: {content}"
);
}
#[test]
fn ops_writes_per_file_json_to_output_dir() {
let dir = TempDir::new().unwrap();
cli()
.args([
"--paths",
&fixture_path(),
"ops",
"-O",
"json",
"-o",
dir.path().to_str().unwrap(),
])
.assert()
.success();
let entries: Vec<_> = walkdir_entries(dir.path(), "json").collect();
assert_eq!(entries.len(), 1);
let content = std::fs::read_to_string(&entries[0]).unwrap();
let parsed: serde_json::Value =
serde_json::from_str(&content).expect("ops output must be valid JSON");
let obj = parsed
.as_object()
.expect("ops output must be a JSON object");
assert!(!obj.is_empty(), "ops record must not be empty: {parsed}");
}
#[test]
fn dump_prints_ast_to_stdout() {
cli()
.args(["--paths", &fixture_path(), "dump"])
.assert()
.success()
.stdout(predicate::str::contains("function_definition"))
.stdout(predicate::str::contains("identifier"));
}
#[test]
fn functions_lists_function_spans() {
cli()
.args(["--paths", &fixture_path(), "functions"])
.assert()
.success()
.stdout(predicate::str::contains("read_csvs"))
.stdout(predicate::str::contains("main"));
}
#[test]
fn find_locates_call_expressions() {
cli()
.args(["--paths", &fixture_path(), "find", "call"])
.assert()
.success()
.stdout(predicate::str::contains("{call:"));
}
#[test]
fn count_reports_node_counts() {
cli()
.args(["--paths", &fixture_path(), "count", "function_definition"])
.assert()
.success()
.stdout(predicate::str::contains("Total nodes"))
.stdout(predicate::str::contains("Found nodes"));
}
#[test]
fn strip_comments_writes_to_stdout_without_comments() {
let dir = TempDir::new().unwrap();
let src = dir.path().join("snippet.py");
std::fs::write(&src, "# this is a comment\nx = 1\n").unwrap();
cli()
.args(["--paths", src.to_str().unwrap(), "strip-comments"])
.assert()
.success()
.stdout(predicate::str::contains("x = 1"))
.stdout(predicate::str::contains("this is a comment").not());
}
#[test]
fn preproc_emits_json_to_stdout_without_output() {
let dir = TempDir::new().unwrap();
let src = dir.path().join("nothing.txt");
std::fs::write(&src, "no preproc here\n").unwrap();
let output = cli()
.args(["--paths", src.to_str().unwrap(), "preproc"])
.output()
.unwrap();
assert!(output.status.success(), "preproc should succeed");
let stdout = String::from_utf8(output.stdout).unwrap();
let _: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("preproc must emit valid JSON to stdout");
}
#[test]
fn preproc_writes_json_to_output_file() {
let dir = TempDir::new().unwrap();
let src = dir.path().join("dummy.txt");
std::fs::write(&src, "irrelevant\n").unwrap();
let out = dir.path().join("preproc.json");
cli()
.args([
"--paths",
src.to_str().unwrap(),
"preproc",
"-o",
out.to_str().unwrap(),
])
.assert()
.success();
let content = std::fs::read_to_string(&out).unwrap();
let _: serde_json::Value =
serde_json::from_str(&content).expect("preproc output file must be valid JSON");
}
fn walkdir_entries(dir: &std::path::Path, ext: &str) -> impl Iterator<Item = std::path::PathBuf> {
fn visit(dir: &std::path::Path, ext: &str, found: &mut Vec<std::path::PathBuf>) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let p = entry.path();
if p.is_dir() {
visit(&p, ext, found);
} else if p.extension().and_then(|e| e.to_str()) == Some(ext) {
found.push(p);
}
}
}
}
let mut found = Vec::new();
visit(dir, ext, &mut found);
found.into_iter()
}