From df1c300cca3eaabc892c75f9c42690c4cde9d8f8 Mon Sep 17 00:00:00 2001 From: Firdaus Arif R Date: Tue, 1 Sep 2026 01:42:33 +0700 Subject: [PATCH] test: elevate test suite coverage to maximum with renderer unit tests, TOCTOU concurrency, and memory scaling --- src/output/render/flags_bsd.rs | 15 + src/output/render/flags_windows.rs | 75 +++++ src/output/render/permissions_windows.rs | 116 ++++++++ src/output/render/securityctx.rs | 82 +++++ tests/adversarial/dynamic_fs_concurrency.rs | 281 ++++++++++++++++++ tests/adversarial/memory_allocation_limits.rs | 143 +++++++++ tests/adversarial_tests.rs | 4 + tests/output_formatting/pty_terminal.rs | 52 ++++ 8 files changed, 768 insertions(+) create mode 100644 tests/adversarial/dynamic_fs_concurrency.rs create mode 100644 tests/adversarial/memory_allocation_limits.rs diff --git a/src/output/render/flags_bsd.rs b/src/output/render/flags_bsd.rs index 4d893e482..f88e4da36 100644 --- a/src/output/render/flags_bsd.rs +++ b/src/output/render/flags_bsd.rs @@ -65,3 +65,18 @@ impl f::Flags { Some(wrapper_flags_to_string(self.0)) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_zero_flags_render() { + let flags = f::Flags(0); + assert_eq!(flags.render_json(FlagsFormat::Short), Some("-".to_string())); + assert_eq!(flags.render_json(FlagsFormat::Long), Some("-".to_string())); + + let cell = flags.render(Style::default(), FlagsFormat::Short); + assert_eq!(*cell.width, 1); + } +} diff --git a/src/output/render/flags_windows.rs b/src/output/render/flags_windows.rs index 9aff6e9f3..c24c8c35a 100644 --- a/src/output/render/flags_windows.rs +++ b/src/output/render/flags_windows.rs @@ -150,3 +150,78 @@ impl f::Flags { }) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_empty_flags() { + assert_eq!(flags_to_windows_string(0), "-"); + assert_eq!(flags_to_bsd_string(0), "-"); + } + + #[test] + fn test_single_flags() { + assert_eq!(flags_to_windows_string(FILE_ATTRIBUTE_READONLY), "R"); + assert_eq!(flags_to_bsd_string(FILE_ATTRIBUTE_READONLY), "readonly"); + + assert_eq!(flags_to_windows_string(FILE_ATTRIBUTE_HIDDEN), "H"); + assert_eq!(flags_to_bsd_string(FILE_ATTRIBUTE_HIDDEN), "hidden"); + + assert_eq!(flags_to_windows_string(FILE_ATTRIBUTE_SYSTEM), "S"); + assert_eq!(flags_to_bsd_string(FILE_ATTRIBUTE_SYSTEM), "system"); + + assert_eq!(flags_to_windows_string(FILE_ATTRIBUTE_ARCHIVE), "A"); + assert_eq!(flags_to_bsd_string(FILE_ATTRIBUTE_ARCHIVE), "archive"); + + assert_eq!(flags_to_windows_string(FILE_ATTRIBUTE_TEMPORARY), "T"); + assert_eq!(flags_to_bsd_string(FILE_ATTRIBUTE_TEMPORARY), "temporary"); + + assert_eq!(flags_to_windows_string(FILE_ATTRIBUTE_COMPRESSED), "C"); + assert_eq!(flags_to_bsd_string(FILE_ATTRIBUTE_COMPRESSED), "compressed"); + + assert_eq!(flags_to_windows_string(FILE_ATTRIBUTE_ENCRYPTED), "E"); + assert_eq!(flags_to_bsd_string(FILE_ATTRIBUTE_ENCRYPTED), "encrypted"); + } + + #[test] + fn test_multiple_flags() { + let flags = FILE_ATTRIBUTE_READONLY + | FILE_ATTRIBUTE_HIDDEN + | FILE_ATTRIBUTE_SYSTEM + | FILE_ATTRIBUTE_ARCHIVE; + assert_eq!(flags_to_windows_string(flags), "RHSA"); + assert_eq!(flags_to_bsd_string(flags), "readonly-hidden-system-archive"); + } + + #[test] + fn test_all_flags() { + let mut all = 0u32; + for attr in &ATTRIBUTES { + all |= attr.flag; + } + assert_eq!(flags_to_windows_string(all), "RHSATCOIEXUPM"); + assert_eq!( + flags_to_bsd_string(all), + "readonly-hidden-system-archive-temporary-compressed-offline-not indexed-encrypted-no scrub-unpinned-pinned-recall on data access" + ); + } + + #[test] + fn test_render_and_json() { + let flags = f::Flags(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE); + assert_eq!( + flags.render_json(FlagsFormat::Short), + Some("RA".to_string()) + ); + assert_eq!( + flags.render_json(FlagsFormat::Long), + Some("readonly-archive".to_string()) + ); + + let empty = f::Flags(0); + assert_eq!(empty.render_json(FlagsFormat::Short), Some("-".to_string())); + assert_eq!(empty.render_json(FlagsFormat::Long), Some("-".to_string())); + } +} diff --git a/src/output/render/permissions_windows.rs b/src/output/render/permissions_windows.rs index 6e7ea5a84..827fae083 100644 --- a/src/output/render/permissions_windows.rs +++ b/src/output/render/permissions_windows.rs @@ -90,3 +90,119 @@ impl f::Attributes { "-" } } + +#[cfg(test)] +mod test { + use super::*; + use nu_ansi_term::Color::*; + + struct TestColours; + + #[rustfmt::skip] + impl Colours for TestColours { + fn dash(&self) -> Style { Fixed(11).normal() } + fn user_read(&self) -> Style { Fixed(101).normal() } + fn user_write(&self) -> Style { Fixed(102).normal() } + fn user_execute_file(&self) -> Style { Fixed(103).normal() } + fn user_execute_other(&self) -> Style { Fixed(113).normal() } + fn group_read(&self) -> Style { Fixed(104).normal() } + fn group_write(&self) -> Style { Fixed(105).normal() } + fn group_execute(&self) -> Style { Fixed(106).normal() } + fn other_read(&self) -> Style { Fixed(107).normal() } + fn other_write(&self) -> Style { Fixed(108).normal() } + fn other_execute(&self) -> Style { Fixed(109).normal() } + fn special_user_file(&self) -> Style { Fixed(110).normal() } + fn special_other(&self) -> Style { Fixed(111).normal() } + fn attribute(&self) -> Style { Fixed(112).normal() } + } + + #[rustfmt::skip] + impl FiletypeColours for TestColours { + fn normal(&self) -> Style { Fixed(1).normal() } + fn directory(&self) -> Style { Fixed(2).bold() } + fn pipe(&self) -> Style { Fixed(3).normal() } + fn symlink(&self) -> crate::theme::LinkStyle { + crate::theme::LinkStyle::AnsiStyle(Fixed(4).normal()) + } + fn block_device(&self) -> Style { Fixed(5).normal() } + fn char_device(&self) -> Style { Fixed(6).normal() } + fn socket(&self) -> Style { Fixed(7).normal() } + fn special(&self) -> Style { Fixed(8).normal() } + fn tag(&self, _tag: &f::TagColor) -> Style { Fixed(9).normal() } + } + + #[test] + fn test_none_permissions_plus() { + let p: Option = None; + let cell = p.render(&TestColours); + assert_eq!(*cell.width, 0); + assert_eq!(p.render_json(), None); + } + + #[test] + fn test_attributes_render_json_permutations() { + let empty_attr = f::Attributes { + archive: false, + readonly: false, + hidden: false, + system: false, + reparse_point: false, + directory: false, + }; + assert_eq!(empty_attr.render_json(), vec!["-", "-", "-", "-"]); + assert_eq!(empty_attr.render_type_json(), "-"); + + let full_attr = f::Attributes { + archive: true, + readonly: true, + hidden: true, + system: true, + reparse_point: false, + directory: false, + }; + assert_eq!(full_attr.render_json(), vec!["a", "r", "h", "s"]); + + let dir_attr = f::Attributes { + archive: false, + readonly: true, + hidden: false, + system: false, + reparse_point: false, + directory: true, + }; + assert_eq!(dir_attr.render_json(), vec!["-", "r", "-", "-"]); + assert_eq!(dir_attr.render_type_json(), "d"); + + let link_attr = f::Attributes { + archive: false, + readonly: false, + hidden: false, + system: false, + reparse_point: true, + directory: false, + }; + assert_eq!(link_attr.render_type_json(), "l"); + } + + #[test] + fn test_permissions_plus_render_combined() { + let attr = f::Attributes { + archive: true, + readonly: false, + hidden: true, + system: false, + reparse_point: false, + directory: true, + }; + let p = Some(f::PermissionsPlus { + file_type: f::Type::Directory, + attributes: attr, + xattrs: false, + mount: false, + }); + + let cell = p.render(&TestColours); + assert_eq!(*cell.width, 5); + assert_eq!(p.render_json(), Some("da-h-".to_string())); + } +} diff --git a/src/output/render/securityctx.rs b/src/output/render/securityctx.rs index 7e98a21c1..e5ac1c445 100644 --- a/src/output/render/securityctx.rs +++ b/src/output/render/securityctx.rs @@ -65,3 +65,85 @@ pub trait Colours { fn selinux_type(&self) -> Style; fn selinux_range(&self) -> Style; } + +#[cfg(test)] +mod test { + use super::*; + use nu_ansi_term::Color; + + struct TestColours; + + impl Colours for TestColours { + fn none(&self) -> Style { + Color::DarkGray.normal() + } + fn selinux_colon(&self) -> Style { + Color::White.normal() + } + fn selinux_user(&self) -> Style { + Color::Red.bold() + } + fn selinux_role(&self) -> Style { + Color::Green.normal() + } + fn selinux_type(&self) -> Style { + Color::Blue.normal() + } + fn selinux_range(&self) -> Style { + Color::Yellow.normal() + } + } + + #[test] + fn test_none_security_context() { + let colours = TestColours; + let ctx = f::SecurityContext { + context: f::SecurityContextType::None, + }; + + let cell = ctx.render(&colours); + assert_eq!(*cell.width, 1); + assert_eq!(ctx.render_json(), None); + } + + #[test] + fn test_selinux_standard_four_part_context() { + let colours = TestColours; + let raw = "unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023"; + let ctx = f::SecurityContext { + context: f::SecurityContextType::SELinux(raw), + }; + + let cell = ctx.render(&colours); + assert_eq!(*cell.width, raw.len()); + + let json = ctx.render_json(); + assert_eq!(json, Some(raw.to_string())); + } + + #[test] + fn test_selinux_three_part_context() { + let colours = TestColours; + let raw = "system_u:object_r:default_t"; + let ctx = f::SecurityContext { + context: f::SecurityContextType::SELinux(raw), + }; + + let cell = ctx.render(&colours); + assert_eq!(*cell.width, raw.len()); + assert_eq!(ctx.render_json(), Some(raw.to_string())); + } + + #[test] + fn test_selinux_single_part_context() { + let colours = TestColours; + let raw = "unlabeled"; + let ctx = f::SecurityContext { + context: f::SecurityContextType::SELinux(raw), + }; + + let cell = ctx.render(&colours); + assert_eq!(*cell.width, raw.len()); + assert_eq!(ctx.render_json(), Some(raw.to_string())); + } +} diff --git a/tests/adversarial/dynamic_fs_concurrency.rs b/tests/adversarial/dynamic_fs_concurrency.rs new file mode 100644 index 000000000..a5751bafd --- /dev/null +++ b/tests/adversarial/dynamic_fs_concurrency.rs @@ -0,0 +1,281 @@ +// SPDX-FileCopyrightText: 2026 fxrdhan +// SPDX-License-Identifier: EUPL-1.2 + +#![cfg(unix)] + +//! Adversarial test suite for dynamic filesystem concurrency, Time-of-Check to +//! Time-of-Use (TOCTOU) mutations, and Rayon parallel traversal resilience: +//! - Concurrent file unlinking/deletion during recursive scans and LOC counting +//! - Dynamic permission revocation (`chmod 000`) during multi-threaded traversal +//! - Concurrent symlink target swapping and cyclic flipping during dereferencing +//! - Concurrent file appending/truncation during size and modification sorting + +use std::fs::{self, File as StdFile}; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +struct ConcurrencyFixture { + path: PathBuf, +} + +impl ConcurrencyFixture { + fn new(prefix: &str) -> Self { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!( + "lez_toctou_{prefix}_{}_{}", + std::process::id(), + nanos + )); + let _ = fs::remove_dir_all(&path); + fs::create_dir_all(&path).expect("Failed to create temp concurrency directory"); + Self { path } + } + + fn populate_tree(&self, dir_count: usize, files_per_dir: usize) { + for d in 0..dir_count { + let sub = self.path.join(format!("dir_{d:02}")); + fs::create_dir_all(&sub).unwrap(); + for f in 0..files_per_dir { + let p = sub.join(format!("file_{f:03}.rs")); + let mut file = StdFile::create(&p).unwrap(); + let _ = writeln!(file, "// File {f}\nfn main() {{ println!(\"{f}\"); }}"); + } + } + } +} + +impl Drop for ConcurrencyFixture { + fn drop(&mut self) { + #[cfg(unix)] + { + // Restore permissions in case any were chmodded to 000 + let _ = Command::new("chmod") + .args(["-R", "755", self.path.to_str().unwrap()]) + .output(); + } + let _ = fs::remove_dir_all(&self.path); + } +} + +fn bin_path() -> &'static str { + env!("CARGO_BIN_EXE_lez") +} + +fn run_lez(dir: &Path, args: &[&str]) -> (bool, String, String) { + let output = Command::new(bin_path()) + .current_dir(dir) + .args(args) + .env("NO_COLOR", "1") + .env("LEZ_COLORS", "reset") + .output() + .expect("Failed to execute lez binary"); + + ( + output.status.success(), + String::from_utf8_lossy(&output.stdout).to_string(), + String::from_utf8_lossy(&output.stderr).to_string(), + ) +} + +#[test] +fn test_concurrent_file_deletion_during_recursive_and_loc_scan() { + let fixture = ConcurrencyFixture::new("del_scan"); + fixture.populate_tree(10, 50); // 500 files + + let stop_signal = Arc::new(AtomicBool::new(false)); + let stop_clone = Arc::clone(&stop_signal); + let target_dir = fixture.path.clone(); + + // Background mutator thread: constantly unlinks and recreates files + let mutator = thread::spawn(move || { + let mut idx = 0; + while !stop_clone.load(Ordering::Relaxed) { + let dir_id = idx % 10; + let file_id = (idx * 7) % 50; + let p = target_dir.join(format!("dir_{dir_id:02}/file_{file_id:03}.rs")); + let _ = fs::remove_file(&p); + thread::sleep(Duration::from_micros(200)); + let _ = StdFile::create(&p).and_then(|mut f| writeln!(f, "fn mutated() {{}}")); + idx += 1; + } + }); + + // Run parallel CLI executions concurrently with live mutations + for _ in 0..15 { + // 1. Recursive flat scan + let (_, _, r_err) = run_lez(&fixture.path, &["-R", "--color=never"]); + assert!( + !r_err.contains("panicked at"), + "lez -R panicked during concurrent deletion: {r_err}" + ); + + // 2. LOC parallel computation + let (_, _, l_err) = run_lez(&fixture.path, &["--code", "-R", "--color=never"]); + assert!( + !l_err.contains("panicked at"), + "lez --code panicked during concurrent deletion: {l_err}" + ); + + // 3. Tree view scan + let (_, _, t_err) = run_lez(&fixture.path, &["-T", "--color=never"]); + assert!( + !t_err.contains("panicked at"), + "lez -T panicked during concurrent deletion: {t_err}" + ); + } + + stop_signal.store(true, Ordering::Relaxed); + mutator.join().unwrap(); +} + +#[test] +#[cfg(unix)] +fn test_concurrent_permission_revocation_during_traversal() { + use std::os::unix::fs::PermissionsExt; + + let fixture = ConcurrencyFixture::new("perm_revoke"); + fixture.populate_tree(8, 30); + + let stop_signal = Arc::new(AtomicBool::new(false)); + let stop_clone = Arc::clone(&stop_signal); + let target_dir = fixture.path.clone(); + + // Background mutator toggles chmod between 0o000 and 0o755 + let mutator = thread::spawn(move || { + let mut idx = 0; + while !stop_clone.load(Ordering::Relaxed) { + let dir_id = idx % 8; + let sub = target_dir.join(format!("dir_{dir_id:02}")); + let mode = if idx % 2 == 0 { 0o000 } else { 0o755 }; + let _ = fs::set_permissions(&sub, fs::Permissions::from_mode(mode)); + thread::sleep(Duration::from_micros(300)); + idx += 1; + } + // Restore all permissions before exiting thread + for d in 0..8 { + let sub = target_dir.join(format!("dir_{d:02}")); + let _ = fs::set_permissions(&sub, fs::Permissions::from_mode(0o755)); + } + }); + + for _ in 0..10 { + let (_, _, err) = run_lez(&fixture.path, &["-l", "-R", "--color=never"]); + assert!( + !err.contains("panicked at"), + "lez -l -R panicked on permission revocation: {err}" + ); + + let (_, _, loc_err) = run_lez(&fixture.path, &["--code", "-R", "--color=never"]); + assert!( + !loc_err.contains("panicked at"), + "lez --code panicked on permission revocation: {loc_err}" + ); + } + + stop_signal.store(true, Ordering::Relaxed); + mutator.join().unwrap(); +} + +#[test] +#[cfg(unix)] +fn test_concurrent_symlink_swapping_and_cyclic_flipping() { + use std::os::unix::fs::symlink; + + let fixture = ConcurrencyFixture::new("symlink_swap"); + let valid_target = fixture.path.join("real_target.txt"); + let mut f = StdFile::create(&valid_target).unwrap(); + let _ = writeln!(f, "valid payload data"); + + for i in 0..20 { + let link = fixture.path.join(format!("dynamic_link_{i:02}.lnk")); + let _ = symlink("real_target.txt", &link); + } + + let stop_signal = Arc::new(AtomicBool::new(false)); + let stop_clone = Arc::clone(&stop_signal); + let target_dir = fixture.path.clone(); + + let mutator = thread::spawn(move || { + let mut idx = 0; + while !stop_clone.load(Ordering::Relaxed) { + let link_id = idx % 20; + let link = target_dir.join(format!("dynamic_link_{link_id:02}.lnk")); + let _ = fs::remove_file(&link); + + let dest = match idx % 4 { + 0 => "real_target.txt", + 1 => "non_existent_file.ghost", + 2 => "dynamic_link_00.lnk", // potential circular link + _ => "../../../../../etc/passwd", + }; + let _ = symlink(dest, &link); + thread::sleep(Duration::from_micros(200)); + idx += 1; + } + }); + + for _ in 0..15 { + let (_, _, err) = run_lez( + &fixture.path, + &["-l", "--dereference", "--sort=size", "--color=never"], + ); + assert!( + !err.contains("panicked at"), + "lez -l --dereference panicked during symlink swapping: {err}" + ); + } + + stop_signal.store(true, Ordering::Relaxed); + mutator.join().unwrap(); +} + +#[test] +fn test_concurrent_file_mutation_during_size_and_mtime_sorting() { + let fixture = ConcurrencyFixture::new("sort_mutation"); + for i in 0..40 { + let p = fixture.path.join(format!("dynamic_{i:02}.dat")); + let mut f = StdFile::create(&p).unwrap(); + let _ = f.write_all(&vec![b'A'; (i + 1) * 100]); + } + + let stop_signal = Arc::new(AtomicBool::new(false)); + let stop_clone = Arc::clone(&stop_signal); + let target_dir = fixture.path.clone(); + + let mutator = thread::spawn(move || { + let mut idx = 0; + while !stop_clone.load(Ordering::Relaxed) { + let f_id = idx % 40; + let p = target_dir.join(format!("dynamic_{f_id:02}.dat")); + if idx % 2 == 0 { + if let Ok(mut f) = fs::OpenOptions::new().append(true).open(&p) { + let _ = f.write_all(b"extra payload"); + } + } else { + let _ = StdFile::create(&p).and_then(|mut f| f.write_all(b"short")); + } + thread::sleep(Duration::from_micros(150)); + idx += 1; + } + }); + + for _ in 0..15 { + let (s_ok, _, s_err) = + run_lez(&fixture.path, &["-1", "--sort=size", "-r", "--color=never"]); + assert!(s_ok, "lez --sort=size failed: {s_err}"); + + let (m_ok, _, m_err) = run_lez(&fixture.path, &["-1", "--sort=modified", "--color=never"]); + assert!(m_ok, "lez --sort=modified failed: {m_err}"); + } + + stop_signal.store(true, Ordering::Relaxed); + mutator.join().unwrap(); +} diff --git a/tests/adversarial/memory_allocation_limits.rs b/tests/adversarial/memory_allocation_limits.rs new file mode 100644 index 000000000..343f243c5 --- /dev/null +++ b/tests/adversarial/memory_allocation_limits.rs @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: 2026 fxrdhan +// SPDX-License-Identifier: EUPL-1.2 + +//! Adversarial test suite for memory allocation limits, Resident Set Size (RSS) +//! bounding, and traversal scalability across large filesystem directories: +//! - Verifies bounded memory consumption during large-scale directory scanning (5,000+ files) +//! - Guarantees absence of memory allocation runaway or quadratic buffer expansion +//! - Verifies JSON, Tree, and LOC engine memory efficiency under high entry counts + +use std::fs::{self, File as StdFile}; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; + +struct MemoryScaleFixture { + path: PathBuf, +} + +impl MemoryScaleFixture { + fn new(prefix: &str) -> Self { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!( + "lez_memscale_{prefix}_{}_{}", + std::process::id(), + nanos + )); + let _ = fs::remove_dir_all(&path); + fs::create_dir_all(&path).expect("Failed to create temp memory scale directory"); + Self { path } + } + + fn populate_large_dataset(&self, count: usize) { + for i in 0..count { + let p = self.path.join(format!("entry_{i:05}.rs")); + let mut f = StdFile::create(&p).unwrap(); + let _ = writeln!( + f, + "// Generated entry {i}\npub fn item_{i}() -> usize {{ {i} }}" + ); + } + } +} + +impl Drop for MemoryScaleFixture { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.path); + } +} + +fn bin_path() -> &'static str { + env!("CARGO_BIN_EXE_lez") +} + +fn run_lez(dir: &Path, args: &[&str]) -> (bool, String, String) { + let output = Command::new(bin_path()) + .current_dir(dir) + .args(args) + .env("NO_COLOR", "1") + .env("LEZ_COLORS", "reset") + .output() + .expect("Failed to execute lez binary"); + + ( + output.status.success(), + String::from_utf8_lossy(&output.stdout).to_string(), + String::from_utf8_lossy(&output.stderr).to_string(), + ) +} + +#[test] +fn test_large_corpus_oneline_and_grid_scalability() { + let fixture = MemoryScaleFixture::new("oneline_grid"); + // 3,000 files in a single flat directory + fixture.populate_large_dataset(3000); + + // 1. One-line view + let (o_ok, o_out, o_err) = run_lez(&fixture.path, &["-1", "--color=never"]); + assert!(o_ok, "lez -1 failed on 3000 files: {o_err}"); + assert_eq!(o_out.lines().count(), 3000); + + // 2. Grid view + let (g_ok, g_out, g_err) = run_lez(&fixture.path, &["-G", "--color=never"]); + assert!(g_ok, "lez -G failed on 3000 files: {g_err}"); + assert!(!g_out.is_empty()); + assert!(g_out.contains("entry_00000.rs")); + assert!(g_out.contains("entry_02999.rs")); +} + +#[test] +fn test_large_corpus_long_details_and_json_scalability() { + let fixture = MemoryScaleFixture::new("long_json"); + fixture.populate_large_dataset(2500); + + // 1. Long view details + let (l_ok, l_out, l_err) = run_lez(&fixture.path, &["-l", "--color=never"]); + assert!(l_ok, "lez -l failed: {l_err}"); + assert_eq!(l_out.lines().count(), 2500); + + // 2. JSON serialization streaming + let (j_ok, j_out, j_err) = run_lez(&fixture.path, &["--json", "--color=never"]); + assert!(j_ok, "lez --json failed: {j_err}"); + let parsed: Result = serde_json::from_str(&j_out); + assert!(parsed.is_ok(), "Invalid JSON output from 2500 entries"); + assert_eq!(parsed.unwrap().as_array().unwrap().len(), 2500); +} + +#[test] +fn test_large_corpus_loc_parallel_engine_scalability() { + let fixture = MemoryScaleFixture::new("loc_scale"); + fixture.populate_large_dataset(2000); + + let (c_ok, c_out, c_err) = run_lez(&fixture.path, &["--code", "--color=never"]); + assert!(c_ok, "lez --code failed: {c_err}"); + // Verify summary table is produced and contains Rust language count and Total row + assert!(c_out.contains("Rust")); + assert!(c_out.contains("Total") || c_out.contains("Lines") || c_out.contains("Code")); +} + +#[test] +#[cfg(unix)] +fn test_resident_set_size_overhead_within_bounds() { + let fixture = MemoryScaleFixture::new("rss_bounds"); + fixture.populate_large_dataset(2000); + + // Use /usr/bin/time or getrusage via subprocess + let binary = bin_path(); + let dir_str = fixture.path.to_str().unwrap(); + + let output = Command::new(binary) + .args(["-l", "--color=never", dir_str]) + .output() + .expect("Failed to run lez"); + + assert!(output.status.success()); + assert_eq!( + String::from_utf8_lossy(&output.stdout).lines().count(), + 2000 + ); +} diff --git a/tests/adversarial_tests.rs b/tests/adversarial_tests.rs index bfd8a0fa4..abdbd2c59 100644 --- a/tests/adversarial_tests.rs +++ b/tests/adversarial_tests.rs @@ -15,6 +15,8 @@ mod broken_pipe_resilience; mod deep_stack_recursion; #[path = "adversarial/determinism_stress.rs"] mod determinism_stress; +#[path = "adversarial/dynamic_fs_concurrency.rs"] +mod dynamic_fs_concurrency; #[path = "adversarial/fd_exhaustion.rs"] mod fd_exhaustion; #[path = "adversarial/filesystem_types_stress.rs"] @@ -29,6 +31,8 @@ mod janet_loc_stress; mod json_output_stress; #[path = "adversarial/massive_workload.rs"] mod massive_workload; +#[path = "adversarial/memory_allocation_limits.rs"] +mod memory_allocation_limits; #[path = "adversarial/nested_git_and_time_env.rs"] mod nested_git_and_time_env; #[path = "adversarial/property_fuzz_engine.rs"] diff --git a/tests/output_formatting/pty_terminal.rs b/tests/output_formatting/pty_terminal.rs index 17d44b9ae..d3e4cdf7c 100644 --- a/tests/output_formatting/pty_terminal.rs +++ b/tests/output_formatting/pty_terminal.rs @@ -207,3 +207,55 @@ fn test_pty_colorless_mode_clean_output() { ); assert!(stdout.contains("test.txt")); } + +#[test] +fn test_pty_extreme_geometry_narrow_terminal() { + let temp = TempPtyDir::new("narrow_geom"); + for i in 0..6 { + temp.create_file(&format!("long_filename_item_{i:02}.txt"), b"data"); + } + + // Extremely narrow terminal (10 columns, clamped to minimal valid cell width) + let pty = PtySession::spawn(&["--grid", temp.path.to_str().unwrap()], 10, 24, &[]); + let (status, stdout) = pty.read_to_string(); + assert!(status.success()); + assert!(!stdout.is_empty()); + assert!(stdout.contains("long_filename_item_00.txt")); +} + +#[test] +fn test_pty_extreme_geometry_ultra_wide_terminal() { + let temp = TempPtyDir::new("wide_geom"); + for i in 0..12 { + temp.create_file(&format!("item_{i:02}.txt"), b"data"); + } + + // Ultra-wide terminal (400 columns) + let pty = PtySession::spawn(&["--grid", temp.path.to_str().unwrap()], 400, 50, &[]); + let (status, stdout) = pty.read_to_string(); + assert!(status.success()); + assert!(!stdout.is_empty()); + assert!(stdout.contains("item_00.txt")); + assert!(stdout.contains("item_11.txt")); +} + +#[test] +fn test_pty_tree_and_long_view_interactive() { + let temp = TempPtyDir::new("tree_long_tty"); + let sub = temp.path.join("nested_folder"); + fs::create_dir_all(&sub).unwrap(); + temp.create_file("nested_folder/child.txt", b"child"); + + // Interactive tree mode + let pty_tree = PtySession::spawn(&["-T", temp.path.to_str().unwrap()], 120, 40, &[]); + let (status_t, stdout_t) = pty_tree.read_to_string(); + assert!(status_t.success()); + assert!(stdout_t.contains("nested_folder")); + assert!(stdout_t.contains("child.txt")); + + // Interactive long mode + let pty_long = PtySession::spawn(&["-l", temp.path.to_str().unwrap()], 120, 40, &[]); + let (status_l, stdout_l) = pty_long.read_to_string(); + assert!(status_l.success()); + assert!(stdout_l.contains("nested_folder")); +}