use crate::ported::options::optlookup;
use crate::ported::params::{getsparam, TERMFLAGS};
use crate::ported::utils::{zsetupterm, zwarnnam};
use crate::ported::zsh_h::{features, isset, module, INTERACTIVE};
use crate::zsh_h::TERM_UNKNOWN;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{LazyLock, Mutex, OnceLock};
pub fn ztgetflag(s: &str) -> i32 {
if !ensure_termcap_loaded() {
return -1; }
let s_c = match std::ffi::CString::new(s) {
Ok(c) => c,
Err(_) => return -1,
};
let flag = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
unsafe { tgetflag(s_c.as_ptr()) }
};
match flag {
1 => 1, _ => {
let sb = s.as_bytes();
for b in BOOLCODES.iter() {
let bb = b.as_bytes();
let s0 = sb.first().copied().unwrap_or(0);
let s1 = sb.get(1).copied().unwrap_or(0);
if bb.len() >= 2 && s0 == bb[0] && s1 == bb[1] {
return 0; }
}
-1 }
}
}
pub fn bin_echotc(
name: &str,
argv: &[String],
_ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
use crate::ported::zsh_h::TERM_BAD;
if argv.is_empty() {
zwarnnam(name, "missing argument");
return 1;
}
let s: &str = argv[0].as_str();
let argv_rest: Vec<&str> = argv[1..].iter().map(String::as_str).collect();
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_BAD) != 0 {
return 1; }
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_UNKNOWN) != 0 {
let interactive = isset(INTERACTIVE);
if interactive || crate::ported::init::init_term() == 0 {
return 1; }
}
if !ensure_termcap_loaded() {
return 1;
}
let s_c = match std::ffi::CString::new(s) {
Ok(c) => c,
Err(_) => return 1,
};
let num = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
unsafe { tgetnum(s_c.as_ptr()) }
}; if num != -1 {
println!("{}", num); return 0; }
match ztgetflag(s) {
-1 => {} 0 => {
println!("no"); return 0; }
_ => {
println!("yes"); return 0; }
}
let mut buf: [libc::c_char; 2048] = [0; 2048]; let mut area = buf.as_mut_ptr();
let value = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let t = unsafe { tgetstr(s_c.as_ptr(), &mut area) }; if t.is_null() || (t as isize) == -1 || unsafe { *t } == 0 {
drop(_g);
zwarnnam(name, &format!("no such capability: {}", s)); return 1; }
unsafe { std::ffi::CStr::from_ptr(t) }
.to_string_lossy()
.into_owned()
};
let mut argct = 0usize; let bytes = value.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' {
i += 1;
if i < bytes.len() {
match bytes[i] {
b'd' | b'2' | b'3' | b'.' | b'+' => argct += 1, _ => {}
}
}
}
i += 1;
}
if argv_rest.len() != argct {
let msg = if argv_rest.len() < argct {
"not enough arguments"
}
else {
"too many arguments"
}; zwarnnam(name, msg); return 1; }
let value_cstr = match std::ffi::CString::new(value.as_str()) {
Ok(c) => c,
Err(_) => return 1,
};
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
if argct == 0 {
unsafe {
tputs(value_cstr.as_ptr(), 1, putraw);
}
} else {
let num: libc::c_int = argv_rest
.get(1)
.and_then(|s| s.parse().ok())
.or_else(|| argv_rest.first().and_then(|s| s.parse().ok()))
.unwrap_or(0); let arg0_n: libc::c_int = argv_rest.first().and_then(|s| s.parse().ok()).unwrap_or(0); unsafe {
let resolved = tgoto(value_cstr.as_ptr(), num, arg0_n);
if !resolved.is_null() {
tputs(resolved, 1, putraw);
}
}
}
drop(_g);
use std::io::Write;
let _ = std::io::stdout().flush();
0 }
pub fn gettermcap(
_ht: *mut crate::ported::zsh_h::HashTable,
name: &str,
) -> Option<crate::ported::zsh_h::Param> {
use crate::ported::zsh_h::{hashnode, param, Param, PM_READONLY, PM_SCALAR, PM_UNSET};
let mk = |s: String, extra: i32| -> Param {
Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: PM_READONLY as i32 | extra,
},
u_data: 0,
u_tied: None,
u_arr: None,
u_str: Some(s),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
})
};
use crate::ported::zsh_h::TERM_BAD;
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_BAD) != 0 {
return None; }
if (TERMFLAGS.load(Ordering::Relaxed) & TERM_UNKNOWN) != 0 {
if isset(INTERACTIVE) || crate::ported::init::init_term() == 0 {
return None; }
}
if !ensure_termcap_loaded() {
return None;
}
let n_c = std::ffi::CString::new(name).ok()?;
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let num = unsafe { tgetnum(n_c.as_ptr()) }; if num != -1 {
return Some(mk(num.to_string(), PM_SCALAR as i32));
}
drop(_g);
match ztgetflag(name) {
1 => Some(mk("yes".to_string(), PM_SCALAR as i32)), 0 => Some(mk("no".to_string(), PM_SCALAR as i32)), _ => {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut buf: [libc::c_char; 1024] = [0; 1024];
let mut area = buf.as_mut_ptr();
let raw = unsafe { tgetstr(n_c.as_ptr(), &mut area) }; if !raw.is_null() && (raw as isize) != -1 {
let s = unsafe { std::ffi::CStr::from_ptr(raw) }
.to_string_lossy()
.into_owned();
Some(mk(s, PM_SCALAR as i32))
} else {
Some(mk(String::new(), PM_SCALAR as i32 | PM_UNSET as i32))
}
}
}
}
pub fn scantermcap(
_ht: *mut crate::ported::zsh_h::HashTable,
func: Option<crate::ported::zsh_h::ParamScanFunc>,
flags: i32,
) {
use crate::ported::zsh_h::{hashnode, param, PM_SCALAR};
let f = match func {
Some(f) => f,
None => return,
};
if !ensure_termcap_loaded() {
return;
}
let emit_cap = |cap: &str, val: &str| {
let pm = param {
node: hashnode {
next: None,
nam: cap.to_string(), flags: PM_SCALAR as i32,
},
u_data: 0,
u_tied: None,
u_arr: None,
u_str: Some(val.to_string()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
};
f(&pm, flags); };
for cap in BOOLCODES.iter() {
let num = ztgetflag(cap); if num != -1 {
emit_cap(cap, if num != 0 { "yes" } else { "no" });
}
}
for cap in NUMCODES.iter() {
let c_c = match std::ffi::CString::new(*cap) {
Ok(c) => c,
Err(_) => continue,
};
let num = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
unsafe { tgetnum(c_c.as_ptr()) } };
if num != -1 {
emit_cap(cap, &num.to_string());
}
}
for cap in STRCODES.iter() {
let c_c = match std::ffi::CString::new(*cap) {
Ok(c) => c,
Err(_) => continue,
};
let mut buf = [0i8; 2048]; let tcstr = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut u = buf.as_mut_ptr() as *mut libc::c_char; unsafe { tgetstr(c_c.as_ptr(), &mut u) } };
if !tcstr.is_null() && tcstr as isize != -1 {
let s = unsafe { std::ffi::CStr::from_ptr(tcstr) }
.to_string_lossy()
.into_owned();
emit_cap(cap, &s); }
}
}
unsafe extern "C" {
fn setupterm(
term: *const libc::c_char,
filedes: libc::c_int,
errret: *mut libc::c_int,
) -> libc::c_int;
fn tgetflag(id: *const libc::c_char) -> libc::c_int;
fn tgetnum(id: *const libc::c_char) -> libc::c_int;
fn tgetstr(id: *const libc::c_char, area: *mut *mut libc::c_char) -> *mut libc::c_char;
fn tputs(
str_: *const libc::c_char,
affcnt: libc::c_int,
putc_fn: extern "C" fn(libc::c_int) -> libc::c_int,
) -> libc::c_int;
fn tgoto(cap: *const libc::c_char, col: libc::c_int, row: libc::c_int) -> *mut libc::c_char;
}
extern "C" fn putraw(c: libc::c_int) -> libc::c_int {
let byte = c as u8;
unsafe {
libc::write(1, &byte as *const u8 as *const libc::c_void, 1);
}
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 {
let _ = zsetupterm(); 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 TERMCAP_LOCK: Mutex<()> = Mutex::new(());
static BOOLCODES_FALLBACK: &[&str] = &[
"bw", "am", "ut", "cc", "xs", "YA", "YF", "YB", "xt", "xn", "eo", "gn", "hc", "HC", "km",
"YC", "hs", "hl", "in", "YG", "da", "db", "mi", "ms", "nx", "xb", "NP", "ND", "NR", "os",
"5i", "YD", "YE", "es", "hz", "ul", "xo"
];
static NUMCODES_FALLBACK: &[&str] = &[
"co", "it", "lh", "lw", "li", "lm", "sg", "ma", "Co", "pa", "MW", "NC", "Nl", "pb", "vt",
"ws", "Yo", "Yp", "Ya", "BT", "Yc", "Yb", "Yd", "Ye", "Yf", "Yg", "Yh", "Yi", "Yk", "Yj",
"Yl", "Ym", "Yn"
];
static ZSTRCODES_FALLBACK: &[&str] = &[
"ac", "bt", "bl", "cr", "ZA", "ZB", "ZC", "ZD", "cs", "rP", "ct", "MC", "cl", "cb", "ce",
"cd", "ch", "CC", "CW", "cm", "do", "ho", "vi", "le", "CM", "ve", "nd", "ll", "up", "vs",
"ZE", "dc", "dl", "DI", "ds", "DK", "hd", "eA", "as", "SA", "mb", "md", "ti", "dm", "mh",
"ZF", "ZG", "im", "ZH", "ZI", "ZJ", "ZK", "ZL", "mp", "mr", "mk", "ZM", "so", "ZN", "ZO",
"us", "ZP", "SX", "ec", "ae", "RA", "me", "te", "ed", "ZQ", "ei", "ZR", "ZS", "ZT", "ZU",
"se", "ZV", "ZW", "ue", "ZX", "RX", "PA", "fh", "vb", "ff", "fs", "WG", "HU", "i1", "is",
"i3", "if", "iP", "Ic", "Ip", "ic", "al", "ip", "K1", "K3", "K2", "kb", "@1", "kB", "K4",
"K5", "@2", "ka", "kC", "@3", "@4", "@5", "@6", "kt", "kD", "kL", "kd", "kM", "@7", "@8",
"kE", "kS", "@9", "k0", "k1", "k;", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9",
"k2", "FA", "FB", "FC", "FD", "FE", "FF", "FG", "FH", "FI", "FJ", "k3", "FK", "FL", "FM",
"FN", "FO", "FP", "FQ", "FR", "FS", "FT", "k4", "FU", "FV", "FW", "FX", "FY", "FZ", "Fa",
"Fb", "Fc", "Fd", "k5", "Fe", "Ff", "Fg", "Fh", "Fi", "Fj", "Fk", "Fl", "Fm", "Fn", "k6",
"Fo", "Fp", "Fq", "Fr", "k7", "k8", "k9", "@0", "%1", "kh", "kI", "kA", "kl", "kH", "%2",
"%3", "%4", "%5", "kN", "%6", "%7", "kP", "%8", "%9", "%0", "&1", "&2", "&3", "&4", "&5",
"kr", "&6", "&9", "&0", "*1", "*2", "*3", "*4", "*5", "*6", "*7", "*8", "*9", "kF", "*0",
"#1", "#2", "#3", "#4", "%a", "%b", "%c", "%d", "%e", "%f", "kR", "%g", "%h", "%i", "%j",
"!1", "!2", "kT", "!3", "&7", "&8", "ku", "ke", "ks", "l0", "l1", "la", "l2", "l3", "l4",
"l5", "l6", "l7", "l8", "l9", "Lf", "LF", "LO", "mo", "mm", "ZY", "ZZ", "Za", "Zb", "Zc",
"Zd", "nw", "Ze", "oc", "op", "pc", "DC", "DL", "DO", "Zf", "IC", "SF", "AL", "LE", "Zg",
"RI", "Zh", "SR", "UP", "Zi", "pk", "pl", "px", "pn", "ps", "pO", "pf", "po", "PU", "QD",
"RC", "rp", "RF", "r1", "r2", "r3", "rf", "rc", "cv", "sc", "sf", "sr", "Zj", "sa", "Sb",
"Zk", "Zl", "SC", "sp", "Sf", "ML", "Zm", "MR", "Zn", "st", "Zo", "Zp", "wi", "Zq", "Zr",
"Zs", "Zt", "Zu", "Zv", "ta", "Zw", "ts", "TO", "uc", "hu", "u0", "u1", "u2", "u3", "u4",
"u5", "u6", "u7", "u8", "u9", "WA", "XF", "XN", "Zx", "S8", "Yv", "Zz", "Xy", "Zy", "ci",
"Yw", "Yx", "dv", "S1", "Yy", "S2", "S4", "S3", "S5", "Gm", "Km", "Mi", "S6", "xl", "RQ",
"S7", "s0", "s1", "s2", "s3", "AB", "AF", "Yz", "ML", "YZ", "MT", "Xh", "Xl", "Xo", "Xr",
"Xt", "Xv", "sA", "sL"
];
#[allow(non_upper_case_globals)]
unsafe extern "C" {
static boolcodes: [*const libc::c_char; 0];
static numcodes: [*const libc::c_char; 0];
static strcodes: [*const libc::c_char; 0];
}
static BOOLCODES: LazyLock<Vec<&'static str>> = LazyLock::new(|| unsafe {
let mut v: Vec<&'static str> = Vec::new();
let base = boolcodes.as_ptr();
let mut i = 0isize;
while !(*base.offset(i)).is_null() {
if let Ok(s) = std::ffi::CStr::from_ptr(*base.offset(i)).to_str() {
v.push(s);
}
i += 1;
}
if v.is_empty() { BOOLCODES_FALLBACK.to_vec() } else { v }
});
static NUMCODES: LazyLock<Vec<&'static str>> = LazyLock::new(|| unsafe {
let mut v: Vec<&'static str> = Vec::new();
let base = numcodes.as_ptr();
let mut i = 0isize;
while !(*base.offset(i)).is_null() {
if let Ok(s) = std::ffi::CStr::from_ptr(*base.offset(i)).to_str() {
v.push(s);
}
i += 1;
}
if v.is_empty() { NUMCODES_FALLBACK.to_vec() } else { v }
});
static STRCODES: LazyLock<Vec<&'static str>> = LazyLock::new(|| unsafe {
let mut v: Vec<&'static str> = Vec::new();
let base = strcodes.as_ptr();
let mut i = 0isize;
while !(*base.offset(i)).is_null() {
if let Ok(s) = std::ffi::CStr::from_ptr(*base.offset(i)).to_str() {
v.push(s);
}
i += 1;
}
if v.is_empty() { ZSTRCODES_FALLBACK.to_vec() } else { v }
});
fn ensure_termcap_loaded() -> bool {
static STATE: AtomicI32 = AtomicI32::new(0);
match STATE.load(Ordering::Relaxed) {
1 => true,
-1 => false,
_ => {
let term = getsparam("TERM").unwrap_or_else(|| "dumb".into());
let term_c = match std::ffi::CString::new(term) {
Ok(c) => c,
Err(_) => return false,
};
let r = {
let _g = TERMCAP_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut errret: libc::c_int = 0;
unsafe { setupterm(term_c.as_ptr(), 1, &mut errret) }
};
let ok = r == 0; STATE.store(if ok { 1 } else { -1 }, Ordering::Relaxed);
ok
}
}
}
static MODULE_FEATURES: OnceLock<Mutex<features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<features>) -> Vec<String> {
vec!["b:echotc".to_string(), "p:termcap".to_string()]
}
fn handlefeatures(_m: *const module, _f: &Mutex<features>, enables: &mut Option<Vec<i32>>) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 2]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<features>, _e: Option<&[i32]>) -> i32 {
0
}
fn module_features() -> &'static Mutex<features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(features {
bn_list: None,
bn_size: 1,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 1,
n_abstract: 0,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ztgetflag_known_on_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ztgetflag("am"), 1);
}
#[test]
fn ztgetflag_unknown_returns_minus_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ztgetflag("zz"), -1);
}
#[test]
fn gettermcap_co_returns_columns() {
let _g = crate::test_util::global_state_lock();
let pm = gettermcap(std::ptr::null_mut(), "co").expect("co must resolve to Param");
let v = pm.u_str.as_deref().unwrap_or("");
let n: i32 = v.parse().unwrap_or(0);
assert!(n > 0);
}
#[test]
fn gettermcap_unknown_returns_unset_param() {
let _g = crate::test_util::global_state_lock();
use crate::ported::zsh_h::PM_UNSET;
if let Some(pm) = gettermcap(std::ptr::null_mut(), "zz_nonexistent") {
assert!(pm.node.flags & PM_UNSET as i32 != 0, "PM_UNSET set");
}
}
#[test]
fn scantermcap_emits_bool_caps() {
let _g = crate::test_util::global_state_lock();
use std::sync::Mutex;
static SEEN: Mutex<Vec<String>> = Mutex::new(Vec::new());
SEEN.lock().unwrap().clear();
fn cb(node: &crate::ported::zsh_h::param, _flags: i32) {
SEEN.lock().unwrap().push(node.node.nam.clone());
}
scantermcap(std::ptr::null_mut(), Some(cb), 0);
let seen = SEEN.lock().unwrap().clone();
assert!(seen.iter().any(|k| k == "am"));
}
#[test]
fn echotc_with_no_args_returns_one() {
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_echotc("echotc", &[], &ops, 0);
assert_eq!(r, 1, "echotc must report missing-arg error");
}
#[test]
fn echotc_unknown_cap_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_echotc("echotc", &["zz_definitely_not_a_cap".to_string()], &ops, 0);
assert_ne!(r, 0, "unknown cap must error");
}
#[test]
fn ztgetflag_rejects_embedded_nul() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
ztgetflag("a\0m"),
-1,
"embedded NUL must surface as -1, not panic or false-match"
);
}
#[test]
fn ztgetflag_empty_string_returns_neg_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ztgetflag(""), -1);
}
#[test]
fn scantermcap_keys_are_unique() {
let _g = crate::test_util::global_state_lock();
use std::sync::Mutex;
static KEYS: Mutex<Vec<String>> = Mutex::new(Vec::new());
KEYS.lock().unwrap().clear();
fn cb(node: &crate::ported::zsh_h::param, _flags: i32) {
KEYS.lock().unwrap().push(node.node.nam.clone());
}
scantermcap(std::ptr::null_mut(), Some(cb), 0);
let collected = KEYS.lock().unwrap().clone();
let mut seen = std::collections::HashSet::new();
for k in &collected {
assert!(
seen.insert(k.clone()),
"duplicate termcap key emitted: {}",
k
);
}
}
#[test]
fn scantermcap_keys_are_nonempty() {
let _g = crate::test_util::global_state_lock();
use std::sync::Mutex;
static KEYS: Mutex<Vec<String>> = Mutex::new(Vec::new());
KEYS.lock().unwrap().clear();
fn cb(node: &crate::ported::zsh_h::param, _flags: i32) {
KEYS.lock().unwrap().push(node.node.nam.clone());
}
scantermcap(std::ptr::null_mut(), Some(cb), 0);
for k in KEYS.lock().unwrap().iter() {
assert!(
!k.is_empty(),
"scantermcap emitted empty key — null entry leak?"
);
}
}
#[test]
fn gettermcap_is_case_sensitive() {
let _g = crate::test_util::global_state_lock();
use crate::ported::zsh_h::PM_UNSET;
let r1 = gettermcap(std::ptr::null_mut(), "co").expect("co Param");
assert!(r1.node.flags & PM_UNSET as i32 == 0, "co must be set");
if let Some(r2) = gettermcap(std::ptr::null_mut(), "CO") {
assert!(
r2.node.flags & PM_UNSET as i32 != 0,
"termcap names case-sensitive; CO must be PM_UNSET"
);
}
}
#[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 ztgetflag_empty_string_returns_minus_one() {
let _g = crate::test_util::global_state_lock();
let r = ztgetflag("");
assert_eq!(r, -1, "empty cap name → -1");
}
#[test]
fn ztgetflag_unknown_cap_returns_minus_one() {
let _g = crate::test_util::global_state_lock();
let r = ztgetflag("zz");
assert_eq!(r, -1, "unknown cap → -1");
}
#[test]
fn ztgetflag_known_cap_returns_zero_or_one() {
let _g = crate::test_util::global_state_lock();
let r = ztgetflag("am");
assert!(r == 0 || r == 1, "known cap → 0 or 1, got {}", r);
}
#[test]
fn ztgetflag_is_deterministic() {
let _g = crate::test_util::global_state_lock();
for cap in &["am", "co", "zz", ""] {
let first = ztgetflag(cap);
for _ in 0..5 {
assert_eq!(ztgetflag(cap), first, "{:?} must be pure", cap);
}
}
}
#[test]
fn bin_echotc_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_echotc("echotc", &[], &ops, 0);
assert_ne!(r, 0, "no cap name → usage error");
}
#[test]
fn gettermcap_empty_name_returns_pm_unset() {
let _g = crate::test_util::global_state_lock();
use crate::ported::zsh_h::PM_UNSET;
if let Some(pm) = gettermcap(std::ptr::null_mut(), "") {
assert!(pm.node.flags & PM_UNSET as i32 != 0);
}
}
#[test]
fn termcap_setup_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
assert_eq!(setup_(std::ptr::null()), 0);
}
#[test]
fn termcap_features_returns_zero_pin() {
let _g = crate::test_util::global_state_lock();
let mut f = Vec::new();
assert_eq!(features_(std::ptr::null(), &mut f), 0);
}
#[test]
fn ztgetflag_is_pure() {
let _g = crate::test_util::global_state_lock();
let r = ztgetflag("am");
for _ in 0..5 {
assert_eq!(ztgetflag("am"), r, "ztgetflag must be pure");
}
}
#[test]
fn ztgetflag_return_value_in_canonical_set() {
let _g = crate::test_util::global_state_lock();
for cap in ["am", "bw", "xn", "co", "li", "xyz_unknown_cap"] {
let r = ztgetflag(cap);
assert!(
r == -1 || r == 0 || r == 1,
"ztgetflag({:?}) = {} not in {{-1, 0, 1}}",
cap,
r
);
}
}
#[test]
fn ztgetflag_arbitrary_strings_return_minus_one() {
let _g = crate::test_util::global_state_lock();
for s in ["xyz", "long_unknown_cap_name", "AAA", "zzz"] {
assert_eq!(ztgetflag(s), -1, "unknown cap {:?} must return -1", s);
}
}
#[test]
fn bin_echotc_returns_i32_type() {
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 _: i32 = bin_echotc("echotc", &[], &ops, 0);
}
#[test]
fn bin_echotc_empty_string_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_echotc("echotc", &["".into()], &ops, 0);
assert_ne!(r, 0, "empty cap name → error");
}
#[test]
fn gettermcap_empty_string_returns_none() {
let _g = crate::test_util::global_state_lock();
let r = gettermcap(std::ptr::null_mut(), "");
let _ = r; }
#[test]
fn gettermcap_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let a = gettermcap(std::ptr::null_mut(), "co").is_some();
for _ in 0..5 {
let b = gettermcap(std::ptr::null_mut(), "co").is_some();
assert_eq!(a, b, "gettermcap must be deterministic");
}
}
#[test]
fn termcap_full_lifecycle_returns_zero() {
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 termcap_setup_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(setup_(std::ptr::null()), 0);
}
}
#[test]
fn termcap_finish_idempotent() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(finish_(std::ptr::null()), 0);
}
}
#[test]
fn ztgetflag_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = ztgetflag("am");
}
#[test]
fn ztgetflag_empty_string_returns_negative() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ztgetflag(""), -1, "empty cap name must return -1 (unknown)");
}
#[test]
fn ztgetflag_deterministic_for_unknown() {
let _g = crate::test_util::global_state_lock();
for s in ["zz_unknown", "AAA", "garbage"] {
let first = ztgetflag(s);
for _ in 0..5 {
assert_eq!(ztgetflag(s), first, "ztgetflag({:?}) must be pure", s);
}
}
}
#[test]
fn bin_echotc_no_args_usage_error_alt() {
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_echotc("echotc", &[], &ops, 0);
assert_ne!(r, 0, "no args → usage error");
}
#[test]
fn bin_echotc_exit_code_non_negative() {
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 argv in [
vec![],
vec!["".into()],
vec!["bl".into()],
vec!["zz_unknown".into()],
vec!["cm".into(), "5".into(), "10".into()],
] {
let r = bin_echotc("echotc", &argv, &ops, 0);
assert!(
r >= 0,
"exit code must be non-negative, got {} for {:?}",
r,
argv
);
}
}
#[test]
fn gettermcap_returns_option_param_type() {
let _g = crate::test_util::global_state_lock();
let _: Option<crate::ported::zsh_h::Param> = gettermcap(std::ptr::null_mut(), "co");
}
#[test]
fn scantermcap_none_callback_returns_void_type() {
let _g = crate::test_util::global_state_lock();
let _: () = scantermcap(std::ptr::null_mut(), None, 0);
}
#[test]
fn scantermcap_various_flags_no_panic() {
let _g = crate::test_util::global_state_lock();
for flags in [0i32, 1, 2, 0xff, -1] {
scantermcap(std::ptr::null_mut(), None, flags);
}
}
#[test]
fn termcap_setup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = setup_(std::ptr::null());
}
#[test]
fn termcap_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 termcap_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:365 setup_");
assert_eq!(features_(null, &mut v), 0, "c:373 features_");
assert_eq!(enables_(null, &mut e), 0, "c:381 enables_");
assert_eq!(boot_(null), 0, "c:388 boot_");
assert_eq!(cleanup_(null), 0, "c:399 cleanup_");
assert_eq!(finish_(null), 0, "c:410 finish_");
}
#[test]
fn termcap_cleanup_idempotent_repeated_calls() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
assert_eq!(cleanup_(std::ptr::null()), 0);
}
}
#[test]
fn termcap_cleanup_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = cleanup_(std::ptr::null());
}
#[test]
fn termcap_finish_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = finish_(std::ptr::null());
}
#[test]
fn termcap_boot_returns_i32_type() {
let _g = crate::test_util::global_state_lock();
let _: i32 = boot_(std::ptr::null());
}
#[test]
fn termcap_boot_idempotent_repeated_calls() {
let _g = crate::test_util::global_state_lock();
for _ in 0..10 {
let _ = boot_(std::ptr::null());
}
}
#[test]
fn ztgetflag_deterministic_for_common_caps() {
let _g = crate::test_util::global_state_lock();
for cap in ["am", "bw", "xn", "km"] {
let a = ztgetflag(cap);
let b = ztgetflag(cap);
assert_eq!(a, b, "ztgetflag({:?}) must be deterministic", cap);
}
}
#[test]
fn ztgetflag_long_cap_name_no_panic() {
let _g = crate::test_util::global_state_lock();
let long = "x".repeat(500);
let _ = ztgetflag(&long);
}
#[test]
fn bin_echotc_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_echotc("echotc", &[], &ops, func);
}
}
#[test]
fn bin_echotc_deterministic_unknown_cap() {
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 args = vec!["__never_real_cap_xyz__".to_string()];
let r1 = bin_echotc("echotc", &args, &ops, 0);
let r2 = bin_echotc("echotc", &args, &ops, 0);
assert_eq!(r1, r2, "bin_echotc unknown cap must be deterministic");
}
#[test]
fn gettermcap_empty_name_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = gettermcap(std::ptr::null_mut(), "");
}
#[test]
fn scantermcap_none_callback_repeated_safe() {
let _g = crate::test_util::global_state_lock();
for _ in 0..5 {
scantermcap(std::ptr::null_mut(), None, 0);
}
}
#[test]
fn termcap_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);
}
}