use crate::lexer::Token;
use crate::parser::{parse, Ast, ShellCommand};
#[test]
fn test_single_command() {
let tokens = vec![Token::Word("ls".to_string())];
let result = parse(tokens).unwrap();
assert_eq!(
result,
Ast::Pipeline(vec![ShellCommand {
args: vec!["ls".to_string()],
redirections: Vec::new(),
compound: None,
}])
);
}
#[test]
fn test_command_with_args() {
let tokens = vec![
Token::Word("ls".to_string()),
Token::Word("-la".to_string()),
];
let result = parse(tokens).unwrap();
assert_eq!(
result,
Ast::Pipeline(vec![ShellCommand {
args: vec!["ls".to_string(), "-la".to_string()],
redirections: Vec::new(),
compound: None,
}])
);
}
#[test]
fn test_empty_tokens() {
let tokens = vec![];
let result = parse(tokens);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "No commands found");
}
#[test]
fn test_only_pipe() {
let tokens = vec![Token::Pipe];
let result = parse(tokens);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "No commands found");
}
#[test]
fn test_parse_assignment() {
let tokens = vec![Token::Word("MY_VAR=test_value".to_string())];
let result = parse(tokens).unwrap();
if let Ast::Assignment { var, value } = result {
assert_eq!(var, "MY_VAR");
assert_eq!(value, "test_value");
} else {
panic!("not assignment");
}
}
#[test]
fn test_parse_assignment_quoted() {
let tokens = vec![Token::Word("MY_VAR=hello world".to_string())];
let result = parse(tokens).unwrap();
if let Ast::Assignment { var, value } = result {
assert_eq!(var, "MY_VAR");
assert_eq!(value, "hello world");
} else {
panic!("not assignment");
}
}
#[test]
fn test_parse_assignment_invalid() {
let tokens = vec![Token::Word("123VAR=value".to_string())];
let result = parse(tokens).unwrap();
if let Ast::Pipeline(cmds) = result {
assert_eq!(cmds[0].args, vec!["123VAR=value"]);
} else {
panic!("should be parsed as pipeline");
}
}
#[test]
fn test_parse_local_assignment() {
let tokens = vec![Token::Local, Token::Word("MY_VAR=test_value".to_string())];
let result = parse(tokens).unwrap();
if let Ast::LocalAssignment { var, value } = result {
assert_eq!(var, "MY_VAR");
assert_eq!(value, "test_value");
} else {
panic!("should be parsed as local assignment");
}
}
#[test]
fn test_parse_local_assignment_separate_tokens() {
let tokens = vec![
Token::Local,
Token::Word("MY_VAR".to_string()),
Token::Word("test_value".to_string()),
];
let result = parse(tokens).unwrap();
if let Ast::LocalAssignment { var, value } = result {
assert_eq!(var, "MY_VAR");
assert_eq!(value, "test_value");
} else {
panic!("should be parsed as local assignment");
}
}
#[test]
fn test_parse_local_assignment_invalid_var_name() {
let tokens = vec![Token::Local, Token::Word("123VAR=value".to_string())];
let result = parse(tokens);
assert!(result.is_err());
}
#[test]
fn test_parse_function_definition() {
let tokens = vec![
Token::Word("myfunc".to_string()),
Token::LeftParen,
Token::RightParen,
Token::LeftBrace,
Token::Word("echo".to_string()),
Token::Word("hello".to_string()),
Token::RightBrace,
];
let result = parse(tokens).unwrap();
if let Ast::FunctionDefinition { name, body } = result {
assert_eq!(name, "myfunc");
if let Ast::Pipeline(cmds) = *body {
assert_eq!(cmds[0].args, vec!["echo", "hello"]);
} else {
panic!("function body should be a pipeline");
}
} else {
panic!("should be parsed as function definition");
}
}
#[test]
fn test_parse_function_definition_empty() {
let tokens = vec![
Token::Word("emptyfunc".to_string()),
Token::LeftParen,
Token::RightParen,
Token::LeftBrace,
Token::RightBrace,
];
let result = parse(tokens).unwrap();
if let Ast::FunctionDefinition { name, body } = result {
assert_eq!(name, "emptyfunc");
if let Ast::Pipeline(cmds) = *body {
assert_eq!(cmds[0].args, vec!["true"]);
} else {
panic!("function body should be a pipeline");
}
} else {
panic!("should be parsed as function definition");
}
}
#[test]
fn test_parse_function_definition_legacy_format() {
let tokens = vec![
Token::Word("legacyfunc()".to_string()),
Token::LeftBrace,
Token::Word("echo".to_string()),
Token::Word("hello".to_string()),
Token::RightBrace,
];
let result = parse(tokens).unwrap();
if let Ast::FunctionDefinition { name, body } = result {
assert_eq!(name, "legacyfunc");
if let Ast::Pipeline(cmds) = *body {
assert_eq!(cmds[0].args, vec!["echo", "hello"]);
} else {
panic!("function body should be a pipeline");
}
} else {
panic!("should be parsed as function definition");
}
}
#[test]
fn test_parse_async_command_simple() {
let tokens = vec![
Token::Word("sleep".to_string()),
Token::Word("10".to_string()),
Token::Ampersand,
];
let result = parse(tokens).unwrap();
if let Ast::AsyncCommand { command } = result {
if let Ast::Pipeline(cmds) = *command {
assert_eq!(cmds[0].args, vec!["sleep", "10"]);
} else {
panic!("async command should contain a pipeline");
}
} else {
panic!("should be parsed as AsyncCommand");
}
}
#[test]
fn test_parse_async_command_with_redirection() {
let tokens = vec![
Token::Word("cmd".to_string()),
Token::RedirOut,
Token::Word("output.txt".to_string()),
Token::Ampersand,
];
let result = parse(tokens).unwrap();
if let Ast::AsyncCommand { command } = result {
if let Ast::Pipeline(cmds) = *command {
assert_eq!(cmds[0].args, vec!["cmd"]);
assert_eq!(cmds[0].redirections.len(), 1);
} else {
panic!("async command should contain a pipeline");
}
} else {
panic!("should be parsed as AsyncCommand");
}
}
#[test]
fn test_parse_async_command_pipeline() {
let tokens = vec![
Token::Word("ls".to_string()),
Token::Pipe,
Token::Word("grep".to_string()),
Token::Word("txt".to_string()),
Token::Ampersand,
];
let result = parse(tokens).unwrap();
if let Ast::AsyncCommand { command } = result {
if let Ast::Pipeline(cmds) = *command {
assert_eq!(cmds.len(), 2);
assert_eq!(cmds[0].args, vec!["ls"]);
assert_eq!(cmds[1].args, vec!["grep", "txt"]);
} else {
panic!("async command should contain a pipeline");
}
} else {
panic!("should be parsed as AsyncCommand");
}
}
#[test]
fn test_parse_multiple_async_commands() {
let tokens = vec![
Token::Word("cmd1".to_string()),
Token::Ampersand,
Token::Word("cmd2".to_string()),
Token::Ampersand,
];
let result = parse(tokens).unwrap();
if let Ast::Sequence(cmds) = result {
assert_eq!(cmds.len(), 2);
assert!(matches!(cmds[0], Ast::AsyncCommand { .. }));
assert!(matches!(cmds[1], Ast::AsyncCommand { .. }));
} else {
panic!("should be parsed as Sequence of AsyncCommands");
}
}
#[test]
fn test_parse_async_command_with_semicolon() {
let tokens = vec![
Token::Word("cmd1".to_string()),
Token::Ampersand,
Token::Semicolon,
Token::Word("cmd2".to_string()),
];
let result = parse(tokens).unwrap();
if let Ast::Sequence(cmds) = result {
assert_eq!(cmds.len(), 2);
assert!(matches!(cmds[0], Ast::AsyncCommand { .. }));
assert!(matches!(cmds[1], Ast::Pipeline(_)));
} else {
panic!("should be parsed as Sequence");
}
}
#[test]
fn test_parse_async_command_with_newline() {
let tokens = vec![
Token::Word("cmd1".to_string()),
Token::Ampersand,
Token::Newline,
Token::Word("cmd2".to_string()),
];
let result = parse(tokens).unwrap();
if let Ast::Sequence(cmds) = result {
assert_eq!(cmds.len(), 2);
assert!(matches!(cmds[0], Ast::AsyncCommand { .. }));
assert!(matches!(cmds[1], Ast::Pipeline(_)));
} else {
panic!("should be parsed as Sequence");
}
}