#![allow(clippy::unwrap_used, clippy::expect_used)]
#![cfg(feature = "localfs")]
mod common;
use std::fs;
use tempfile::tempdir;
use common::{kernel_at, run};
fn mixed_tree(dir: &std::path::Path) {
fs::write(dir.join("main.rs"), "let needle = 1;\n").expect("write rs");
fs::write(dir.join("util.py"), "needle = 1\n").expect("write py");
fs::write(dir.join("README.md"), "the needle doc\n").expect("write md");
}
#[tokio::test]
async fn ftype_rust_only_searches_rust_files() {
let dir = tempdir().unwrap();
mixed_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -r needle . --ftype rust").await;
assert_eq!(code, 0, "match expected; out={out:?}");
assert!(out.contains("main.rs"), "rust file must match: {out:?}");
assert!(!out.contains("util.py"), "python file must be filtered out: {out:?}");
assert!(!out.contains("README.md"), "markdown file must be filtered out: {out:?}");
}
#[tokio::test]
async fn ftype_not_excludes_the_type() {
let dir = tempdir().unwrap();
mixed_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -r needle . --ftype-not rust").await;
assert_eq!(code, 0, "match expected; out={out:?}");
assert!(!out.contains("main.rs"), "rust file must be excluded: {out:?}");
assert!(out.contains("util.py"), "python file must remain: {out:?}");
assert!(out.contains("README.md"), "markdown file must remain: {out:?}");
}
#[tokio::test]
async fn ftype_repeatable_unions_types() {
let dir = tempdir().unwrap();
mixed_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -r needle . --ftype rust --ftype py").await;
assert_eq!(code, 0, "match expected; out={out:?}");
assert!(out.contains("main.rs"), "rust selected: {out:?}");
assert!(out.contains("util.py"), "python selected: {out:?}");
assert!(!out.contains("README.md"), "markdown not selected: {out:?}");
}
#[tokio::test]
async fn ftype_unknown_is_loud() {
let dir = tempdir().unwrap();
mixed_tree(dir.path());
let kernel = kernel_at(dir.path());
let result = kernel
.execute("grep -r needle . --ftype definitely-not-a-type")
.await
.expect("execute");
assert_eq!(result.code, 2, "unknown type must exit 2; out={:?}", result.text_out());
assert!(
result.err.contains("definitely-not-a-type") || result.err.contains("unknown file type"),
"error must name the bad type: {:?}",
result.err
);
}
#[tokio::test]
async fn ftype_list_emits_table() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep --ftype-list").await;
assert_eq!(code, 0, "ftype-list must exit 0; out={out:?}");
assert!(out.contains("rust"), "list must include rust: {out:?}");
assert!(out.contains("*.rs"), "list must show rust's globs: {out:?}");
}
#[tokio::test]
async fn ftype_list_json_shape() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep --ftype-list --json").await;
assert_eq!(code, 0, "out={out:?}");
let v: serde_json::Value = serde_json::from_str(&out).expect("valid json");
assert!(v.is_array(), "table --json is an array of rows: {out:?}");
let has_rust = v
.as_array()
.unwrap()
.iter()
.any(|row| row.get("TYPE").and_then(|t| t.as_str()) == Some("rust"));
assert!(has_rust, "rust row present: {out:?}");
}
#[tokio::test]
async fn hidden_flag_reaches_dotfiles() {
let dir = tempdir().unwrap();
fs::write(dir.path().join(".env"), "needle=secret\n").expect("write dotfile");
fs::write(dir.path().join("visible.txt"), "needle plain\n").expect("write visible");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -r needle .").await;
assert_eq!(code, 0, "visible match expected; out={out:?}");
assert!(out.contains("visible.txt"), "visible file matches: {out:?}");
assert!(!out.contains(".env"), "dotfile skipped by default: {out:?}");
let (out_h, code_h) = run(&kernel, "grep -r needle . --hidden").await;
assert_eq!(code_h, 0, "out={out_h:?}");
assert!(out_h.contains(".env"), "--hidden must reach the dotfile: {out_h:?}");
assert!(out_h.contains("visible.txt"), "visible still matches: {out_h:?}");
}
#[tokio::test]
async fn max_count_caps_single_file() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("f.txt"), "x\nx\nx\nx\nx\n").expect("write");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep --max-count 2 x f.txt").await;
assert_eq!(code, 0, "match expected; out={out:?}");
assert_eq!(out.lines().count(), 2, "must cap at 2 lines: {out:?}");
}
#[tokio::test]
async fn max_count_zero_matches_nothing() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("f.txt"), "x\nx\nx\n").expect("write");
let kernel = kernel_at(dir.path());
let result = kernel.execute("grep --max-count 0 x f.txt").await.expect("execute");
assert_eq!(result.code, 1, "max-count 0 matches nothing → exit 1");
assert!(result.text_out().trim().is_empty(), "no output: {:?}", result.text_out());
}
#[tokio::test]
async fn max_count_with_count_reports_capped() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("f.txt"), "x\nx\nx\nx\nx\n").expect("write");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -c --max-count 2 x f.txt").await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.trim(), "2", "count must be capped to max-count: {out:?}");
}
#[tokio::test]
async fn max_count_is_per_file_when_recursive() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("a.txt"), "x\nx\nx\nx\nx\n").expect("write a");
fs::write(dir.path().join("b.txt"), "x\nx\nx\nx\nx\n").expect("write b");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -r --max-count 2 x .").await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.lines().count(), 4, "2 per file across 2 files: {out:?}");
}
#[tokio::test]
async fn max_count_caps_streaming_stdin() {
let dir = tempdir().unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "seq 5 | grep --max-count 2 .").await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.lines().count(), 2, "stdin stream capped at 2: {out:?}");
}
#[tokio::test]
async fn recursive_flag_searches_a_file_operand() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("notes.txt"), "needle a\nhay\nneedle b\n").expect("write");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -r needle notes.txt").await;
assert_eq!(code, 0, "a file operand under -r must match, not silently miss: {out:?}");
assert!(out.contains("needle a"), "first match: {out:?}");
assert!(out.contains("needle b"), "second match: {out:?}");
}
#[tokio::test]
async fn recursive_count_on_a_file_operand() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("notes.txt"), "needle\nneedle\nhay\nneedle\n").expect("write");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -rc needle notes.txt").await;
assert_eq!(code, 0, "out={out:?}");
assert_eq!(out.trim(), "3", "count of a single file operand: {out:?}");
}
#[tokio::test]
async fn recursive_upper_flag_searches_a_file_operand() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("notes.txt"), "needle a\nhay\n").expect("write");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -R needle notes.txt").await;
assert_eq!(code, 0, "-R on a file operand must match: {out:?}");
assert!(out.contains("needle a"), "match: {out:?}");
}
#[tokio::test]
async fn recursive_mixed_file_and_dir_operands() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("top.txt"), "needle top\n").expect("write top");
fs::create_dir(dir.path().join("sub")).expect("mkdir sub");
fs::write(dir.path().join("sub/inner.txt"), "needle inner\n").expect("write inner");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -r needle top.txt sub").await;
assert_eq!(code, 0, "out={out:?}");
assert!(out.contains("top.txt"), "the file operand is searched: {out:?}");
assert!(out.contains("inner.txt"), "the dir operand is walked: {out:?}");
assert!(out.contains("needle top") && out.contains("needle inner"), "both matches: {out:?}");
}
#[tokio::test]
async fn bad_encoding_on_recursive_search_is_a_loud_usage_error() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("a.txt"), "needle a\n").expect("write a");
let kernel = kernel_at(dir.path());
let result = kernel
.execute("grep -r needle . --encoding no-such-encoding")
.await
.expect("kernel execute");
assert_eq!(result.code, 2, "bad --encoding is a usage error; err={:?}", result.err);
assert!(
result.err.contains("invalid encoding") && result.err.contains("no-such-encoding"),
"the bad label must be named: {:?}",
result.err
);
}