use crate::ported::hashtable::hashtable_nodes;
use std::io::Write;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::ported::utils::adduserdir;
use crate::ported::zsh_h::{nameddir, ND_USERNAME, PRINT_LIST, PRINT_NAMEONLY};
use crate::utils::{errflag, quotedzputs};
#[allow(non_upper_case_globals)]
pub static allusersadded: AtomicI32 = AtomicI32::new(0);
pub fn createnameddirtable() {
let _ = nameddirtab();
allusersadded.store(0, Ordering::Relaxed); }
pub fn emptynameddirtable() {
if let Ok(mut t) = nameddirtab().lock() {
t.clear(); }
allusersadded.store(0, Ordering::Relaxed); }
pub fn fillnameddirtable() {
if allusersadded.load(Ordering::Relaxed) != 0 {
return;
}
#[cfg(unix)]
unsafe {
libc::setpwent(); loop {
let pw = libc::getpwent();
if pw.is_null() {
break;
}
if errflag.load(Ordering::Relaxed) != 0 {
break;
}
let name = std::ffi::CStr::from_ptr((*pw).pw_name)
.to_string_lossy()
.into_owned();
let dir = std::ffi::CStr::from_ptr((*pw).pw_dir)
.to_string_lossy()
.into_owned();
adduserdir(&name, &dir, ND_USERNAME, true);
}
libc::endpwent(); }
allusersadded.store(1, Ordering::Relaxed); }
pub fn addnameddirnode(nam: &str, mut nd: nameddir) {
nd.diff = nd.dir.len() as i32 - nam.len() as i32;
if let Ok(mut t) = nameddirtab().lock() {
nd.node.nam = nam.to_string();
t.insert(nam.to_string(), nd);
}
}
pub fn removenameddirnode(nam: &str) -> Option<nameddir> {
let removed = nameddirtab().lock().ok().and_then(|mut t| t.remove(nam));
if removed.is_some() { }
removed }
pub fn freenameddirnode(hn: nameddir) { }
pub fn printnameddirnode(hn: &nameddir, printflags: i32) {
let stdout = std::io::stdout();
let mut out = stdout.lock();
if (printflags & PRINT_NAMEONLY) != 0 {
let _ = writeln!(out, "{}", hn.node.nam); return;
}
if (printflags & PRINT_LIST) != 0 {
let _ = write!(out, "hash -d "); if hn.node.nam.starts_with('-') {
let _ = write!(out, "-- "); }
}
let _ = write!(
out,
"{}={}",
quotedzputs(&hn.node.nam), quotedzputs(&hn.dir), );
let _ = writeln!(out); }
static NAMEDDIRTAB_INNER: OnceLock<Mutex<hashtable_nodes<nameddir>>> = OnceLock::new();
#[allow(non_snake_case)]
pub fn nameddirtab() -> &'static Mutex<hashtable_nodes<nameddir>> {
NAMEDDIRTAB_INNER.get_or_init(|| Mutex::new(hashtable_nodes::newhashtable(201)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::zsh_h::hashnode;
static NAMEDDIR_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn fresh_table() {
if let Ok(mut t) = nameddirtab().lock() {
t.clear();
}
allusersadded.store(0, Ordering::Relaxed);
}
fn make_nd(name: &str, dir: &str, flags: i32) -> nameddir {
nameddir {
node: hashnode {
next: None,
nam: name.to_string(),
flags,
},
dir: dir.to_string(),
diff: 0,
}
}
#[test]
fn addnameddirnode_sets_diff_and_inserts() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("p", make_nd("p", "/home/user/projects", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("p").expect("entry inserted");
assert_eq!(nd.dir, "/home/user/projects");
assert_eq!(nd.diff, 18);
assert_eq!(nd.node.nam, "p");
}
#[test]
fn removenameddirnode_returns_node_and_clears_entry() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("k", make_nd("k", "/tmp/k", 0));
assert!(nameddirtab().lock().unwrap().contains_key("k"));
let dropped = removenameddirnode("k");
assert!(dropped.is_some());
assert_eq!(dropped.unwrap().dir, "/tmp/k");
assert!(!nameddirtab().lock().unwrap().contains_key("k"));
}
#[test]
fn removenameddirnode_missing_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
assert!(removenameddirnode("absent").is_none());
}
#[test]
fn emptynameddirtable_clears_and_resets_allusersadded() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("a", make_nd("a", "/a", 0));
addnameddirnode("b", make_nd("b", "/b", 0));
allusersadded.store(1, Ordering::Relaxed);
emptynameddirtable();
assert!(nameddirtab().lock().unwrap().is_empty());
assert_eq!(allusersadded.load(Ordering::Relaxed), 0);
}
#[test]
fn createnameddirtable_resets_allusersadded() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
allusersadded.store(1, Ordering::Relaxed);
createnameddirtable();
assert_eq!(allusersadded.load(Ordering::Relaxed), 0);
}
#[test]
fn nd_username_value_matches_zsh_h() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ND_USERNAME, 1 << 1);
}
#[test]
fn fillnameddirtable_short_circuits_when_allusersadded_set() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
allusersadded.store(1, Ordering::Relaxed);
fillnameddirtable();
assert_eq!(
allusersadded.load(Ordering::Relaxed),
1,
"c:98 conditional must early-exit; flag remains 1 unchanged"
);
}
#[test]
fn addnameddirnode_diff_can_be_negative() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("longname", make_nd("longname", "/x", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("longname").expect("entry inserted");
assert_eq!(nd.diff, -6, "c:125 — signed subtraction may underflow zero");
}
#[test]
fn addnameddirnode_overwrites_existing_entry() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("p", make_nd("p", "/old", 0));
addnameddirnode("p", make_nd("p", "/new/longer/path", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("p").expect("entry present");
assert_eq!(nd.dir, "/new/longer/path", "c:127 — addhashnode replaces");
assert_eq!(nd.diff, "/new/longer/path".len() as i32 - 1);
assert_eq!(t.len(), 1, "must not accumulate duplicate keys");
}
#[test]
fn addnameddirnode_diff_zero_when_lengths_equal() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("abcde", make_nd("abcde", "/etc/", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("abcde").expect("entry inserted");
assert_eq!(
nd.diff, 0,
"len(\"/etc/\") == len(\"abcde\") == 5 → diff = 0"
);
}
#[test]
fn emptynameddirtable_resets_table_and_flag() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("a", make_nd("a", "/x", 0));
addnameddirnode("b", make_nd("b", "/y", 0));
allusersadded.store(1, Ordering::Relaxed);
emptynameddirtable();
assert!(
nameddirtab().lock().unwrap().is_empty(),
"emptynameddirtable must clear the table"
);
assert_eq!(
allusersadded.load(Ordering::Relaxed),
0,
"emptynameddirtable must reset allusersadded to 0"
);
}
#[test]
fn removenameddirnode_returns_some_for_present_and_none_for_absent() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("here", make_nd("here", "/tmp/here", 0));
let removed = removenameddirnode("here");
assert!(removed.is_some(), "present entry must return Some");
assert_eq!(removed.unwrap().dir, "/tmp/here");
assert!(
removenameddirnode("here").is_none(),
"second remove must return None"
);
assert!(removenameddirnode("never_was").is_none());
}
#[test]
fn removenameddirnode_actually_removes_from_table() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("a", make_nd("a", "/a", 0));
addnameddirnode("b", make_nd("b", "/b", 0));
assert_eq!(nameddirtab().lock().unwrap().len(), 2);
removenameddirnode("a");
let t = nameddirtab().lock().unwrap();
assert_eq!(t.len(), 1);
assert!(!t.contains_key("a"));
assert!(t.contains_key("b"), "removing 'a' must NOT touch 'b'");
}
#[test]
fn createnameddirtable_is_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
createnameddirtable();
addnameddirnode("preserved", make_nd("preserved", "/x", 0));
createnameddirtable(); let t = nameddirtab().lock().unwrap();
assert!(
t.contains_key("preserved"),
"second createnameddirtable must NOT wipe existing entries"
);
}
#[test]
fn freenameddirnode_consumes_node() {
let _g = crate::test_util::global_state_lock();
let nd = make_nd("doomed", "/tmp", 0);
freenameddirnode(nd);
}
#[test]
fn nameddir_corpus_add_then_remove() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("proj", make_nd("proj", "/home/u/proj", 0));
let removed = removenameddirnode("proj");
assert!(removed.is_some(), "removed value returned");
assert_eq!(removed.unwrap().dir, "/home/u/proj");
let t = nameddirtab().lock().unwrap();
assert!(!t.contains_key("proj"), "entry gone after remove");
}
#[test]
fn nameddir_corpus_remove_missing_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
assert!(removenameddirnode("never_added").is_none());
}
#[test]
fn nameddir_corpus_add_duplicate_overwrites() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("d", make_nd("d", "/old/path", 0));
addnameddirnode("d", make_nd("d", "/new/path", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("d").expect("entry present");
assert_eq!(nd.dir, "/new/path", "duplicate add overwrote");
}
#[test]
fn nameddir_corpus_diff_is_dir_minus_name_length() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("x", make_nd("x", "/a", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("x").unwrap();
assert_eq!(nd.diff, 1, "diff = len('/a') - len('x') = 2 - 1 = 1");
}
#[test]
fn nameddir_corpus_emptynameddirtable_clears() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("a", make_nd("a", "/a", 0));
addnameddirnode("b", make_nd("b", "/b", 0));
emptynameddirtable();
let t = nameddirtab().lock().unwrap();
assert!(t.get("a").is_none(), "user entry a cleared");
assert!(t.get("b").is_none(), "user entry b cleared");
}
#[test]
fn addnameddirnode_diff_can_be_negative_pin() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("verylongname", make_nd("verylongname", "/x", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("verylongname").unwrap();
assert_eq!(nd.diff, -10, "diff = 2 - 12 = -10");
}
#[test]
fn addnameddirnode_diff_zero_when_lengths_equal_pin() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("xyz", make_nd("xyz", "abc", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("xyz").unwrap();
assert_eq!(nd.diff, 0, "diff = 3 - 3 = 0");
}
#[test]
fn addnameddirnode_sets_node_nam() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("real_name", make_nd("", "/x", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("real_name").unwrap();
assert_eq!(nd.node.nam, "real_name", "node.nam set by addnameddirnode");
}
#[test]
fn removenameddirnode_missing_returns_none_pin() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
let r = removenameddirnode("zshrs_never_dir");
assert!(r.is_none(), "missing entry → None");
}
#[test]
fn removenameddirnode_existing_removes_entry() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("rmme", make_nd("rmme", "/rmme", 0));
assert!(nameddirtab().lock().unwrap().contains_key("rmme"));
let removed = removenameddirnode("rmme");
assert!(removed.is_some(), "returned the removed entry");
assert!(!nameddirtab().lock().unwrap().contains_key("rmme"));
}
#[test]
fn freenameddirnode_is_drop_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let nd = make_nd("ok", "/ok", 0);
freenameddirnode(nd);
}
#[test]
fn createnameddirtable_is_idempotent_pin() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
createnameddirtable();
createnameddirtable();
createnameddirtable();
}
#[test]
fn emptynameddirtable_resets_allusersadded() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
allusersadded.store(1, Ordering::Relaxed); emptynameddirtable();
assert_eq!(
allusersadded.load(Ordering::Relaxed),
0,
"emptynameddirtable must reset allusersadded → 0"
);
}
#[test]
fn addnameddirnode_overwrites_existing() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("dup", make_nd("dup", "/first", 0));
addnameddirnode("dup", make_nd("dup", "/second", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("dup").unwrap();
assert_eq!(nd.dir, "/second", "second insert wins");
}
#[test]
fn addnameddirnode_uses_argument_name_not_input_node_name() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
let mut nd = make_nd("ignored", "/dir", 0);
nd.node.nam = "ignored".to_string();
addnameddirnode("real", nd);
let t = nameddirtab().lock().unwrap();
let stored = t.get("real").unwrap();
assert_eq!(stored.node.nam, "real", "argument name must win");
assert!(!t.contains_key("ignored"), "ignored name not used");
}
#[test]
fn addnameddirnode_diff_can_be_strongly_negative() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
let long_name = "x".repeat(100);
addnameddirnode(&long_name, make_nd(&long_name, "/a", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get(long_name.as_str()).unwrap();
assert_eq!(nd.diff, 2 - 100, "diff = 2 - 100 = -98");
}
#[test]
fn addnameddirnode_empty_name_accepted() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("", make_nd("", "/somewhere", 0));
let t = nameddirtab().lock().unwrap();
assert!(t.contains_key(""), "empty name must insert");
let nd = t.get("").unwrap();
assert_eq!(nd.diff, 10, "diff = 10 - 0 = 10");
}
#[test]
fn removenameddirnode_idempotent_removes_once() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("once", make_nd("once", "/dir", 0));
let first = removenameddirnode("once");
assert!(first.is_some(), "first remove returns Some");
let second = removenameddirnode("once");
assert!(second.is_none(), "second remove returns None");
}
#[test]
fn removenameddirnode_only_removes_target_key() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("a", make_nd("a", "/A", 0));
addnameddirnode("b", make_nd("b", "/B", 0));
addnameddirnode("c", make_nd("c", "/C", 0));
removenameddirnode("b");
let t = nameddirtab().lock().unwrap();
assert!(t.contains_key("a"));
assert!(!t.contains_key("b"));
assert!(t.contains_key("c"));
}
#[test]
fn addnameddirnode_diff_positive_when_dir_longer() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("h", make_nd("h", "/home/user", 0));
let t = nameddirtab().lock().unwrap();
let nd = t.get("h").unwrap();
assert!(nd.diff > 0, "diff must be > 0 when dir longer");
assert_eq!(nd.diff, "/home/user".len() as i32 - "h".len() as i32);
}
#[test]
fn emptynameddirtable_drops_all_entries() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
for i in 0..10 {
let name = format!("entry{}", i);
addnameddirnode(&name, make_nd(&name, "/p", 0));
}
emptynameddirtable();
let t = nameddirtab().lock().unwrap();
assert_eq!(t.len(), 0, "all entries dropped");
}
#[test]
fn createnameddirtable_resets_allusersadded_flag() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
allusersadded.store(1, Ordering::Relaxed);
createnameddirtable();
assert_eq!(
allusersadded.load(Ordering::Relaxed),
0,
"createnameddirtable must reset allusersadded → 0"
);
}
#[test]
fn addnameddirnode_followed_by_lookup_finds_entry() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("zshrs_test_lookup", make_nd("zshrs_test_lookup", "/tmp", 0));
let t = nameddirtab().lock().unwrap();
assert!(
t.contains_key("zshrs_test_lookup"),
"added entry must be findable"
);
}
#[test]
fn removenameddirnode_returns_option_nameddir_type() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
let _: Option<nameddir> = removenameddirnode("nothing");
}
#[test]
fn removenameddirnode_empty_name_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
assert!(removenameddirnode("").is_none(), "empty name → None");
}
#[test]
fn addnameddirnode_signature_void() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
let _: () = addnameddirnode("zshrs_void_pin", make_nd("zshrs_void_pin", "/tmp", 0));
}
#[test]
fn emptynameddirtable_signature_void() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let _: () = emptynameddirtable();
}
#[test]
fn addnameddirnode_two_distinct_entries_grows_by_two() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
let before = nameddirtab().lock().unwrap().len();
addnameddirnode("aa", make_nd("aa", "/a", 0));
addnameddirnode("bb", make_nd("bb", "/b", 0));
let after = nameddirtab().lock().unwrap().len();
assert_eq!(after, before + 2, "2 distinct names → grow by 2");
}
#[test]
fn addnameddirnode_same_name_twice_keeps_count_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
let before = nameddirtab().lock().unwrap().len();
addnameddirnode("dup", make_nd("dup", "/v1", 0));
addnameddirnode("dup", make_nd("dup", "/v2", 0));
let after = nameddirtab().lock().unwrap().len();
assert_eq!(after, before + 1, "duplicate insert grows by 1 only");
}
#[test]
fn removenameddirnode_decrements_count_by_one() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("rem_test", make_nd("rem_test", "/x", 0));
let before = nameddirtab().lock().unwrap().len();
removenameddirnode("rem_test");
let after = nameddirtab().lock().unwrap().len();
assert_eq!(after, before - 1, "remove decrements count by 1");
}
#[test]
fn freenameddirnode_consumes_node_signature_void() {
let nd = nameddir {
node: crate::zsh_h::hashnode {
next: None,
nam: "free_test".to_string(),
flags: 0,
},
dir: "/tmp".to_string(),
diff: 0,
};
let _: () = freenameddirnode(nd);
}
#[test]
fn nameddirtab_returns_same_ref_across_calls() {
let a = nameddirtab() as *const _;
let b = nameddirtab() as *const _;
assert_eq!(a, b, "nameddirtab must return singleton");
}
#[test]
fn createnameddirtable_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
for _ in 0..10 {
createnameddirtable();
}
}
#[test]
fn emptynameddirtable_idempotent() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
for _ in 0..10 {
emptynameddirtable();
}
}
#[test]
fn emptynameddirtable_actually_empties_table() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
addnameddirnode("e_test", make_nd("e_test", "/tmp", 0));
emptynameddirtable();
assert_eq!(
nameddirtab().lock().unwrap().len(),
0,
"empty must zero the table"
);
}
#[test]
fn removenameddirnode_returns_option_nameddir_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let _: Option<nameddir> = removenameddirnode("__never__");
}
#[test]
fn removenameddirnode_nonexistent_returns_none() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
assert!(
removenameddirnode("__never_added_xyz__").is_none(),
"removing never-added returns None"
);
}
#[test]
fn add_then_remove_then_remove_again_round_trip() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode("rt_test", make_nd("rt_test", "/tmp", 0));
let first = removenameddirnode("rt_test");
assert!(first.is_some(), "first remove → Some");
let second = removenameddirnode("rt_test");
assert!(second.is_none(), "second remove → None");
}
#[test]
fn printnameddirnode_various_flags_no_panic() {
let _g = crate::test_util::global_state_lock();
let nd = nameddir {
node: crate::zsh_h::hashnode {
next: None,
nam: "p_test".to_string(),
flags: 0,
},
dir: "/tmp".to_string(),
diff: 0,
};
for flags in [0i32, 1, 2, 0xff, -1] {
printnameddirnode(&nd, flags);
}
}
#[test]
fn addnameddirnode_many_distinct_grows_by_count() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
let before = nameddirtab().lock().unwrap().len();
for i in 0..5 {
let name = format!("many_{}", i);
addnameddirnode(&name, make_nd(&name, "/p", 0));
}
let after = nameddirtab().lock().unwrap().len();
assert_eq!(after, before + 5, "5 distinct adds → grow by 5");
}
#[test]
fn addnameddirnode_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
addnameddirnode("", make_nd("", "/tmp", 0));
}
#[test]
fn addnameddirnode_computes_diff_from_dir_minus_nam_len() {
let _g = crate::test_util::global_state_lock();
let _g2 = NAMEDDIR_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
fresh_table();
addnameddirnode(
"preserve_test",
make_nd("preserve_test", "/unique/path", 42),
);
let tab = nameddirtab().lock().unwrap();
let entry = tab.get("preserve_test").expect("entry exists");
assert_eq!(entry.dir, "/unique/path", "dir field preserved");
let expected = "/unique/path".len() as i32 - "preserve_test".len() as i32;
assert_eq!(
entry.diff,
expected,
"c:121 — diff = dir.len({}) - nam.len({}) = {}",
"/unique/path".len(),
"preserve_test".len(),
expected
);
}
#[test]
fn fillnameddirtable_symbol_exists_pin() {
let _: fn() = fillnameddirtable;
}
fn mk_nd(dir: &str) -> nameddir {
nameddir {
node: crate::ported::zsh_h::hashnode::default(),
dir: dir.to_string(),
diff: 0,
}
}
#[test]
fn addnameddirnode_overwrite_replaces_dir() {
let _g = crate::test_util::global_state_lock();
emptynameddirtable();
addnameddirnode("homed", mk_nd("/old/path"));
addnameddirnode("homed", mk_nd("/new/path"));
let tab = nameddirtab().lock().unwrap();
assert_eq!(
tab.get("homed").map(|n| n.dir.clone()),
Some("/new/path".to_string()),
"overwrite must replace dir verbatim"
);
drop(tab);
emptynameddirtable();
}
#[test]
fn addnameddirnode_overwrite_does_not_grow() {
let _g = crate::test_util::global_state_lock();
emptynameddirtable();
addnameddirnode("key1", mk_nd("/a"));
let n1 = nameddirtab().lock().unwrap().len();
addnameddirnode("key1", mk_nd("/b"));
let n2 = nameddirtab().lock().unwrap().len();
assert_eq!(n1, n2, "overwrite must not grow table size");
emptynameddirtable();
}
#[test]
fn removenameddirnode_returns_payload_on_hit() {
let _g = crate::test_util::global_state_lock();
emptynameddirtable();
addnameddirnode("toremove", mk_nd("/path/x"));
let r = removenameddirnode("toremove");
assert!(r.is_some(), "remove must return Some payload");
assert_eq!(r.unwrap().dir, "/path/x");
emptynameddirtable();
}
#[test]
fn removenameddirnode_shrinks_table_by_one() {
let _g = crate::test_util::global_state_lock();
emptynameddirtable();
addnameddirnode("a", mk_nd("/1"));
addnameddirnode("b", mk_nd("/2"));
addnameddirnode("c", mk_nd("/3"));
let before = nameddirtab().lock().unwrap().len();
let _ = removenameddirnode("b");
let after = nameddirtab().lock().unwrap().len();
assert_eq!(after, before - 1, "remove must shrink by 1");
emptynameddirtable();
}
#[test]
fn removenameddirnode_miss_does_not_shrink() {
let _g = crate::test_util::global_state_lock();
emptynameddirtable();
addnameddirnode("a", mk_nd("/1"));
let before = nameddirtab().lock().unwrap().len();
let _ = removenameddirnode("__never__");
let after = nameddirtab().lock().unwrap().len();
assert_eq!(after, before, "miss must not change size");
emptynameddirtable();
}
#[test]
fn freenameddirnode_returns_void_type() {
let _g = crate::test_util::global_state_lock();
let _: () = freenameddirnode(mk_nd("/x"));
}
#[test]
fn printnameddirnode_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
let nd = mk_nd("");
printnameddirnode(&nd, 0);
}
#[test]
fn printnameddirnode_username_flag_no_panic() {
let _g = crate::test_util::global_state_lock();
let mut nd = mk_nd("/home/u");
nd.node.nam = "u".to_string();
nd.node.flags = ND_USERNAME as i32;
printnameddirnode(&nd, 0);
}
#[test]
fn printnameddirnode_combo_flags_no_panic() {
let _g = crate::test_util::global_state_lock();
let mut nd = mk_nd("/dir");
nd.node.nam = "n".to_string();
printnameddirnode(&nd, (PRINT_NAMEONLY | PRINT_LIST) as i32);
}
#[test]
fn addnameddirnode_diff_negative_when_name_longer_than_dir() {
let _g = crate::test_util::global_state_lock();
emptynameddirtable();
addnameddirnode("very_long_name_xyz", mk_nd("/x"));
let tab = nameddirtab().lock().unwrap();
let nd = tab.get("very_long_name_xyz").unwrap();
assert_eq!(
nd.diff,
2i32 - "very_long_name_xyz".len() as i32,
"diff must be dir.len() - nam.len(), even when negative"
);
drop(tab);
emptynameddirtable();
}
#[test]
fn emptynameddirtable_clears_all_entries() {
let _g = crate::test_util::global_state_lock();
emptynameddirtable();
for i in 0..10 {
addnameddirnode(&format!("k{}", i), mk_nd(&format!("/d{}", i)));
}
assert_eq!(nameddirtab().lock().unwrap().len(), 10);
emptynameddirtable();
assert_eq!(
nameddirtab().lock().unwrap().len(),
0,
"emptynameddirtable must remove every entry"
);
}
}