From ac8a22ad99784a493354b2d2c793b140d6c895f6 Mon Sep 17 00:00:00 2001 From: Charlotte Meyer Date: Wed, 2 Nov 2022 15:33:29 +0000 Subject: [PATCH] refactor(runtime tests): split off builtin tests Signed-off-by: Charlotte Meyer --- .../oyster_runtime/tests/it/builtins/mod.rs | 70 ++++++++++ crates/oyster_runtime/tests/it/main.rs | 58 ++++++++ crates/oyster_runtime/tests/it/pipeline.rs | 132 +----------------- 3 files changed, 135 insertions(+), 125 deletions(-) create mode 100644 crates/oyster_runtime/tests/it/builtins/mod.rs diff --git a/crates/oyster_runtime/tests/it/builtins/mod.rs b/crates/oyster_runtime/tests/it/builtins/mod.rs new file mode 100644 index 0000000..a68b1ff --- /dev/null +++ b/crates/oyster_runtime/tests/it/builtins/mod.rs @@ -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]) { + // 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" + "#]], + ); +} diff --git a/crates/oyster_runtime/tests/it/main.rs b/crates/oyster_runtime/tests/it/main.rs index eab2e1f..47e4850 100644 --- a/crates/oyster_runtime/tests/it/main.rs +++ b/crates/oyster_runtime/tests/it/main.rs @@ -1 +1,59 @@ +mod builtins; 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(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()) +} diff --git a/crates/oyster_runtime/tests/it/pipeline.rs b/crates/oyster_runtime/tests/it/pipeline.rs index aa3269f..7093b25 100644 --- a/crates/oyster_runtime/tests/it/pipeline.rs +++ b/crates/oyster_runtime/tests/it/pipeline.rs @@ -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 nix::{ - ioctl_write_int_bad, libc, - pty::{self, OpenptyResult}, - sys, - unistd::{self, ForkResult}, -}; -use oyster_builtin_proc::builtin; use oyster_parser::ast; use oyster_runtime::Shell; +use crate::collect_output; + // TODO: test signal return codes -ioctl_write_int_bad!(tiocsctty, libc::TIOCSCTTY); - -#[builtin(description = "test builtin")] -fn test_builtin(_: &mut Shell, _: &[Cow]) { - // 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(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) { +fn check(ast: &ast::Code, expect: Expect) { let actual = collect_output(|| { let mut shell = Shell::new().unwrap(); shell.run(ast).unwrap(); @@ -76,15 +14,6 @@ fn check_collect(ast: &ast::Code, expect: Expect) { 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] fn simple_command() { let ast = { @@ -99,7 +28,7 @@ fn simple_command() { )]))]) }; - check_collect( + check( &ast, expect![[r#" "hi\r\n" @@ -130,7 +59,7 @@ fn pipeline() { ]))]) }; - check_collect( + check( &ast, expect![[r#" "3\r\n" @@ -201,7 +130,7 @@ fn multipart_word() { )]))]) }; - check_collect( + check( &ast, expect![[r#" "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] fn command_substitution() { let ast = { @@ -278,7 +160,7 @@ fn command_substitution() { )]))]) }; - check_builtin( + check( &ast, expect![[r#" "hello\r\n"