use crate::ported::utils::{adjustcolumns, adjustlines};
use std::sync::atomic::Ordering;
use crate::ported::builtin::{BREAKS, CONTFLAG, LASTVAL, LOOPS, RETFLAG};
use crate::ported::exec::{execlist, execsubst, noerrexit, simple_pline, this_noerrexit};
use crate::ported::lex::untokenize;
use crate::ported::math::{matheval as wc_matheval, mathevali as wc_mathevali};
use crate::ported::mem::{freeheap, popheap, pushheap};
use crate::ported::params::setloopvar;
use crate::ported::parse::{ecgetlist, ecgetstr, ecrawstr};
use crate::ported::pattern::{patcompile, pattry};
use crate::ported::prompt::{cmdpop, cmdpush};
use crate::ported::signals::{queue_signals, unqueue_signals};
use crate::ported::subst::singsub;
use crate::ported::utils::{errflag, printprompt4, zerr, ERRFLAG_ERROR};
use crate::ported::zsh_h::{
estate, isset, wc_code, CS_ALWAYS, CS_CASE, CS_CURSH, CS_ELIF, CS_ELIFTHEN, CS_ELSE, CS_FOR,
CS_IF, CS_IFTHEN, CS_REPEAT, CS_UNTIL, CS_WHILE, EC_DUP, EC_DUPTOK, EC_NODUP, ERRFLAG_INT,
NOERREXIT_EXIT, NOERREXIT_RETURN, PAT_STATIC, WC_CASE, WC_CASE_AND, WC_CASE_OR, WC_CASE_SKIP,
WC_CASE_TESTAND, WC_CASE_TYPE, WC_END, WC_FOR_COND, WC_FOR_LIST, WC_FOR_SKIP, WC_FOR_TYPE,
WC_IF, WC_IF_ELSE, WC_IF_SKIP, WC_IF_TYPE, WC_REPEAT_SKIP, WC_TRY_SKIP, WC_WHILE_SKIP,
WC_WHILE_TYPE, WC_WHILE_UNTIL,
};
use crate::zsh_h::XTRACE;
use std::io::Write;
use std::sync::atomic::AtomicI32;
pub static try_tryflag: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
pub static try_errflag: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1);
pub static try_interrupt: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1);
pub fn selectlist(items: &[&str], start: usize) -> usize {
let mut stderr = std::io::stderr().lock();
let ct = items.len(); if ct == 0 {
return 0;
}
let mut longest: usize = 1; for ap in items {
let aplen = ap.chars().count(); if aplen > longest {
longest = aplen; }
}
longest += 1; let mut t0 = ct; while t0 != 0 {
t0 /= 10; longest += 1; }
let zterm_columns = adjustcolumns(); let zterm_lines = adjustlines();
let mut fct: usize = (zterm_columns.saturating_sub(1)) / (longest + 3); let fw: usize;
if fct == 0 {
fct = 1; fw = 0; } else {
fw = (zterm_columns - 1) / fct; }
let colsz = (ct + fct - 1) / fct;
let mut t1 = start;
let max_lines = zterm_lines.saturating_sub(2);
while t1 < colsz && (t1.saturating_sub(start)) < max_lines {
let mut idx = t1; if idx >= ct {
let _ = stderr.write_all(b"\n");
t1 += 1;
continue;
}
loop {
let entry = items[idx];
let mut t2 = entry.chars().count() + 2;
let t3 = idx + 1;
let _ = write!(stderr, "{}) {}", t3, entry);
let mut digits = t3;
while digits != 0 {
t2 += 1;
digits /= 10;
}
while t2 < fw {
let _ = stderr.write_all(b" ");
t2 += 1;
}
let mut t0 = colsz;
while t0 != 0 && idx + 1 < ct {
t0 -= 1;
idx += 1;
if t0 == 0 {
break;
}
}
if idx + 1 >= ct {
break;
} }
let _ = stderr.write_all(b"\n"); t1 += 1;
}
let _ = stderr.flush();
if t1 < colsz {
t1
} else {
0
} }
static LOOP_DEPTH: AtomicI32 = AtomicI32::new(0);
static CONT_FLAG: AtomicI32 = AtomicI32::new(0);
static BREAK_LEVEL: AtomicI32 = AtomicI32::new(0);
pub fn execfor(state: &mut estate, do_exec: i32) -> i32 {
use crate::ported::zsh_h::Z_END;
let code = state.prog.prog[state.pc.wrapping_sub(1)]; let iscond = WC_FOR_TYPE(code) == WC_FOR_COND; let mut last_iter = false; let mut val: i64 = 0; let mut vars: Vec<String> = Vec::new();
let mut args: Vec<String> = Vec::new();
let mut cond_expr: String = String::new();
let mut advance_expr: String = String::new();
let old_simple_pline = simple_pline.swap(1, Ordering::Relaxed); let end_pc = state.pc + WC_FOR_SKIP(code) as usize; let mut ctok = 0i32;
let mut atok = 0i32;
if iscond {
let init = ecgetstr(state, EC_NODUP, None); let init_sub = singsub(&init); if isset(XTRACE) {
let init_show = untokenize(&init_sub);
printprompt4();
eprintln!("{}", init_show);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let _ = wc_matheval(&init_sub); }
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = end_pc;
simple_pline.store(old_simple_pline, Ordering::Relaxed);
return 1;
}
cond_expr = ecgetstr(state, EC_NODUP, Some(&mut ctok)); advance_expr = ecgetstr(state, EC_NODUP, Some(&mut atok)); } else {
let count = state.prog.prog[state.pc] as usize;
state.pc += 1;
vars = ecgetlist(state, count, EC_NODUP, None);
if WC_FOR_TYPE(code) == WC_FOR_LIST {
let mut htok = 0i32;
let arg_count = state.prog.prog[state.pc] as usize;
state.pc += 1;
args = ecgetlist(state, arg_count, EC_DUPTOK, Some(&mut htok));
if args.is_empty() {
state.pc = end_pc;
simple_pline.store(old_simple_pline, Ordering::Relaxed);
return 0;
}
if htok != 0 {
execsubst(&mut args); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
state.pc = end_pc;
simple_pline.store(old_simple_pline, Ordering::Relaxed);
return 1;
}
}
} else {
args = crate::ported::builtin::PPARAMS
.lock()
.map(|p| p.clone())
.unwrap_or_default();
}
}
if !iscond && args.is_empty() {
LASTVAL.store(0, Ordering::Relaxed);
}
LOOPS.fetch_add(1, Ordering::SeqCst); pushheap(); cmdpush(CS_FOR as u8); let loop_pc = state.pc; let mut args_iter = args.into_iter();
while !last_iter {
if iscond {
let mut cs = cond_expr.clone();
if ctok != 0 {
cs = singsub(&cs);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let trimmed = cs.trim_start();
if !trimmed.is_empty() {
if isset(XTRACE) {
printprompt4();
eprintln!("{}", trimmed);
}
val = wc_mathevali(trimmed).unwrap_or(0);
} else {
val = 1;
}
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
if BREAKS.load(Ordering::SeqCst) > 0 {
BREAKS.fetch_sub(1, Ordering::SeqCst);
}
LASTVAL.store(1, Ordering::Relaxed);
break;
}
if val == 0 {
break;
}
} else {
let mut count = 0;
for name in &vars {
let value = match args_iter.next() {
Some(v) => v,
None => {
if count != 0 {
last_iter = true;
String::new()
} else {
break;
}
}
};
if isset(XTRACE) {
printprompt4();
eprintln!("{}={}", name, value);
}
setloopvar(name, &value);
count += 1;
}
if count == 0 {
break;
}
}
state.pc = loop_pc; let _do_exec_now = do_exec != 0 && !args_iter.clone().any(|_| true); let _ = execlist(state, 1, if _do_exec_now { 1 } else { 0 });
if BREAKS.load(Ordering::SeqCst) > 0 {
let prev = BREAKS.fetch_sub(1, Ordering::SeqCst);
if prev - 1 > 0 || CONTFLAG.load(Ordering::SeqCst) == 0 {
break;
}
CONTFLAG.store(0, Ordering::SeqCst);
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
break;
}
if iscond && (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let mut adv = advance_expr.clone();
if atok != 0 {
adv = singsub(&adv);
}
if isset(XTRACE) {
printprompt4();
eprintln!("{}", adv);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
let _ = wc_matheval(&adv);
}
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
if BREAKS.load(Ordering::SeqCst) > 0 {
BREAKS.fetch_sub(1, Ordering::SeqCst);
}
LASTVAL.store(1, Ordering::Relaxed);
break;
}
freeheap(); }
popheap(); cmdpop(); LOOPS.fetch_sub(1, Ordering::SeqCst); simple_pline.store(old_simple_pline, Ordering::Relaxed);
state.pc = end_pc;
this_noerrexit.store(1, Ordering::Relaxed);
let _ = Z_END;
LASTVAL.load(Ordering::Relaxed)
}
pub fn execselect(state: &mut estate, _do_exec: i32) -> i32 {
let code = state.prog.prog[state.pc.wrapping_sub(1)];
let end_pc = state.pc + WC_FOR_SKIP(code) as usize;
state.pc = end_pc;
this_noerrexit.store(1, Ordering::Relaxed);
LASTVAL.load(Ordering::Relaxed)
}
pub fn execwhile(state: &mut estate, _do_exec: i32) -> i32 {
let code = state.prog.prog[state.pc.wrapping_sub(1)]; let isuntil = WC_WHILE_TYPE(code) == WC_WHILE_UNTIL; let end_pc = state.pc + WC_WHILE_SKIP(code) as usize; let olderrexit = noerrexit.load(Ordering::Relaxed); let mut oldval: i32 = 0; pushheap(); cmdpush(if isuntil {
CS_UNTIL as u8
} else {
CS_WHILE as u8
}); LOOPS.fetch_add(1, Ordering::SeqCst); let loop_pc = state.pc; let old_simple_pline = simple_pline.load(Ordering::Relaxed); if state.prog.prog.get(loop_pc) == Some(&WC_END)
&& state.prog.prog.get(loop_pc + 1) == Some(&WC_END)
{
simple_pline.store(1, Ordering::Relaxed);
while BREAKS.load(Ordering::SeqCst) == 0 {
std::thread::yield_now();
}
BREAKS.fetch_sub(1, Ordering::SeqCst);
simple_pline.store(old_simple_pline, Ordering::Relaxed);
} else {
loop {
state.pc = loop_pc; noerrexit.fetch_or(NOERREXIT_EXIT | NOERREXIT_RETURN, Ordering::Relaxed); simple_pline.store(1, Ordering::Relaxed); let _ = execlist(state, 1, 0); simple_pline.store(old_simple_pline, Ordering::Relaxed);
noerrexit.store(olderrexit, Ordering::Relaxed); let cond_status = LASTVAL.load(Ordering::Relaxed); let cond_passed = (cond_status == 0) ^ isuntil;
if !cond_passed {
if BREAKS.load(Ordering::SeqCst) > 0 {
BREAKS.fetch_sub(1, Ordering::SeqCst);
}
if RETFLAG.load(Ordering::SeqCst) == 0 {
LASTVAL.store(oldval, Ordering::Relaxed);
}
break;
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
if BREAKS.load(Ordering::SeqCst) > 0 {
BREAKS.fetch_sub(1, Ordering::SeqCst);
}
break;
}
simple_pline.store(1, Ordering::Relaxed); let _ = execlist(state, 1, 0); simple_pline.store(old_simple_pline, Ordering::Relaxed);
if BREAKS.load(Ordering::SeqCst) > 0 {
let prev = BREAKS.fetch_sub(1, Ordering::SeqCst);
if prev - 1 > 0 || CONTFLAG.load(Ordering::SeqCst) == 0 {
break;
}
CONTFLAG.store(0, Ordering::SeqCst);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
LASTVAL.store(1, Ordering::Relaxed);
break;
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
break;
}
freeheap(); oldval = LASTVAL.load(Ordering::Relaxed); }
}
cmdpop(); popheap(); LOOPS.fetch_sub(1, Ordering::SeqCst); state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
}
pub fn execrepeat(state: &mut estate, _do_exec: i32) -> i32 {
let code = state.prog.prog[state.pc.wrapping_sub(1)]; let old_simple_pline = simple_pline.swap(1, Ordering::Relaxed); let end_pc = state.pc + WC_REPEAT_SKIP(code) as usize; let mut htok = 0i32;
let mut tmp = ecgetstr(state, EC_DUPTOK, Some(&mut htok)); if htok != 0 {
tmp = singsub(&tmp); tmp = untokenize(&tmp); }
let count = wc_mathevali(&tmp).unwrap_or(0); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
simple_pline.store(old_simple_pline, Ordering::Relaxed);
return 1;
}
LASTVAL.store(0, Ordering::Relaxed); pushheap(); cmdpush(CS_REPEAT as u8); LOOPS.fetch_add(1, Ordering::SeqCst); let loop_pc = state.pc; let mut remaining = count;
while remaining > 0 {
remaining -= 1;
state.pc = loop_pc;
let _ = execlist(state, 1, 0); freeheap(); if BREAKS.load(Ordering::SeqCst) > 0 {
let prev = BREAKS.fetch_sub(1, Ordering::SeqCst);
if prev - 1 > 0 || CONTFLAG.load(Ordering::SeqCst) == 0 {
break;
}
CONTFLAG.store(0, Ordering::SeqCst);
}
if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
LASTVAL.store(1, Ordering::Relaxed);
break;
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
break;
}
}
cmdpop(); popheap(); LOOPS.fetch_sub(1, Ordering::SeqCst); simple_pline.store(old_simple_pline, Ordering::Relaxed);
state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
}
pub fn execif(state: &mut estate, do_exec: i32) -> i32 {
let code0 = state.prog.prog[state.pc.wrapping_sub(1)]; let olderrexit = noerrexit.load(Ordering::Relaxed); let end_pc = state.pc + WC_IF_SKIP(code0) as usize; noerrexit.fetch_or(NOERREXIT_EXIT | NOERREXIT_RETURN, Ordering::Relaxed); let mut s = 0i32; let mut run = 0i32; while state.pc < end_pc {
let code = state.prog.prog[state.pc];
state.pc += 1;
if wc_code(code) != WC_IF || WC_IF_TYPE(code) == WC_IF_ELSE {
run = if wc_code(code) == WC_IF && WC_IF_TYPE(code) == WC_IF_ELSE {
2
} else {
1
};
if run == 1 {
state.pc -= 1; }
break;
}
let next_pc = state.pc + WC_IF_SKIP(code) as usize; cmdpush(if s != 0 { CS_ELIF as u8 } else { CS_IF as u8 }); let _ = execlist(state, 1, 0); cmdpop(); if LASTVAL.load(Ordering::Relaxed) == 0 {
run = 1;
break;
}
if RETFLAG.load(Ordering::SeqCst) != 0 {
break;
}
s = 1;
state.pc = next_pc;
}
noerrexit.store(olderrexit, Ordering::Relaxed); if run != 0 {
cmdpush(if run == 2 {
CS_ELSE as u8
} else if s != 0 {
CS_ELIFTHEN as u8
} else {
CS_IFTHEN as u8
});
let _ = execlist(state, 1, do_exec);
cmdpop();
} else if RETFLAG.load(Ordering::SeqCst) == 0
&& (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0
{
LASTVAL.store(0, Ordering::Relaxed); }
state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
}
pub fn execcase(state: &mut estate, do_exec: i32) -> i32 {
let code0 = state.prog.prog[state.pc.wrapping_sub(1)]; let end_pc = state.pc + WC_CASE_SKIP(code0) as usize; let raw_word = ecgetstr(state, EC_DUP, None);
let word_sub = singsub(&raw_word);
let word = untokenize(&word_sub);
let mut anypatok = false; cmdpush(CS_CASE as u8); let mut code = 0u32;
while state.pc < end_pc {
code = state.prog.prog[state.pc];
state.pc += 1;
if wc_code(code) != WC_CASE {
break;
}
let next_pc = state.pc + WC_CASE_SKIP(code) as usize; let nalts = state.prog.prog[state.pc] as i32; state.pc += 1;
let mut patok = false;
let mut nalts_remaining = nalts;
while !patok && nalts_remaining > 0 {
queue_signals(); let mut htok = 0i32;
let pat_raw = ecrawstr(&state.prog, state.pc, Some(&mut htok));
let pat = if htok != 0 {
singsub(&pat_raw)
} else {
pat_raw
};
if let Some(pprog) = patcompile(
&{
let mut __pat_tok = (&pat).to_string();
crate::ported::glob::tokenize(&mut __pat_tok);
__pat_tok
},
PAT_STATIC,
None,
) {
if pattry(&pprog, &word) {
patok = true;
anypatok = true;
}
} else {
zerr(&format!("bad pattern: {}", pat)); }
state.pc += 2; nalts_remaining -= 1;
unqueue_signals(); }
state.pc += (2 * nalts_remaining) as usize; if patok {
let _ = execlist(
state,
1,
((WC_CASE_TYPE(code) == WC_CASE_OR) as i32) & do_exec,
);
while RETFLAG.load(Ordering::SeqCst) == 0
&& wc_code(code) == WC_CASE
&& WC_CASE_TYPE(code) == WC_CASE_AND
&& state.pc < end_pc
{
state.pc = next_pc;
code = state.prog.prog[state.pc];
state.pc += 1;
let inner_next = state.pc + WC_CASE_SKIP(code) as usize;
let inner_nalts = state.prog.prog[state.pc] as usize;
state.pc += 1 + 2 * inner_nalts;
let _ = execlist(
state,
1,
((WC_CASE_TYPE(code) == WC_CASE_OR) as i32) & do_exec,
);
let _ = inner_next;
}
if WC_CASE_TYPE(code) != WC_CASE_TESTAND {
break;
}
}
state.pc = next_pc; }
cmdpop(); state.pc = end_pc; if !anypatok {
LASTVAL.store(0, Ordering::Relaxed);
}
this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
}
pub fn exectry(state: &mut estate, _do_exec: i32) -> i32 {
let header = state.prog.prog[state.pc.wrapping_sub(1)]; let end_pc = state.pc + WC_TRY_SKIP(header) as usize; let try_inner = state.prog.prog[state.pc]; let always_pc = state.pc + 1 + WC_TRY_SKIP(try_inner) as usize; state.pc += 1; pushheap(); cmdpush(CS_CURSH as u8); try_tryflag.fetch_add(1, Ordering::SeqCst); let _ = execlist(state, 1, 0); try_tryflag.fetch_sub(1, Ordering::SeqCst); let try_status = LASTVAL.load(Ordering::Relaxed);
let endval = if try_status != 0 {
try_status
} else {
(errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) as i32
};
freeheap(); cmdpop(); cmdpush(CS_ALWAYS as u8); let saved_err = errflag.load(Ordering::Relaxed);
let save_try_err = (saved_err & ERRFLAG_ERROR) != 0;
let save_try_int = (saved_err & ERRFLAG_INT) != 0;
let saved_try_errflag = crate::ported::r#loop::try_errflag.load(Ordering::Relaxed);
let saved_try_interrupt = crate::ported::r#loop::try_interrupt.load(Ordering::Relaxed);
crate::ported::r#loop::try_errflag.store((saved_err & ERRFLAG_ERROR) as i64, Ordering::Relaxed); crate::ported::r#loop::try_interrupt.store(
if (saved_err & ERRFLAG_INT) != 0 { 1 } else { 0 },
Ordering::Relaxed,
); errflag.fetch_and(!(ERRFLAG_ERROR | ERRFLAG_INT), Ordering::Relaxed);
let save_retflag = RETFLAG.swap(0, Ordering::SeqCst);
let save_breaks = BREAKS.swap(0, Ordering::SeqCst);
let save_contflag = CONTFLAG.swap(0, Ordering::SeqCst);
state.pc = always_pc; let _ = execlist(state, 1, 0); if save_try_err {
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
} else {
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
}
if save_try_int {
errflag.fetch_or(ERRFLAG_INT, Ordering::Relaxed);
} else {
errflag.fetch_and(!ERRFLAG_INT, Ordering::Relaxed);
}
crate::ported::r#loop::try_errflag.store(saved_try_errflag, Ordering::Relaxed);
crate::ported::r#loop::try_interrupt.store(saved_try_interrupt, Ordering::Relaxed);
if RETFLAG.load(Ordering::SeqCst) == 0 {
RETFLAG.store(save_retflag, Ordering::SeqCst);
}
if BREAKS.load(Ordering::SeqCst) == 0 {
BREAKS.store(save_breaks, Ordering::SeqCst);
}
if CONTFLAG.load(Ordering::SeqCst) == 0 {
CONTFLAG.store(save_contflag, Ordering::SeqCst);
}
cmdpop(); popheap(); state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); endval
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_selectlist_returns_zero_when_full_fits() {
let _g = crate::test_util::global_state_lock();
let items = ["one", "two", "three"];
let r = selectlist(&items, 0);
assert!(r < items.len() || r == 0);
}
#[test]
fn selectlist_empty_returns_zero() {
let _g = crate::test_util::global_state_lock();
assert_eq!(selectlist(&[], 0), 0);
}
#[test]
fn selectlist_start_zero_returns_valid_count() {
let _g = crate::test_util::global_state_lock();
let items = ["a", "b", "c"];
let r = selectlist(&items, 0);
assert!(r <= items.len());
}
#[test]
fn selectlist_single_item_no_panic() {
let _g = crate::test_util::global_state_lock();
let r = selectlist(&["only"], 0);
let _ = r;
}
#[test]
fn selectlist_empty_string_entries_no_panic() {
let _g = crate::test_util::global_state_lock();
let items = ["", "", ""];
let _ = selectlist(&items, 0);
}
#[test]
fn selectlist_long_items_no_panic() {
let _g = crate::test_util::global_state_lock();
let long = "x".repeat(500);
let items = [long.as_str(), "short"];
let _ = selectlist(&items, 0);
}
#[test]
fn selectlist_multibyte_entries_no_panic() {
let _g = crate::test_util::global_state_lock();
let items = ["日本", "中文", "한국어"];
let _ = selectlist(&items, 0);
}
#[test]
fn selectlist_many_items_no_panic() {
let _g = crate::test_util::global_state_lock();
let items: Vec<String> = (0..100).map(|i| format!("item{}", i)).collect();
let refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
let _ = selectlist(&refs, 0);
}
#[test]
fn selectlist_start_in_range_no_panic() {
let _g = crate::test_util::global_state_lock();
let items = ["a", "b", "c", "d", "e"];
let _ = selectlist(&items, 2);
}
#[test]
fn selectlist_is_deterministic() {
let _g = crate::test_util::global_state_lock();
let items = ["a", "b", "c"];
let first = selectlist(&items, 0);
for _ in 0..5 {
assert_eq!(selectlist(&items, 0), first);
}
}
#[test]
fn selectlist_return_bounded_by_items_len() {
let _g = crate::test_util::global_state_lock();
for sz in [1usize, 3, 5, 20] {
let items: Vec<String> = (0..sz).map(|i| format!("i{}", i)).collect();
let refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
let r = selectlist(&refs, 0);
assert!(r <= sz, "selectlist({} items, 0) = {} > {}", sz, r, sz);
}
}
#[test]
fn selectlist_returns_usize_type() {
let _g = crate::test_util::global_state_lock();
let _: usize = selectlist(&["x"], 0);
}
#[test]
fn selectlist_two_items_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["a", "b"], 0);
}
#[test]
fn selectlist_entries_with_tabs_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["a\tb", "c\td"], 0);
}
#[test]
fn try_tryflag_is_atomic_i64() {
use std::sync::atomic::Ordering;
let v: i64 = try_tryflag.load(Ordering::SeqCst);
let _ = v;
}
#[test]
fn selectlist_ansi_escape_entries_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["\x1b[31mred\x1b[0m", "plain"], 0);
}
#[test]
fn selectlist_single_space_entry_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&[" "], 0);
}
#[test]
fn loop_depth_initial_state_is_zero() {
use std::sync::atomic::Ordering;
let v = LOOP_DEPTH.load(Ordering::SeqCst);
assert!(v >= 0, "loop depth must never be negative; got {}", v);
}
#[test]
fn cont_flag_initial_state_non_negative() {
use std::sync::atomic::Ordering;
let v = CONT_FLAG.load(Ordering::SeqCst);
assert!(v >= 0, "cont flag must be non-negative; got {}", v);
}
#[test]
fn break_level_initial_state_non_negative() {
use std::sync::atomic::Ordering;
let v = BREAK_LEVEL.load(Ordering::SeqCst);
assert!(v >= 0, "break level must be non-negative; got {}", v);
}
#[test]
fn selectlist_no_mutation_across_calls() {
let _g = crate::test_util::global_state_lock();
let items = ["alpha", "beta", "gamma"];
let first = selectlist(&items, 0);
for _ in 0..20 {
assert_eq!(
selectlist(&items, 0),
first,
"selectlist must be pure across repeated calls"
);
}
}
#[test]
fn selectlist_four_items_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["a", "b", "c", "d"], 0);
}
#[test]
fn selectlist_newline_entry_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["a\nb"], 0);
}
#[test]
fn selectlist_mixed_length_entries_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["a", "ab", "abc", "abcd", "abcde"], 0);
}
#[test]
fn selectlist_emoji_entries_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["foo", "bar"], 0);
}
#[test]
fn try_tryflag_load_idempotent() {
use std::sync::atomic::Ordering;
let first = try_tryflag.load(Ordering::SeqCst);
for _ in 0..50 {
assert_eq!(
try_tryflag.load(Ordering::SeqCst),
first,
"try_tryflag pure load must be deterministic"
);
}
}
#[test]
fn try_tryflag_address_is_stable() {
let p1 = &try_tryflag as *const _;
let p2 = &try_tryflag as *const _;
assert_eq!(p1, p2, "static must have a single, stable address");
}
#[test]
fn selectlist_whitespace_only_entries_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&[" ", "\t\t", " \t "], 0);
}
#[test]
fn loop_static_addresses_stable() {
let a1 = &LOOP_DEPTH as *const _;
let a2 = &LOOP_DEPTH as *const _;
assert_eq!(a1, a2, "LOOP_DEPTH must be a true static");
let b1 = &CONT_FLAG as *const _;
let b2 = &CONT_FLAG as *const _;
assert_eq!(b1, b2, "CONT_FLAG must be a true static");
let c1 = &BREAK_LEVEL as *const _;
let c2 = &BREAK_LEVEL as *const _;
assert_eq!(c1, c2, "BREAK_LEVEL must be a true static");
}
#[test]
fn selectlist_empty_returns_zero_alt() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
selectlist(&[], 0),
0,
"empty items must produce zero-line output"
);
}
#[test]
fn selectlist_single_entry_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["one"], 0);
}
#[test]
fn selectlist_returns_usize_type_alt() {
let _g = crate::test_util::global_state_lock();
let _: usize = selectlist(&["a"], 0);
}
#[test]
fn selectlist_start_past_end_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["a", "b"], 100);
let _ = selectlist(&["a", "b"], usize::MAX / 2);
}
#[test]
fn selectlist_large_list_no_panic() {
let _g = crate::test_util::global_state_lock();
let items: Vec<String> = (0..200).map(|i| format!("item{}", i)).collect();
let refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
let _ = selectlist(&refs, 0);
}
#[test]
fn selectlist_start_at_last_index_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = selectlist(&["a", "b", "c"], 2);
}
#[test]
fn loop_statics_are_atomic_i32() {
let _: &AtomicI32 = &LOOP_DEPTH;
let _: &AtomicI32 = &CONT_FLAG;
let _: &AtomicI32 = &BREAK_LEVEL;
}
#[test]
fn loop_depth_store_load_round_trip() {
use std::sync::atomic::Ordering;
let _g = crate::test_util::global_state_lock();
let saved = LOOP_DEPTH.load(Ordering::SeqCst);
for v in [0, 1, 5, 100, -1, i32::MAX, i32::MIN] {
LOOP_DEPTH.store(v, Ordering::SeqCst);
assert_eq!(
LOOP_DEPTH.load(Ordering::SeqCst),
v,
"LOOP_DEPTH must round-trip {}",
v
);
}
LOOP_DEPTH.store(saved, Ordering::SeqCst);
}
#[test]
fn cont_flag_store_load_round_trip() {
use std::sync::atomic::Ordering;
let _g = crate::test_util::global_state_lock();
let saved = CONT_FLAG.load(Ordering::SeqCst);
for v in [0, 1, 5, 100, -1, i32::MAX, i32::MIN] {
CONT_FLAG.store(v, Ordering::SeqCst);
assert_eq!(CONT_FLAG.load(Ordering::SeqCst), v);
}
CONT_FLAG.store(saved, Ordering::SeqCst);
}
#[test]
fn break_level_store_load_round_trip() {
use std::sync::atomic::Ordering;
let _g = crate::test_util::global_state_lock();
let saved = BREAK_LEVEL.load(Ordering::SeqCst);
for v in [0, 1, 5, 100, -1, i32::MAX, i32::MIN] {
BREAK_LEVEL.store(v, Ordering::SeqCst);
assert_eq!(BREAK_LEVEL.load(Ordering::SeqCst), v);
}
BREAK_LEVEL.store(saved, Ordering::SeqCst);
}
#[test]
fn selectlist_deterministic_on_identical_input() {
let _g = crate::test_util::global_state_lock();
let first = selectlist(&["one", "two", "three"], 0);
for _ in 0..5 {
assert_eq!(
selectlist(&["one", "two", "three"], 0),
first,
"selectlist must be deterministic"
);
}
}
}