refactor(runtime tests): split off builtin tests

Signed-off-by: Charlotte Meyer <dev@buffet.sh>
This commit is contained in:
buffet 2022-11-02 15:33:29 +00:00
parent e1fcd94ef1
commit ac8a22ad99
3 changed files with 135 additions and 125 deletions

View file

@ -0,0 +1,70 @@
use std::{borrow::Cow, ffi::OsStr, io::Write};
use expect_test::{expect, Expect};
use oyster_builtin_proc::builtin;
use oyster_parser::ast;
use oyster_runtime::Shell;
use crate::collect_output;
#[builtin(description = "test builtin")]
fn test_builtin(_: &mut Shell, _: &[Cow<OsStr>]) {
// XXX: this is a workaround around libtest's use of io::set_output_capture
let _ = write!(std::io::stdout(), "this is a test\n");
}
fn check(ast: &ast::Code, expect: Expect) {
let actual = collect_output(|| {
let mut shell = Shell::new().unwrap();
shell.builtins_mut().add(test_builtin);
shell.run(ast).unwrap();
});
expect.assert_debug_eq(&actual);
}
#[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,
)]))])
};
check(
&ast,
expect![[r#"
"this is a test\r\n"
"#]],
);
}
#[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,
),
]))])
};
check(
&ast,
expect![[r#"
"15\r\n"
"#]],
);
}

View file

@ -1 +1,59 @@
mod builtins;
mod pipeline; mod pipeline;
use std::{
ffi::OsString,
fs::File,
io::{BufRead, BufReader, Write},
os::unix::{ffi::OsStringExt, io::FromRawFd},
process,
};
use nix::{
ioctl_write_int_bad, libc,
pty::{self, OpenptyResult},
sys,
unistd::{self, ForkResult},
};
ioctl_write_int_bad!(tiocsctty, libc::TIOCSCTTY);
/// 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) -> OsString
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();
OsString::from_vec(buf[..buf.len() - 1].to_vec())
}

View file

@ -1,74 +1,12 @@
use std::{
borrow::Cow,
ffi::{OsStr, OsString},
fs::File,
io::{BufRead, BufReader, Write},
os::unix::{ffi::OsStringExt, io::FromRawFd},
process,
};
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};
use nix::{
ioctl_write_int_bad, libc,
pty::{self, OpenptyResult},
sys,
unistd::{self, ForkResult},
};
use oyster_builtin_proc::builtin;
use oyster_parser::ast; use oyster_parser::ast;
use oyster_runtime::Shell; use oyster_runtime::Shell;
use crate::collect_output;
// TODO: test signal return codes // TODO: test signal return codes
ioctl_write_int_bad!(tiocsctty, libc::TIOCSCTTY); fn check(ast: &ast::Code, expect: Expect) {
#[builtin(description = "test builtin")]
fn test_builtin(_: &mut Shell, _: &[Cow<OsStr>]) {
// XXX: this is a workaround around libtest's use of io::set_output_capture
let _ = write!(std::io::stdout(), "this is a test\n");
}
/// 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) -> OsString
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();
OsString::from_vec(buf[..buf.len() - 1].to_vec())
}
fn check_collect(ast: &ast::Code, expect: Expect) {
let actual = collect_output(|| { let actual = collect_output(|| {
let mut shell = Shell::new().unwrap(); let mut shell = Shell::new().unwrap();
shell.run(ast).unwrap(); shell.run(ast).unwrap();
@ -76,15 +14,6 @@ fn check_collect(ast: &ast::Code, expect: Expect) {
expect.assert_debug_eq(&actual); expect.assert_debug_eq(&actual);
} }
fn check_builtin(ast: &ast::Code, expect: Expect) {
let actual = collect_output(|| {
let mut shell = Shell::new().unwrap();
shell.builtins_mut().add(test_builtin);
shell.run(ast).unwrap();
});
expect.assert_debug_eq(&actual);
}
#[test] #[test]
fn simple_command() { fn simple_command() {
let ast = { let ast = {
@ -99,7 +28,7 @@ fn simple_command() {
)]))]) )]))])
}; };
check_collect( check(
&ast, &ast,
expect![[r#" expect![[r#"
"hi\r\n" "hi\r\n"
@ -130,7 +59,7 @@ fn pipeline() {
]))]) ]))])
}; };
check_collect( check(
&ast, &ast,
expect![[r#" expect![[r#"
"3\r\n" "3\r\n"
@ -201,7 +130,7 @@ fn multipart_word() {
)]))]) )]))])
}; };
check_collect( check(
&ast, &ast,
expect![[r#" expect![[r#"
"hello\r\n" "hello\r\n"
@ -209,53 +138,6 @@ fn multipart_word() {
); );
} }
#[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,
)]))])
};
check_builtin(
&ast,
expect![[r#"
"this is a test\r\n"
"#]],
);
}
#[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,
),
]))])
};
check_builtin(
&ast,
expect![[r#"
"15\r\n"
"#]],
);
}
#[test] #[test]
fn command_substitution() { fn command_substitution() {
let ast = { let ast = {
@ -278,7 +160,7 @@ fn command_substitution() {
)]))]) )]))])
}; };
check_builtin( check(
&ast, &ast,
expect![[r#" expect![[r#"
"hello\r\n" "hello\r\n"