use crate::ported::params::isident;
use crate::ported::utils::zwarnnam;
use crate::ported::zsh_h::module;
use std::sync::{Mutex, OnceLock};
pub fn handle_digits(
nam: &str,
argptr: &str,
fdset: &mut libc::fd_set,
fdmax: &mut libc::c_int,
) -> i32 {
let first = argptr.chars().next();
if !matches!(first, Some(c) if c.is_ascii_digit()) {
zwarnnam(nam, &format!("expecting file descriptor: {}", argptr));
return 1; }
let (fd_val, endptr) = crate::ported::utils::zstrtol(argptr, 10);
let fd = fd_val as libc::c_int;
if !endptr.is_empty() {
zwarnnam(nam, &format!("garbage after file descriptor: {}", endptr));
return 1; }
if fd < 0 || fd as usize >= libc::FD_SETSIZE {
zwarnnam(nam, &format!("file descriptor out of range: {}", argptr));
return 1;
}
unsafe {
libc::FD_SET(fd, fdset);
} if fd + 1 > *fdmax {
*fdmax = fd + 1; }
0 }
pub fn bin_zselect(
nam: &str,
args: &[String], _ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
let args: Vec<&str> = args.iter().map(String::as_str).collect();
let args = &args[..];
let mut fdset: [libc::fd_set; 3] = unsafe { std::mem::zeroed() };
for s in &mut fdset {
unsafe {
libc::FD_ZERO(s);
}
}
let fdchar: [u8; 3] = *b"rwe"; let mut fdmax: libc::c_int = 0; let mut fdsetind: usize = 0; let mut tv: libc::timeval = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
let mut have_timeout = false; let mut outarray: String = "reply".to_string(); let mut outhash: Option<String> = None;
let mut i = 0;
while i < args.len() {
let arg = args[i];
if let Some(rest) = arg.strip_prefix('-') {
let mut chars: Vec<char> = rest.chars().collect();
let mut j = 0;
while j < chars.len() {
let c = chars[j];
match c {
'a' | 'A' => {
let arg_str: String = if j + 1 < chars.len() {
j += 1; chars[j..].iter().collect()
} else if i + 1 < args.len() {
i += 1; args[i].to_string()
} else {
zwarnnam(nam, &format!("argument expected after -{}", c));
return 1; };
if arg_str.is_empty()
|| arg_str.chars().next().unwrap().is_ascii_digit()
|| !isident(&arg_str)
{
zwarnnam(nam, &format!("invalid array name: {}", arg_str));
return 1;
}
if c == 'a' {
outarray = arg_str; } else {
outhash = Some(arg_str); }
break;
}
'r' => fdsetind = 0, 'w' => fdsetind = 1, 'e' => fdsetind = 2, 't' => {
let arg_str: String = if j + 1 < chars.len() {
j += 1;
chars[j..].iter().collect()
} else if i + 1 < args.len() {
i += 1;
args[i].to_string()
} else {
zwarnnam(nam, &format!("argument expected after -{}", c));
return 1;
};
let first = arg_str.chars().next();
if !matches!(first, Some(d) if d.is_ascii_digit()) {
zwarnnam(nam, "number expected after -t");
return 1;
}
let (tempnum, endptr) = crate::ported::utils::zstrtol(&arg_str, 10);
if !endptr.is_empty() {
zwarnnam(nam, &format!("garbage after -t argument: {}", endptr));
return 1; }
have_timeout = true;
tv.tv_sec = (tempnum / 100) as libc::time_t;
tv.tv_usec = ((tempnum % 100) * 10000) as libc::suseconds_t;
break; }
_ => {
let argptr_rest: String = chars[j..].iter().collect();
if handle_digits(nam, &argptr_rest, &mut fdset[fdsetind], &mut fdmax) != 0 {
return 1; }
break; }
}
j += 1;
}
} else if handle_digits(nam, arg, &mut fdset[fdsetind], &mut fdmax) != 0 {
return 1; }
i += 1;
}
let tvptr: *mut libc::timeval = if have_timeout {
&mut tv
} else {
std::ptr::null_mut()
};
if fdmax == 0 && !have_timeout {
zwarnnam(nam, "no file descriptors and no timeout: would block forever");
return 1;
}
let mut sel: libc::c_int;
loop {
sel = unsafe { libc::select(fdmax, &mut fdset[0], &mut fdset[1], &mut fdset[2], tvptr) };
if sel >= 0 {
break;
}
let err = std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::EINTR) {
continue;
} break;
}
if sel <= 0 {
if sel < 0 {
zwarnnam(
nam,
&format!("error on select: {}", std::io::Error::last_os_error()),
); }
return 1; }
if let Some(hash_name) = &outhash {
let mut hash: indexmap::IndexMap<String, String> = indexmap::IndexMap::new();
for ii in 0..3 {
for fd in 0..fdmax {
if unsafe { libc::FD_ISSET(fd, &fdset[ii]) } {
let key = fd.to_string();
let mask_char = fdchar[ii] as char;
hash.entry(key.clone())
.and_modify(|v| {
if !v.contains(mask_char) {
v.push(mask_char);
}
})
.or_insert_with(|| mask_char.to_string());
}
}
}
let pairs: Vec<String> = hash
.into_iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
crate::ported::params::setsparam(hash_name, &pairs.join("\t"));
} else {
let mut out: Vec<String> = Vec::new();
for ii in 0..3 {
let mut emitted_flag = false; for fd in 0..fdmax {
if unsafe { libc::FD_ISSET(fd, &fdset[ii]) } {
if !emitted_flag {
out.push(format!("-{}", fdchar[ii] as char)); emitted_flag = true; }
out.push(fd.to_string()); }
}
}
crate::ported::params::setsparam(&outarray, &out.join(":"));
}
0 }
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 {
0 }
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
*features = featuresarray(m, module_features());
0 }
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
handlefeatures(m, module_features(), enables) }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
0 }
pub fn cleanup_(m: *const module) -> i32 {
setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
0 }
static MODULE_FEATURES: OnceLock<Mutex<crate::ported::zsh_h::features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<crate::ported::zsh_h::features>) -> Vec<String> {
vec!["b:zselect".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<crate::ported::zsh_h::features>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<crate::ported::zsh_h::features>,
_e: Option<&[i32]>,
) -> i32 {
0
}
fn module_features() -> &'static Mutex<crate::ported::zsh_h::features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(crate::ported::zsh_h::features {
bn_list: None,
bn_size: 1,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 0,
n_abstract: 0,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::zsh_h::{options, MAX_OPS};
fn empty_ops_zs() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
fn s(args: &[&str]) -> Vec<String> {
args.iter().map(|a| a.to_string()).collect()
}
#[test]
fn empty_args_with_zero_timeout_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &s(&["-t", "0"]), &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn invalid_array_name_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &s(&["-a", "1bad"]), &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn timeout_garbage_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &s(&["-t", "100x"]), &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn no_arg_after_a_returns_one() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &s(&["-a"]), &ops, 0);
assert_eq!(r, 1);
}
#[test]
fn handle_digits_invalid_input() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
assert_eq!(handle_digits("zselect", "abc", &mut fdset, &mut fdmax), 1);
assert_eq!(handle_digits("zselect", "12abc", &mut fdset, &mut fdmax), 1);
}
#[test]
fn handle_digits_sets_fd_and_fdmax() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
assert_eq!(handle_digits("zselect", "5", &mut fdset, &mut fdmax), 0);
assert_eq!(fdmax, 6);
assert!(unsafe { libc::FD_ISSET(5, &fdset) });
}
#[test]
fn handle_digits_accepts_fd_zero() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
assert_eq!(handle_digits("zselect", "0", &mut fdset, &mut fdmax), 0);
assert_eq!(fdmax, 1, "fdmax should be fd+1 = 1");
assert!(unsafe { libc::FD_ISSET(0, &fdset) });
}
#[test]
fn handle_digits_fdmax_tracks_highest_fd() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
handle_digits("zselect", "10", &mut fdset, &mut fdmax);
assert_eq!(fdmax, 11);
handle_digits("zselect", "3", &mut fdset, &mut fdmax);
assert_eq!(fdmax, 11, "fdmax must not regress when smaller fd is added");
handle_digits("zselect", "20", &mut fdset, &mut fdmax);
assert_eq!(fdmax, 21);
}
#[test]
fn handle_digits_rejects_negative() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
let r = handle_digits("zselect", "-5", &mut fdset, &mut fdmax);
assert_eq!(r, 1, "negative fd must be rejected");
}
#[test]
fn handle_digits_rejects_empty_string() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
let r = handle_digits("zselect", "", &mut fdset, &mut fdmax);
assert_eq!(r, 1, "empty fd string must be rejected");
}
#[test]
fn module_lifecycle_shims_all_return_zero() {
let _g = crate::test_util::global_state_lock();
let m: *const module = std::ptr::null();
assert_eq!(setup_(m), 0);
assert_eq!(boot_(m), 0);
assert_eq!(cleanup_(m), 0);
assert_eq!(finish_(m), 0);
}
#[test]
fn zselect_corpus_handle_digits_zero() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
let r = handle_digits("zselect", "0", &mut fdset, &mut fdmax);
assert_eq!(r, 0);
assert!(
unsafe { libc::FD_ISSET(0, &fdset) },
"fd 0 must be set in fdset"
);
assert_eq!(fdmax, 1, "fdmax = fd+1 = 1");
}
#[test]
fn zselect_corpus_handle_digits_five() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
let r = handle_digits("zselect", "5", &mut fdset, &mut fdmax);
assert_eq!(r, 0);
assert!(unsafe { libc::FD_ISSET(5, &fdset) });
assert_eq!(fdmax, 6);
}
#[test]
fn zselect_corpus_handle_digits_does_not_lower_fdmax() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 10;
let r = handle_digits("zselect", "3", &mut fdset, &mut fdmax);
assert_eq!(r, 0);
assert_eq!(fdmax, 10, "fdmax stays at 10 when new fd is lower");
}
#[test]
fn zselect_corpus_handle_digits_rejects_letter_prefix() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
let r = handle_digits("zselect", "a5", &mut fdset, &mut fdmax);
assert_eq!(r, 1, "non-digit start rejected");
}
#[test]
fn zselect_corpus_handle_digits_rejects_trailing_garbage() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
let r = handle_digits("zselect", "5abc", &mut fdset, &mut fdmax);
assert_eq!(r, 1, "trailing garbage rejected");
}
#[test]
fn zselect_corpus_handle_digits_rejects_negative() {
let _g = crate::test_util::global_state_lock();
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe {
libc::FD_ZERO(&mut fdset);
}
let mut fdmax: libc::c_int = 0;
let r = handle_digits("zselect", "-3", &mut fdset, &mut fdmax);
assert_eq!(r, 1, "leading minus not a digit");
}
fn fresh_set() -> (libc::fd_set, libc::c_int) {
let mut s: libc::fd_set = unsafe { std::mem::zeroed() };
unsafe { libc::FD_ZERO(&mut s) };
(s, 0)
}
#[test]
fn handle_digits_zero_succeeds() {
let _g = crate::test_util::global_state_lock();
let (mut s, mut max) = fresh_set();
let r = handle_digits("zselect", "0", &mut s, &mut max);
assert_eq!(r, 0, "fd 0 (stdin) valid");
assert_eq!(max, 1, "fdmax = fd + 1");
}
#[test]
fn handle_digits_two_sets_fdmax_to_three() {
let _g = crate::test_util::global_state_lock();
let (mut s, mut max) = fresh_set();
let r = handle_digits("zselect", "2", &mut s, &mut max);
assert_eq!(r, 0);
assert_eq!(max, 3, "fdmax = 2 + 1");
}
#[test]
fn handle_digits_only_raises_fdmax() {
let _g = crate::test_util::global_state_lock();
let (mut s, mut max) = fresh_set();
max = 10; let r = handle_digits("zselect", "2", &mut s, &mut max);
assert_eq!(r, 0);
assert_eq!(max, 10, "fdmax stays at 10 (not lowered to 3)");
}
#[test]
fn handle_digits_registers_fd_in_set() {
let _g = crate::test_util::global_state_lock();
let (mut s, mut max) = fresh_set();
let r = handle_digits("zselect", "7", &mut s, &mut max);
assert_eq!(r, 0);
let isset = unsafe { libc::FD_ISSET(7, &s) };
assert!(isset, "fd 7 must be set in fdset after handle_digits");
}
#[test]
fn handle_digits_empty_rejected() {
let _g = crate::test_util::global_state_lock();
let (mut s, mut max) = fresh_set();
let r = handle_digits("zselect", "", &mut s, &mut max);
assert_eq!(r, 1, "empty string → 1");
}
#[test]
fn bin_zselect_no_args_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_zselect("zselect", &[], &ops, 0);
assert_ne!(r, 0, "no args → error");
}
#[test]
fn zselect_setup_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(setup_(std::ptr::null()), 0);
}
#[test]
fn zselect_boot_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(boot_(std::ptr::null()), 0);
}
#[test]
fn zselect_finish_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(finish_(std::ptr::null()), 0);
}
#[test]
fn handle_digits_returns_i32_type() {
let (mut set, mut fdmax) = fresh_set();
let _: i32 = handle_digits("zselect", "0", &mut set, &mut fdmax);
}
#[test]
fn handle_digits_is_deterministic() {
for s in ["0", "5", "abc", ""] {
let (mut set, mut fdmax) = fresh_set();
let first = handle_digits("zselect", s, &mut set, &mut fdmax);
for _ in 0..3 {
let (mut s2, mut f2) = fresh_set();
let r = handle_digits("zselect", s, &mut s2, &mut f2);
assert_eq!(r, first, "handle_digits({:?}) must be deterministic", s);
}
}
}
#[test]
fn handle_digits_return_in_canonical_set() {
let (mut set, mut fdmax) = fresh_set();
for s in ["0", "1", "100", "abc", "", "-1", "0x10"] {
let r = handle_digits("zselect", s, &mut set, &mut fdmax);
assert!(
r == 0 || r == 1,
"handle_digits({:?}) = {} not in {{0,1}}",
s,
r
);
}
}
#[test]
fn handle_digits_fd_zero_returns_success_pin() {
let (mut set, mut fdmax) = fresh_set();
let r = handle_digits("zselect", "0", &mut set, &mut fdmax);
assert_eq!(r, 0, "fd 0 must succeed");
}
#[test]
fn handle_digits_high_fd_no_panic() {
let (mut set, mut fdmax) = fresh_set();
let _ = handle_digits("zselect", "100", &mut set, &mut fdmax);
}
#[test]
fn bin_zselect_garbage_arg_return_in_exit_code_range() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
for args in [vec!["garbage".to_string()], vec!["abc".to_string()]] {
let r = bin_zselect("zselect", &args, &ops, 0);
assert!(
(0..256).contains(&r),
"exit code {} must fit in u8 range for {:?}",
r,
args
);
}
}
#[test]
fn bin_zselect_empty_args_returns_nonzero() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &[], &ops, 0);
assert_ne!(r, 0, "no args → error");
}
#[test]
fn zselect_full_lifecycle_returns_zero_for_all() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
assert_eq!(setup_(null), 0);
let mut feats = Vec::new();
let _ = features_(null, &mut feats);
let mut enables: Option<Vec<i32>> = None;
let _ = enables_(null, &mut enables);
assert_eq!(boot_(null), 0);
assert_eq!(cleanup_(null), 0);
assert_eq!(finish_(null), 0);
}
#[test]
fn zselect_setup_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(setup_(std::ptr::null()), 0);
}
}
#[test]
fn zselect_finish_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(finish_(std::ptr::null()), 0);
}
}
#[test]
fn handle_digits_returns_i32_type_pin2() {
let (mut set, mut fdmax) = fresh_set();
let _: i32 = handle_digits("zselect", "1", &mut set, &mut fdmax);
}
#[test]
fn bin_zselect_returns_i32_type_pin2() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
let _: i32 = bin_zselect("zselect", &["garbage".to_string()], &ops, 0);
}
#[test]
fn handle_digits_is_deterministic_for_same_input() {
for fd in ["1", "5", "100", "garbage"] {
let (mut set, mut fdmax) = fresh_set();
let first = handle_digits("zselect", fd, &mut set, &mut fdmax);
for _ in 0..3 {
let (mut set2, mut fdmax2) = fresh_set();
assert_eq!(
handle_digits("zselect", fd, &mut set2, &mut fdmax2),
first,
"handle_digits({:?}) must be deterministic",
fd
);
}
}
}
#[test]
fn handle_digits_empty_returns_nonzero_pin() {
let (mut set, mut fdmax) = fresh_set();
let r = handle_digits("zselect", "", &mut set, &mut fdmax);
assert_ne!(r, 0, "empty string → error");
}
#[test]
fn handle_digits_negative_returns_nonzero_pin() {
let (mut set, mut fdmax) = fresh_set();
let r = handle_digits("zselect", "-1", &mut set, &mut fdmax);
assert_ne!(r, 0, "negative fd → error");
}
#[test]
fn handle_digits_non_numeric_returns_nonzero_pin() {
for s in ["xyz", "1a", "a1", "abc"] {
let (mut set, mut fdmax) = fresh_set();
assert_ne!(
handle_digits("zselect", s, &mut set, &mut fdmax),
0,
"non-numeric {:?} → error",
s
);
}
}
#[test]
fn handle_digits_fdmax_monotonically_non_decreasing() {
let (mut set, mut fdmax) = fresh_set();
let _ = handle_digits("zselect", "5", &mut set, &mut fdmax);
let after_5 = fdmax;
let _ = handle_digits("zselect", "2", &mut set, &mut fdmax);
assert!(fdmax >= after_5, "fdmax must never decrease");
let _ = handle_digits("zselect", "10", &mut set, &mut fdmax);
assert!(fdmax >= 10, "fdmax must rise to 10 after fd 10");
}
#[test]
fn zselect_setup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = setup_(std::ptr::null());
}
#[test]
fn zselect_features_nonempty() {
let _g = crate::test_util::global_state_lock();
let mut feats = Vec::new();
features_(std::ptr::null(), &mut feats);
assert!(!feats.is_empty(), "zselect must advertise ≥1 feature");
}
#[test]
fn zselect_features_use_canonical_prefix() {
let _g = crate::test_util::global_state_lock();
let mut feats = Vec::new();
features_(std::ptr::null(), &mut feats);
for f in &feats {
assert!(
f.starts_with("b:") || f.starts_with("p:"),
"feature {:?} must use b:/p: prefix",
f
);
}
}
#[test]
fn zselect_cleanup_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(cleanup_(std::ptr::null()), 0);
}
}
#[test]
fn zselect_boot_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(boot_(std::ptr::null()), 0);
}
}
#[test]
fn handle_digits_returns_i32_pin_alt() {
let (mut set, mut fdmax) = fresh_set();
let _: i32 = handle_digits("zselect", "0", &mut set, &mut fdmax);
}
#[test]
fn handle_digits_stdin_zero_succeeds() {
let (mut set, mut fdmax) = fresh_set();
let r = handle_digits("zselect", "0", &mut set, &mut fdmax);
assert_eq!(r, 0, "fd 0 (stdin) must succeed");
}
#[test]
fn handle_digits_deterministic_for_stdin() {
let (mut s1, mut f1) = fresh_set();
let first = handle_digits("zselect", "0", &mut s1, &mut f1);
for _ in 0..5 {
let (mut s, mut f) = fresh_set();
assert_eq!(
handle_digits("zselect", "0", &mut s, &mut f),
first,
"handle_digits('0') must be pure"
);
}
}
#[test]
fn handle_digits_huge_fd_no_panic() {
let (mut set, mut fdmax) = fresh_set();
let _ = handle_digits("zselect", "99999999", &mut set, &mut fdmax);
}
#[test]
fn bin_zselect_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
let _: i32 = bin_zselect("zselect", &[], &ops, 0);
}
#[test]
fn bin_zselect_no_args_usage_error_alt() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
let r = bin_zselect("zselect", &[], &ops, 0);
assert_ne!(r, 0, "no args → usage error");
}
#[test]
fn bin_zselect_usage_error_exit_codes_non_negative() {
let _g = crate::test_util::global_state_lock();
let ops = empty_ops_zs();
for argv in [vec![], vec!["bogus".into()], vec!["-X".into()]] {
let r = bin_zselect("zselect", &argv, &ops, 0);
assert!(
r >= 0,
"exit code must be non-negative; got {} for {:?}",
r,
argv
);
}
}
#[test]
fn zselect_features_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let mut v: Vec<String> = Vec::new();
let _: i32 = features_(std::ptr::null(), &mut v);
}
#[test]
fn zselect_enables_with_none_returns_i32() {
let _g = crate::test_util::global_state_lock();
let mut e: Option<Vec<i32>> = None;
let _: i32 = enables_(std::ptr::null(), &mut e);
}
#[test]
fn zselect_finish_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(finish_(std::ptr::null()), 0);
}
#[test]
fn zselect_each_lifecycle_hook_returns_zero_individually() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
let mut v: Vec<String> = Vec::new();
let mut e: Option<Vec<i32>> = None;
assert_eq!(setup_(null), 0, "c:295 setup_");
assert_eq!(features_(null, &mut v), 0, "c:312 features_");
assert_eq!(enables_(null, &mut e), 0, "c:320 enables_");
assert_eq!(boot_(null), 0, "c:327 boot_");
assert_eq!(cleanup_(null), 0, "c:334 cleanup_");
assert_eq!(finish_(null), 0, "c:341 finish_");
}
#[test]
fn zselect_setup_idempotent_alt_pin() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(setup_(std::ptr::null()), 0);
}
}
#[test]
fn zselect_cleanup_idempotent_alt_pin() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(cleanup_(std::ptr::null()), 0);
}
}
#[test]
fn zselect_finish_idempotent_alt_pin() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(finish_(std::ptr::null()), 0);
}
}
#[test]
fn zselect_cleanup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = cleanup_(std::ptr::null());
}
#[test]
fn zselect_finish_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = finish_(std::ptr::null());
}
#[test]
fn zselect_boot_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = boot_(std::ptr::null());
}
#[test]
fn bin_zselect_empty_args_non_negative_alt_pin() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r = bin_zselect("zselect", &[], &ops, 0);
assert!(r >= 0, "bin_zselect empty must be ≥ 0, got {}", r);
}
#[test]
fn bin_zselect_various_func_values_no_panic() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
for func in [-1, 0, 1, 100, i32::MAX] {
let _ = bin_zselect("zselect", &[], &ops, func);
}
}
#[test]
fn bin_zselect_deterministic_for_empty_args() {
let _g = crate::test_util::global_state_lock();
let ops = crate::ported::zsh_h::options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
};
let r1 = bin_zselect("zselect", &[], &ops, 0);
let r2 = bin_zselect("zselect", &[], &ops, 0);
assert_eq!(r1, r2, "bin_zselect empty args must be deterministic");
}
#[test]
fn zselect_features_deterministic_alt_pin() {
let _g = crate::test_util::global_state_lock();
let mut v1: Vec<String> = Vec::new();
let mut v2: Vec<String> = Vec::new();
let _ = features_(std::ptr::null(), &mut v1);
let _ = features_(std::ptr::null(), &mut v2);
assert_eq!(v1, v2, "features_ must be deterministic");
}
#[test]
fn zselect_enables_with_some_non_empty_no_panic() {
let _g = crate::test_util::global_state_lock();
let mut e: Option<Vec<i32>> = Some(vec![1, 2, 3]);
let _ = enables_(std::ptr::null(), &mut e);
}
#[test]
fn zselect_setup_boot_finish_chain_returns_zero_each() {
let _g = crate::test_util::global_state_lock();
let null = std::ptr::null();
assert_eq!(setup_(null), 0);
assert_eq!(boot_(null), 0);
assert_eq!(finish_(null), 0);
}
}