use super::*;
use crate::bash_parser::parser_arith::ArithToken;
#[test]
fn test_COMPOUND_003_while_and_condition() {
let input = r#"while [ "$i" -lt 10 ] && [ "$done" = "false" ]; do
echo loop
break
done"#;
let mut parser = BashParser::new(input).expect("parser should init");
let ast = parser.parse().expect("should parse && in while condition");
match &ast.statements[0] {
BashStmt::While { condition, .. } => {
let cond_str = format!("{condition:?}");
assert!(
cond_str.contains("And"),
"condition should contain And: {cond_str}"
);
}
other => panic!("Expected While, got {other:?}"),
}
}
#[test]
fn test_SPECIAL_001_dollar_hash() {
let input = r#"echo $#"#;
let mut parser = BashParser::new(input).expect("parser should init");
let ast = parser.parse().expect("should parse $#");
match &ast.statements[0] {
BashStmt::Command { name, args, .. } => {
assert_eq!(name, "echo");
assert_eq!(args.len(), 1);
assert!(
matches!(&args[0], BashExpr::Variable(v) if v == "#"),
"should have $# as variable: {args:?}"
);
}
other => panic!("Expected Command, got {other:?}"),
}
}
#[test]
fn test_SPECIAL_002_dollar_question() {
let input = r#"echo $?"#;
let mut parser = BashParser::new(input).expect("parser should init");
let ast = parser.parse().expect("should parse $?");
match &ast.statements[0] {
BashStmt::Command { args, .. } => {
assert!(
matches!(&args[0], BashExpr::Variable(v) if v == "?"),
"should have $? as variable: {args:?}"
);
}
other => panic!("Expected Command, got {other:?}"),
}
}
#[test]
fn test_SPECIAL_003_dollar_bang() {
let input = r#"echo $!"#;
let mut parser = BashParser::new(input).expect("parser should init");
let ast = parser.parse().expect("should parse $!");
match &ast.statements[0] {
BashStmt::Command { args, .. } => {
assert!(
matches!(&args[0], BashExpr::Variable(v) if v == "!"),
"should have $! as variable: {args:?}"
);
}
other => panic!("Expected Command, got {other:?}"),
}
}
#[test]
fn test_SPECIAL_004_while_dollar_hash_gt() {
let input = r#"while [ $# -gt 0 ]; do
shift
done"#;
let mut parser = BashParser::new(input).expect("parser should init");
let ast = parser.parse().expect("should parse while [ $# -gt 0 ]");
match &ast.statements[0] {
BashStmt::While { .. } => {} other => panic!("Expected While, got {other:?}"),
}
}
#[test]
fn test_CASE_MULTI_001_shift_then_assign() {
let input = r#"case "$1" in
-c) shift; CONFIG="$1" ;;
*) break ;;
esac"#;
let mut parser = BashParser::new(input).expect("parser should init");
let ast = parser
.parse()
.expect("should parse multi-statement case arm");
match &ast.statements[0] {
BashStmt::Case { arms, .. } => {
assert_eq!(arms.len(), 2, "should have 2 arms");
assert!(
arms[0].body.len() >= 2,
"first arm should have >=2 statements (shift + assign), got {}: {:?}",
arms[0].body.len(),
arms[0].body
);
}
other => panic!("Expected Case, got {other:?}"),
}
}
#[test]
fn test_CASE_MULTI_002_option_loop() {
let input = r#"while [ $# -gt 0 ]; do
case "$1" in
-v) VERBOSE=true ;;
-d) DAEMON=true ;;
-c) shift; CONFIG="$1" ;;
-*) echo "Unknown option: $1" >&2; exit 1 ;;
*) break ;;
esac
shift
done"#;
let mut parser = BashParser::new(input).expect("parser should init");
let ast = parser
.parse()
.expect("should parse option loop with multi-stmt arms");
match &ast.statements[0] {
BashStmt::While { body, .. } => {
assert!(!body.is_empty(), "while body should not be empty");
}
other => panic!("Expected While, got {other:?}"),
}
}
#[test]
fn test_CASE_MULTI_003_three_statements() {
let input = r#"case "$1" in
start) echo "starting"; setup; run ;;
stop) cleanup; echo "stopped" ;;
esac"#;
let mut parser = BashParser::new(input).expect("parser should init");
let ast = parser.parse().expect("should parse 3-statement case arm");
match &ast.statements[0] {
BashStmt::Case { arms, .. } => {
assert_eq!(arms.len(), 2);
assert!(
arms[0].body.len() >= 3,
"first arm should have >=3 statements, got {}: {:?}",
arms[0].body.len(),
arms[0].body
);
assert!(
arms[1].body.len() >= 2,
"second arm should have >=2 statements, got {}: {:?}",
arms[1].body.len(),
arms[1].body
);
}
other => panic!("Expected Case, got {other:?}"),
}
}
#[test]
fn test_coverage_declare_statement() {
let input = "declare -a array";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(!ast.statements.is_empty());
}
#[test]
fn test_coverage_test_bracket_single() {
let input = "[ -f file.txt ]";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(!ast.statements.is_empty());
}
#[test]
fn test_coverage_test_bracket_double_simple() {
let input = "[[ -f file.txt ]]";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(!ast.statements.is_empty());
}
#[test]
fn test_coverage_test_bracket_double_compound_unsupported() {
let input = "[[ -f file.txt && -r file.txt ]]";
let mut parser = BashParser::new(input).unwrap();
let result = parser.parse();
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_coverage_arithmetic_test() {
let input = "(( x > 5 ))";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(!ast.statements.is_empty());
}
#[test]
fn test_coverage_cstyle_for() {
let input = "for ((i=0; i<10; i++)); do echo $i; done";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(ast
.statements
.iter()
.any(|s| matches!(s, BashStmt::ForCStyle { .. })));
}
#[test]
fn test_coverage_coprocess() {
let input = "coproc myproc { sleep 10; }";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(ast
.statements
.iter()
.any(|s| matches!(s, BashStmt::Coproc { .. })));
}
#[test]
fn test_coverage_newline_separated() {
let input = "echo one\necho two\necho three";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(ast.statements.len() >= 3);
}
#[test]
fn test_coverage_line_continuation() {
let input = "echo hello \\\nworld";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(!ast.statements.is_empty());
}
#[test]
fn test_coverage_complex_nested_if() {
let input = r#"if [ $a -eq 1 ]; then
if [ $b -eq 2 ]; then
echo "nested"
fi
fi"#;
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(ast
.statements
.iter()
.any(|s| matches!(s, BashStmt::If { .. })));
}
#[test]
fn test_coverage_elif_chain() {
let input = r#"if [ $x -eq 1 ]; then
echo "one"
elif [ $x -eq 2 ]; then
echo "two"
elif [ $x -eq 3 ]; then
echo "three"
else
echo "other"
fi"#;
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(ast
.statements
.iter()
.any(|s| matches!(s, BashStmt::If { .. })));
}
#[test]
fn test_coverage_env_prefix() {
let input = "VAR=value cmd";
let mut parser = BashParser::new(input).unwrap();
let ast = parser.parse().unwrap();
assert!(!ast.statements.is_empty());
}
mod tokenize_arithmetic_tests {
#![allow(clippy::unwrap_used)]
use super::*;
fn tokenize(input: &str) -> Vec<ArithToken> {
let parser = BashParser::new("echo x").unwrap();
parser.tokenize_arithmetic(input).unwrap()
}
fn tokenize_err(input: &str) -> ParseError {
let parser = BashParser::new("echo x").unwrap();
parser.tokenize_arithmetic(input).unwrap_err()
}
#[test]
fn test_arith_tok_001_empty_input() {
let tokens = tokenize("");
assert!(tokens.is_empty());
}
#[test]
fn test_arith_tok_002_basic_arithmetic_operators() {
let tokens = tokenize("+ - * / %");
assert_eq!(
tokens,
vec![
ArithToken::Plus,
ArithToken::Minus,
ArithToken::Multiply,
ArithToken::Divide,
ArithToken::Modulo,
]
);
}
#[test]
fn test_arith_tok_003_parentheses() {
let tokens = tokenize("(1+2)");
assert_eq!(
tokens,
vec![
ArithToken::LeftParen,
ArithToken::Number(1),
ArithToken::Plus,
ArithToken::Number(2),
ArithToken::RightParen,
]
);
}
#[test]
fn test_arith_tok_004_less_than_variants() {
let tokens = tokenize("<");
assert_eq!(tokens, vec![ArithToken::Lt]);
let tokens = tokenize("<=");
assert_eq!(tokens, vec![ArithToken::Le]);
let tokens = tokenize("<<");
assert_eq!(tokens, vec![ArithToken::ShiftLeft]);
}
#[test]
fn test_arith_tok_005_greater_than_variants() {
let tokens = tokenize(">");
assert_eq!(tokens, vec![ArithToken::Gt]);
let tokens = tokenize(">=");
assert_eq!(tokens, vec![ArithToken::Ge]);
let tokens = tokenize(">>");
assert_eq!(tokens, vec![ArithToken::ShiftRight]);
}
#[test]
fn test_arith_tok_006_equality_and_assign() {
let tokens = tokenize("==");
assert_eq!(tokens, vec![ArithToken::Eq]);
let tokens = tokenize("=");
assert_eq!(tokens, vec![ArithToken::Assign]);
let tokens = tokenize("!=");
assert_eq!(tokens, vec![ArithToken::Ne]);
}
#[test]
fn test_arith_tok_007_logical_not() {
let tokens = tokenize("!");
assert_eq!(tokens, vec![ArithToken::LogicalNot]);
}
#[test]
fn test_arith_tok_008_ternary_operator() {
let tokens = tokenize("a ? 1 : 0");
assert_eq!(
tokens,
vec![
ArithToken::Variable("a".to_string()),
ArithToken::Question,
ArithToken::Number(1),
ArithToken::Colon,
ArithToken::Number(0),
]
);
}
#[test]
fn test_arith_tok_009_bitwise_and_logical_and() {
let tokens = tokenize("&");
assert_eq!(tokens, vec![ArithToken::BitAnd]);
let tokens = tokenize("&&");
assert_eq!(tokens, vec![ArithToken::LogicalAnd]);
}
#[test]
fn test_arith_tok_010_bitwise_and_logical_or() {
let tokens = tokenize("|");
assert_eq!(tokens, vec![ArithToken::BitOr]);
let tokens = tokenize("||");
assert_eq!(tokens, vec![ArithToken::LogicalOr]);
}
#[test]
fn test_arith_tok_011_bitwise_xor_and_not() {
let tokens = tokenize("^ ~");
assert_eq!(tokens, vec![ArithToken::BitXor, ArithToken::BitNot]);
}
#[test]
fn test_arith_tok_012_comma_operator() {
let tokens = tokenize("1 , 2");
assert_eq!(
tokens,
vec![
ArithToken::Number(1),
ArithToken::Comma,
ArithToken::Number(2),
]
);
}
#[test]
fn test_arith_tok_013_decimal_numbers() {
let tokens = tokenize("42");
assert_eq!(tokens, vec![ArithToken::Number(42)]);
let tokens = tokenize("0");
assert_eq!(tokens, vec![ArithToken::Number(0)]);
let tokens = tokenize("123456789");
assert_eq!(tokens, vec![ArithToken::Number(123_456_789)]);
}
#[test]
fn test_arith_tok_014_hex_numbers() {
let tokens = tokenize("0xFF");
assert_eq!(tokens, vec![ArithToken::Number(255)]);
let tokens = tokenize("0x0");
assert_eq!(tokens, vec![ArithToken::Number(0)]);
let tokens = tokenize("0XAB");
assert_eq!(tokens, vec![ArithToken::Number(0xAB)]);
let tokens = tokenize("0x1F");
assert_eq!(tokens, vec![ArithToken::Number(31)]);
}
}