tightened up some logic with indenting and joining lines
added more linebuf tests extracted all verb match arms into private methods on LineBuf
This commit is contained in:
375
src/testutil.rs
375
src/testutil.rs
@@ -1,219 +1,240 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
env,
|
||||
os::fd::{AsRawFd, BorrowedFd, OwnedFd},
|
||||
path::PathBuf,
|
||||
sync::{self, Arc, MutexGuard},
|
||||
collections::{HashMap, HashSet},
|
||||
env,
|
||||
os::fd::{AsRawFd, BorrowedFd, OwnedFd},
|
||||
path::PathBuf,
|
||||
sync::{self, Arc, MutexGuard},
|
||||
};
|
||||
|
||||
use nix::{
|
||||
fcntl::{FcntlArg, OFlag, fcntl},
|
||||
pty::openpty,
|
||||
sys::termios::{OutputFlags, SetArg, tcgetattr, tcsetattr},
|
||||
unistd::read,
|
||||
fcntl::{FcntlArg, OFlag, fcntl},
|
||||
pty::openpty,
|
||||
sys::termios::{OutputFlags, SetArg, tcgetattr, tcsetattr},
|
||||
unistd::read,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
expand::expand_aliases, libsh::error::ShResult, parse::{ParsedSrc, Redir, RedirType, execute::exec_input, lex::LexFlags}, procio::{IoFrame, IoMode, RedirGuard}, readline::register::{restore_registers, save_registers}, state::{MetaTab, SHED, read_logic}
|
||||
expand::expand_aliases,
|
||||
libsh::error::ShResult,
|
||||
parse::{ParsedSrc, Redir, RedirType, execute::exec_input, lex::LexFlags},
|
||||
procio::{IoFrame, IoMode, RedirGuard},
|
||||
readline::register::{restore_registers, save_registers},
|
||||
state::{MetaTab, SHED, read_logic},
|
||||
};
|
||||
|
||||
static TEST_MUTEX: sync::Mutex<()> = sync::Mutex::new(());
|
||||
|
||||
pub fn has_cmds(cmds: &[&str]) -> bool {
|
||||
let path_cmds = MetaTab::get_cmds_in_path();
|
||||
path_cmds.iter().all(|c| cmds.iter().any(|&cmd| c == cmd))
|
||||
let path_cmds = MetaTab::get_cmds_in_path();
|
||||
path_cmds.iter().all(|c| cmds.iter().any(|&cmd| c == cmd))
|
||||
}
|
||||
|
||||
pub fn has_cmd(cmd: &str) -> bool {
|
||||
MetaTab::get_cmds_in_path().into_iter().any(|c| c == cmd)
|
||||
MetaTab::get_cmds_in_path().into_iter().any(|c| c == cmd)
|
||||
}
|
||||
|
||||
pub fn test_input(input: impl Into<String>) -> ShResult<()> {
|
||||
exec_input(input.into(), None, false, None)
|
||||
exec_input(input.into(), None, false, None)
|
||||
}
|
||||
|
||||
pub struct TestGuard {
|
||||
_lock: MutexGuard<'static, ()>,
|
||||
_redir_guard: RedirGuard,
|
||||
old_cwd: PathBuf,
|
||||
saved_env: HashMap<String, String>,
|
||||
pty_master: OwnedFd,
|
||||
pty_slave: OwnedFd,
|
||||
_lock: MutexGuard<'static, ()>,
|
||||
_redir_guard: RedirGuard,
|
||||
old_cwd: PathBuf,
|
||||
saved_env: HashMap<String, String>,
|
||||
pty_master: OwnedFd,
|
||||
pty_slave: OwnedFd,
|
||||
|
||||
cleanups: Vec<Box<dyn FnOnce()>>
|
||||
cleanups: Vec<Box<dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
impl TestGuard {
|
||||
pub fn new() -> Self {
|
||||
let _lock = TEST_MUTEX.lock().unwrap();
|
||||
pub fn new() -> Self {
|
||||
let _lock = TEST_MUTEX.lock().unwrap();
|
||||
|
||||
let pty = openpty(None, None).unwrap();
|
||||
let (pty_master,pty_slave) = (pty.master, pty.slave);
|
||||
let mut attrs = tcgetattr(&pty_slave).unwrap();
|
||||
attrs.output_flags &= !OutputFlags::ONLCR;
|
||||
tcsetattr(&pty_slave, SetArg::TCSANOW, &attrs).unwrap();
|
||||
let pty = openpty(None, None).unwrap();
|
||||
let (pty_master, pty_slave) = (pty.master, pty.slave);
|
||||
let mut attrs = tcgetattr(&pty_slave).unwrap();
|
||||
attrs.output_flags &= !OutputFlags::ONLCR;
|
||||
tcsetattr(&pty_slave, SetArg::TCSANOW, &attrs).unwrap();
|
||||
|
||||
let mut frame = IoFrame::new();
|
||||
frame.push(
|
||||
Redir::new(
|
||||
IoMode::Fd {
|
||||
tgt_fd: 0,
|
||||
src_fd: pty_slave.as_raw_fd(),
|
||||
},
|
||||
RedirType::Input,
|
||||
),
|
||||
);
|
||||
frame.push(
|
||||
Redir::new(
|
||||
IoMode::Fd {
|
||||
tgt_fd: 1,
|
||||
src_fd: pty_slave.as_raw_fd(),
|
||||
},
|
||||
RedirType::Output,
|
||||
),
|
||||
);
|
||||
frame.push(
|
||||
Redir::new(
|
||||
IoMode::Fd {
|
||||
tgt_fd: 2,
|
||||
src_fd: pty_slave.as_raw_fd(),
|
||||
},
|
||||
RedirType::Output,
|
||||
),
|
||||
);
|
||||
let mut frame = IoFrame::new();
|
||||
frame.push(Redir::new(
|
||||
IoMode::Fd {
|
||||
tgt_fd: 0,
|
||||
src_fd: pty_slave.as_raw_fd(),
|
||||
},
|
||||
RedirType::Input,
|
||||
));
|
||||
frame.push(Redir::new(
|
||||
IoMode::Fd {
|
||||
tgt_fd: 1,
|
||||
src_fd: pty_slave.as_raw_fd(),
|
||||
},
|
||||
RedirType::Output,
|
||||
));
|
||||
frame.push(Redir::new(
|
||||
IoMode::Fd {
|
||||
tgt_fd: 2,
|
||||
src_fd: pty_slave.as_raw_fd(),
|
||||
},
|
||||
RedirType::Output,
|
||||
));
|
||||
|
||||
let _redir_guard = frame.redirect().unwrap();
|
||||
let _redir_guard = frame.redirect().unwrap();
|
||||
|
||||
let old_cwd = env::current_dir().unwrap();
|
||||
let saved_env = env::vars().collect();
|
||||
SHED.with(|s| s.save());
|
||||
save_registers();
|
||||
Self {
|
||||
_lock,
|
||||
_redir_guard,
|
||||
old_cwd,
|
||||
saved_env,
|
||||
pty_master,
|
||||
pty_slave,
|
||||
cleanups: vec![],
|
||||
}
|
||||
}
|
||||
let old_cwd = env::current_dir().unwrap();
|
||||
let saved_env = env::vars().collect();
|
||||
SHED.with(|s| s.save());
|
||||
save_registers();
|
||||
Self {
|
||||
_lock,
|
||||
_redir_guard,
|
||||
old_cwd,
|
||||
saved_env,
|
||||
pty_master,
|
||||
pty_slave,
|
||||
cleanups: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pty_slave(&self) -> BorrowedFd {
|
||||
unsafe { BorrowedFd::borrow_raw(self.pty_slave.as_raw_fd()) }
|
||||
}
|
||||
pub fn pty_slave(&self) -> BorrowedFd {
|
||||
unsafe { BorrowedFd::borrow_raw(self.pty_slave.as_raw_fd()) }
|
||||
}
|
||||
|
||||
pub fn add_cleanup(&mut self, f: impl FnOnce() + 'static) {
|
||||
self.cleanups.push(Box::new(f));
|
||||
}
|
||||
pub fn add_cleanup(&mut self, f: impl FnOnce() + 'static) {
|
||||
self.cleanups.push(Box::new(f));
|
||||
}
|
||||
|
||||
pub fn read_output(&self) -> String {
|
||||
let flags = fcntl(self.pty_master.as_raw_fd(), FcntlArg::F_GETFL).unwrap();
|
||||
let flags = OFlag::from_bits_truncate(flags);
|
||||
fcntl(
|
||||
self.pty_master.as_raw_fd(),
|
||||
FcntlArg::F_SETFL(flags | OFlag::O_NONBLOCK),
|
||||
).unwrap();
|
||||
pub fn read_output(&self) -> String {
|
||||
let flags = fcntl(self.pty_master.as_raw_fd(), FcntlArg::F_GETFL).unwrap();
|
||||
let flags = OFlag::from_bits_truncate(flags);
|
||||
fcntl(
|
||||
self.pty_master.as_raw_fd(),
|
||||
FcntlArg::F_SETFL(flags | OFlag::O_NONBLOCK),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut out = vec![];
|
||||
let mut buf = [0;4096];
|
||||
loop {
|
||||
match read(self.pty_master.as_raw_fd(), &mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => out.extend_from_slice(&buf[..n]),
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
let mut out = vec![];
|
||||
let mut buf = [0; 4096];
|
||||
loop {
|
||||
match read(self.pty_master.as_raw_fd(), &mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => out.extend_from_slice(&buf[..n]),
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
fcntl(
|
||||
self.pty_master.as_raw_fd(),
|
||||
FcntlArg::F_SETFL(flags),
|
||||
).unwrap();
|
||||
fcntl(self.pty_master.as_raw_fd(), FcntlArg::F_SETFL(flags)).unwrap();
|
||||
|
||||
String::from_utf8_lossy(&out).to_string()
|
||||
}
|
||||
String::from_utf8_lossy(&out).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TestGuard {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestGuard {
|
||||
fn drop(&mut self) {
|
||||
env::set_current_dir(&self.old_cwd).ok();
|
||||
for (k, _) in env::vars() {
|
||||
unsafe { env::remove_var(&k); }
|
||||
}
|
||||
for (k, v) in &self.saved_env {
|
||||
unsafe { env::set_var(k, v); }
|
||||
}
|
||||
for cleanup in self.cleanups.drain(..).rev() {
|
||||
cleanup();
|
||||
}
|
||||
SHED.with(|s| s.restore());
|
||||
restore_registers();
|
||||
}
|
||||
fn drop(&mut self) {
|
||||
env::set_current_dir(&self.old_cwd).ok();
|
||||
for (k, _) in env::vars() {
|
||||
unsafe {
|
||||
env::remove_var(&k);
|
||||
}
|
||||
}
|
||||
for (k, v) in &self.saved_env {
|
||||
unsafe {
|
||||
env::set_var(k, v);
|
||||
}
|
||||
}
|
||||
for cleanup in self.cleanups.drain(..).rev() {
|
||||
cleanup();
|
||||
}
|
||||
SHED.with(|s| s.restore());
|
||||
restore_registers();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ast(input: &str) -> ShResult<Vec<crate::parse::Node>> {
|
||||
let log_tab = read_logic(|l| l.clone());
|
||||
let input = expand_aliases(input.into(), HashSet::new(), &log_tab);
|
||||
let log_tab = read_logic(|l| l.clone());
|
||||
let input = expand_aliases(input.into(), HashSet::new(), &log_tab);
|
||||
|
||||
let source_name = "test_input".to_string();
|
||||
let mut parser = ParsedSrc::new(Arc::new(input))
|
||||
.with_lex_flags(LexFlags::empty())
|
||||
.with_name(source_name.clone());
|
||||
let source_name = "test_input".to_string();
|
||||
let mut parser = ParsedSrc::new(Arc::new(input))
|
||||
.with_lex_flags(LexFlags::empty())
|
||||
.with_name(source_name.clone());
|
||||
|
||||
parser.parse_src().map_err(|e| e.into_iter().next().unwrap())?;
|
||||
parser
|
||||
.parse_src()
|
||||
.map_err(|e| e.into_iter().next().unwrap())?;
|
||||
|
||||
Ok(parser.extract_nodes())
|
||||
Ok(parser.extract_nodes())
|
||||
}
|
||||
|
||||
impl crate::parse::Node {
|
||||
pub fn assert_structure(&mut self, expected: &mut impl Iterator<Item = NdKind>) -> Result<(), String> {
|
||||
let mut full_structure = vec![];
|
||||
let mut before = vec![];
|
||||
let mut after = vec![];
|
||||
let mut offender = None;
|
||||
pub fn assert_structure(
|
||||
&mut self,
|
||||
expected: &mut impl Iterator<Item = NdKind>,
|
||||
) -> Result<(), String> {
|
||||
let mut full_structure = vec![];
|
||||
let mut before = vec![];
|
||||
let mut after = vec![];
|
||||
let mut offender = None;
|
||||
|
||||
self.walk_tree(&mut |s| {
|
||||
let expected_rule = expected.next();
|
||||
full_structure.push(s.class.as_nd_kind());
|
||||
self.walk_tree(&mut |s| {
|
||||
let expected_rule = expected.next();
|
||||
full_structure.push(s.class.as_nd_kind());
|
||||
|
||||
if offender.is_none() && expected_rule.as_ref().map_or(true, |e| *e != s.class.as_nd_kind()) {
|
||||
offender = Some((s.class.as_nd_kind(), expected_rule));
|
||||
} else if offender.is_none() {
|
||||
before.push(s.class.as_nd_kind());
|
||||
} else {
|
||||
after.push(s.class.as_nd_kind());
|
||||
}
|
||||
});
|
||||
if offender.is_none()
|
||||
&& expected_rule
|
||||
.as_ref()
|
||||
.map_or(true, |e| *e != s.class.as_nd_kind())
|
||||
{
|
||||
offender = Some((s.class.as_nd_kind(), expected_rule));
|
||||
} else if offender.is_none() {
|
||||
before.push(s.class.as_nd_kind());
|
||||
} else {
|
||||
after.push(s.class.as_nd_kind());
|
||||
}
|
||||
});
|
||||
|
||||
assert!(expected.next().is_none(), "Expected structure has more nodes than actual structure");
|
||||
assert!(
|
||||
expected.next().is_none(),
|
||||
"Expected structure has more nodes than actual structure"
|
||||
);
|
||||
|
||||
if let Some((nd_kind, expected_rule)) = offender {
|
||||
let expected_rule = expected_rule.map_or("(none — expected array too short)".into(), |e| format!("{e:?}"));
|
||||
let full_structure_hint = full_structure.into_iter()
|
||||
.map(|s| format!("\tNdKind::{s:?},"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
let full_structure_hint = format!("let expected = &mut [\n{full_structure_hint}\n].into_iter();");
|
||||
if let Some((nd_kind, expected_rule)) = offender {
|
||||
let expected_rule = expected_rule.map_or("(none — expected array too short)".into(), |e| {
|
||||
format!("{e:?}")
|
||||
});
|
||||
let full_structure_hint = full_structure
|
||||
.into_iter()
|
||||
.map(|s| format!("\tNdKind::{s:?},"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
let full_structure_hint =
|
||||
format!("let expected = &mut [\n{full_structure_hint}\n].into_iter();");
|
||||
|
||||
let output = [
|
||||
"Structure assertion failed!\n".into(),
|
||||
format!("Expected node type '{:?}', found '{:?}'", expected_rule, nd_kind),
|
||||
format!("Before offender: {:?}", before),
|
||||
format!("After offender: {:?}\n", after),
|
||||
format!("hint: here is the full structure as an array\n {full_structure_hint}"),
|
||||
].join("\n");
|
||||
let output = [
|
||||
"Structure assertion failed!\n".into(),
|
||||
format!(
|
||||
"Expected node type '{:?}', found '{:?}'",
|
||||
expected_rule, nd_kind
|
||||
),
|
||||
format!("Before offender: {:?}", before),
|
||||
format!("After offender: {:?}\n", after),
|
||||
format!("hint: here is the full structure as an array\n {full_structure_hint}"),
|
||||
]
|
||||
.join("\n");
|
||||
|
||||
Err(output)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Err(output)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -227,26 +248,26 @@ pub enum NdKind {
|
||||
Conjunction,
|
||||
Assignment,
|
||||
BraceGrp,
|
||||
Negate,
|
||||
Negate,
|
||||
Test,
|
||||
FuncDef,
|
||||
}
|
||||
|
||||
impl crate::parse::NdRule {
|
||||
pub fn as_nd_kind(&self) -> NdKind {
|
||||
match self {
|
||||
Self::Negate { .. } => NdKind::Negate,
|
||||
Self::IfNode { .. } => NdKind::IfNode,
|
||||
Self::LoopNode { .. } => NdKind::LoopNode,
|
||||
Self::ForNode { .. } => NdKind::ForNode,
|
||||
Self::CaseNode { .. } => NdKind::CaseNode,
|
||||
Self::Command { .. } => NdKind::Command,
|
||||
Self::Pipeline { .. } => NdKind::Pipeline,
|
||||
Self::Conjunction { .. } => NdKind::Conjunction,
|
||||
Self::Assignment { .. } => NdKind::Assignment,
|
||||
Self::BraceGrp { .. } => NdKind::BraceGrp,
|
||||
Self::Test { .. } => NdKind::Test,
|
||||
Self::FuncDef { .. } => NdKind::FuncDef,
|
||||
}
|
||||
}
|
||||
pub fn as_nd_kind(&self) -> NdKind {
|
||||
match self {
|
||||
Self::Negate { .. } => NdKind::Negate,
|
||||
Self::IfNode { .. } => NdKind::IfNode,
|
||||
Self::LoopNode { .. } => NdKind::LoopNode,
|
||||
Self::ForNode { .. } => NdKind::ForNode,
|
||||
Self::CaseNode { .. } => NdKind::CaseNode,
|
||||
Self::Command { .. } => NdKind::Command,
|
||||
Self::Pipeline { .. } => NdKind::Pipeline,
|
||||
Self::Conjunction { .. } => NdKind::Conjunction,
|
||||
Self::Assignment { .. } => NdKind::Assignment,
|
||||
Self::BraceGrp { .. } => NdKind::BraceGrp,
|
||||
Self::Test { .. } => NdKind::Test,
|
||||
Self::FuncDef { .. } => NdKind::FuncDef,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user