Commit d195ccaf authored by Sag0Sag0's avatar Sag0Sag0
Browse files

Add tests for builtin is

parent 22244125
......@@ -23,7 +23,10 @@ impl AsciiReplaceInPlace for str {
// I tried replacing these `assert!` calls with `debug_assert!` but it looks
// like they get const-folded away anyway since it doesn't affect the speed
fn ascii_replace_in_place(&mut self, needle: char, haystack: char) {
assert!(needle.is_ascii(), "AsciiReplace functions can only be used for ascii characters");
assert!(
needle.is_ascii(),
"AsciiReplace functions can only be used for ascii characters"
);
assert!(
haystack.is_ascii(),
"AsciiReplace functions can only be used for ascii characters"
......
......@@ -27,9 +27,9 @@ fn evaluate_arguments(arguments: &[&str], shell: &Shell) -> Result<bool, String>
})
}
Some(&s) if s.starts_with("-") => {
// Access the second character in the flag string: this will be type of the flag.
// If no flag was given, return `SUCCESS`, as this means a string with value "-" was
// checked.
// Access the second character in the flag string: this will be type of the
// flag. If no flag was given, return `SUCCESS`, as this means a
// string with value "-" was checked.
s.chars().nth(1).map_or(Ok(true), |flag| {
// If no argument was given, return `SUCCESS`, as this means a string starting
// with a dash was given
......@@ -45,7 +45,8 @@ fn evaluate_arguments(arguments: &[&str], shell: &Shell) -> Result<bool, String>
}
}
/// Matches flag arguments to their respective functionaity when the `-` character is detected.
/// Matches flag arguments to their respective functionaity when the `-`
/// character is detected.
fn match_flag_argument(flag: char, argument: &str, shell: &Shell) -> bool {
match flag {
'a' => array_var_is_not_empty(argument, shell),
......@@ -67,20 +68,24 @@ fn match_option_argument(option: &str, argument: &str, shell: &Shell) -> bool {
/// Returns true if the file is a regular file
fn path_is_file(filepath: &str) -> bool {
fs::metadata(filepath).ok().map_or(false, |metadata| metadata.file_type().is_file())
fs::metadata(filepath)
.ok()
.map_or(false, |metadata| metadata.file_type().is_file())
}
/// Returns true if the file is a directory
fn path_is_directory(filepath: &str) -> bool {
fs::metadata(filepath).ok().map_or(false, |metadata| metadata.file_type().is_dir())
fs::metadata(filepath)
.ok()
.map_or(false, |metadata| metadata.file_type().is_dir())
}
/// Returns true if the binary is found in path (and is executable)
fn binary_is_in_path(binaryname: &str, shell: &Shell) -> bool {
// TODO: Maybe this function should reflect the logic for spawning new processes
// TODO: Right now they use an entirely different logic which means that it *might* be possible
// TODO: that `exists` reports a binary to be in the path, while the shell cannot find it or
// TODO: vice-versa
// TODO: Right now they use an entirely different logic which means that it
// *might* be possible TODO: that `exists` reports a binary to be in the
// path, while the shell cannot find it or TODO: vice-versa
if let Some(path) = shell.get_var("PATH") {
for dir in path.split(":") {
let fname = format!("{}/{}", dir, binaryname);
......@@ -113,7 +118,9 @@ fn file_has_execute_permission(filepath: &str) -> bool {
}
/// Returns true if the string is not empty
fn string_is_nonzero(string: &str) -> bool { !string.is_empty() }
fn string_is_nonzero(string: &str) -> bool {
!string.is_empty()
}
/// Returns true if the variable is an array and the array is not empty
fn array_var_is_not_empty(arrayvar: &str, shell: &Shell) -> bool {
......@@ -160,7 +167,9 @@ fn test_evaluate_arguments() {
// check `exists -a`
// no argument means we treat it as a string
assert_eq!(evaluate_arguments(&["-a"], &shell), Ok(true));
shell.variables.set_array("emptyarray", SmallVec::from_vec(Vec::new()));
shell
.variables
.set_array("emptyarray", SmallVec::from_vec(Vec::new()));
assert_eq!(evaluate_arguments(&["-a", "emptyarray"], &shell), Ok(false));
let mut vec = Vec::new();
vec.push("element".to_owned());
......@@ -176,27 +185,45 @@ fn test_evaluate_arguments() {
let oldpath = shell.get_var("PATH").unwrap_or("/usr/bin".to_owned());
shell.set_var("PATH", "testing/");
assert_eq!(evaluate_arguments(&["-b", "executable_file"], &shell), Ok(true));
assert_eq!(
evaluate_arguments(&["-b", "executable_file"], &shell),
Ok(true)
);
assert_eq!(evaluate_arguments(&["-b", "empty_file"], &shell), Ok(false));
assert_eq!(evaluate_arguments(&["-b", "file_does_not_exist"], &shell), Ok(false));
assert_eq!(
evaluate_arguments(&["-b", "file_does_not_exist"], &shell),
Ok(false)
);
// restore original PATH. Not necessary for the currently defined test cases but this might
// change in the future? Better safe than sorry!
// restore original PATH. Not necessary for the currently defined test cases
// but this might change in the future? Better safe than sorry!
shell.set_var("PATH", &oldpath);
// check `exists -d`
// no argument means we treat it as a string
assert_eq!(evaluate_arguments(&["-d"], &shell), Ok(true));
assert_eq!(evaluate_arguments(&["-d", "testing/"], &shell), Ok(true));
assert_eq!(evaluate_arguments(&["-d", "testing/empty_file"], &shell), Ok(false));
assert_eq!(evaluate_arguments(&["-d", "does/not/exist/"], &shell), Ok(false));
assert_eq!(
evaluate_arguments(&["-d", "testing/empty_file"], &shell),
Ok(false)
);
assert_eq!(
evaluate_arguments(&["-d", "does/not/exist/"], &shell),
Ok(false)
);
// check `exists -f`
// no argument means we treat it as a string
assert_eq!(evaluate_arguments(&["-f"], &shell), Ok(true));
assert_eq!(evaluate_arguments(&["-f", "testing/"], &shell), Ok(false));
assert_eq!(evaluate_arguments(&["-f", "testing/empty_file"], &shell), Ok(true));
assert_eq!(evaluate_arguments(&["-f", "does-not-exist"], &shell), Ok(false));
assert_eq!(
evaluate_arguments(&["-f", "testing/empty_file"], &shell),
Ok(true)
);
assert_eq!(
evaluate_arguments(&["-f", "does-not-exist"], &shell),
Ok(false)
);
// check `exists -s`
// no argument means we treat it as a string
......@@ -218,18 +245,25 @@ fn test_evaluate_arguments() {
let name_str = "test_function";
let name = SmallString::from_str(name_str);
let mut args = Vec::new();
args.push(KeyBuf { name: "testy".into(), kind: Primitive::Any });
args.push(KeyBuf {
name: "testy".into(),
kind: Primitive::Any,
});
let mut statements = Vec::new();
statements.push(Statement::End);
let description = "description".to_owned();
shell.functions.insert(name.clone(), Function::new(Some(description), name, args, statements));
shell.functions.insert(
name.clone(),
Function::new(Some(description), name, args, statements),
);
assert_eq!(evaluate_arguments(&["--fn", name_str], &shell), Ok(true));
shell.functions.remove(name_str);
assert_eq!(evaluate_arguments(&["--fn", name_str], &shell), Ok(false));
// check invalid flags / parameters (should all be treated as strings and therefore succeed)
// check invalid flags / parameters (should all be treated as strings and
// therefore succeed)
assert_eq!(evaluate_arguments(&["--foo"], &shell), Ok(true));
assert_eq!(evaluate_arguments(&["-x"], &shell), Ok(true));
}
......@@ -238,12 +272,28 @@ fn test_evaluate_arguments() {
fn test_match_flag_argument() {
let shell = Shell::new();
// we don't really care about the passed values, as long as both sited return the same value
assert_eq!(match_flag_argument('a', "ARRAY", &shell), array_var_is_not_empty("ARRAY", &shell));
assert_eq!(match_flag_argument('b', "binary", &shell), binary_is_in_path("binary", &shell));
assert_eq!(match_flag_argument('d', "path", &shell), path_is_directory("path"));
assert_eq!(match_flag_argument('f', "file", &shell), path_is_file("file"));
assert_eq!(match_flag_argument('s', "STR", &shell), string_var_is_not_empty("STR", &shell));
// we don't really care about the passed values, as long as both sited return
// the same value
assert_eq!(
match_flag_argument('a', "ARRAY", &shell),
array_var_is_not_empty("ARRAY", &shell)
);
assert_eq!(
match_flag_argument('b', "binary", &shell),
binary_is_in_path("binary", &shell)
);
assert_eq!(
match_flag_argument('d', "path", &shell),
path_is_directory("path")
);
assert_eq!(
match_flag_argument('f', "file", &shell),
path_is_file("file")
);
assert_eq!(
match_flag_argument('s', "STR", &shell),
string_var_is_not_empty("STR", &shell)
);
// Any flag which is not implemented
assert_eq!(match_flag_argument('x', "ARG", &shell), false);
......@@ -253,8 +303,12 @@ fn test_match_flag_argument() {
fn test_match_option_argument() {
let shell = Shell::new();
// we don't really care about the passed values, as long as both sited return the same value
assert_eq!(match_option_argument("fn", "FUN", &shell), array_var_is_not_empty("FUN", &shell));
// we don't really care about the passed values, as long as both sited return
// the same value
assert_eq!(
match_option_argument("fn", "FUN", &shell),
array_var_is_not_empty("FUN", &shell)
);
// Any option which is not implemented
assert_eq!(match_option_argument("foo", "ARG", &shell), false);
......@@ -280,8 +334,8 @@ fn test_binary_is_in_path() {
// TODO: multiple/:directories/
// TODO: PATH containing directories which do not exist
// TODO: PATH containing directories without read permission (for user)
// TODO: PATH containing directories without execute ("enter") permission (for user)
// TODO: empty PATH?
// TODO: PATH containing directories without execute ("enter") permission (for
// user) TODO: empty PATH?
shell.set_var("PATH", "testing/");
assert_eq!(binary_is_in_path("executable_file", &shell), true);
......@@ -307,19 +361,24 @@ fn test_string_is_nonzero() {
fn test_array_var_is_not_empty() {
let mut shell = Shell::new();
shell.variables.set_array("EMPTY_ARRAY", SmallVec::from_vec(Vec::new()));
shell
.variables
.set_array("EMPTY_ARRAY", SmallVec::from_vec(Vec::new()));
assert_eq!(array_var_is_not_empty("EMPTY_ARRAY", &shell), false);
let mut not_empty_vec = Vec::new();
not_empty_vec.push("array not empty".to_owned());
shell.variables.set_array("NOT_EMPTY_ARRAY", SmallVec::from_vec(not_empty_vec));
shell
.variables
.set_array("NOT_EMPTY_ARRAY", SmallVec::from_vec(not_empty_vec));
assert_eq!(array_var_is_not_empty("NOT_EMPTY_ARRAY", &shell), true);
// test for array which does not even exist
shell.variables.unset_array("NOT_EMPTY_ARRAY");
assert_eq!(array_var_is_not_empty("NOT_EMPTY_ARRAY", &shell), false);
// array_var_is_not_empty should NOT match for non-array variables with the same name
// array_var_is_not_empty should NOT match for non-array variables with the
// same name
shell.set_var("VARIABLE", "notempty-variable");
assert_eq!(array_var_is_not_empty("VARIABLE", &shell), false);
}
......@@ -337,7 +396,9 @@ fn test_string_var_is_not_empty() {
// string_var_is_not_empty should NOT match for arrays with the same name
let mut vec = Vec::new();
vec.push("not-empty".to_owned());
shell.variables.set_array("ARRAY_NOT_EMPTY", SmallVec::from_vec(vec));
shell
.variables
.set_array("ARRAY_NOT_EMPTY", SmallVec::from_vec(vec));
assert_eq!(string_var_is_not_empty("ARRAY_NOT_EMPTY", &shell), false);
// test for a variable which does not even exist
......@@ -354,12 +415,18 @@ fn test_function_is_defined() {
let name_str = "test_function";
let name = SmallString::from_str(name_str);
let mut args = Vec::new();
args.push(KeyBuf { name: "testy".into(), kind: Primitive::Any });
args.push(KeyBuf {
name: "testy".into(),
kind: Primitive::Any,
});
let mut statements = Vec::new();
statements.push(Statement::End);
let description = "description".to_owned();
shell.functions.insert(name.clone(), Function::new(Some(description), name, args, statements));
shell.functions.insert(
name.clone(),
Function::new(Some(description), name, args, statements),
);
assert_eq!(function_is_defined(name_str, &shell), true);
shell.functions.remove(name_str);
......
......@@ -21,10 +21,9 @@ pub(crate) fn is(args: &[&str], shell: &mut Shell) -> Result<(), String> {
}
fn eval_arg(arg: &str, shell: &mut Shell) -> String {
let var_value = get_var_string(arg, shell);
if var_value != "" {
return var_value;
let value = get_var_string(arg, shell);
if value != "" {
return value;
}
arg.to_string()
}
......@@ -43,3 +42,45 @@ fn get_var_string(name: &str, shell: &mut Shell) -> String {
sh_var.to_string()
}
#[test]
fn test_is() {
let mut shell = Shell::new();
shell.set_var("x", "value");
shell.set_var("y", "0");
// Four arguments
assert_eq!(
is(&["is", " ", " ", " "], &mut shell),
Err("Expected 'not' instead found ' '\n".to_string())
);
assert_eq!(
is(&["is", "not", " ", " "], &mut shell),
Err("".to_string())
);
assert_eq!(
is(&["is", "not", "$x", "$x"], &mut shell),
Err("".to_string())
);
assert_eq!(is(&["is", "not", "2", "1"], &mut shell), Ok(()));
assert_eq!(is(&["is", "not", "$x", "$y"], &mut shell), Ok(()));
// Three arguments
assert_eq!(is(&["is", "1", "2"], &mut shell), Err("".to_string()));
assert_eq!(is(&["is", "$x", "$y"], &mut shell), Err("".to_string()));
assert_eq!(is(&["is", " ", " "], &mut shell), Ok(()));
assert_eq!(is(&["is", "$x", "$x"], &mut shell), Ok(()));
// Two arguments
assert_eq!(
is(&["is", " "], &mut shell),
Err("is needs 3 or 4 arguments\n".to_string())
);
assert_eq!(is(&["is", "-h"], &mut shell), Ok(()));
// One argument
assert_eq!(
is(&["is"], &mut shell),
Err("is needs 3 or 4 arguments\n".to_string())
);
}
//! Contains the `jobs`, `disown`, `bg`, and `fg` commands that manage job control in the shell.
//! Contains the `jobs`, `disown`, `bg`, and `fg` commands that manage job
//! control in the shell.
use shell::Shell;
use shell::job_control::{JobControl, ProcessState};
......@@ -10,7 +11,6 @@ use std::io::{stderr, Write};
/// The `-a` flag selects all jobs, `-r` selects all running jobs, and `-h` specifies to mark
/// SIGHUP ignoral.
pub(crate) fn disown(shell: &mut Shell, args: &[&str]) -> Result<(), String> {
const NO_SIGHUP: u8 = 1;
const ALL_JOBS: u8 = 2;
const RUN_JOBS: u8 = 4;
......@@ -84,8 +84,14 @@ pub(crate) fn jobs(shell: &mut Shell) {
let mut stderr = stderr.lock();
for (id, process) in shell.background.lock().unwrap().iter().enumerate() {
if process.state != ProcessState::Empty {
let _ =
writeln!(stderr, "[{}] {} {}\t{}", id, process.pid, process.state, process.name);
let _ = writeln!(
stderr,
"[{}] {} {}\t{}",
id,
process.pid,
process.state,
process.name
);
}
}
}
......@@ -146,7 +152,13 @@ pub(crate) fn fg(shell: &mut Shell, args: &[&str]) -> i32 {
/// Resumes a stopped background process, if it was stopped.
pub(crate) fn bg(shell: &mut Shell, args: &[&str]) -> i32 {
fn bg_job(shell: &mut Shell, njob: u32) -> bool {
if let Some(job) = shell.background.lock().unwrap().iter_mut().nth(njob as usize) {
if let Some(job) = shell
.background
.lock()
.unwrap()
.iter_mut()
.nth(njob as usize)
{
match job.state {
ProcessState::Running => {
eprintln!("ion: bg: job {} is already running", njob);
......
......@@ -4,7 +4,10 @@ use std::io::{stdout, Write};
pub(crate) fn print_man(man_page: &'static str) {
let stdout = stdout();
let mut stdout = stdout.lock();
match stdout.write_all(man_page.as_bytes()).and_then(|_| stdout.flush()) {
match stdout
.write_all(man_page.as_bytes())
.and_then(|_| stdout.flush())
{
Ok(_) => (),
Err(err) => panic!("{}", err.description().to_owned()),
}
......@@ -14,7 +17,7 @@ pub(crate) fn check_help(args: &[&str], man_page: &'static str) -> bool {
for arg in args {
if *arg == "-h" || *arg == "--help" {
print_man(man_page);
return true
return true;
}
}
false
......@@ -106,31 +109,33 @@ DESCRIPTION
pushd adds directories to the stack.
"#;
/*pub(crate) const MAN_FN: &'static str = r#"NAME
fn - print a list of all functions or create a function
SYNOPSIS
fn
fn example arg:int
echo $arg
end
DESCRIPTION
fn prints a list of all functions that exist in the shell or creates a function when combined
with the 'end' keyword. Functions can have type hints, to tell ion to check the type of a
functions arguments. An error will occur if an argument supplied to a function is of the wrong type.
The supported types in ion are, [], bool, bool[], float, float[], int, int[], str, str[].
Functions are called by typing the function name and then the function arguments, separated
by a space.
fn example arg0:int arg1:int
echo $arg
end
example 1
"#;*/
// pub(crate) const MAN_FN: &'static str = r#"NAME
// fn - print a list of all functions or create a function
//
// SYNOPSIS
// fn
//
// fn example arg:int
// echo $arg
// end
//
// DESCRIPTION
// fn prints a list of all functions that exist in the shell or creates a
// function when combined with the 'end' keyword. Functions can have type
// hints, to tell ion to check the type of a functions arguments. An error will
// occur if an argument supplied to a function is of the wrong type.
// The supported types in ion are, [], bool, bool[], float, float[], int,
// int[], str, str[].
//
// Functions are called by typing the function name and then the function
// arguments, separated by a space.
//
// fn example arg0:int arg1:int
// echo $arg
// end
//
// example 1
// "#;
pub(crate) const MAN_READ: &'static str = r#"NAME
read - read a line of input into some variables
......@@ -582,4 +587,4 @@ SYNOPSIS
DESCRIPTION
The which utility takes a list of command names and searches for the
alias/builtin/function/executable that would be executed if you ran that command.
"#;
\ No newline at end of file
"#;
......@@ -60,8 +60,8 @@ macro_rules! map {
}
}}
/// If you are implementing a builtin add it to the table below, create a well named manpage in man_pages
/// and check for help flags by adding to the start of your builtin the following
/// If you are implementing a builtin add it to the table below, create a well named manpage in
/// man_pages and check for help flags by adding to the start of your builtin the following
/// if check_help(args, MAN_CD) {
/// return SUCCESS
/// }
......@@ -120,8 +120,8 @@ pub struct Builtin {
}
pub struct BuiltinMap {
pub(crate) name: &'static [&'static str],
pub(crate) help: &'static [&'static str],
pub(crate) name: &'static [&'static str],
pub(crate) help: &'static [&'static str],
pub(crate) functions: &'static [BuiltinFunction],
}
......@@ -136,9 +136,13 @@ impl BuiltinMap {
})
}
pub fn keys(&self) -> &'static [&'static str] { self.name }
pub fn keys(&self) -> &'static [&'static str] {
self.name
}
pub fn contains_key(&self, func: &str) -> bool { self.name.iter().any(|&name| name == func) }
pub fn contains_key(&self, func: &str) -> bool {
self.name.iter().any(|&name| name == func)
}
}
// Definitions of simple builtins go here
......@@ -156,7 +160,7 @@ fn builtin_status(args: &[&str], shell: &mut Shell) -> i32 {
pub fn builtin_cd(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_CD) {
return SUCCESS
return SUCCESS;
}
match shell.directory_stack.cd(args, &shell.variables) {
......@@ -212,15 +216,15 @@ fn builtin_is(args: &[&str], shell: &mut Shell) -> i32 {
fn builtin_dirs(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_DIRS) {
return SUCCESS
return SUCCESS;
}
shell.directory_stack.dirs(args)
shell.directory_stack.dirs(args)
}
fn builtin_pushd(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_PUSHD) {
return SUCCESS
return SUCCESS;
}
match shell.directory_stack.pushd(args, &shell.variables) {
Ok(()) => SUCCESS,
......@@ -235,7 +239,7 @@ fn builtin_pushd(args: &[&str], shell: &mut Shell) -> i32 {
fn builtin_popd(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_POPD) {
return SUCCESS
return SUCCESS;
}
match shell.directory_stack.popd(args) {
......@@ -258,21 +262,22 @@ fn builtin_unalias(args: &[&str], shell: &mut Shell) -> i32 {
drop_alias(&mut shell.variables, args)
}
// TODO There is a man page for fn however the -h and --help flags are not checked for.
// TODO There is a man page for fn however the -h and --help flags are not
// checked for.
fn builtin_fn(_: &[&str], shell: &mut Shell) -> i32 {
fn_(&mut shell.functions)
}
fn builtin_read(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_READ) {
return SUCCESS
return SUCCESS;
}
shell.variables.read(args)
shell.variables.read(args)
}
fn builtin_drop(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_DROP) {
return SUCCESS
return SUCCESS;
}
if args.len() >= 2 && args[1] == "-a" {
drop_array(&mut shell.variables, args)
......@@ -283,14 +288,14 @@ fn builtin_drop(args: &[&str], shell: &mut Shell) -> i32 {
fn builtin_set(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_SET) {
return SUCCESS
return SUCCESS;
}
set::set(args, shell)
}
fn builtin_eval(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_EVAL) {
return SUCCESS
return SUCCESS;
}
let evaluated_command = args[1..].join(" ");
let mut buffer = Terminator::new(evaluated_command);
......@@ -305,16 +310,16 @@ fn builtin_eval(args: &[&str], shell: &mut Shell) -> i32 {
}
}
fn builtin_history(args: &[&str], shell: &mut Shell) -> i32 {
fn builtin_history(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_HISTORY) {
return SUCCESS
return SUCCESS;
}
shell.print_history(args)
}
fn builtin_source(args: &[&str], shell: &mut Shell) -> i32 {
if check_help(args, MAN_SOURCE) {
return SUCCESS
return SUCCESS;
}
match source(shell, args) {
Ok(()) => SUCCESS,
......@@ -329,7 +334,7 @@ fn builtin_source(args: &[&str], shell: &mut Shell) -> i32 {
fn builtin_echo(args: &[&str], _: &mut Shell) -> i32 {
if check_help(args, MAN_ECHO) {
return SUCCESS