#![allow(clippy::unwrap_used)]
use crate::bash_parser::ast::*;
use crate::bash_parser::parser::*;
use crate::bash_parser::parser_arith::ArithToken;
#[test]
fn test_ARITH_EXPR_033_complex_nested() {
assert_eq!(
parse_arith("(a + b) * (c - d)"),
ArithExpr::Mul(
Box::new(ArithExpr::Add(
Box::new(ArithExpr::Variable("a".to_string())),
Box::new(ArithExpr::Variable("b".to_string())),
)),
Box::new(ArithExpr::Sub(
Box::new(ArithExpr::Variable("c".to_string())),
Box::new(ArithExpr::Variable("d".to_string())),
)),
)
);
}
#[test]
fn test_ARITH_EXPR_034_negative_number_literal() {
assert_eq!(
parse_arith("-1"),
ArithExpr::Sub(
Box::new(ArithExpr::Number(0)),
Box::new(ArithExpr::Number(1)),
)
);
}
#[test]
fn test_ARITH_EXPR_035_zero() {
assert_eq!(parse_arith("0"), ArithExpr::Number(0));
}
#[test]
fn test_ARITH_EXPR_036_missing_closing_paren() {
let err = parse_arith_err("(1 + 2");
assert!(matches!(err, ParseError::InvalidSyntax(_)));
}
#[test]
fn test_ARITH_EXPR_037_empty_parentheses() {
let err = parse_arith_err("()");
assert!(matches!(err, ParseError::InvalidSyntax(_)));
}
#[test]
fn test_ARITH_EXPR_038_trailing_operator() {
let err = parse_arith_err("1 +");
assert!(matches!(err, ParseError::InvalidSyntax(_)));
}
#[test]
fn test_ARITH_EXPR_039_ternary_missing_colon() {
let err = parse_arith_err("a ? b");
assert!(matches!(err, ParseError::InvalidSyntax(_)));
}
#[test]
fn test_ARITH_EXPR_040_left_associative_subtraction() {
assert_eq!(
parse_arith("a - b - c"),
ArithExpr::Sub(
Box::new(ArithExpr::Sub(
Box::new(ArithExpr::Variable("a".to_string())),
Box::new(ArithExpr::Variable("b".to_string())),
)),
Box::new(ArithExpr::Variable("c".to_string())),
)
);
}
#[test]
fn test_ARITH_EXPR_041_unary_minus_in_expression() {
assert_eq!(
parse_arith("a + -b"),
ArithExpr::Add(
Box::new(ArithExpr::Variable("a".to_string())),
Box::new(ArithExpr::Sub(
Box::new(ArithExpr::Number(0)),
Box::new(ArithExpr::Variable("b".to_string())),
)),
)
);
}
#[test]
fn test_ARITH_EXPR_042_comma_chain_returns_last() {
assert_eq!(parse_arith("1 , 2 , 3"), ArithExpr::Number(3));
}
#[test]
fn test_SEMICOLON_SEP_001_simple() {
let input = "a=10; b=3";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"Semicolon-separated assignments should parse: {:?}",
ast.err()
);
assert_eq!(ast.as_ref().expect("ok").statements.len(), 2);
}
#[test]
fn test_SEMICOLON_SEP_002_multiple() {
let input = "echo a; echo b; echo c";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"Multiple semicolons should parse: {:?}",
ast.err()
);
assert_eq!(ast.as_ref().expect("ok").statements.len(), 3);
}
#[test]
fn test_V_TEST_001_variable_set() {
let input = "if [[ -v MYVAR ]]; then\n echo set\nfi";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"-v test operator should parse: {:?}",
ast.err()
);
}
#[test]
fn test_ENV_PREFIX_001_while_ifs() {
let input =
"while IFS='=' read -r key value; do\n echo \"$key=$value\"\ndone < input.txt";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"IFS= env prefix in while should parse: {:?}",
ast.err()
);
}
#[test]
fn test_REGEX_POSIX_CLASS_001_bracket_depth() {
let input = "if [[ \"$key\" =~ ^[[:space:]]*# ]]; then\n echo comment\nfi";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"=~ with [[:space:]] should parse: {:?}",
ast.err()
);
}
#[test]
fn test_COMBINED_REDIR_001_if_condition() {
let input = "if command -v git &>/dev/null; then\n echo found\nfi";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"&>/dev/null in if condition should parse: {:?}",
ast.err()
);
}
#[test]
fn test_COMBINED_REDIR_002_negated_condition() {
let input = "if ! command -v git &>/dev/null; then\n echo missing\nfi";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"negated &>/dev/null in condition should parse: {:?}",
ast.err()
);
}
#[test]
fn test_COMBINED_REDIR_003_in_command() {
let input = "echo hello &> output.log";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(ast.is_ok(), "&> in command should parse: {:?}", ast.err());
if let BashStmt::Command { redirects, .. } = &ast.expect("ok").statements[0] {
assert_eq!(redirects.len(), 1, "Should have one Combined redirect");
assert!(matches!(&redirects[0], Redirect::Combined { .. }));
}
}
#[test]
fn test_DOGFOOD_022_assoc_arrays_and_arithmetic() {
let input = r#"declare -A config
config[host]="localhost"
config[port]="8080"
for key in "${!config[@]}"; do
printf "%s = %s\n" "$key" "${config[$key]}"
done
arr=(zero one two three four five)
echo "Elements 2-4: ${arr[@]:2:3}"
echo "Last element: ${arr[-1]}"
a=10; b=3
echo "Add: $((a + b))"
echo "Mul: $((a * b))"
max=$((a > b ? a : b))
echo "Max: $max"
"#;
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"dogfood_22 constructs should parse: {:?}",
ast.err()
);
}
#[test]
fn test_DOGFOOD_023_deployment_script() {
let input = r#"set -euo pipefail
readonly LOG_FILE="/var/log/deploy.log"
readonly TIMESTAMP_FMT="+%Y-%m-%d %H:%M:%S"
log() {
local level="$1"
shift
local msg="$*"
echo "[$level] $msg" >&2
}
info() { log "INFO" "$@"; }
health_check() {
local url="$1"
local max_retries="${2:-10}"
local attempt=0
while (( attempt < max_retries )); do
if curl -sf -o /dev/null "$url" 2>/dev/null; then
return 0
fi
attempt=$((attempt + 1))
sleep 5
done
return 1
}
deploy_service() {
local service_name="$1"
for cmd in docker curl jq; do
if ! command -v "$cmd" &>/dev/null; then
return 1
fi
done
if ! docker pull "$service_name" 2>/dev/null; then
return 1
fi
}
main() {
info "Starting deployment"
deploy_service "${SERVICE_NAME:-myapp}"
health_check "${HEALTH_URL:-http://localhost:8080/health}"
}
main "$@"
"#;
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"dogfood_23 key constructs should parse: {:?}",
ast.err()
);
}
#[test]
fn test_ANSI_C_QUOTE_001_tab() {
let input = "IFS=$'\\t' read -r a b";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"$'\\t' ANSI-C quoting should parse: {:?}",
ast.err()
);
}
#[test]
fn test_ANSI_C_QUOTE_002_newline() {
let input = "echo $'hello\\nworld'";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"$'\\n' ANSI-C quoting should parse: {:?}",
ast.err()
);
}
#[test]
fn test_HEREDOC_COMPOUND_001_done_heredoc() {
let input = "while read -r line; do\n echo \"$line\"\ndone <<EOF\napple\nbanana\nEOF";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(ast.is_ok(), "done <<EOF should parse: {:?}", ast.err());
}
#[test]
fn test_FILE_TEST_001_symlink() {
let input = "if [ -L /tmp/link ]; then echo symlink; fi";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(ast.is_ok(), "-L test should parse: {:?}", ast.err());
}
#[test]
fn test_FILE_TEST_002_all_operators() {
for op in [
"-f", "-e", "-s", "-d", "-r", "-w", "-x", "-L", "-h", "-p", "-b", "-c", "-g", "-k",
"-u", "-t", "-O", "-G", "-N", "-v", "-n", "-z",
] {
let input = format!("[ {} /tmp/test ]", op);
let mut parser = BashParser::new(&input).expect("parser");
let ast = parser.parse();
assert!(ast.is_ok(), "{} test should parse: {:?}", op, ast.err());
}
}
#[test]
fn test_TRIPLE_ELIF_001_with_else() {
let input = "if [ -f x ]; then\n echo a\nelif [ -d x ]; then\n echo b\nelif [ -L x ]; then\n echo c\nelse\n echo d\nfi";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(ast.is_ok(), "triple elif should parse: {:?}", ast.err());
if let BashStmt::If {
elif_blocks,
else_block,
..
} = &ast.expect("ok").statements[0]
{
assert_eq!(elif_blocks.len(), 2, "Should have 2 elif blocks");
assert!(else_block.is_some(), "Should have else block");
}
}
#[test]
fn test_DOGFOOD_024_traps_and_ansi_c() {
let input = r#"set -euo pipefail
TMPDIR=$(mktemp -d)
cleanup() {
local exit_code=$?
rm -rf "$TMPDIR"
exit "$exit_code"
}
trap cleanup EXIT
trap 'echo "Caught SIGINT" >&2; cleanup' INT
exec 200>"$LOCKFILE"
flock -n 200 || { echo "Already running" >&2; exit 1; }
"#;
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"dogfood_24 traps should parse: {:?}",
ast.err()
);
}
#[test]
fn test_DOGFOOD_026_git_and_find() {
let input = r#"current_branch=$(git branch --show-current)
default_branch=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|origin/||' || echo "main")
if [[ "$current_branch" != "$default_branch" ]]; then
echo "Not on $default_branch branch"
fi
find /var/log -type f -name "*.log" -exec gzip {} \;
find . -name "*.txt" -print0 | xargs -0 grep -l "pattern" 2>/dev/null || true
"#;
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"dogfood_26 git/find should parse: {:?}",
ast.err()
);
}
#[test]
fn test_DOGFOOD_027_detect_os_and_install() {
let input = r#"detect_os() {
if [[ -f /etc/os-release ]]; then
. /etc/os-release
echo "$ID"
elif [[ -f /etc/redhat-release ]]; then
echo "rhel"
elif command -v sw_vers &>/dev/null; then
echo "macos"
else
echo "unknown"
fi
}
install_package() {
local pkg="$1"
case "$(detect_os)" in
ubuntu|debian)
sudo apt-get install -y "$pkg"
;;
centos|rhel|fedora)
sudo yum install -y "$pkg"
;;
*)
echo "Unknown OS" >&2
return 1
;;
esac
}
"#;
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(
ast.is_ok(),
"dogfood_27 detect_os should parse: {:?}",
ast.err()
);
}
#[test]
fn test_TEST_AND_001_double_bracket() {
let input = r#"if [[ "$a" == "1" && "$b" == "2" ]]; then echo ok; fi"#;
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(ast.is_ok(), "&& inside [[ ]] should parse: {:?}", ast.err());
}
#[test]
fn test_TEST_OR_001_double_bracket() {
let input = r#"if [[ "$a" == "1" || "$b" == "2" ]]; then echo ok; fi"#;
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(ast.is_ok(), "|| inside [[ ]] should parse: {:?}", ast.err());
}
#[test]
fn test_TEST_AND_002_single_bracket() {
let input = "if [ -f /etc/passwd -a -r /etc/passwd ]; then echo ok; fi";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(ast.is_ok(), "-a inside [ ] should parse: {:?}", ast.err());
}
#[test]
fn test_TEST_OR_002_single_bracket() {
let input = "if [ -f /tmp/a -o -f /tmp/b ]; then echo ok; fi";
let mut parser = BashParser::new(input).expect("parser");
let ast = parser.parse();
assert!(ast.is_ok(), "-o inside [ ] should parse: {:?}", ast.err());
}