use assert_cmd::Command;
use predicates::prelude::*;
#[test]
fn test_help_output() {
Command::new(assert_cmd::cargo_bin!("cxpak"))
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("overview"))
.stdout(predicate::str::contains("trace"));
}
#[test]
fn test_version_output() {
Command::new(assert_cmd::cargo_bin!("cxpak"))
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains(env!("CARGO_PKG_VERSION")));
}
#[test]
fn test_overview_uses_default_tokens() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["overview", repo.path().to_str().unwrap()])
.assert()
.success();
}
#[test]
fn test_trace_requires_target() {
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["trace"])
.assert()
.failure();
}
#[test]
fn test_tokens_parses_k_suffix() {
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["overview", "--tokens", "50k"])
.assert()
.success();
}
fn make_test_repo() -> tempfile::TempDir {
let dir = tempfile::TempDir::new().unwrap();
let repo = git2::Repository::init(dir.path()).unwrap();
let sig = git2::Signature::now("Test", "t@t.com").unwrap();
std::fs::create_dir_all(dir.path().join("src")).unwrap();
std::fs::write(
dir.path().join("src/main.rs"),
"fn main() { println!(\"hello\"); }\n",
)
.unwrap();
std::fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\n",
)
.unwrap();
let mut index = repo.index().unwrap();
index
.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)
.unwrap();
index.write().unwrap();
let tree_id = index.write_tree().unwrap();
let tree = repo.find_tree(tree_id).unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, &[])
.unwrap();
dir
}
#[test]
fn test_overview_markdown() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["overview", "--tokens", "10k", repo.path().to_str().unwrap()])
.assert()
.success()
.stdout(predicate::str::contains("Files:"));
}
#[test]
fn test_overview_json() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"10k",
"--format",
"json",
repo.path().to_str().unwrap(),
])
.assert()
.success()
.stdout(predicate::str::contains("{"));
}
#[test]
fn test_overview_xml() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"10k",
"--format",
"xml",
repo.path().to_str().unwrap(),
])
.assert()
.success()
.stdout(predicate::str::contains("<cxpak"));
}
#[test]
fn test_trace_not_found() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"10k",
"nonexistent_symbol_xyz",
repo.path().to_str().unwrap(),
])
.assert()
.failure();
}
#[test]
fn test_trace_found() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"10k",
"main",
repo.path().to_str().unwrap(),
])
.assert()
.success()
.stdout(predicate::str::contains("main"));
}
#[test]
fn test_diff_no_changes() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["diff", "--tokens", "10k", repo.path().to_str().unwrap()])
.assert()
.success()
.stdout(predicate::str::contains("No changes detected"));
}
#[test]
fn test_diff_with_changes() {
let repo = make_test_repo();
std::fs::write(
repo.path().join("src/main.rs"),
"fn main() { println!(\"changed\"); }\n",
)
.unwrap();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["diff", "--tokens", "10k", repo.path().to_str().unwrap()])
.assert()
.success()
.stdout(predicate::str::contains("src/main.rs"));
}
#[test]
fn test_clean_command() {
let repo = make_test_repo();
std::fs::create_dir_all(repo.path().join(".cxpak/cache")).unwrap();
std::fs::write(repo.path().join(".cxpak/test.md"), "test").unwrap();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["clean", repo.path().to_str().unwrap()])
.assert()
.success();
assert!(!repo.path().join(".cxpak").exists());
}
#[test]
fn test_overview_verbose() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"10k",
"--verbose",
repo.path().to_str().unwrap(),
])
.assert()
.success()
.stderr(predicate::str::contains("scanning"));
}
#[test]
fn test_bad_subcommand() {
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["nonsense"])
.assert()
.failure();
}
#[test]
fn test_no_subcommand() {
Command::new(assert_cmd::cargo_bin!("cxpak"))
.assert()
.failure();
}
#[test]
fn test_overview_with_output_file() {
let repo = make_test_repo();
let out = repo.path().join("output.md");
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"10k",
"--out",
out.to_str().unwrap(),
repo.path().to_str().unwrap(),
])
.assert()
.success();
assert!(out.exists());
let content = std::fs::read_to_string(&out).unwrap();
assert!(content.contains("Files:"));
}
#[test]
fn test_overview_with_focus() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"50k",
"--focus",
"src",
repo.path().to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn test_overview_with_focus_no_match() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"50k",
"--focus",
"nonexistent/path",
repo.path().to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn test_diff_with_focus() {
let repo = make_test_repo();
std::fs::write(
repo.path().join("src/main.rs"),
"fn main() { println!(\"focused\"); }\n",
)
.unwrap();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"diff",
"--tokens",
"50k",
"--focus",
"src",
repo.path().to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn test_trace_with_focus() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"50k",
"--focus",
"src",
"main",
repo.path().to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn test_overview_with_timing() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"50k",
"--timing",
repo.path().to_str().unwrap(),
])
.assert()
.success()
.stderr(predicate::str::contains("cxpak [timing]:"));
}
#[test]
fn test_timing_flag_accepted_by_diff() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"diff",
"--tokens",
"50k",
"--timing",
repo.path().to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn test_timing_flag_accepted_by_trace() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"50k",
"--timing",
"main",
repo.path().to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn test_trace_timing_produces_output() {
let repo = make_test_repo();
let output = Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"50k",
"--timing",
"main",
repo.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("cxpak [timing]: scan"),
"stderr should contain scan timing, got: {stderr}"
);
assert!(
stderr.contains("cxpak [timing]: total"),
"stderr should contain total timing, got: {stderr}"
);
}
#[test]
fn test_diff_timing_produces_output() {
let repo = make_test_repo();
std::fs::write(
repo.path().join("src/main.rs"),
"fn main() { println!(\"changed\"); }\n",
)
.unwrap();
let output = Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"diff",
"--tokens",
"50k",
"--timing",
repo.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("cxpak [timing]: scan"),
"stderr should contain scan timing, got: {stderr}"
);
assert!(
stderr.contains("cxpak [timing]: total"),
"stderr should contain total timing, got: {stderr}"
);
}
#[test]
fn test_trace_no_timing_by_default() {
let repo = make_test_repo();
let output = Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"50k",
"main",
repo.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("cxpak [timing]"),
"no timing output expected without --timing flag, got: {stderr}"
);
}
#[test]
fn test_trace_focus_produces_output() {
let repo = make_test_repo();
let output = Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"50k",
"--focus",
"src/",
"main",
repo.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!stdout.is_empty(),
"trace with --focus should produce output"
);
assert!(
stdout.contains("main"),
"output should contain the traced symbol, got: {stdout}"
);
}
#[test]
fn test_diff_focus_produces_output() {
let repo = make_test_repo();
std::fs::write(
repo.path().join("src/main.rs"),
"fn main() { println!(\"focused\"); }\n",
)
.unwrap();
let output = Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"diff",
"--tokens",
"50k",
"--focus",
"src/",
repo.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!stdout.is_empty(),
"diff with --focus should produce output"
);
}
#[test]
fn test_overview_tokens_zero_fails() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["overview", "--tokens", "0", repo.path().to_str().unwrap()])
.assert()
.failure()
.stderr(predicate::str::contains("--tokens must be greater than 0"));
}
#[test]
fn test_diff_tokens_zero_fails() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["diff", "--tokens", "0", repo.path().to_str().unwrap()])
.assert()
.failure()
.stderr(predicate::str::contains("--tokens must be greater than 0"));
}
#[test]
fn test_trace_tokens_zero_fails() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"0",
"main",
repo.path().to_str().unwrap(),
])
.assert()
.failure()
.stderr(predicate::str::contains("--tokens must be greater than 0"));
}
#[test]
fn test_overview_tokens_invalid_fails() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["overview", "--tokens", "abc", repo.path().to_str().unwrap()])
.assert()
.failure()
.stderr(predicate::str::contains("invalid token count"));
}
#[test]
fn test_diff_tokens_invalid_fails() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["diff", "--tokens", "abc", repo.path().to_str().unwrap()])
.assert()
.failure()
.stderr(predicate::str::contains("invalid token count"));
}
#[test]
fn test_trace_tokens_invalid_fails() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"abc",
"main",
repo.path().to_str().unwrap(),
])
.assert()
.failure()
.stderr(predicate::str::contains("invalid token count"));
}
#[test]
fn test_overview_tiny_budget_triggers_pack_mode() {
let repo = make_test_repo();
std::fs::create_dir_all(repo.path().join("src/lib")).unwrap();
std::fs::write(
repo.path().join("src/lib/utils.rs"),
"pub fn helper() -> i32 { 42 }\npub fn another() -> String { String::new() }\n",
)
.unwrap();
std::fs::write(
repo.path().join("src/lib/models.rs"),
"pub struct User { pub name: String }\npub struct Item { pub id: u64 }\n",
)
.unwrap();
let output = Command::new(assert_cmd::cargo_bin!("cxpak"))
.args(["overview", "--tokens", "1", repo.path().to_str().unwrap()])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.len() < 5000,
"tiny budget should produce small output"
);
}
#[test]
fn test_overview_verbose_and_timing_combined() {
let repo = make_test_repo();
let output = Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"10k",
"--verbose",
"--timing",
repo.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("scanning"),
"verbose output should contain scanning, got: {stderr}"
);
assert!(
stderr.contains("cxpak [timing]:"),
"timing output should appear, got: {stderr}"
);
}
#[test]
fn test_diff_verbose() {
let repo = make_test_repo();
std::fs::write(
repo.path().join("src/main.rs"),
"fn main() { println!(\"verbose diff\"); }\n",
)
.unwrap();
let output = Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"diff",
"--tokens",
"10k",
"--verbose",
repo.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("scanning"),
"diff --verbose should show scanning, got: {stderr}"
);
}
#[test]
fn test_trace_verbose() {
let repo = make_test_repo();
let output = Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"10k",
"--verbose",
"main",
repo.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("scanning"),
"trace --verbose should show scanning, got: {stderr}"
);
}
#[test]
fn test_overview_json_format_detail_files() {
let repo = make_test_repo();
std::fs::create_dir_all(repo.path().join("src/extra")).unwrap();
for i in 0..5 {
std::fs::write(
repo.path().join(format!("src/extra/mod{i}.rs")),
format!("pub fn func{i}() -> i32 {{ {i} }}\n"),
)
.unwrap();
}
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"1",
"--format",
"json",
repo.path().to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn test_overview_xml_format_detail_files() {
let repo = make_test_repo();
std::fs::create_dir_all(repo.path().join("src/extra")).unwrap();
for i in 0..5 {
std::fs::write(
repo.path().join(format!("src/extra/mod{i}.rs")),
format!("pub fn func{i}() -> i32 {{ {i} }}\n"),
)
.unwrap();
}
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"overview",
"--tokens",
"1",
"--format",
"xml",
repo.path().to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn test_trace_all_flag() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"10k",
"--all",
"main",
repo.path().to_str().unwrap(),
])
.assert()
.success();
}
#[test]
fn test_diff_json_format() {
let repo = make_test_repo();
std::fs::write(
repo.path().join("src/main.rs"),
"fn main() { println!(\"json diff\"); }\n",
)
.unwrap();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"diff",
"--tokens",
"10k",
"--format",
"json",
repo.path().to_str().unwrap(),
])
.assert()
.success()
.stdout(predicate::str::contains("{"));
}
#[test]
fn test_trace_json_format() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"10k",
"--format",
"json",
"main",
repo.path().to_str().unwrap(),
])
.assert()
.success()
.stdout(predicate::str::contains("{"));
}
#[test]
fn test_trace_xml_format() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"trace",
"--tokens",
"10k",
"--format",
"xml",
"main",
repo.path().to_str().unwrap(),
])
.assert()
.success()
.stdout(predicate::str::contains("<cxpak"));
}
#[test]
fn test_diff_invalid_since_fails() {
let repo = make_test_repo();
Command::new(assert_cmd::cargo_bin!("cxpak"))
.args([
"diff",
"--since",
"not_a_valid_time",
repo.path().to_str().unwrap(),
])
.assert()
.failure();
}