use super::*;
use crate::exec::CommandOutput;
fn make_output(exit_code: i32, stdout: &str) -> CommandOutput {
CommandOutput {
stdout: stdout.as_bytes().to_vec(),
stderr: Vec::new(),
exit_code,
}
}
#[test]
fn test_passthrough_small_output() {
let out = make_output(0, "hello world\n");
let result = classify(&out, "echo hello", &[]);
assert!(matches!(result, Classification::Passthrough { output } if output == "hello world\n"));
}
#[test]
fn test_failure_output() {
let out = make_output(1, "error: something broke\n");
let result = classify(&out, "some_cmd", &[]);
match result {
Classification::Failure { label, output } => {
assert_eq!(label, "some_cmd");
assert!(output.contains("something broke"));
}
_ => panic!("expected Failure"),
}
}
#[test]
fn test_large_output_no_pattern() {
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "unknown_cmd", &[]);
match result {
Classification::Bounded {
label,
output,
display,
size,
} => {
assert_eq!(label, "unknown_cmd");
assert_eq!(output, big, "full output must be preserved for indexing");
assert_eq!(size, big.len());
assert!(
display.len() <= DISPLAY_CAP + 200,
"display ({} bytes) exceeds cap + marker allowance",
display.len()
);
let marker_count = display.matches("bytes truncated").count();
assert_eq!(marker_count, 1, "exactly one truncation marker expected");
}
_ => panic!("expected Bounded for unknown command with large output"),
}
}
#[test]
fn test_large_output_with_pattern() {
let patterns = pattern::builtins();
let big = format!("{}\n47 passed in 3.2s\n", ".\n".repeat(3000));
let out = make_output(0, &big);
let result = classify(&out, "pytest tests/", patterns);
match result {
Classification::Success { label, summary } => {
assert_eq!(label, "pytest");
assert_eq!(summary, "47 passed, 3.2s");
}
_ => panic!("expected Success"),
}
}
#[test]
fn test_smart_truncation_short() {
let lines: String = (0..50).map(|i| format!("line {i}\n")).collect();
let result = smart_truncate(&lines);
assert_eq!(result, lines);
assert!(!result.contains("truncated"));
}
#[test]
fn test_smart_truncation_long() {
let lines: String = (0..200)
.map(|i| format!("line {i}"))
.collect::<Vec<_>>()
.join("\n");
let result = smart_truncate(&lines);
assert!(result.contains("line 0"));
assert!(result.contains("line 199"));
assert!(result.contains("truncated"));
let result_lines: Vec<&str> = result.lines().collect();
assert!(result_lines.len() <= MAX_LINES + 1); }
#[test]
fn test_label_derivation() {
assert_eq!(label("pytest -x"), "pytest");
assert_eq!(label("cargo test"), "cargo");
assert_eq!(label("gh issue list"), "gh");
assert_eq!(label("/usr/bin/python test.py"), "python");
assert_eq!(label("sudo cargo test"), "cargo");
assert_eq!(label("env FOO=bar cargo test"), "cargo");
assert_eq!(label("sudo git status"), "git");
assert_eq!(label("env A=1 B=2 pytest"), "pytest");
assert_eq!(label("sudo"), "sudo");
assert_eq!(label("env"), "env");
}
#[test]
fn test_failure_with_pattern() {
let patterns = pattern::builtins();
let big_fail: String = (0..100).map(|i| format!("error line {i}\n")).collect();
let out = make_output(1, &big_fail);
let result = classify(&out, "pytest -x", &patterns);
match result {
Classification::Failure { label, output } => {
assert_eq!(label, "pytest");
assert!(output.contains("error line 70"));
assert!(output.contains("error line 99"));
}
_ => panic!("expected Failure"),
}
}
#[test]
fn test_empty_output_passthrough() {
let out = make_output(0, "");
let result = classify(&out, "true", &[]);
assert!(matches!(result, Classification::Passthrough { output } if output.is_empty()));
}
#[test]
fn test_success_with_empty_summary_is_quiet() {
let patterns = pattern::builtins();
let big = "Compiling foo\n".repeat(500);
let out = make_output(0, &big);
let result = classify(&out, "cargo build --release", &patterns);
match result {
Classification::Success { summary, .. } => {
assert!(summary.is_empty()); }
_ => panic!("expected Success with empty summary"),
}
}
#[test]
fn test_detect_category_status_commands() {
assert_eq!(detect_category("cargo test"), CommandCategory::Status);
assert_eq!(detect_category("cargo build"), CommandCategory::Status);
assert_eq!(detect_category("cargo clippy"), CommandCategory::Status);
assert_eq!(detect_category("cargo fmt"), CommandCategory::Status);
assert_eq!(detect_category("pytest tests/"), CommandCategory::Status);
assert_eq!(detect_category("jest"), CommandCategory::Status);
assert_eq!(detect_category("eslint src/"), CommandCategory::Status);
assert_eq!(detect_category("ruff check"), CommandCategory::Status);
assert_eq!(detect_category("sudo cargo test"), CommandCategory::Status);
assert_eq!(detect_category("sudo cargo build"), CommandCategory::Status);
assert_eq!(detect_category("env cargo test"), CommandCategory::Status);
assert_eq!(
detect_category("env FOO=bar cargo test"),
CommandCategory::Status
);
assert_eq!(
detect_category("env A=1 B=2 pytest"),
CommandCategory::Status
);
assert_eq!(
detect_category("cargo nextest run"),
CommandCategory::Status
);
}
#[test]
fn test_detect_category_prefix_stripped_data_and_content() {
assert_eq!(detect_category("sudo git status"), CommandCategory::Data);
assert_eq!(detect_category("sudo git log"), CommandCategory::Data);
assert_eq!(
detect_category("env FOO=bar git show"),
CommandCategory::Content
);
assert_eq!(
detect_category("env A=1 git diff"),
CommandCategory::Content
);
assert_eq!(
detect_category("/usr/bin/sudo cargo test"),
CommandCategory::Status
);
assert_eq!(
detect_category("/usr/bin/env FOO=bar cargo test"),
CommandCategory::Status
);
}
#[test]
fn test_detect_category_content_commands() {
assert_eq!(
detect_category("git show HEAD:file"),
CommandCategory::Content
);
assert_eq!(detect_category("git diff HEAD~1"), CommandCategory::Content);
assert_eq!(detect_category("cat file.txt"), CommandCategory::Content);
assert_eq!(detect_category("bat src/main.rs"), CommandCategory::Content);
}
#[test]
fn test_detect_category_data_commands() {
assert_eq!(detect_category("git log"), CommandCategory::Data);
assert_eq!(detect_category("git status"), CommandCategory::Data);
assert_eq!(detect_category("gh issue list"), CommandCategory::Data);
assert_eq!(detect_category("gh pr list"), CommandCategory::Data);
assert_eq!(detect_category("ls -la"), CommandCategory::Data);
assert_eq!(detect_category("find . -name test"), CommandCategory::Data);
assert_eq!(detect_category("grep pattern file"), CommandCategory::Data);
}
#[test]
fn test_detect_category_unknown_defaults() {
assert_eq!(
detect_category("curl https://example.com"),
CommandCategory::Unknown
);
assert_eq!(detect_category("wget file.zip"), CommandCategory::Unknown);
assert_eq!(
detect_category("docker run image"),
CommandCategory::Unknown
);
assert_eq!(
detect_category("random-binary arg"),
CommandCategory::Unknown
);
assert_eq!(detect_category("cargo run"), CommandCategory::Unknown);
assert_eq!(detect_category("cargo doc"), CommandCategory::Unknown);
assert_eq!(
detect_category("cargo run test-runner"),
CommandCategory::Unknown
);
assert_eq!(
detect_category("someunknown foo sudo bar"),
CommandCategory::Unknown
);
assert_eq!(
detect_category("sudo sudo cargo test"),
CommandCategory::Unknown
);
assert_eq!(
detect_category("sudo env FOO=bar cargo test"),
CommandCategory::Unknown
);
}
#[test]
fn test_status_no_pattern_quiet_success() {
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "cargo test", &[]);
match result {
Classification::Success { label, summary } => {
assert_eq!(label, "cargo");
assert!(summary.is_empty()); }
_ => panic!("expected Success with empty summary for status command"),
}
}
#[test]
fn test_nextest_run_quiet_success() {
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "cargo nextest run", &[]);
match result {
Classification::Success { label, summary } => {
assert_eq!(label, "cargo");
assert!(summary.is_empty()); }
other => panic!("expected Success (quiet) for cargo nextest run, got: {other:?}"),
}
}
#[test]
fn test_sudo_nextest_run_quiet_success() {
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "sudo cargo nextest run", &[]);
match result {
Classification::Success { label, summary } => {
assert_eq!(label, "cargo");
assert!(summary.is_empty()); }
other => panic!("expected Success (quiet) for sudo cargo nextest run, got: {other:?}"),
}
}
#[test]
fn test_sudo_git_log_data_indexes() {
let big = "line\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "sudo git log", &[]);
match result {
Classification::Large { label, size, .. } => {
assert_eq!(label, "git");
assert!(size > SMALL_THRESHOLD);
}
other => panic!("expected Large for sudo git log, got: {other:?}"),
}
}
#[test]
fn test_env_git_show_content_bounded() {
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "env FOO=bar git show", &[]);
match result {
Classification::Bounded { label, .. } => {
assert_eq!(label, "git");
}
other => panic!("expected Bounded for env FOO=bar git show, got: {other:?}"),
}
}
#[test]
fn test_content_bounded_with_indexing() {
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "git show HEAD:file", &[]);
match result {
Classification::Bounded {
label,
output,
display,
size,
} => {
assert_eq!(label, "git");
assert_eq!(output, big, "full output must be preserved for indexing");
assert_eq!(size, big.len());
assert!(
display.len() <= DISPLAY_CAP + 200,
"display must be byte-bounded, got {} bytes",
display.len()
);
assert!(
display.contains("bytes truncated"),
"display must contain truncation marker"
);
}
_ => panic!("expected Bounded for content command with large output"),
}
}
#[test]
fn test_data_no_pattern_indexes() {
let big = "line\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "git log", &[]);
match result {
Classification::Large { label, size, .. } => {
assert_eq!(label, "git");
assert!(size > SMALL_THRESHOLD);
}
_ => panic!("expected Large (indexed) for data command"),
}
}
#[test]
fn test_unknown_bounded_with_indexing() {
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "curl https://example.com", &[]);
match result {
Classification::Bounded {
label,
output,
display,
size,
} => {
assert_eq!(label, "curl");
assert_eq!(output, big, "full output must be preserved for indexing");
assert_eq!(size, big.len());
assert!(
display.len() <= DISPLAY_CAP + 200,
"display must be byte-bounded, got {} bytes",
display.len()
);
assert!(
display.contains("bytes truncated"),
"display must contain truncation marker"
);
}
_ => panic!("expected Bounded for unknown command with large output"),
}
}
#[test]
fn test_pattern_overrides_category() {
let patterns = pattern::builtins();
let big = format!("{}\n47 passed in 3.2s\n", ".\n".repeat(3000));
let out = make_output(0, &big);
let result = classify(&out, "pytest", &patterns);
match result {
Classification::Success { summary, .. } => {
assert_eq!(summary, "47 passed, 3.2s");
}
_ => panic!("expected pattern-matched Success"),
}
}
#[test]
fn test_category_detection_with_full_paths() {
assert_eq!(
detect_category("/usr/bin/cargo test"),
CommandCategory::Status
);
assert_eq!(
detect_category("/usr/local/bin/pytest"),
CommandCategory::Status
);
assert_eq!(
detect_category("/usr/bin/git show"),
CommandCategory::Content
);
assert_eq!(
detect_category("/bin/cat file.txt"),
CommandCategory::Content
);
assert_eq!(
detect_category("/usr/bin/gh issue list"),
CommandCategory::Data
);
assert_eq!(detect_category("/bin/ls -la"), CommandCategory::Data);
}
#[test]
fn test_category_detection_full_paths_with_prefix_stripping() {
assert_eq!(
detect_category("/usr/bin/sudo cargo test"),
CommandCategory::Status
);
assert_eq!(
detect_category("/usr/bin/env FOO=bar cargo test"),
CommandCategory::Status
);
assert_eq!(
detect_category("sudo /usr/local/bin/cargo test"),
CommandCategory::Status
);
}
#[test]
fn test_package_manager_regression_lock() {
assert_eq!(detect_category("npm test"), CommandCategory::Status);
assert_eq!(detect_category("npm run test"), CommandCategory::Status);
assert_eq!(detect_category("yarn run build"), CommandCategory::Status);
assert_eq!(detect_category("bun run test"), CommandCategory::Status);
assert_eq!(detect_category("pnpm run test"), CommandCategory::Status);
assert_eq!(detect_category("npm install"), CommandCategory::Status);
assert_eq!(detect_category("go run main.go"), CommandCategory::Status);
assert_eq!(detect_category("go build"), CommandCategory::Status);
}
#[test]
fn test_bounded_small_output_stays_passthrough() {
let small = "x".repeat(4096);
let out = make_output(0, &small);
let result = classify(&out, "cat file.txt", &[]);
assert!(
matches!(result, Classification::Passthrough { ref output } if output == &small),
"output at exactly SMALL_THRESHOLD must stay Passthrough"
);
}
#[test]
fn test_bounded_just_above_threshold() {
let big = "x".repeat(4097);
let out = make_output(0, &big);
let result = classify(&out, "cat file.txt", &[]);
match result {
Classification::Bounded {
output,
display,
size,
..
} => {
assert_eq!(size, 4097);
assert_eq!(output, big, "full output preserved");
assert!(display.contains("bytes truncated"));
}
other => panic!("expected Bounded for 4097-byte output, got: {other:?}"),
}
}
#[test]
fn test_bounded_display_has_head_and_tail() {
let mut content = String::new();
content.push_str("HEAD_MARKER_abc123 first line\n");
for i in 0..2000 {
content.push_str(&format!("middle line {i}\n"));
}
content.push_str("TAIL_MARKER_xyz789 last line\n");
let out = make_output(0, &content);
let result = classify(&out, "cat big_file.txt", &[]);
match result {
Classification::Bounded {
display, output, ..
} => {
assert!(
display.starts_with("HEAD_MARKER_abc123 first line"),
"display must start with head content"
);
assert!(
display.ends_with("TAIL_MARKER_xyz789 last line\n"),
"display must end with tail content"
);
assert_eq!(output, content);
}
other => panic!("expected Bounded, got: {other:?}"),
}
}
#[test]
fn test_bounded_single_line_no_newlines() {
let big = "a".repeat(10_000);
let out = make_output(0, &big);
let result = classify(&out, "curl https://api.example.com/data", &[]);
match result {
Classification::Bounded {
display, output, ..
} => {
assert!(
display.len() <= DISPLAY_CAP + 200,
"display ({} bytes) must be bounded",
display.len()
);
assert!(display.contains("bytes truncated"));
assert!(std::str::from_utf8(display.as_bytes()).is_ok());
assert_eq!(output, big);
}
other => panic!("expected Bounded, got: {other:?}"),
}
}
#[test]
fn test_bounded_multibyte_char_safety() {
let mut content = String::new();
content.push_str("line with é and 中\n");
for i in 0..3000 {
content.push_str(&format!("line {i}: é中\n"));
}
content.push_str("last line with é and 中\n");
let out = make_output(0, &content);
let result = classify(&out, "cat utf8_file.txt", &[]);
match result {
Classification::Bounded {
display, output, ..
} => {
assert!(
std::str::from_utf8(display.as_bytes()).is_ok(),
"display must be valid UTF-8, no split multi-byte chars"
);
assert_eq!(output, content);
assert!(display.contains("bytes truncated"));
}
other => panic!("expected Bounded, got: {other:?}"),
}
}
#[test]
fn test_bounded_marker_appears_exactly_once() {
let big = "x\n".repeat(5000);
let out = make_output(0, &big);
let result = classify(&out, "cat big.txt", &[]);
match result {
Classification::Bounded { display, .. } => {
let count = display.matches("bytes truncated").count();
assert_eq!(count, 1, "truncation marker must appear exactly once");
}
other => panic!("expected Bounded, got: {other:?}"),
}
}
#[test]
fn test_bounded_git_diff_content() {
let mut diff = String::new();
diff.push_str("diff --git a/file.rs b/file.rs\n");
for i in 0..2000 {
diff.push_str(&format!("-old line {i}\n+new line {i}\n"));
}
let out = make_output(0, &diff);
let result = classify(&out, "git diff HEAD~1", &[]);
match result {
Classification::Bounded { label, display, .. } => {
assert_eq!(label, "git");
assert!(display.contains("bytes truncated"));
}
other => panic!("expected Bounded for git diff, got: {other:?}"),
}
}
#[test]
fn test_bounded_jq_unknown() {
let big_json = "x".repeat(10_000);
let out = make_output(0, &big_json);
let result = classify(&out, "jq . data.json", &[]);
match result {
Classification::Bounded { display, .. } => {
assert!(display.contains("bytes truncated"));
}
other => panic!("expected Bounded for jq, got: {other:?}"),
}
}
#[test]
fn test_bounded_cargo_run_unknown() {
let big = "x\n".repeat(3000);
let out = make_output(0, &big);
let result = classify(&out, "cargo run", &[]);
match result {
Classification::Bounded { .. } => {}
other => panic!("expected Bounded for cargo run, got: {other:?}"),
}
}
#[test]
fn test_bounded_sh_c_unknown() {
let big = "x\n".repeat(3000);
let out = make_output(0, &big);
let result = classify(&out, "sh -c 'echo hello'", &[]);
match result {
Classification::Bounded { .. } => {}
other => panic!("expected Bounded for sh -c, got: {other:?}"),
}
}
#[test]
fn test_bounded_truncate_fewer_than_two_newlines() {
let big = "x".repeat(8000);
let result = bounded_truncate(&big);
assert!(result.contains("bytes truncated"));
assert!(std::str::from_utf8(result.as_bytes()).is_ok());
assert!(result.len() <= DISPLAY_CAP + 200);
let mut one_nl = String::new();
one_nl.push_str(&"a".repeat(4000));
one_nl.push('\n');
one_nl.push_str(&"b".repeat(4000));
let result = bounded_truncate(&one_nl);
assert!(result.contains("bytes truncated"));
assert!(std::str::from_utf8(result.as_bytes()).is_ok());
}
#[test]
fn test_bounded_truncate_exact_threshold_no_truncation() {
let content = "x".repeat(DISPLAY_CAP);
let result = bounded_truncate(&content);
assert_eq!(result, content, "at DISPLAY_CAP, no truncation needed");
assert!(!result.contains("bytes truncated"));
}
#[test]
fn test_bounded_truncate_head_fallback_multibyte_no_panic() {
let output = format!("ab\ncd\n{}", "中".repeat(4000));
assert_eq!(
output.len(),
12_006,
"sanity: 6-byte prefix + 12000 bytes of 中"
);
let result = bounded_truncate(&output);
assert!(std::str::from_utf8(result.as_bytes()).is_ok());
assert!(result.contains("bytes truncated"));
assert!(
result.len() <= DISPLAY_CAP + 200,
"display ({} bytes) must be bounded",
result.len()
);
assert!(result.starts_with("ab\ncd\n"));
}
#[test]
fn test_bounded_truncate_tail_fallback_overlap_guard_no_panic() {
let output = format!("ab\ncd\n{}ef", "中".repeat(1000));
let len = output.len();
let head_budget = (DISPLAY_CAP as f64 * 0.6) as usize; let tail_budget = DISPLAY_CAP - head_budget; let raw_tail = len.saturating_sub(tail_budget);
let last_nl = 6 + 3 * 1000;
assert!(last_nl >= head_budget, "head find must succeed");
assert!(last_nl >= raw_tail, "tail find must fall back");
assert!(
!output.is_char_boundary(raw_tail),
"precondition: raw_tail is mid-char"
);
let result = bounded_truncate(&output);
assert!(std::str::from_utf8(result.as_bytes()).is_ok());
assert!(result.starts_with("ab\ncd\n"));
assert!(result.ends_with("ef"));
}
#[test]
fn test_bounded_truncate_overlap_guard_tail_max_is_load_bearing() {
let mut output = String::new();
output.push_str(&"x".repeat(5000));
output.push('\n'); output.push_str("ab\n"); output.push_str("cd\n"); assert_eq!(output.len(), 5007, "sanity: 5000 x's + \n + ab\n + cd\n");
let head_budget = (DISPLAY_CAP as f64 * 0.6) as usize; let tail_budget = DISPLAY_CAP - head_budget; let raw_tail = output.len().saturating_sub(tail_budget); assert_eq!(raw_tail, 3368, "raw_tail for this shape");
let (head_end, tail_start) = cut_boundaries(&output, head_budget, tail_budget);
assert_eq!(
tail_start,
output.len(),
"overlap guard must return len as tail_start"
);
assert_eq!(head_end, 5001, "head must snap to newline at 5000 (+1)");
let result = bounded_truncate(&output);
assert!(std::str::from_utf8(result.as_bytes()).is_ok());
assert!(result.contains("bytes truncated"));
assert!(
result.len() <= DISPLAY_CAP + 200,
"display ({} bytes) must be bounded",
result.len()
);
let after_marker = result.split("to query] ...\n").nth(1).map(|s| s);
assert!(
after_marker.is_some_and(|tail| tail.is_empty()),
"tail after marker must be empty when overlap guard wins, got: {after_marker:?}"
);
}
#[test]
fn test_bounded_truncate_long_lines_cap_still_enforced() {
let mut output = String::new();
output.push_str(&"a".repeat(100_000));
output.push('\n');
output.push_str(&"b".repeat(100_000));
output.push('\n');
output.push_str(&"c".repeat(99_999));
assert_eq!(
output.len(),
300_001,
"sanity: two 100KB lines + 99999 tail"
);
let result = bounded_truncate(&output);
assert!(
result.len() <= DISPLAY_CAP + 200,
"display ({} bytes) must be bounded by DISPLAY_CAP + marker allowance for \
output with only {} newlines",
result.len(),
output.bytes().filter(|b| *b == b'\n').count()
);
assert!(
std::str::from_utf8(result.as_bytes()).is_ok(),
"display must remain valid UTF-8"
);
assert!(
result.contains("bytes truncated"),
"display must still carry the truncation marker"
);
}
#[test]
fn test_bounded_truncate_single_mid_output_newline() {
let mut output = String::new();
output.push_str(&"x".repeat(150_000));
output.push('\n');
output.push_str(&"y".repeat(150_000));
let result = bounded_truncate(&output);
assert!(
result.len() <= DISPLAY_CAP + 200,
"display ({} bytes) must be bounded",
result.len()
);
assert!(std::str::from_utf8(result.as_bytes()).is_ok());
}
#[test]
fn test_bounded_truncate_display_cap_boundary() {
let big = "x".repeat(DISPLAY_CAP + 100);
let result = bounded_truncate(&big);
assert!(result.contains("bytes truncated"));
let marker_lines: Vec<&str> = result
.lines()
.filter(|l| l.contains("bytes truncated"))
.collect();
assert_eq!(marker_lines.len(), 1, "exactly one marker line");
}
#[test]
fn test_detect_category_sudo_prefixed() {
assert_eq!(detect_category("sudo cargo test"), CommandCategory::Status,);
assert_eq!(detect_category("sudo pytest"), CommandCategory::Status,);
assert_eq!(detect_category("sudo git status"), CommandCategory::Data,);
}
#[test]
fn test_detect_category_env_prefixed() {
assert_eq!(detect_category("env cargo test"), CommandCategory::Status,);
assert_eq!(
detect_category("env FOO=bar cargo test"),
CommandCategory::Status,
);
assert_eq!(
detect_category("env A=1 B=2 pytest"),
CommandCategory::Status,
);
}
#[test]
fn test_detect_category_cargo_nextest() {
assert_eq!(
detect_category("cargo nextest run"),
CommandCategory::Status,
);
}
#[test]
fn test_detect_category_sudo_full_path() {
assert_eq!(
detect_category("/usr/bin/sudo cargo test"),
CommandCategory::Status,
);
}
#[test]
fn test_detect_category_locked_unknowns() {
assert_eq!(detect_category("cargo run"), CommandCategory::Unknown,);
assert_eq!(detect_category("cargo doc"), CommandCategory::Unknown,);
assert_eq!(
detect_category("cargo run test-runner"),
CommandCategory::Unknown,
);
assert_eq!(
detect_category("someunknown foo sudo bar"),
CommandCategory::Unknown,
);
}
#[test]
fn test_detect_category_regression_locks() {
assert_eq!(detect_category("npm test"), CommandCategory::Status);
assert_eq!(detect_category("npm run test"), CommandCategory::Status,);
assert_eq!(detect_category("yarn run build"), CommandCategory::Status,);
assert_eq!(detect_category("bun run test"), CommandCategory::Status,);
assert_eq!(detect_category("pnpm run build"), CommandCategory::Status,);
assert_eq!(detect_category("npm install"), CommandCategory::Status,);
}
#[test]
fn test_label_prefix_stripping() {
assert_eq!(label("sudo cargo test"), "cargo");
assert_eq!(label("env FOO=bar cargo test"), "cargo");
assert_eq!(label("env cargo test"), "cargo");
assert_eq!(label("sudo git status"), "git");
}
#[test]
fn test_label_degenerate_prefix_only() {
assert_eq!(label("sudo"), "sudo");
assert_eq!(label("env"), "env");
assert_eq!(label("cargo"), "cargo");
}
#[test]
fn test_classify_sudo_cargo_test_quiet_success() {
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "sudo cargo test", &[]);
match result {
Classification::Success { label, summary } => {
assert_eq!(
label, "cargo",
"label must be the stripped binary, not 'sudo'"
);
assert!(summary.is_empty(), "quiet success must have empty summary");
}
other => panic!("expected Success (quiet) for sudo cargo test, got: {other:?}"),
}
}
#[test]
fn test_classify_env_cargo_test_quiet_success() {
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "env FOO=bar cargo test", &[]);
match result {
Classification::Success { label, summary } => {
assert_eq!(
label, "cargo",
"label must be the stripped binary, not 'env'"
);
assert!(summary.is_empty(), "quiet success must have empty summary");
}
other => panic!("expected Success (quiet) for env FOO=bar cargo test, got: {other:?}"),
}
}
#[test]
fn test_classify_cargo_nextest_run_quiet_success() {
let patterns = pattern::builtins();
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "cargo nextest run", &patterns);
match result {
Classification::Success { label, summary } => {
assert_eq!(label, "cargo");
assert!(summary.is_empty(), "quiet success must have empty summary");
}
other => panic!("expected Success (quiet) for cargo nextest run, got: {other:?}"),
}
}
#[test]
fn test_classify_sudo_cargo_nextest_run_quiet_success() {
let patterns = pattern::builtins();
let big = "x\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "sudo cargo nextest run", &patterns);
match result {
Classification::Success { label, summary } => {
assert_eq!(
label, "cargo",
"label must be the stripped binary, not 'sudo'"
);
assert!(summary.is_empty(), "quiet success must have empty summary");
}
other => panic!("expected Success (quiet) for sudo cargo nextest run, got: {other:?}"),
}
}
#[test]
fn test_detect_category_env_flags_are_not_assignments() {
assert_eq!(
detect_category("env -u FOO cargo test"),
CommandCategory::Unknown,
"env -u FOO cargo test must be Unknown (flag not treated as assignment)"
);
assert_eq!(
detect_category("env --split-string=x cargo test"),
CommandCategory::Unknown,
"env --split-string=x cargo test: flag starts with `-`, so it is NOT treated as a KV assignment → Unknown (output preserved, not silently suppressed)"
);
}
#[test]
fn test_detect_category_nextest_uses_known_position() {
assert_eq!(
detect_category("env nextest=foo cargo nextest run"),
CommandCategory::Status,
);
assert_eq!(
detect_category("env nextest=foo cargo nextest"),
CommandCategory::Unknown,
);
assert_eq!(
detect_category("env nextest=foo cargo nextest run --all"),
CommandCategory::Status,
);
}
#[test]
fn test_classify_sudo_git_log_large() {
let big = "line\n".repeat(3000); let out = make_output(0, &big);
let result = classify(&out, "sudo git log", &[]);
match result {
Classification::Large { label, size, .. } => {
assert_eq!(
label, "git",
"label must be the stripped binary, not 'sudo'"
);
assert!(size > SMALL_THRESHOLD);
}
other => panic!("expected Large (Data category) for sudo git log, got: {other:?}"),
}
}