use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use async_trait::async_trait;
use super::{Builtin, Context};
use crate::error::Result;
use crate::fs::FileSystem;
use crate::interpreter::ExecResult;
pub struct Test;
#[async_trait]
impl Builtin for Test {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
if ctx.args.is_empty() {
return Ok(ExecResult::err(String::new(), 1));
}
let cwd = ctx.cwd.clone();
let result = evaluate_expression(ctx.args, &ctx.fs, &cwd, ctx.variables).await;
if result {
Ok(ExecResult::ok(String::new()))
} else {
Ok(ExecResult::err(String::new(), 1))
}
}
}
pub struct Bracket;
#[async_trait]
impl Builtin for Bracket {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
if ctx.args.is_empty() || ctx.args.last() != Some(&"]".to_string()) {
return Ok(ExecResult::err("missing ]\n".to_string(), 2));
}
let args: Vec<String> = ctx.args[..ctx.args.len() - 1].to_vec();
if args.is_empty() {
return Ok(ExecResult::err(String::new(), 1));
}
let cwd = ctx.cwd.clone();
let result = evaluate_expression(&args, &ctx.fs, &cwd, ctx.variables).await;
if result {
Ok(ExecResult::ok(String::new()))
} else {
Ok(ExecResult::err(String::new(), 1))
}
}
}
fn resolve_file_path(cwd: &Path, arg: &str) -> PathBuf {
let p = Path::new(arg);
if p.is_absolute() {
p.to_path_buf()
} else {
cwd.join(p)
}
}
fn evaluate_expression<'a>(
args: &'a [String],
fs: &'a Arc<dyn FileSystem>,
cwd: &'a Path,
variables: &'a HashMap<String, String>,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = bool> + Send + 'a>> {
Box::pin(async move {
if args.is_empty() {
return false;
}
if args[0] == "!" {
return !evaluate_expression(&args[1..], fs, cwd, variables).await;
}
if args[0] == "(" && args.last().map(|s| s.as_str()) == Some(")") {
return evaluate_expression(&args[1..args.len() - 1], fs, cwd, variables).await;
}
for (i, arg) in args.iter().enumerate() {
if arg == "-o" && i > 0 {
return evaluate_expression(&args[..i], fs, cwd, variables).await
|| evaluate_expression(&args[i + 1..], fs, cwd, variables).await;
}
}
for (i, arg) in args.iter().enumerate() {
if arg == "-a" && i > 0 {
return evaluate_expression(&args[..i], fs, cwd, variables).await
&& evaluate_expression(&args[i + 1..], fs, cwd, variables).await;
}
}
match args.len() {
1 => {
!args[0].is_empty()
}
2 => {
evaluate_unary(&args[0], &args[1], fs, cwd, variables).await
}
3 => {
evaluate_binary(&args[0], &args[1], &args[2], fs, cwd).await
}
_ => false,
}
})
}
async fn evaluate_unary(
op: &str,
arg: &str,
fs: &Arc<dyn FileSystem>,
cwd: &Path,
variables: &HashMap<String, String>,
) -> bool {
match op {
"-z" => arg.is_empty(),
"-n" => !arg.is_empty(),
"-e" | "-a" => {
let path = resolve_file_path(cwd, arg);
fs.exists(&path).await.unwrap_or(false)
}
"-f" => {
let path = resolve_file_path(cwd, arg);
if let Ok(meta) = fs.stat(&path).await {
meta.file_type.is_file()
} else {
false
}
}
"-d" => {
let path = resolve_file_path(cwd, arg);
if let Ok(meta) = fs.stat(&path).await {
meta.file_type.is_dir()
} else {
false
}
}
"-r" => {
let path = resolve_file_path(cwd, arg);
fs.exists(&path).await.unwrap_or(false)
}
"-w" => {
let path = resolve_file_path(cwd, arg);
fs.exists(&path).await.unwrap_or(false)
}
"-x" => {
let path = resolve_file_path(cwd, arg);
if let Ok(meta) = fs.stat(&path).await {
(meta.mode & 0o111) != 0
} else {
false
}
}
"-s" => {
let path = resolve_file_path(cwd, arg);
if let Ok(meta) = fs.stat(&path).await {
meta.size > 0
} else {
false
}
}
"-L" | "-h" => {
let path = resolve_file_path(cwd, arg);
if let Ok(meta) = fs.stat(&path).await {
meta.file_type.is_symlink()
} else {
false
}
}
"-p" => {
let path = resolve_file_path(cwd, arg);
if let Ok(meta) = fs.stat(&path).await {
meta.file_type.is_fifo()
} else {
false
}
}
"-S" => false, "-b" => false, "-c" => false, "-t" => {
let fd_key = format!("_TTY_{}", arg);
variables.get(&fd_key).map(|v| v == "1").unwrap_or(false)
}
_ => false,
}
}
async fn evaluate_binary(
left: &str,
op: &str,
right: &str,
fs: &Arc<dyn FileSystem>,
cwd: &Path,
) -> bool {
match op {
"=" | "==" => left == right,
"!=" => left != right,
"<" => left < right,
">" => left > right,
"-eq" => parse_int(left) == parse_int(right),
"-ne" => parse_int(left) != parse_int(right),
"-lt" => parse_int(left) < parse_int(right),
"-le" => parse_int(left) <= parse_int(right),
"-gt" => parse_int(left) > parse_int(right),
"-ge" => parse_int(left) >= parse_int(right),
"-nt" => {
let left_meta = fs.stat(&resolve_file_path(cwd, left)).await;
let right_meta = fs.stat(&resolve_file_path(cwd, right)).await;
match (left_meta, right_meta) {
(Ok(lm), Ok(rm)) => lm.modified > rm.modified,
(Ok(_), Err(_)) => true, _ => false,
}
}
"-ot" => {
let left_meta = fs.stat(&resolve_file_path(cwd, left)).await;
let right_meta = fs.stat(&resolve_file_path(cwd, right)).await;
match (left_meta, right_meta) {
(Ok(lm), Ok(rm)) => lm.modified < rm.modified,
(Err(_), Ok(_)) => true, _ => false,
}
}
"-ef" => {
let left_path = super::resolve_path(cwd, left);
let right_path = super::resolve_path(cwd, right);
left_path == right_path
}
_ => false,
}
}
fn parse_int(s: &str) -> i64 {
s.trim().parse().unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use crate::fs::{FileSystem, InMemoryFs};
async fn setup() -> (Arc<InMemoryFs>, PathBuf, HashMap<String, String>) {
let fs = Arc::new(InMemoryFs::new());
let cwd = PathBuf::from("/home/user");
let variables = HashMap::new();
fs.mkdir(&cwd, true).await.unwrap();
(fs, cwd, variables)
}
#[tokio::test]
async fn test_empty_args_returns_false() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args: Vec<String> = vec![];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_nonempty_string_is_true() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["hello".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_z_empty_string() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["-z".to_string(), "".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_z_nonempty_string() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["-z".to_string(), "abc".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_n_nonempty_string() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["-n".to_string(), "abc".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_n_empty_string() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["-n".to_string(), "".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_string_equality() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["abc".to_string(), "=".to_string(), "abc".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_string_inequality() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["abc".to_string(), "!=".to_string(), "def".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_string_equality_fails() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["abc".to_string(), "=".to_string(), "xyz".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_eq_numeric() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["42".to_string(), "-eq".to_string(), "42".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_eq_numeric_fails() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["42".to_string(), "-eq".to_string(), "99".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_lt_numeric() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["5".to_string(), "-lt".to_string(), "10".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_lt_numeric_fails() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["10".to_string(), "-lt".to_string(), "5".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_gt_numeric() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["10".to_string(), "-gt".to_string(), "5".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_e_file_exists() {
let (fs, mut cwd, mut variables) = setup().await;
fs.write_file(Path::new("/home/user/file.txt"), b"hello")
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-e".to_string(), "file.txt".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_e_file_not_exists() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["-e".to_string(), "nope.txt".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_f_regular_file() {
let (fs, mut cwd, mut variables) = setup().await;
fs.write_file(Path::new("/home/user/file.txt"), b"data")
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-f".to_string(), "file.txt".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_f_directory_is_not_file() {
let (fs, mut cwd, mut variables) = setup().await;
fs.mkdir(Path::new("/home/user/subdir"), true)
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-f".to_string(), "subdir".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_d_directory() {
let (fs, mut cwd, mut variables) = setup().await;
fs.mkdir(Path::new("/home/user/subdir"), true)
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-d".to_string(), "subdir".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_d_file_is_not_dir() {
let (fs, mut cwd, mut variables) = setup().await;
fs.write_file(Path::new("/home/user/file.txt"), b"data")
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-d".to_string(), "file.txt".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_negation() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["!".to_string(), "-z".to_string(), "abc".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0); }
#[tokio::test]
async fn test_negation_true_becomes_false() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["!".to_string(), "-n".to_string(), "abc".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1); }
#[tokio::test]
async fn bracket_missing_closing() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["-z".to_string(), "".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Bracket.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 2);
assert!(result.stderr.contains("missing ]"));
}
#[tokio::test]
async fn bracket_with_closing() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["-z".to_string(), "".to_string(), "]".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Bracket.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn bracket_empty_expression() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["]".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Bracket.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1); }
#[tokio::test]
async fn bracket_empty_args() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args: Vec<String> = vec![];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Bracket.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 2);
}
#[tokio::test]
async fn test_ne_numeric() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["1".to_string(), "-ne".to_string(), "2".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_ne_numeric_equal() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["5".to_string(), "-ne".to_string(), "5".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_le_numeric() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["5".to_string(), "-le".to_string(), "5".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_le_numeric_greater() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["10".to_string(), "-le".to_string(), "5".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_ge_numeric() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["5".to_string(), "-ge".to_string(), "5".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_ge_numeric_less() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["3".to_string(), "-ge".to_string(), "5".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_double_eq_string() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["foo".to_string(), "==".to_string(), "foo".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_string_less_than() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["abc".to_string(), "<".to_string(), "def".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_string_greater_than() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["xyz".to_string(), ">".to_string(), "abc".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_s_file_has_size() {
let (fs, mut cwd, mut variables) = setup().await;
fs.write_file(Path::new("/home/user/nonempty.txt"), b"data")
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-s".to_string(), "nonempty.txt".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_s_empty_file() {
let (fs, mut cwd, mut variables) = setup().await;
fs.write_file(Path::new("/home/user/empty.txt"), b"")
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-s".to_string(), "empty.txt".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_r_readable_file() {
let (fs, mut cwd, mut variables) = setup().await;
fs.write_file(Path::new("/home/user/readable.txt"), b"x")
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-r".to_string(), "readable.txt".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_w_writable_file() {
let (fs, mut cwd, mut variables) = setup().await;
fs.write_file(Path::new("/home/user/writable.txt"), b"x")
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-w".to_string(), "writable.txt".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_r_nonexistent() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["-r".to_string(), "nope".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 1);
}
#[tokio::test]
async fn test_e_absolute_path() {
let (fs, mut cwd, mut variables) = setup().await;
fs.write_file(Path::new("/tmp/abs.txt"), b"hi")
.await
.unwrap();
let env = HashMap::new();
let args = vec!["-e".to_string(), "/tmp/abs.txt".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_eq_non_numeric_treated_as_zero() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["abc".to_string(), "-eq".to_string(), "0".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_negative_numbers() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec!["-5".to_string(), "-lt".to_string(), "0".to_string()];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn bracket_numeric_eq() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec![
"3".to_string(),
"-eq".to_string(),
"3".to_string(),
"]".to_string(),
];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Bracket.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
#[tokio::test]
async fn test_or_and_precedence() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec![
"1".to_string(),
"-eq".to_string(),
"1".to_string(),
"-o".to_string(),
"1".to_string(),
"-eq".to_string(),
"2".to_string(),
"-a".to_string(),
"1".to_string(),
"-eq".to_string(),
"2".to_string(),
];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Test.execute(ctx).await.unwrap();
assert_eq!(
result.exit_code, 0,
"-a should have higher precedence than -o"
);
}
#[tokio::test]
async fn bracket_string_neq() {
let (fs, mut cwd, mut variables) = setup().await;
let env = HashMap::new();
let args = vec![
"a".to_string(),
"!=".to_string(),
"b".to_string(),
"]".to_string(),
];
let ctx = Context::new_for_test(&args, &env, &mut variables, &mut cwd, fs.clone(), None);
let result = Bracket.execute(ctx).await.unwrap();
assert_eq!(result.exit_code, 0);
}
}