#![allow(clippy::unwrap_used, clippy::expect_used)]
#![cfg(feature = "localfs")]
mod common;
use common::{kernel_at, run};
use std::fs;
use tempfile::tempdir;
fn touch(dir: &std::path::Path, rel: &str, contents: &str) {
let path = dir.join(rel);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("create parent dirs");
}
fs::write(&path, contents).expect("write file");
}
fn build_tree(dir: &std::path::Path) {
touch(dir, "top.txt", "top\n");
fs::create_dir_all(dir.join("sub/deep")).expect("create sub/deep");
touch(dir, "sub/mid.txt", "mid\n");
touch(dir, "sub/deep/bottom.txt", "bottom\n");
}
#[tokio::test]
async fn find_regular_file_prints_itself() {
let dir = tempdir().unwrap();
touch(dir.path(), "hello.txt", "hello\n");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find hello.txt").await;
assert_eq!(code, 0, "find on a regular file should exit 0, got: {out:?}");
assert!(
out.contains("hello.txt"),
"find on a regular file should print the file path, got: {out:?}"
);
}
#[tokio::test]
async fn find_regular_file_no_extra_output() {
let dir = tempdir().unwrap();
build_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find top.txt").await;
assert_eq!(code, 0, "exit code: {out:?}");
assert!(out.contains("top.txt"), "file path missing: {out:?}");
assert!(
!out.contains("mid.txt"),
"find on a file should not recurse: {out:?}"
);
}
#[tokio::test]
async fn find_maxdepth_0_prints_start_dir_only() {
let dir = tempdir().unwrap();
build_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find . -maxdepth 0").await;
assert_eq!(code, 0, "exit 0 expected: {out:?}");
assert!(!out.is_empty(), "output should not be empty: {out:?}");
assert!(
!out.contains("top.txt"),
"-maxdepth 0 must not list children, got: {out:?}"
);
assert!(
!out.contains("sub"),
"-maxdepth 0 must not list children, got: {out:?}"
);
}
#[tokio::test]
async fn find_maxdepth_1_prints_dir_and_direct_children() {
let dir = tempdir().unwrap();
build_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find . -maxdepth 1").await;
assert_eq!(code, 0, "exit code: {out:?}");
assert!(out.contains("top.txt"), "depth-1 child missing: {out:?}");
assert!(out.contains("sub"), "sub dir missing: {out:?}");
assert!(
!out.contains("bottom.txt"),
"-maxdepth 1 should not reach bottom.txt: {out:?}"
);
}
#[tokio::test]
async fn find_mindepth_1_skips_start_dir() {
let dir = tempdir().unwrap();
build_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find . -mindepth 1").await;
assert_eq!(code, 0, "exit code: {out:?}");
assert!(out.contains("top.txt"), "top.txt should appear: {out:?}");
let dot_line = out
.lines()
.any(|l| l.trim() == "." || l.trim() == "./");
assert!(
!dot_line,
"-mindepth 1 should exclude the start dir itself: {out:?}"
);
}
#[tokio::test]
async fn find_mindepth_2_skips_shallow_files() {
let dir = tempdir().unwrap();
build_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find . -mindepth 2").await;
assert_eq!(code, 0, "exit code: {out:?}");
assert!(
!out.contains("top.txt"),
"-mindepth 2 should exclude depth-1 files: {out:?}"
);
assert!(out.contains("mid.txt"), "mid.txt should appear: {out:?}");
assert!(
out.contains("bottom.txt"),
"bottom.txt should appear: {out:?}"
);
}
#[tokio::test]
async fn find_path_predicate_matches_full_path() {
let dir = tempdir().unwrap();
build_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find . -path '*/sub/*'").await;
assert_eq!(code, 0, "exit code: {out:?}");
assert!(out.contains("mid.txt"), "mid.txt should match -path: {out:?}");
assert!(
out.contains("bottom.txt"),
"bottom.txt should match -path: {out:?}"
);
assert!(
!out.contains("top.txt"),
"top.txt should not match -path '*/sub/*': {out:?}"
);
}
#[tokio::test]
async fn find_path_predicate_no_match_returns_empty() {
let dir = tempdir().unwrap();
build_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find . -path '*/nonexistent/*'").await;
assert_eq!(code, 0, "exit code: {out:?}");
assert!(
out.trim().is_empty(),
"-path with no matches should produce no output, got: {out:?}"
);
}
#[tokio::test]
async fn find_ipath_is_case_insensitive() {
let dir = tempdir().unwrap();
build_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find . -ipath '*/TOP.TXT'").await;
assert_eq!(code, 0, "exit code: {out:?}");
assert!(
out.contains("top.txt"),
"-ipath should match case-insensitively, got: {out:?}"
);
}
#[tokio::test]
async fn find_ipath_does_not_match_wrong_case_sensitive_path() {
let dir = tempdir().unwrap();
build_tree(dir.path());
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find . -path '*/TOP.TXT'").await;
assert_eq!(code, 0, "exit code: {out:?}");
assert!(
out.trim().is_empty(),
"-path should be case-sensitive, got: {out:?}"
);
}
#[tokio::test]
async fn find_file_operand_honors_size_filter() {
let dir = tempdir().unwrap();
touch(dir.path(), "small.txt", "tiny"); let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find small.txt -size +1k").await;
assert_eq!(code, 0, "{out:?}");
assert!(out.is_empty(), "-size +1k should exclude a 4-byte file: {out:?}");
let (out, code) = run(&kernel, "find small.txt -size +0").await;
assert_eq!(code, 0, "{out:?}");
assert!(out.contains("small.txt"), "-size +0 should include a 4-byte file: {out:?}");
}
#[tokio::test]
async fn find_maxdepth0_honors_size_filter() {
let dir = tempdir().unwrap();
touch(dir.path(), "small.txt", "tiny");
let kernel = kernel_at(dir.path());
let (out, code) = run(&kernel, "find small.txt -maxdepth 0 -size +1k").await;
assert_eq!(code, 0, "{out:?}");
assert!(out.is_empty(), "-maxdepth 0 -size +1k should exclude a 4-byte file: {out:?}");
}