#![cfg(feature = "localfs")]
mod common;
use common::{kernel_at, run};
#[tokio::test]
async fn grep_c_zero_matches_exits_1() {
let tmp = tempfile::tempdir().unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, "echo 'foo' | grep -c bar").await;
assert_eq!(out, "0", "count text still printed");
assert_eq!(code, 1, "zero matches must exit 1 (GNU parity)");
}
#[tokio::test]
async fn grep_c_with_matches_exits_0() {
let tmp = tempfile::tempdir().unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, "printf 'foo\\nfoo\\nbar\\n' | grep -c foo").await;
assert_eq!(out, "2");
assert_eq!(code, 0);
}
#[tokio::test]
async fn grep_c_multifile_exits_1_only_if_no_file_matches() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("a.txt"), "alpha\n").unwrap();
std::fs::write(tmp.path().join("b.txt"), "beta\n").unwrap();
let kernel = kernel_at(tmp.path());
let (_out, code) = run(&kernel, "grep -c zzz a.txt b.txt").await;
assert_eq!(code, 1, "no match in any file must exit 1");
let (_out, code) = run(&kernel, "grep -c alpha a.txt b.txt").await;
assert_eq!(code, 0, "a match in any file must exit 0");
}
#[tokio::test]
async fn grep_c_multifile_prints_per_file_counts() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("a.txt"), "alpha\nalpha\n").unwrap();
std::fs::write(tmp.path().join("b.txt"), "beta\n").unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, "grep -c alpha a.txt b.txt").await;
assert_eq!(out, "a.txt:2\nb.txt:0");
assert_eq!(code, 0);
let (out, code) = run(&kernel, "grep -c zzz a.txt b.txt").await;
assert_eq!(out, "a.txt:0\nb.txt:0");
assert_eq!(code, 1);
}
#[tokio::test]
async fn grep_c_recursive_prints_per_file_counts() {
let tmp = tempfile::tempdir().unwrap();
std::fs::create_dir(tmp.path().join("d")).unwrap();
std::fs::write(tmp.path().join("d/a.txt"), "alpha\n").unwrap();
std::fs::write(tmp.path().join("d/b.txt"), "alpha\nalpha\nbeta\n").unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, "grep -rc alpha d").await;
assert_eq!(code, 0);
let mut lines: Vec<&str> = out.lines().collect();
lines.sort_unstable();
assert_eq!(lines, vec!["d/a.txt:1", "d/b.txt:2"]);
}
#[tokio::test]
async fn grep_c_precedence_and_max_count_multifile() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("a.txt"), "x\nx\nx\nx\nx\n").unwrap();
std::fs::write(tmp.path().join("b.txt"), "x\ny\n").unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, "grep -qc x a.txt b.txt").await;
assert_eq!(out, "", "quiet must suppress count lines");
assert_eq!(code, 0);
let (out, code) = run(&kernel, "grep -qc zzz a.txt b.txt").await;
assert_eq!(out, "");
assert_eq!(code, 1);
let (out, code) = run(&kernel, "grep -lc x a.txt b.txt").await;
assert_eq!(out, "a.txt\nb.txt");
assert_eq!(code, 0);
let (out, code) = run(&kernel, "grep -c --max-count 2 x a.txt b.txt").await;
assert_eq!(out, "a.txt:2\nb.txt:1");
assert_eq!(code, 0);
}
#[tokio::test]
async fn grep_c_unreadable_operand_still_counts_the_readable_ones() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("real.txt"), "alpha\n").unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, "grep -c alpha missing.txt real.txt").await;
assert_eq!(code, 2, "an unreadable explicit operand is exit 2");
assert_eq!(out, "real.txt:1", "the readable file still gets its count");
}
#[tokio::test]
async fn cmd_subst_preserves_trailing_spaces() {
let tmp = tempfile::tempdir().unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, "x=$(printf 'a b '); echo \"[$x]\"").await;
assert_eq!(code, 0, "got: {out}");
assert_eq!(out, "[a b ]", "trailing spaces must survive command subst");
}
#[tokio::test]
async fn cmd_subst_still_strips_trailing_newlines() {
let tmp = tempfile::tempdir().unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, "x=$(printf 'hi\\n\\n'); echo \"[$x]\"").await;
assert_eq!(code, 0, "got: {out}");
assert_eq!(out, "[hi]", "trailing newlines must still be stripped");
}
#[tokio::test]
async fn jq_division_by_zero_is_loud() {
let tmp = tempfile::tempdir().unwrap();
let kernel = kernel_at(tmp.path());
let (_out, code) = run(&kernel, "echo '6' | jq '. / 0'").await;
assert_ne!(code, 0, "division by zero must fail loudly, not return null");
}
#[tokio::test]
async fn jq_zero_over_zero_nan_is_loud() {
let tmp = tempfile::tempdir().unwrap();
let kernel = kernel_at(tmp.path());
let (_out, code) = run(&kernel, "echo '0' | jq '. / 0'").await;
assert_ne!(code, 0, "0/0 (NaN) must fail loudly, not return null");
}
#[tokio::test]
async fn jq_finite_division_still_works() {
let tmp = tempfile::tempdir().unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, "echo '6' | jq '. / 2'").await;
assert_eq!(code, 0, "got: {out}");
assert_eq!(out, "3");
}
#[tokio::test]
async fn interpolated_arithmetic_division_by_zero_is_loud() {
let tmp = tempfile::tempdir().unwrap();
let kernel = kernel_at(tmp.path());
let result = kernel
.execute(r#"echo "value: $((1/0))""#)
.await
.expect("kernel execute");
assert_ne!(
result.code, 0,
"division by zero inside a string must fail loudly, not silently splice in \"\""
);
assert!(
result.err.contains("division by zero"),
"got: {}",
result.err
);
}
#[tokio::test]
async fn interpolated_arithmetic_still_works_for_valid_expressions() {
let tmp = tempfile::tempdir().unwrap();
let kernel = kernel_at(tmp.path());
let (out, code) = run(&kernel, r#"echo "value: $((2 + 2))""#).await;
assert_eq!(code, 0, "got: {out}");
assert_eq!(out, "value: 4");
}