use crossbeam_channel::unbounded;
use rand::{Rng, rng};
use runa_tui::worker::{WorkerResponse, WorkerTask, start_worker};
use std::collections::HashSet;
use std::env;
use std::fs;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use tempfile::tempdir;
use unicode_width::UnicodeWidthStr;
#[test]
fn test_worker_load_current_dir() -> Result<(), Box<dyn std::error::Error>> {
let (task_tx, task_rx) = unbounded();
let (res_tx, res_rx) = unbounded();
start_worker(task_rx, res_tx);
let curr_dir = env::current_dir()?;
task_tx.send(WorkerTask::LoadDirectory {
path: curr_dir,
focus: None,
dirs_first: true,
show_hidden: false,
show_system: false,
case_insensitive: true,
always_show: Arc::new(HashSet::new()),
pane_width: 20,
request_id: 1,
})?;
match res_rx.recv()? {
WorkerResponse::DirectoryLoaded { entries, .. } => {
assert!(!entries.is_empty(), "Current dir should not be empty");
for entry in entries {
let disp = entry.display_name();
assert!(
disp.chars().count() <= 20,
"Entry '{}' too wide",
entry.name_str()
);
}
}
WorkerResponse::Error(e) => panic!("Worker error: {}", e),
_ => panic!("Unexpected worker response"),
}
Ok(())
}
#[test]
fn stress_worker_dir_load_requests_multithreaded() -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = tempdir()?;
let safe_subdir = temp_dir.path().join("runa_test_safe_dir");
fs::create_dir_all(&safe_subdir)?;
let curr_dir = env::current_dir()?;
let dirs = vec![curr_dir, temp_dir.path().to_path_buf(), safe_subdir.clone()];
let pane_base = 20;
let thread_count = 2;
let requests_per_thread = 50;
let (task_tx, task_rx) = unbounded();
let (res_tx, res_rx) = unbounded();
start_worker(task_rx, res_tx);
let mut handles = Vec::new();
for t in 0..thread_count {
let task_tx = task_tx.clone();
let dirs = dirs.clone();
let pane_base = pane_base;
handles.push(thread::spawn(move || {
let mut rng = rng();
for i in 0..requests_per_thread {
let dir = &dirs[rng.random_range(0..dirs.len())];
task_tx
.send(WorkerTask::LoadDirectory {
path: dir.clone(),
focus: None,
dirs_first: rng.random_bool(0.5),
show_hidden: rng.random_bool(0.5),
show_system: rng.random_bool(0.5),
case_insensitive: rng.random_bool(0.5),
always_show: Arc::new(HashSet::new()),
pane_width: pane_base + rng.random_range(0..10),
request_id: (t * requests_per_thread + i) as u64,
})
.expect("Couldn't send task to worker");
if i % 50 == 0 {
thread::sleep(Duration::from_millis(rng.random_range(0..10)));
}
}
}));
}
for h in handles {
if let Err(err) = h.join() {
panic!("Thread panicked during stress test: {:?}", err);
}
}
let total_requests = thread_count * requests_per_thread;
let mut valid_responses = 0;
for _ in 0..total_requests {
match res_rx.recv_timeout(Duration::from_secs(2)) {
Ok(WorkerResponse::DirectoryLoaded { entries, .. }) => {
valid_responses += 1;
for entry in &entries {
let disp = entry.display_name();
let visual_width = UnicodeWidthStr::width(disp);
assert!(
visual_width <= pane_base + 10,
"Entry '{}' display width {} > allowed ({}).",
entry.name_str(),
visual_width,
pane_base + 10
);
assert!(
!entry.name_str().is_empty(),
"Entry name_str must not be empty."
);
assert!(
!entry.name_str().contains('\0'),
"Entry name_str must not contain null."
);
}
}
Ok(WorkerResponse::Error(e)) => panic!("Worker error: {}", e),
Ok(_) => panic!("Unexpected WorkerResponse variant"),
Err(_) => panic!("Missing worker response (timeout)"),
}
}
assert_eq!(
valid_responses, total_requests,
"Not all worker requests returned results!"
);
Ok(())
}