#![allow(clippy::unwrap_used, clippy::expect_used)]
#![cfg(feature = "localfs")]
mod common;
use std::fs;
use tempfile::tempdir;
use common::{kernel_at, run};
fn touch(dir: &std::path::Path, name: &str) {
let path = dir.join(name);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("create parent dirs");
}
fs::write(path, b"x").expect("write file");
}
#[tokio::test]
async fn absolute_pattern_preserves_leading_slash() {
let dir = tempdir().unwrap();
touch(dir.path(), "z.txt");
let kernel = kernel_at(dir.path());
let abs_pattern = format!("{}/*.txt", dir.path().display());
let expected = format!("{}/z.txt", dir.path().display());
let (out, code) = run(&kernel, &format!("glob '{abs_pattern}'")).await;
assert_eq!(code, 0, "glob should succeed: {out:?}");
assert!(
out.contains(&expected),
"expected absolute path {expected:?} in output, got {out:?}"
);
let stripped = expected.trim_start_matches('/');
assert!(
!out.split_whitespace().any(|line| line == stripped),
"output must not contain the path with its leading slash stripped: {out:?}"
);
}
#[tokio::test]
async fn relative_pattern_stays_relative() {
let dir = tempdir().unwrap();
touch(dir.path(), "z.txt");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "glob '*.txt'").await;
assert_eq!(code, 0, "glob should succeed: {out:?}");
assert_eq!(out, "z.txt", "relative pattern must report a bare relative name: {out:?}");
}
#[tokio::test]
async fn absolute_pattern_json_preserves_leading_slash() {
let dir = tempdir().unwrap();
touch(dir.path(), "z.txt");
let kernel = kernel_at(dir.path());
let abs_pattern = format!("{}/*.txt", dir.path().display());
let expected_json_string = format!("\"{}/z.txt\"", dir.path().display());
let (out, code) = run(&kernel, &format!("glob --json '{abs_pattern}'")).await;
assert_eq!(code, 0, "glob --json should succeed: {out:?}");
assert!(
out.contains(&expected_json_string),
"expected {expected_json_string:?} in --json output, got {out:?}"
);
}
#[tokio::test]
async fn absolute_pattern_unaffected_by_cd() {
let dir = tempdir().unwrap();
touch(dir.path(), "z.txt");
fs::create_dir_all(dir.path().join("elsewhere")).unwrap();
let kernel = kernel_at(dir.path());
let abs_pattern = format!("{}/*.txt", dir.path().display());
let expected = format!("{}/z.txt", dir.path().display());
let (out, code) = run(
&kernel,
&format!("cd elsewhere; glob '{abs_pattern}'"),
)
.await;
assert_eq!(code, 0, "glob should succeed after cd: {out:?}");
assert!(
out.contains(&expected),
"expected absolute path {expected:?} in output after cd, got {out:?}"
);
}
#[tokio::test]
async fn relative_pattern_after_cd_reports_subdir_relative_names() {
let dir = tempdir().unwrap();
fs::create_dir_all(dir.path().join("sub")).unwrap();
touch(dir.path(), "sub/inner.txt");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "cd sub; glob '*.txt'").await;
assert_eq!(code, 0, "glob should succeed after cd: {out:?}");
assert_eq!(
out, "inner.txt",
"relative pattern after cd must report a bare name relative to the new cwd: {out:?}"
);
}
#[tokio::test]
async fn absolute_pattern_through_expand_paths_keeps_leading_slash() {
let dir = tempdir().unwrap();
touch(dir.path(), "z.txt");
let kernel = kernel_at(dir.path());
let pattern = format!("{}/*.txt", dir.path().display());
let expected = format!("{}/z.txt", dir.path().display());
let (out, code) = run(&kernel, &format!("cd /; ls '{pattern}'")).await;
assert_eq!(code, 0, "ls should succeed: {out:?}");
assert_eq!(
out, expected,
"an absolute pattern must keep its leading slash with cwd=/: {out:?}"
);
}
#[tokio::test]
async fn expand_paths_absolute_pattern_unaffected_by_cwd() {
let dir = tempdir().unwrap();
touch(dir.path(), "z.txt");
let kernel = kernel_at(dir.path());
let pattern = format!("{}/*.txt", dir.path().display());
let expected = format!("{}/z.txt", dir.path().display());
let (out, code) = run(&kernel, &format!("ls '{pattern}'")).await;
assert_eq!(code, 0, "ls should succeed: {out:?}");
assert_eq!(out, expected, "absolute stays absolute from any cwd: {out:?}");
}
#[tokio::test]
async fn relative_pattern_through_expand_paths_stays_relative() {
let dir = tempdir().unwrap();
touch(dir.path(), "z.txt");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "ls '*.txt'").await;
assert_eq!(code, 0, "ls should succeed: {out:?}");
assert_eq!(out, "z.txt", "relative pattern must stay relative: {out:?}");
}
#[tokio::test]
async fn grep_recursive_absolute_operand_reports_absolute_paths() {
let dir = tempdir().unwrap();
touch(dir.path(), "a.txt");
fs::write(dir.path().join("a.txt"), b"HIT\n").unwrap();
let kernel = kernel_at(dir.path());
let expected = format!("{}/a.txt", dir.path().display());
let (out, code) = run(&kernel, &format!("grep -rl HIT {}", dir.path().display())).await;
assert_eq!(code, 0, "grep should match: {out:?}");
assert_eq!(out, expected, "an absolute operand keeps absolute results: {out:?}");
}
#[tokio::test]
async fn grep_recursive_absolute_operand_is_unaffected_by_cwd() {
let dir = tempdir().unwrap();
touch(dir.path(), "a.txt");
fs::write(dir.path().join("a.txt"), b"HIT\n").unwrap();
let kernel = kernel_at(dir.path());
let expected = format!("{}/a.txt", dir.path().display());
let (out, code) = run(
&kernel,
&format!("cd /; grep -rl HIT {}", dir.path().display()),
)
.await;
assert_eq!(code, 0, "grep should match: {out:?}");
assert_eq!(out, expected, "cwd must not change an absolute answer: {out:?}");
}
#[tokio::test]
async fn grep_recursive_relative_operand_matches_gnu_prefix() {
let dir = tempdir().unwrap();
fs::create_dir_all(dir.path().join("d")).unwrap();
fs::write(dir.path().join("d/a.txt"), b"HIT\n").unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -rl HIT d").await;
assert_eq!(code, 0, "grep should match: {out:?}");
assert_eq!(out, "d/a.txt", "a relative operand is prefixed like GNU: {out:?}");
}
#[tokio::test]
async fn grep_recursive_dot_slash_operand_keeps_its_spelling() {
let dir = tempdir().unwrap();
fs::create_dir_all(dir.path().join("d")).unwrap();
fs::write(dir.path().join("d/a.txt"), b"HIT\n").unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -rl HIT ./d").await;
assert_eq!(code, 0, "grep should match: {out:?}");
assert_eq!(out, "./d/a.txt", "a ./-spelled operand keeps its ./ prefix: {out:?}");
}
#[tokio::test]
async fn grep_recursive_dot_operand_keeps_gnu_dot_slash() {
let dir = tempdir().unwrap();
fs::create_dir_all(dir.path().join("d")).unwrap();
fs::write(dir.path().join("d/a.txt"), b"HIT\n").unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -rl HIT .").await;
assert_eq!(code, 0, "grep should match: {out:?}");
assert_eq!(out, "./d/a.txt", "an explicit `.` is joined, as GNU does: {out:?}");
}
#[tokio::test]
async fn grep_recursive_defaulted_operand_has_no_prefix() {
let dir = tempdir().unwrap();
fs::create_dir_all(dir.path().join("d")).unwrap();
fs::write(dir.path().join("d/a.txt"), b"HIT\n").unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -rl HIT").await;
assert_eq!(code, 0, "grep should match: {out:?}");
assert_eq!(out, "d/a.txt", "a defaulted operand stays bare: {out:?}");
}
#[tokio::test]
async fn grep_recursive_multiple_dir_operands_display_is_unchanged() {
let dir = tempdir().unwrap();
fs::create_dir_all(dir.path().join("d")).unwrap();
fs::create_dir_all(dir.path().join("d2")).unwrap();
fs::write(dir.path().join("d/a.txt"), b"HIT\n").unwrap();
fs::write(dir.path().join("d2/b.txt"), b"HIT\n").unwrap();
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "grep -rl HIT d d2").await;
assert_eq!(code, 0, "grep should match: {out:?}");
let mut lines: Vec<&str> = out.lines().collect();
lines.sort_unstable();
assert_eq!(lines, vec!["d/a.txt", "d2/b.txt"]);
}