use proptest::prelude::*;
use rch_common::classify_command;
proptest! {
#[test]
fn p1_determinism_same_input_same_output(
cmd in "\\PC{0,4096}",
) {
let first = classify_command(&cmd);
let second = classify_command(&cmd);
prop_assert_eq!(
&first,
&second,
"classify_command must be deterministic for input: {:?}",
cmd
);
}
}
proptest! {
#[test]
fn p2_trim_invariance(
body in "[a-zA-Z0-9._\\- /=]{0,200}",
prefix_ws in "[ \t\r\n]{0,8}",
suffix_ws in "[ \t\r\n]{0,8}",
) {
let trimmed_input = body.clone();
let padded_input = format!("{prefix_ws}{body}{suffix_ws}");
let trimmed_result = classify_command(&trimmed_input);
let padded_result = classify_command(&padded_input);
prop_assert_eq!(
trimmed_result.is_compilation,
padded_result.is_compilation,
"leading/trailing whitespace must not change is_compilation: trimmed={:?} padded={:?}",
trimmed_input,
padded_input
);
prop_assert_eq!(
trimmed_result.kind,
padded_result.kind,
"leading/trailing whitespace must not change CompilationKind: trimmed={:?} padded={:?}",
trimmed_input,
padded_input
);
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: std::env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(4096),
..ProptestConfig::default()
})]
#[test]
fn p5_no_panic_for_arbitrary_utf8(
cmd in r"\P{Cn}{0,4096}",
) {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
classify_command(&cmd)
}));
let cmd_len = cmd.len();
prop_assert!(
result.is_ok(),
"classify_command panicked on input ({} bytes): {:?}",
cmd_len,
cmd
);
}
}
const NORMALLY_INTERCEPTED_PREFIXES: &[&str] = &[
"cargo build",
"cargo test",
"cargo check",
"cargo clippy",
"cargo run",
"bun test",
"bun typecheck",
"gcc -o out main.c",
"g++ -o out main.cpp",
"clang -o out main.c",
"make",
];
const BENIGN_PAGERS: &[&str] = &[
"tee", "head", "cat", "grep", "less", "more", "tail", "wc", "rg",
];
proptest! {
#[test]
fn p6_pipe_into_non_pager_disables_interception(
prefix_idx in 0usize..NORMALLY_INTERCEPTED_PREFIXES.len(),
middle in "[a-zA-Z0-9._\\-=/ ]{0,40}",
rhs in "[a-zA-Z][a-zA-Z0-9._\\-]{0,20}",
) {
prop_assume!(!BENIGN_PAGERS.contains(&rhs.as_str()));
let prefix = NORMALLY_INTERCEPTED_PREFIXES[prefix_idx];
let baseline = format!("{prefix} {middle}");
let baseline_class = classify_command(baseline.trim());
prop_assume!(baseline_class.is_compilation);
let piped = format!("{prefix} {middle} | {rhs}");
let piped_class = classify_command(&piped);
prop_assert!(
!piped_class.is_compilation,
"pipe into non-pager `{rhs}` must disable interception, \
but {piped:?} classified as compilation ({piped_class:?})"
);
}
}
proptest! {
#[test]
fn p6b_pipe_into_pager_is_offloaded(
prefix_idx in 0usize..NORMALLY_INTERCEPTED_PREFIXES.len(),
middle in "[a-zA-Z0-9._\\-=/ ]{0,40}",
pager_idx in 0usize..BENIGN_PAGERS.len(),
) {
let prefix = NORMALLY_INTERCEPTED_PREFIXES[prefix_idx];
let baseline = format!("{prefix} {middle}");
let baseline_class = classify_command(baseline.trim());
prop_assume!(baseline_class.is_compilation);
let pager = BENIGN_PAGERS[pager_idx];
let piped = format!("{} | {pager}", baseline.trim());
let piped_class = classify_command(&piped);
prop_assert!(
piped_class.is_compilation,
"pipe into benign pager `{pager}` must be offloaded, \
but {piped:?} declined ({piped_class:?})"
);
prop_assert_eq!(
piped_class.extracted_command.as_deref(),
Some(piped.as_str())
);
}
}
proptest! {
#[test]
fn p6c_trailing_ampersand_is_offloaded(
prefix_idx in 0usize..NORMALLY_INTERCEPTED_PREFIXES.len(),
middle in "[a-zA-Z0-9._\\-=/ ]{0,40}",
) {
let prefix = NORMALLY_INTERCEPTED_PREFIXES[prefix_idx];
let baseline = format!("{prefix} {middle}");
let baseline_class = classify_command(baseline.trim());
prop_assume!(baseline_class.is_compilation);
let backgrounded = format!("{} &", baseline.trim());
let bg_class = classify_command(&backgrounded);
prop_assert!(
bg_class.is_compilation,
"trailing top-level `&` must be offloaded, \
but {backgrounded:?} declined ({bg_class:?})"
);
prop_assert_eq!(
bg_class.extracted_command.as_deref(),
Some(backgrounded.as_str())
);
}
}
proptest! {
#[test]
fn p6d_input_redirect_disables_interception(
prefix_idx in 0usize..NORMALLY_INTERCEPTED_PREFIXES.len(),
middle in "[a-zA-Z0-9._\\-=/ ]{0,40}",
file in "[a-zA-Z0-9._/\\-]{1,30}",
) {
let prefix = NORMALLY_INTERCEPTED_PREFIXES[prefix_idx];
let baseline = format!("{prefix} {middle}");
let baseline_class = classify_command(baseline.trim());
prop_assume!(baseline_class.is_compilation);
let redirected = format!("{} < {file}", baseline.trim());
let class = classify_command(&redirected);
prop_assert!(
!class.is_compilation,
"input redirect must disable interception, \
but {redirected:?} classified as compilation ({class:?})"
);
}
}
#[test]
fn known_inputs_classify_as_documented() {
for cmd in [
"cargo build",
"cargo test",
"cargo build --release",
"cargo check --workspace --all-targets",
"bun test",
] {
let c = classify_command(cmd);
assert!(c.is_compilation, "documented compilation missed: {cmd:?}");
}
for cmd in [
"bun install",
"bun run dev",
"bun test --watch",
"echo hi",
"cargo build | xargs rm", "cargo build < in.txt", "cargo build || echo x", ] {
let c = classify_command(cmd);
assert!(!c.is_compilation, "non-intercept got intercepted: {cmd:?}");
}
for (cmd, extracted) in [
("cargo build | tee log", "cargo build | tee log"),
("cargo test > out.log", "cargo test > out.log"),
("cargo run &", "cargo run &"),
] {
let c = classify_command(cmd);
assert!(c.is_compilation, "benign-suffix should offload: {cmd:?}");
assert_eq!(c.extracted_command.as_deref(), Some(extracted));
}
}