use std::fs;
use std::io::Write;
use crate::parser::ShellCommand;
use crate::state::ShellState;
pub struct TestBuiltin;
impl super::Builtin for TestBuiltin {
fn name(&self) -> &'static str {
"test"
}
fn names(&self) -> Vec<&'static str> {
vec!["test", "["]
}
fn description(&self) -> &'static str {
"Evaluate conditional expressions"
}
fn run(
&self,
cmd: &ShellCommand,
_shell_state: &mut ShellState,
_output_writer: &mut dyn Write,
) -> i32 {
let is_bracket = cmd.args[0] == "[";
let args = if is_bracket {
if cmd.args.len() < 2 || cmd.args.last().unwrap() != "]" {
return 2; }
&cmd.args[1..cmd.args.len() - 1] } else {
&cmd.args[1..]
};
if args.is_empty() {
return 1;
}
let (negate, args) = if args[0] == "!" {
if args.len() < 2 {
return 2;
}
(true, &args[1..])
} else {
(false, args)
};
if let Some(option) = args[0].strip_prefix('-') {
match option {
"z" => {
if args.len() < 2 {
return 2; }
let result = if args[1].is_empty() { 0 } else { 1 };
if negate { 1 - result } else { result }
}
"n" => {
if args.len() < 2 {
return 2; }
let result = if !args[1].is_empty() { 0 } else { 1 };
if negate { 1 - result } else { result }
}
"f" => {
if args.len() < 2 {
return 2; }
let result = match fs::metadata(&args[1]) {
Ok(metadata) => {
if metadata.is_file() {
0
} else {
1
}
}
Err(_) => 1, };
if negate { 1 - result } else { result }
}
"d" => {
if args.len() < 2 {
return 2; }
let result = match fs::metadata(&args[1]) {
Ok(metadata) => {
if metadata.is_dir() {
0
} else {
1
}
}
Err(_) => 1, };
if negate { 1 - result } else { result }
}
"e" => {
if args.len() < 2 {
return 2; }
let result = match fs::metadata(&args[1]) {
Ok(_) => 0,
Err(_) => 1,
};
if negate { 1 - result } else { result }
}
_ => {
2
}
}
} else {
if args.len() >= 3 {
if args[1] == "=" {
let result = if args[0] == args[2] { 0 } else { 1 };
return if negate { 1 - result } else { result };
} else if args[1] == "!=" {
let result = if args[0] != args[2] { 0 } else { 1 };
return if negate { 1 - result } else { result };
}
if let Some(operator) = args[1].strip_prefix('-') {
match operator {
"eq" => {
let left = args[0].parse::<i32>();
let right = args[2].parse::<i32>();
match (left, right) {
(Ok(l), Ok(r)) => {
let result = if l == r { 0 } else { 1 };
return if negate { 1 - result } else { result };
}
_ => return 2, }
}
"ne" => {
let left = args[0].parse::<i32>();
let right = args[2].parse::<i32>();
match (left, right) {
(Ok(l), Ok(r)) => {
let result = if l != r { 0 } else { 1 };
return if negate { 1 - result } else { result };
}
_ => return 2, }
}
"lt" => {
let left = args[0].parse::<i32>();
let right = args[2].parse::<i32>();
match (left, right) {
(Ok(l), Ok(r)) => {
let result = if l < r { 0 } else { 1 };
return if negate { 1 - result } else { result };
}
_ => return 2, }
}
"le" => {
let left = args[0].parse::<i32>();
let right = args[2].parse::<i32>();
match (left, right) {
(Ok(l), Ok(r)) => {
let result = if l <= r { 0 } else { 1 };
return if negate { 1 - result } else { result };
}
_ => return 2, }
}
"gt" => {
let left = args[0].parse::<i32>();
let right = args[2].parse::<i32>();
match (left, right) {
(Ok(l), Ok(r)) => {
let result = if l > r { 0 } else { 1 };
return if negate { 1 - result } else { result };
}
_ => return 2, }
}
"ge" => {
let left = args[0].parse::<i32>();
let right = args[2].parse::<i32>();
match (left, right) {
(Ok(l), Ok(r)) => {
let result = if l >= r { 0 } else { 1 };
return if negate { 1 - result } else { result };
}
_ => return 2, }
}
_ => {
return 2;
}
}
}
}
2
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builtins::Builtin;
use std::fs::File;
#[test]
fn test_test_builtin_name() {
let builtin = TestBuiltin;
assert_eq!(builtin.name(), "test");
}
#[test]
fn test_test_builtin_description() {
let builtin = TestBuiltin;
assert_eq!(builtin.description(), "Evaluate conditional expressions");
}
#[test]
fn test_z_option_empty_string() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "-z".to_string(), "".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_z_option_non_empty_string() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "-z".to_string(), "hello".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_z_option_missing_argument() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "-z".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 2); }
#[test]
fn test_n_option_empty_string() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "-n".to_string(), "".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_n_option_non_empty_string() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "-n".to_string(), "hello".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_n_option_missing_argument() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "-n".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 2); }
#[test]
fn test_e_option_existing_file() {
let builtin = TestBuiltin;
let temp_path = "/tmp/test_file.txt";
File::create(temp_path).unwrap();
let cmd = ShellCommand {
args: vec!["test".to_string(), "-e".to_string(), temp_path.to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0);
std::fs::remove_file(temp_path).unwrap();
}
#[test]
fn test_e_option_non_existing_file() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"-e".to_string(),
"/non/existing/file".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_f_option_regular_file() {
let builtin = TestBuiltin;
let temp_path = "/tmp/test_regular_file.txt";
File::create(temp_path).unwrap();
let cmd = ShellCommand {
args: vec!["test".to_string(), "-f".to_string(), temp_path.to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0);
std::fs::remove_file(temp_path).unwrap();
}
#[test]
fn test_f_option_directory() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "-f".to_string(), "/tmp".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_d_option_directory() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "-d".to_string(), "/tmp".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_d_option_regular_file() {
let builtin = TestBuiltin;
let temp_path = "/tmp/test_regular_file_for_d.txt";
File::create(temp_path).unwrap();
let cmd = ShellCommand {
args: vec!["test".to_string(), "-d".to_string(), temp_path.to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1);
std::fs::remove_file(temp_path).unwrap();
}
#[test]
fn test_invalid_option() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "-x".to_string(), "arg".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 2); }
#[test]
fn test_no_arguments() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_eq_equal() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-eq".to_string(),
"2".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_eq_not_equal() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-eq".to_string(),
"3".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_ne_equal() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-ne".to_string(),
"2".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_ne_not_equal() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-ne".to_string(),
"3".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_lt_less() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-lt".to_string(),
"3".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_lt_greater() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"3".to_string(),
"-lt".to_string(),
"2".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_le_less() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-le".to_string(),
"3".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_le_equal() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-le".to_string(),
"2".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_le_greater() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"3".to_string(),
"-le".to_string(),
"2".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_gt_greater() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"3".to_string(),
"-gt".to_string(),
"2".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_gt_less() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-gt".to_string(),
"3".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_ge_greater() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"3".to_string(),
"-ge".to_string(),
"2".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_ge_equal() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-ge".to_string(),
"2".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_ge_less() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-ge".to_string(),
"3".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_numeric_invalid_left_operand() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"abc".to_string(),
"-eq".to_string(),
"2".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 2); }
#[test]
fn test_numeric_invalid_right_operand() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-eq".to_string(),
"abc".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 2); }
#[test]
fn test_numeric_invalid_operator() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"2".to_string(),
"-invalid".to_string(),
"3".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 2); }
#[test]
fn test_negation_with_f_option_nonexistent() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"[".to_string(),
"!".to_string(),
"-f".to_string(),
"/non/existing/file".to_string(),
"]".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_negation_with_f_option_existing() {
let builtin = TestBuiltin;
let temp_path = "/tmp/test_negation_file.txt";
File::create(temp_path).unwrap();
let cmd = ShellCommand {
args: vec![
"[".to_string(),
"!".to_string(),
"-f".to_string(),
temp_path.to_string(),
"]".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1);
std::fs::remove_file(temp_path).unwrap();
}
#[test]
fn test_negation_with_d_option() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"[".to_string(),
"!".to_string(),
"-d".to_string(),
"/tmp".to_string(),
"]".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 1); }
#[test]
fn test_negation_with_z_option() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"!".to_string(),
"-z".to_string(),
"hello".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_negation_with_n_option() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"!".to_string(),
"-n".to_string(),
"".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_negation_with_eq_operator() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"test".to_string(),
"!".to_string(),
"2".to_string(),
"-eq".to_string(),
"3".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_negation_with_string_equality() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec![
"[".to_string(),
"!".to_string(),
"abc".to_string(),
"=".to_string(),
"def".to_string(),
"]".to_string(),
],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 0); }
#[test]
fn test_negation_only() {
let builtin = TestBuiltin;
let cmd = ShellCommand {
args: vec!["test".to_string(), "!".to_string()],
redirections: Vec::new(),
compound: None,
};
let mut shell_state = ShellState::new();
let mut output = Vec::new();
let exit_code = builtin.run(&cmd, &mut shell_state, &mut output);
assert_eq!(exit_code, 2); }
}