2022-09-15 20:16:37 +00:00
|
|
|
use std::{
|
2022-10-21 17:29:42 +00:00
|
|
|
borrow::Cow,
|
2022-09-15 20:16:37 +00:00
|
|
|
env,
|
2022-10-24 19:27:04 +00:00
|
|
|
ffi::OsStr,
|
2022-09-15 20:16:37 +00:00
|
|
|
fs::File,
|
|
|
|
io::{BufRead, BufReader, Write},
|
|
|
|
os::unix::io::FromRawFd,
|
|
|
|
process,
|
|
|
|
};
|
|
|
|
|
|
|
|
use insta::assert_debug_snapshot as assert_snapshot;
|
|
|
|
use nix::{
|
|
|
|
ioctl_write_int_bad, libc,
|
|
|
|
pty::{self, OpenptyResult},
|
|
|
|
sys,
|
|
|
|
unistd::{self, ForkResult},
|
|
|
|
};
|
2022-10-21 17:29:42 +00:00
|
|
|
use oyster_builtin_proc::builtin;
|
|
|
|
use oyster_runtime::Shell;
|
2022-09-15 20:16:37 +00:00
|
|
|
|
|
|
|
// TODO: test signal return codes
|
|
|
|
|
|
|
|
ioctl_write_int_bad!(tiocsctty, libc::TIOCSCTTY);
|
|
|
|
|
2022-10-21 17:29:42 +00:00
|
|
|
#[builtin(description = "test builtin")]
|
2022-10-24 19:27:04 +00:00
|
|
|
fn test_builtin(_: &mut Shell, _: &[Cow<OsStr>]) {
|
2022-10-21 17:29:42 +00:00
|
|
|
// XXX: this is a workaround around libtest's use of io::set_output_capture
|
|
|
|
let _ = write!(std::io::stdout(), "this is a test\n");
|
|
|
|
}
|
|
|
|
|
2022-09-15 20:16:37 +00:00
|
|
|
/// Forks to redirect stdin, stderr, stdout, then run the commands.
|
|
|
|
/// Relies on inserting a NUL byte in the end, so there shouldn't be NUL in the output.
|
|
|
|
fn collect_output<F>(mut f: F) -> String
|
|
|
|
where
|
|
|
|
F: FnMut(),
|
|
|
|
{
|
|
|
|
let OpenptyResult { master, slave } = pty::openpty(None, None).unwrap();
|
|
|
|
|
|
|
|
match unsafe { unistd::fork() }.unwrap() {
|
|
|
|
ForkResult::Parent { child } => {
|
|
|
|
sys::wait::waitpid(child, None).unwrap();
|
|
|
|
}
|
|
|
|
ForkResult::Child => {
|
|
|
|
unistd::setsid().unwrap();
|
|
|
|
unsafe { tiocsctty(slave, 0) }.unwrap();
|
|
|
|
|
|
|
|
unistd::dup2(slave, libc::STDIN_FILENO).unwrap();
|
|
|
|
unistd::dup2(slave, libc::STDOUT_FILENO).unwrap();
|
|
|
|
unistd::dup2(slave, libc::STDERR_FILENO).unwrap();
|
|
|
|
|
|
|
|
let _ = unistd::close(master);
|
|
|
|
let _ = unistd::close(slave);
|
|
|
|
|
|
|
|
f();
|
|
|
|
|
|
|
|
process::exit(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let master = unsafe { File::from_raw_fd(master) };
|
|
|
|
let mut slave = unsafe { File::from_raw_fd(slave) };
|
|
|
|
|
|
|
|
slave.write(&[0]).unwrap();
|
|
|
|
let mut r = BufReader::new(master);
|
|
|
|
let mut buf = vec![];
|
|
|
|
r.read_until(0, &mut buf).unwrap();
|
|
|
|
|
|
|
|
std::str::from_utf8(&buf[..buf.len() - 1])
|
|
|
|
.unwrap()
|
|
|
|
.to_owned()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn simple_command() {
|
|
|
|
let ast = {
|
|
|
|
use oyster_parser::ast::*;
|
|
|
|
|
|
|
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
|
|
|
vec![
|
|
|
|
Word(vec![WordPart::Text("echo")]),
|
|
|
|
Word(vec![WordPart::Text("hi")]),
|
|
|
|
],
|
|
|
|
Redirect::None,
|
|
|
|
)]))])
|
|
|
|
};
|
|
|
|
|
|
|
|
let actual = collect_output(|| {
|
2022-09-23 21:07:57 +00:00
|
|
|
let mut shell = Shell::new().unwrap();
|
2022-09-15 20:16:37 +00:00
|
|
|
shell.run(&ast).unwrap();
|
|
|
|
});
|
|
|
|
|
|
|
|
assert_snapshot!(actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn pipeline() {
|
|
|
|
let ast = {
|
|
|
|
use oyster_parser::ast::*;
|
|
|
|
|
|
|
|
Code(vec![Statement::Pipeline(Pipeline(vec![
|
|
|
|
Command(
|
|
|
|
vec![
|
|
|
|
Word(vec![WordPart::Text("echo")]),
|
|
|
|
Word(vec![WordPart::Text("hi")]),
|
|
|
|
],
|
|
|
|
Redirect::Stdout,
|
|
|
|
),
|
|
|
|
Command(
|
|
|
|
vec![
|
|
|
|
Word(vec![WordPart::Text("wc")]),
|
|
|
|
Word(vec![WordPart::Text("-c")]),
|
|
|
|
],
|
|
|
|
Redirect::None,
|
|
|
|
),
|
|
|
|
]))])
|
|
|
|
};
|
|
|
|
|
|
|
|
let actual = collect_output(|| {
|
2022-09-23 21:07:57 +00:00
|
|
|
let mut shell = Shell::new().unwrap();
|
2022-09-15 20:16:37 +00:00
|
|
|
shell.run(&ast).unwrap();
|
|
|
|
});
|
|
|
|
|
|
|
|
assert_snapshot!(actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn command_not_found() {
|
|
|
|
let ast = {
|
|
|
|
use oyster_parser::ast::*;
|
|
|
|
|
|
|
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
|
|
|
vec![Word(vec![WordPart::Text("this_command_doesnt_exist")])],
|
|
|
|
Redirect::None,
|
|
|
|
)]))])
|
|
|
|
};
|
|
|
|
|
|
|
|
// XXX: this relies on the command actually not existing, as unsetting PATH is rather complex
|
2022-09-23 21:07:57 +00:00
|
|
|
let mut shell = Shell::new().unwrap();
|
2022-09-15 20:16:37 +00:00
|
|
|
let actual = shell.run(&ast);
|
|
|
|
|
|
|
|
assert_snapshot!(actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn permission_denied() {
|
|
|
|
let ast = {
|
|
|
|
use oyster_parser::ast::*;
|
|
|
|
|
|
|
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
|
|
|
vec![Word(vec![WordPart::Text("/")])],
|
|
|
|
Redirect::None,
|
|
|
|
)]))])
|
|
|
|
};
|
|
|
|
|
2022-09-23 21:07:57 +00:00
|
|
|
let mut shell = Shell::new().unwrap();
|
2022-09-15 20:16:37 +00:00
|
|
|
let actual = shell.run(&ast);
|
|
|
|
|
|
|
|
assert_snapshot!(actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn multipart_word() {
|
|
|
|
let ast = {
|
|
|
|
use oyster_parser::ast::*;
|
|
|
|
|
|
|
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
|
|
|
vec![
|
|
|
|
Word(vec![WordPart::Text("ec"), WordPart::Text("ho")]),
|
|
|
|
Word(vec![WordPart::Text("hel"), WordPart::Text("lo")]),
|
|
|
|
],
|
|
|
|
Redirect::None,
|
|
|
|
)]))])
|
|
|
|
};
|
|
|
|
|
|
|
|
let actual = collect_output(|| {
|
2022-09-23 21:07:57 +00:00
|
|
|
let mut shell = Shell::new().unwrap();
|
2022-09-15 20:16:37 +00:00
|
|
|
shell.run(&ast).unwrap();
|
|
|
|
});
|
|
|
|
|
|
|
|
assert_snapshot!(actual);
|
|
|
|
}
|
2022-10-20 13:06:53 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn simple_builtin() {
|
|
|
|
let ast = {
|
|
|
|
use oyster_parser::ast::*;
|
|
|
|
|
|
|
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
|
|
|
vec![Word(vec![WordPart::Text("test_builtin")])],
|
|
|
|
Redirect::None,
|
|
|
|
)]))])
|
|
|
|
};
|
|
|
|
|
|
|
|
let actual = collect_output(|| {
|
|
|
|
let mut shell = Shell::new().unwrap();
|
2022-10-21 17:29:42 +00:00
|
|
|
shell.builtins_mut().add(test_builtin);
|
2022-10-20 13:06:53 +00:00
|
|
|
shell.run(&ast).unwrap();
|
|
|
|
});
|
|
|
|
|
|
|
|
assert_snapshot!(actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn builtin_redirection() {
|
|
|
|
let ast = {
|
|
|
|
use oyster_parser::ast::*;
|
|
|
|
|
|
|
|
Code(vec![Statement::Pipeline(Pipeline(vec![
|
|
|
|
Command(
|
|
|
|
vec![Word(vec![WordPart::Text("test_builtin")])],
|
|
|
|
Redirect::Stdout,
|
|
|
|
),
|
|
|
|
Command(
|
|
|
|
vec![
|
|
|
|
Word(vec![WordPart::Text("wc")]),
|
|
|
|
Word(vec![WordPart::Text("-c")]),
|
|
|
|
],
|
|
|
|
Redirect::None,
|
|
|
|
),
|
|
|
|
]))])
|
|
|
|
};
|
|
|
|
|
|
|
|
let actual = collect_output(|| {
|
|
|
|
let mut shell = Shell::new().unwrap();
|
2022-10-21 17:29:42 +00:00
|
|
|
shell.builtins_mut().add(test_builtin);
|
2022-10-20 13:06:53 +00:00
|
|
|
shell.run(&ast).unwrap();
|
|
|
|
});
|
|
|
|
|
|
|
|
assert_snapshot!(actual);
|
|
|
|
}
|
2022-10-24 19:27:04 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn command_substitution() {
|
|
|
|
let ast = {
|
|
|
|
use oyster_parser::ast::*;
|
|
|
|
|
|
|
|
Code(vec![Statement::Pipeline(Pipeline(vec![Command(
|
|
|
|
vec![
|
|
|
|
Word(vec![WordPart::Text("echo")]),
|
|
|
|
Word(vec![WordPart::CommandSubstitution(Code(vec![
|
|
|
|
Statement::Pipeline(Pipeline(vec![Command(
|
|
|
|
vec![
|
|
|
|
Word(vec![WordPart::Text("echo")]),
|
|
|
|
Word(vec![WordPart::Text("hello")]),
|
|
|
|
],
|
|
|
|
Redirect::None,
|
|
|
|
)])),
|
|
|
|
]))]),
|
|
|
|
],
|
|
|
|
Redirect::None,
|
|
|
|
)]))])
|
|
|
|
};
|
|
|
|
|
|
|
|
let actual = collect_output(|| {
|
|
|
|
let mut shell = Shell::new().unwrap();
|
|
|
|
shell.builtins_mut().add(test_builtin);
|
|
|
|
shell.run(&ast).unwrap();
|
|
|
|
});
|
|
|
|
|
|
|
|
assert_snapshot!(actual);
|
|
|
|
}
|